diff --git a/.ccls b/.ccls new file mode 100644 index 0000000000..2ee6701806 --- /dev/null +++ b/.ccls @@ -0,0 +1,3 @@ +%compile_commands.json +%h -x +%h c++-header diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..7a87b31eb7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +--- +BasedOnStyle: Google +ColumnLimit: 150 +DerivePointerAlignment: false +PointerAlignment: Right +--- diff --git a/CMakeLists.txt b/CMakeLists.txt index 006107a7d2..77621d1df4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ include(FeatureSummary) include(ExternalProject) option(SKIP_TESTS "Skips building all tests." OFF) +option(ENABLE_BENCHMARKING "Enables building of benchmark suites." OFF) option(PORTABLE "Instructs the compiler to remove architecture specific optimizations" ON) @@ -376,6 +377,7 @@ add_subdirectory(thirdparty/yaml-cpp-yaml-cpp-20171024) include_directories(thirdparty/concurrentqueue) include_directories(thirdparty/yaml-cpp-yaml-cpp-20171024/include) include_directories(thirdparty/rapidjson-1.1.0/include) +include_directories(thirdparty/mio/include) ## Expression language extensions option(DISABLE_EXPRESSION_LANGUAGE "Disables the scripting extensions." OFF) @@ -409,6 +411,7 @@ if (WIN32 OR NOT USE_SYSTEM_ZLIB) add_dependencies(minifi zlib-external) endif(WIN32 OR NOT USE_SYSTEM_ZLIB) + createExtension(STANDARD-PROCESSORS "STANDARD PROCESSORS" "Provides standard processors" "extensions/standard-processors" "extensions/standard-processors/tests/") @@ -428,6 +431,7 @@ endif() ## Add the rocks DB extension if (NOT ROCKSDB_FOUND OR BUILD_ROCKSDB) + set(USE_RTTI "TRUE") set(BUILD_RD "TRUE") endif() @@ -706,6 +710,49 @@ include(CPack) if (NOT SKIP_TESTS) include(BuildTests) + + # BENCHMARKING depends on test support code + if (ENABLE_BENCHMARKING) + set(BENCHMARK_LIB_DIR "${CMAKE_CURRENT_BINARY_DIR}/thirdparty/google-benchmark-install/lib64") + set(BENCHMARK_BYPRODUCT "${BENCHMARK_LIB_DIR}/libbenchmark.a") + set(BENCHMARK_MAIN_BYPRODUCT "${BENCHMARK_LIB_DIR}/libbenchmark.a") + # GIT_REPOSITORY "https://github.com/google/benchmark.git" + #GIT_TAG "090faecb454fbd6e6e17a75ef8146acb037118d4" # Version 1.5.0 + ExternalProject_Add( + google-benchmark-external + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/benchmark-1.5.0" + CMAKE_ARGS ${PASSTHROUGH_CMAKE_ARGS} + "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/thirdparty/google-benchmark-install" + "-DBENCHMARK_ENABLE_TESTING=OFF" + BUILD_BYPRODUCTS ${BENCHMARK_BYPRODUCT} ${BENCHMARK_MAIN_BYPRODUCT} + ) + add_library(benchmark STATIC IMPORTED) + set_target_properties(benchmark PROPERTIES IMPORTED_LOCATION ${BENCHMARK_BYPRODUCT}) + add_dependencies(benchmark google-benchmark-external) + add_library(benchmark_main STATIC IMPORTED) + set_target_properties(benchmark_main PROPERTIES IMPORTED_LOCATION ${BENCHMARK_MAIN_BYPRODUCT}) + add_dependencies(benchmark_main google-benchmark-external) + file(GLOB LIBMINIFI_BENCHMARKS "libminifi/benchmark/*.cpp") + set(ALL_BENCHMARKS "${LIBMINIFI_BENCHMARKS}") + + set(BENCHMARK_COUNT 0) + foreach(benchmarkfile ${ALL_BENCHMARKS}) + get_filename_component(benchmarkfilename "${benchmarkfile}" NAME_WE) + add_executable("${benchmarkfilename}" "${benchmarkfile}") + target_link_libraries("${benchmarkfilename}" benchmark benchmark_main "${CMAKE_THREAD_LIBS_INIT}") + target_link_libraries ("${benchmarkfilename}" -Wl,--whole-archive core-minifi minifi -Wl,--no-whole-archive) + appendIncludes("${benchmarkfilename}") + if (DISABLE_ROCKSDB STREQUAL "OFF" OR NOT DISABLE_ROCKSDB) + target_include_directories("${benchmarkfilename}" BEFORE PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/rocksdb/include") + target_include_directories("${benchmarkfilename}" BEFORE PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/extensions/rocksdb-repos") + target_link_libraries ("${benchmarkfilename}" -Wl,--whole-archive minifi-rocksdb-repos -Wl,--no-whole-archive) + add_definitions(-DENABLE_ROCKSDB_BENCHMARKS=1) + endif() + target_include_directories("${benchmarkfilename}" BEFORE PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/benchmark-1.5.0/include") + math(EXPR BENCHMARK_COUNT "${BENCHMARK_COUNT}+1") + endforeach() + message("-- Finished building ${BENCHMARK_COUNT} benchmark file(s)...") + endif() endif() include(BuildDocs) diff --git a/extensions/rocksdb-repos/DatabaseContentRepository.cpp b/extensions/rocksdb-repos/DatabaseContentRepository.cpp index bdcc3e57c9..118d5ec03a 100644 --- a/extensions/rocksdb-repos/DatabaseContentRepository.cpp +++ b/extensions/rocksdb-repos/DatabaseContentRepository.cpp @@ -20,6 +20,7 @@ #include #include #include "RocksDbStream.h" +#include "io/DatabaseMemoryMap.h" #include "rocksdb/merge_operator.h" namespace org { @@ -38,8 +39,8 @@ bool DatabaseContentRepository::initialize(const std::shared_ptr(); options.error_if_exists = false; options.max_successive_merges = 0; @@ -48,7 +49,7 @@ bool DatabaseContentRepository::initialize(const std::shared_ptrlog_debug("NiFi Content DB Repository database open %s success", directory_); is_valid_ = true; } else { - logger_->log_error("NiFi Content DB Repository database open %s fail", directory_); + logger_->log_error("NiFi Content DB Repository database open %s fail due to %s", directory_, status.ToString()); is_valid_ = false; } return is_valid_; @@ -62,19 +63,40 @@ void DatabaseContentRepository::stop() { } std::shared_ptr DatabaseContentRepository::write(const std::shared_ptr &claim, bool append) { - // the traditional approach with these has been to return -1 from the stream; however, since we have the ability here - // we can simply return a nullptr, which is also valid from the API when this stream is not valid. - if (nullptr == claim || !is_valid_ || !db_) - return nullptr; + // the traditional approach with these has been to return -1 from the stream; + // however, since we have the ability here we can simply return a nullptr, + // which is also valid from the API when this stream is not valid. + if (nullptr == claim || !is_valid_ || !db_) return nullptr; // append is already supported in all modes return std::make_shared(claim->getContentFullPath(), db_, true); } +std::shared_ptr DatabaseContentRepository::mmap(const std::shared_ptr &claim, size_t map_size, + bool read_only) { + /** + * Because the underlying does not support direct mapping of the value to memory, we read the entire value in to memory, then write (iff not + * readOnly) it back to the db upon closure of the MemoryMap + */ + + auto mm = std::make_shared(claim, map_size, [this](const std::shared_ptr &claim) { + remove(claim); + return write(claim); + }, read_only); + + auto rs = read(claim); + + if (rs != nullptr) { + rs->readData(reinterpret_cast(mm->getData()), map_size); + } + + return mm; +} + std::shared_ptr DatabaseContentRepository::read(const std::shared_ptr &claim) { - // the traditional approach with these has been to return -1 from the stream; however, since we have the ability here - // we can simply return a nullptr, which is also valid from the API when this stream is not valid. - if (nullptr == claim || !is_valid_ || !db_) - return nullptr; + // the traditional approach with these has been to return -1 from the stream; + // however, since we have the ability here we can simply return a nullptr, + // which is also valid from the API when this stream is not valid. + if (nullptr == claim || !is_valid_ || !db_) return nullptr; return std::make_shared(claim->getContentFullPath(), db_, false); } @@ -92,8 +114,7 @@ bool DatabaseContentRepository::exists(const std::shared_ptr &claim) { - if (nullptr == claim || !is_valid_ || !db_) - return false; + if (nullptr == claim || !is_valid_ || !db_) return false; rocksdb::Status status; status = db_->Delete(rocksdb::WriteOptions(), claim->getContentFullPath()); if (status.ok()) { diff --git a/extensions/rocksdb-repos/DatabaseContentRepository.h b/extensions/rocksdb-repos/DatabaseContentRepository.h index 6d12460c5a..d5b69e4c39 100644 --- a/extensions/rocksdb-repos/DatabaseContentRepository.h +++ b/extensions/rocksdb-repos/DatabaseContentRepository.h @@ -18,13 +18,13 @@ #ifndef LIBMINIFI_INCLUDE_CORE_REPOSITORY_DatabaseContentRepository_H_ #define LIBMINIFI_INCLUDE_CORE_REPOSITORY_DatabaseContentRepository_H_ -#include "rocksdb/db.h" -#include "rocksdb/merge_operator.h" -#include "core/Core.h" #include "core/Connectable.h" #include "core/ContentRepository.h" -#include "properties/Configure.h" +#include "core/Core.h" #include "core/logging/LoggerConfiguration.h" +#include "properties/Configure.h" +#include "rocksdb/db.h" +#include "rocksdb/merge_operator.h" namespace org { namespace apache { namespace nifi { @@ -35,15 +35,15 @@ namespace repository { class StringAppender : public rocksdb::AssociativeMergeOperator { public: // Constructor: specify delimiter - explicit StringAppender() { - - } + explicit StringAppender() {} - virtual bool Merge(const rocksdb::Slice& key, const rocksdb::Slice* existing_value, const rocksdb::Slice& value, std::string* new_value, rocksdb::Logger* logger) const { + virtual bool Merge(const rocksdb::Slice &key, const rocksdb::Slice *existing_value, const rocksdb::Slice &value, std::string *new_value, + rocksdb::Logger *logger) const { // Clear the *new_value for writing. if (nullptr == new_value) { return false; } + new_value->clear(); if (!existing_value) { @@ -58,29 +58,20 @@ class StringAppender : public rocksdb::AssociativeMergeOperator { return true; } - virtual const char* Name() const { - return "StringAppender"; - } + virtual const char *Name() const { return "StringAppender"; } private: - }; /** - * DatabaseContentRepository is a content repository that stores data onto the local file system. + * DatabaseContentRepository is a content repository that stores data onto the + * local file system. */ class DatabaseContentRepository : public core::ContentRepository, public core::Connectable { public: - DatabaseContentRepository(std::string name = getClassName(), utils::Identifier uuid = utils::Identifier()) - : core::Connectable(name, uuid), - is_valid_(false), - db_(nullptr), - logger_(logging::LoggerFactory::getLogger()) { - } - virtual ~DatabaseContentRepository() { - stop(); - } + : core::Connectable(name, uuid), is_valid_(false), db_(nullptr), logger_(logging::LoggerFactory::getLogger()) {} + virtual ~DatabaseContentRepository() { stop(); } virtual bool initialize(const std::shared_ptr &configuration); @@ -88,38 +79,32 @@ class DatabaseContentRepository : public core::ContentRepository, public core::C virtual std::shared_ptr write(const std::shared_ptr &claim, bool append = false); + virtual std::shared_ptr mmap(const std::shared_ptr &claim, size_t mapSize, bool readOnly); + virtual std::shared_ptr read(const std::shared_ptr &claim); - virtual bool close(const std::shared_ptr &claim) { - return remove(claim); - } + virtual bool close(const std::shared_ptr &claim) { return remove(claim); } virtual bool remove(const std::shared_ptr &claim); virtual bool exists(const std::shared_ptr &streamId); - virtual void yield() { - - } + virtual void yield() {} /** * Determines if we are connected and operating */ - virtual bool isRunning() { - return true; - } + virtual bool isRunning() { return true; } /** * Determines if work is available by this connectable * @return boolean if work is available. */ - virtual bool isWorkAvailable() { - return true; - } + virtual bool isWorkAvailable() { return true; } private: bool is_valid_; - rocksdb::DB* db_; + rocksdb::DB *db_; std::shared_ptr logger_; }; diff --git a/libminifi/benchmark/MemoryMapBenchmarks.cpp b/libminifi/benchmark/MemoryMapBenchmarks.cpp new file mode 100644 index 0000000000..3e82a2f65d --- /dev/null +++ b/libminifi/benchmark/MemoryMapBenchmarks.cpp @@ -0,0 +1,494 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "../test/TestBase.h" +#include "ResourceClaim.h" +#include "core/Core.h" +#include "repository/FileSystemRepository.h" +#include "repository/VolatileContentRepository.h" +#include "properties/Configure.h" + +#ifdef ENABLE_ROCKSDB_BENCHMARKS +#include "DatabaseContentRepository.h" +#endif // ENABLE_ROCKSDB_BENCHMARKS + +template +class MemoryMapBMFixture : public benchmark::Fixture { + public: + void SetUp(const ::benchmark::State &state) { + test_controller_ = std::make_shared(); + repo_ = std::make_shared(); + conf_ = std::make_shared(); + char format[] = "/tmp/testRepo.XXXXXX"; + dir_ = std::string(test_controller_->createTempDirectory(format)); + test_file_ = dir_ + "/testfile"; + claim_ = std::make_shared(test_file_, repo_); + } + + void TearDown(const ::benchmark::State &state) { + } + + void init_db_repo() { + conf_->set(minifi::Configure::nifi_dbcontent_repository_directory_default, dir_); + init_repo(); + } + + void init_repo() { + repo_->initialize(conf_); + } + + void set_test_input(size_t size, char c) { + test_string_ = ""; + test_string_.resize(size, c); + auto mm = repo_->mmap(claim_, test_string_.length(), false); + mm->resize(test_string_.length()); + memcpy(mm->getData(), &test_string_[0], test_string_.length()); + } + + void set_test_expected_output(size_t size, char c) { + expected_string_ = ""; + expected_string_.resize(size, c); + } + + void validate_string(const char *read_string) { + if (strncmp(read_string, expected_string_.c_str(), expected_string_.length()) != 0) { + throw std::runtime_error("string read failed"); + } + } + + void validate_byte(size_t pos, const char b) { + if (b != expected_string_[pos]) { + throw std::runtime_error("byte read failed"); + } + } + + /** + * Get deterministic random points to access. Alternates between positions relative to start & end of file so as to not be sequential. + * @return set of random points + */ + std::vector random_points() { + std::vector p; + + for (size_t i = 0; i < test_string_.length() / 2; i += test_string_.length() / 100) { + p.push_back(i); + p.push_back(test_string_.length() - 1); + } + + return p; + } + + std::shared_ptr conf_; + std::shared_ptr test_controller_; + std::shared_ptr repo_; + std::shared_ptr claim_; + std::string test_file_; + std::string test_string_; + std::string expected_string_; + std::string dir_; +}; + +typedef MemoryMapBMFixture FSMemoryMapBMFixture; +typedef MemoryMapBMFixture VolatileMemoryMapBMFixture; + +#ifdef ENABLE_ROCKSDB_BENCHMARKS +typedef MemoryMapBMFixture DatabaseMemoryMapBMFixture; +#endif // ENABLE_ROCKSDB_BENCHMARKS + +template +void mmap_read(T *fixture, benchmark::State &st) { + for (auto _ : st) { + auto mm = fixture->repo_->mmap(fixture->claim_, fixture->test_string_.length(), true); + fixture->validate_string(reinterpret_cast(mm->getData())); + } + + fixture->repo_->stop(); +} + +template +void mmap_read_random(T *fixture, benchmark::State &st) { + auto r = fixture->random_points(); + auto mm = fixture->repo_->mmap(fixture->claim_, fixture->test_string_.length(), true); + for (auto _ : st) { + auto data = reinterpret_cast(mm->getData()); + for (size_t p : r) { + fixture->validate_byte(p, data[p]); + } + } + + fixture->repo_->stop(); +} + +template +void mmap_write_read(T *fixture, benchmark::State &st) { + for (auto _ : st) { + fixture->repo_->remove(fixture->claim_); + auto mm = fixture->repo_->mmap(fixture->claim_, fixture->test_string_.length(), false); + memcpy(mm->getData(), &(fixture->expected_string_[0]), fixture->test_string_.length()); + fixture->validate_string(reinterpret_cast(mm->getData())); + } + + fixture->repo_->stop(); +} + +template +void cb_read(T *fixture, benchmark::State &st) { + for (auto _ : st) { + auto rs = fixture->repo_->read(fixture->claim_); + std::vector buf; + rs->readData(buf, fixture->test_string_.length() + 1); + fixture->validate_string(reinterpret_cast(&buf[0])); + } + + fixture->repo_->stop(); +} + +template +void cb_read_random(T *fixture, benchmark::State &st) { + auto r = fixture->random_points(); + auto rs = fixture->repo_->read(fixture->claim_); + for (auto _ : st) { + for (size_t p : r) { + rs->seek(p); + char b; + rs->read(b); + fixture->validate_byte(p, b); + } + } + + fixture->repo_->stop(); +} + +template +void cb_write_read(T *fixture, benchmark::State &st) { + for (auto _ : st) { + { + fixture->repo_->remove(fixture->claim_); + auto ws = fixture->repo_->write(fixture->claim_, false); + ws->write(reinterpret_cast(&(fixture->expected_string_[0])), fixture->test_string_.length()); + } + + auto rs = fixture->repo_->read(fixture->claim_); + std::vector buf; + rs->readData(buf, fixture->test_string_.length() + 1); + fixture->validate_string(reinterpret_cast(&buf[0])); + } + + + fixture->repo_->stop(); +} + +BENCHMARK_F(FSMemoryMapBMFixture, MemoryMap_FileSystemRepository_Read_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, Callback_FileSystemRepository_Read_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, MemoryMap_FileSystemRepository_WriteRead_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, Callback_FileSystemRepository_WriteRead_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, MemoryMap_FileSystemRepository_Read_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, Callback_FileSystemRepository_Read_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, MemoryMap_FileSystemRepository_WriteRead_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, Callback_FileSystemRepository_WriteRead_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, MemoryMap_FileSystemRepository_Read_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, Callback_FileSystemRepository_Read_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, MemoryMap_FileSystemRepository_WriteRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, Callback_FileSystemRepository_WriteRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, MemoryMap_FileSystemRepository_RandomRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + mmap_read_random(this, st); +} + +BENCHMARK_F(FSMemoryMapBMFixture, Callback_FileSystemRepository_RandomRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + cb_read_random(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, MemoryMap_VolatileRepository_Read_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, Callback_VolatileRepository_Read_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, MemoryMap_VolatileRepository_WriteRead_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, Callback_VolatileRepository_WriteRead_Tiny)(benchmark::State &st) { + init_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, MemoryMap_VolatileRepository_Read_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, Callback_VolatileRepository_Read_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, MemoryMap_VolatileRepository_WriteRead_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, Callback_VolatileRepository_WriteRead_Small)(benchmark::State &st) { + init_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, MemoryMap_VolatileRepository_Read_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, Callback_VolatileRepository_Read_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, MemoryMap_VolatileRepository_WriteRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, Callback_VolatileRepository_WriteRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, MemoryMap_VolatileRepository_RandomRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + mmap_read_random(this, st); +} + +BENCHMARK_F(VolatileMemoryMapBMFixture, Callback_VolatileRepository_RandomRead_Large)(benchmark::State &st) { + init_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + cb_read_random(this, st); +} + +#ifdef ENABLE_ROCKSDB_BENCHMARKS + +BENCHMARK_F(DatabaseMemoryMapBMFixture, MemoryMap_DatabaseRepository_Read_Tiny)(benchmark::State &st) { + init_db_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, Callback_DatabaseRepository_Read_Tiny)(benchmark::State &st) { + init_db_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, MemoryMap_DatabaseRepository_WriteRead_Tiny)(benchmark::State &st) { + init_db_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, Callback_DatabaseRepository_WriteRead_Tiny)(benchmark::State &st) { + init_db_repo(); + set_test_input(10, 'x'); + set_test_expected_output(10, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, MemoryMap_DatabaseRepository_Read_Small)(benchmark::State &st) { + init_db_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, Callback_DatabaseRepository_Read_Small)(benchmark::State &st) { + init_db_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, MemoryMap_DatabaseRepository_WriteRead_Small)(benchmark::State &st) { + init_db_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, Callback_DatabaseRepository_WriteRead_Small)(benchmark::State &st) { + init_db_repo(); + set_test_input(131072, 'x'); + set_test_expected_output(131072, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, MemoryMap_DatabaseRepository_Read_Large)(benchmark::State &st) { + init_db_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + mmap_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, Callback_DatabaseRepository_Read_Large)(benchmark::State &st) { + init_db_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + cb_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, MemoryMap_DatabaseRepository_WriteRead_Large)(benchmark::State &st) { + init_db_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'y'); + mmap_write_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, Callback_DatabaseRepository_WriteRead_Large)(benchmark::State &st) { + init_db_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'y'); + cb_write_read(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, MemoryMap_DatabaseRepository_RandomRead_Large)(benchmark::State &st) { + init_db_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + mmap_read_random(this, st); +} + +BENCHMARK_F(DatabaseMemoryMapBMFixture, Callback_DatabaseRepository_RandomRead_Large)(benchmark::State &st) { + init_db_repo(); + set_test_input(33554432, 'x'); + set_test_expected_output(33554432, 'x'); + cb_read_random(this, st); +} + +#endif // ENABLE_ROCKSDB_BENCHMARKS + +BENCHMARK_MAIN(); diff --git a/libminifi/include/FlowFileRecord.h b/libminifi/include/FlowFileRecord.h index 795003fd01..3b8cd67468 100644 --- a/libminifi/include/FlowFileRecord.h +++ b/libminifi/include/FlowFileRecord.h @@ -99,6 +99,11 @@ class OutputStreamCallback { virtual int64_t process(std::shared_ptr stream) = 0; }; +class MemoryMapCallback { +public: + virtual ~MemoryMapCallback() {} + virtual bool process(std::shared_ptr map) = 0; +}; class FlowFileRecord : public core::FlowFile, public io::Serializable { public: diff --git a/libminifi/include/core/ContentRepository.h b/libminifi/include/core/ContentRepository.h index f42400af34..3410fe5d77 100644 --- a/libminifi/include/core/ContentRepository.h +++ b/libminifi/include/core/ContentRepository.h @@ -18,12 +18,14 @@ #ifndef LIBMINIFI_INCLUDE_CORE_CONTENTREPOSITORY_H_ #define LIBMINIFI_INCLUDE_CORE_CONTENTREPOSITORY_H_ -#include "properties/Configure.h" +#include "MemoryMapManager.h" #include "ResourceClaim.h" -#include "io/DataStream.h" -#include "io/BaseStream.h" #include "StreamManager.h" #include "core/Connectable.h" +#include "io/BaseMemoryMap.h" +#include "io/BaseStream.h" +#include "io/DataStream.h" +#include "properties/Configure.h" namespace org { namespace apache { @@ -34,21 +36,16 @@ namespace core { /** * Content repository definition that extends StreamManager. */ -class ContentRepository : public StreamManager { +class ContentRepository : public StreamManager, public MemoryMapManager { public: - - virtual ~ContentRepository() { - - } + virtual ~ContentRepository() {} /** * initialize this content repository using the provided configuration. */ virtual bool initialize(const std::shared_ptr &configure) = 0; - virtual std::string getStoragePath() { - return directory_; - } + virtual std::string getStoragePath() { return directory_; } /** * Stops this repository. @@ -109,7 +106,6 @@ class ContentRepository : public StreamManager { } protected: - std::string directory_; std::mutex count_map_mutex_; diff --git a/libminifi/include/core/MemoryMapManager.h b/libminifi/include/core/MemoryMapManager.h new file mode 100644 index 0000000000..c8a87533c0 --- /dev/null +++ b/libminifi/include/core/MemoryMapManager.h @@ -0,0 +1,55 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIBMINIFI_INCLUDE_CORE_MEMORYMAPMANAGER_H_ +#define LIBMINIFI_INCLUDE_CORE_MEMORYMAPMANAGER_H_ + +#include "io/BaseMemoryMap.h" + +#include + +namespace org { +namespace apache { +namespace nifi { +namespace minifi { +namespace core { + +/** + * Purpose: Provides a base for all memory-mapping managers. The goal here is to + * provide a small set of interfaces that provide a small set of operations to + * provide state management of memory maps. + */ +template +class MemoryMapManager { + public: + virtual ~MemoryMapManager() {} + + /** + * Create a memory map to the object. + * @param map_obj the object to map + * @return result of operation (true of succeeded) + */ + virtual std::shared_ptr mmap(const std::shared_ptr &mapObj, size_t mapSize, bool readOnly) = 0; +}; + +} /* namespace core */ +} /* namespace minifi */ +} /* namespace nifi */ +} /* namespace apache */ +} /* namespace org */ + +#endif /* LIBMINIFI_INCLUDE_CORE_MEMORYMAPMANAGER_H_ */ diff --git a/libminifi/include/core/ProcessSession.h b/libminifi/include/core/ProcessSession.h index 7bcd7f5403..daf9513374 100644 --- a/libminifi/include/core/ProcessSession.h +++ b/libminifi/include/core/ProcessSession.h @@ -19,21 +19,22 @@ #define __PROCESS_SESSION_H__ #include -#include -#include +#include +#include #include #include -#include -#include +#include #include +#include -#include "ProcessContext.h" -#include "FlowFileRecord.h" #include "Exception.h" #include "core/logging/LoggerConfiguration.h" #include "core/Deprecated.h" #include "FlowFile.h" +#include "FlowFileRecord.h" +#include "ProcessContext.h" #include "WeakReference.h" +#include "core/logging/LoggerConfiguration.h" #include "provenance/Provenance.h" namespace org { @@ -50,41 +51,48 @@ class ProcessSession : public ReferenceContainer { * Create a new process session */ ProcessSession(std::shared_ptr processContext = nullptr) - : process_context_(processContext), - logger_(logging::LoggerFactory::getLogger()) { + : process_context_(processContext), logger_(logging::LoggerFactory::getLogger()) { logger_->log_trace("ProcessSession created for %s", process_context_->getProcessorNode()->getName()); auto repo = processContext->getProvenanceRepository(); - //provenance_report_ = new provenance::ProvenanceReporter(repo, process_context_->getProcessorNode()->getName(), process_context_->getProcessorNode()->getName()); - provenance_report_ = std::make_shared(repo, process_context_->getProcessorNode()->getName(), process_context_->getProcessorNode()->getName()); + // provenance_report_ = new provenance::ProvenanceReporter(repo, + // process_context_->getProcessorNode()->getName(), + // process_context_->getProcessorNode()->getName()); + provenance_report_ = std::make_shared(repo, process_context_->getProcessorNode()->getName(), + process_context_->getProcessorNode()->getName()); } -// Destructor + // Destructor virtual ~ProcessSession(); -// Commit the session + // Commit the session void commit(); // Roll Back the session void rollback(); // Get Provenance Report - std::shared_ptr getProvenanceReporter() { - return provenance_report_; - } + std::shared_ptr getProvenanceReporter() { return provenance_report_; } // // Get the FlowFile from the highest priority queue virtual std::shared_ptr get(); - // Create a new UUID FlowFile with no content resource claim and without parent + // Create a new UUID FlowFile with no content resource claim and without + // parent std::shared_ptr create(); - // Create a new UUID FlowFile with no content resource claim and inherit all attributes from parent - //std::shared_ptr create(std::shared_ptr &&parent); - // Create a new UUID FlowFile with no content resource claim and inherit all attributes from parent + // Create a new UUID FlowFile with no content resource claim and inherit all + // attributes from parent + // std::shared_ptr create(std::shared_ptr + // &&parent); + // Create a new UUID FlowFile with no content resource claim and inherit all + // attributes from parent std::shared_ptr create(const std::shared_ptr &parent); // Add a FlowFile to the session virtual void add(const std::shared_ptr &flow); -// Clone a new UUID FlowFile from parent both for content resource claim and attributes + // Clone a new UUID FlowFile from parent both for content resource claim and + // attributes std::shared_ptr clone(const std::shared_ptr &parent); - // Clone a new UUID FlowFile from parent for attributes and sub set of parent content resource claim + // Clone a new UUID FlowFile from parent for attributes and sub set of parent + // content resource claim std::shared_ptr clone(const std::shared_ptr &parent, int64_t offset, int64_t size); - // Duplicate a FlowFile with the same UUID and all attributes and content resource claim for the roll back of the session + // Duplicate a FlowFile with the same UUID and all attributes and content + // resource claim for the roll back of the session std::shared_ptr duplicate(const std::shared_ptr &original); // Transfer the FlowFile to the relationship virtual void transfer(const std::shared_ptr &flow, Relationship relationship); @@ -98,6 +106,8 @@ class ProcessSession : public ReferenceContainer { void read(const std::shared_ptr &flow, InputStreamCallback *callback); // Execute the given write callback against the content void write(const std::shared_ptr &flow, OutputStreamCallback *callback); + // Execute the given mmap callback against the content + void mmap(const std::shared_ptr &flow, MemoryMapCallback *callback, size_t map_size, bool read_only); // Execute the given write/append callback against the content void append(const std::shared_ptr &flow, OutputStreamCallback *callback); // Penalize the flow @@ -105,7 +115,8 @@ class ProcessSession : public ReferenceContainer { /** * Imports a file from the data stream - * @param stream incoming data stream that contains the data to store into a file + * @param stream incoming data stream that contains the data to store into a + * file * @param flow flow file */ void importFrom(io::DataStream &stream, const std::shared_ptr &flow); @@ -120,38 +131,38 @@ class ProcessSession : public ReferenceContainer { * @param flow flow file * @param bool whether or not to keep the content in the flow file */ - bool exportContent(const std::string &destination, const std::shared_ptr &flow, - bool keepContent); + bool exportContent(const std::string &destination, const std::shared_ptr &flow, bool keepContent); - bool exportContent(const std::string &destination, const std::string &tmpFileName, const std::shared_ptr &flow, - bool keepContent); + bool exportContent(const std::string &destination, const std::string &tmpFileName, const std::shared_ptr &flow, bool keepContent); // Stash the content to a key void stash(const std::string &key, const std::shared_ptr &flow); // Restore content previously stashed to a key void restore(const std::string &key, const std::shared_ptr &flow); -// Prevent default copy constructor and assignment operation -// Only support pass by reference or pointer + // Prevent default copy constructor and assignment operation + // Only support pass by reference or pointer ProcessSession(const ProcessSession &parent) = delete; ProcessSession &operator=(const ProcessSession &parent) = delete; protected: -// FlowFiles being modified by current process session - std::map > _updatedFlowFiles; - // Copy of the original FlowFiles being modified by current process session as above - std::map > _originalFlowFiles; + // FlowFiles being modified by current process session + std::map> _updatedFlowFiles; + // Copy of the original FlowFiles being modified by current process session as + // above + std::map> _originalFlowFiles; // FlowFiles being added by current process session - std::map > _addedFlowFiles; + std::map> _addedFlowFiles; // FlowFiles being deleted by current process session - std::map > _deletedFlowFiles; + std::map> _deletedFlowFiles; // FlowFiles being transfered to the relationship std::map _transferRelationship; // FlowFiles being cloned for multiple connections per relationship - std::map > _clonedFlowFiles; + std::map> _clonedFlowFiles; private: -// Clone the flow file during transfer to multiple connections for a relationship + // Clone the flow file during transfer to multiple connections for a + // relationship std::shared_ptr cloneDuringTransfer(std::shared_ptr &parent); // ProcessContext std::shared_ptr process_context_; diff --git a/libminifi/include/core/repository/AtomicRepoEntries.h b/libminifi/include/core/repository/AtomicRepoEntries.h index 4235a23092..975152ea41 100644 --- a/libminifi/include/core/repository/AtomicRepoEntries.h +++ b/libminifi/include/core/repository/AtomicRepoEntries.h @@ -18,15 +18,15 @@ #ifndef LIBMINIFI_INCLUDE_CORE_REPOSITORY_ATOMICREPOENTRIES_H_ #define LIBMINIFI_INCLUDE_CORE_REPOSITORY_ATOMICREPOENTRIES_H_ -#include -#include -#include +#include #include +#include +#include #include -#include -#include -#include +#include #include +#include +#include namespace org { namespace apache { @@ -41,12 +41,10 @@ namespace repository { * Justification: Since AtomicEntry is a static entry that does not move or change, the underlying * RepoValue can be changed to support atomic operations. */ -template +template class RepoValue { public: - - explicit RepoValue() { - } + explicit RepoValue() {} /** * Constructor that populates the item allowing for a custom key comparator. @@ -55,9 +53,7 @@ class RepoValue { * @param size size buffer * @param comparator custom comparator. */ - explicit RepoValue(T key, const uint8_t *ptr, size_t size, std::function comparator = nullptr) - : key_(key), - comparator_(comparator) { + explicit RepoValue(T key, const uint8_t *ptr, size_t size, std::function comparator = nullptr) : key_(key), comparator_(comparator) { if (nullptr == ptr) { size = 0; } @@ -70,124 +66,97 @@ class RepoValue { /** * RepoValue that moves the other object into this. */ - explicit RepoValue(RepoValue &&other) -noexcept : key_(std::move(other.key_)), - buffer_(std::move(other.buffer_)), - comparator_(std::move(other.comparator_)) { - } + explicit RepoValue(RepoValue &&other) noexcept + : key_(std::move(other.key_)), buffer_(std::move(other.buffer_)), comparator_(std::move(other.comparator_)) {} - ~RepoValue() - { - } + ~RepoValue() {} - T &getKey() { - return key_; - } + T &getKey() { return key_; } - /** - * Sets the key, relacing the custom comparator if needed. - */ - void setKey(const T key, std::function comparator = nullptr) { - key_ = key; - comparator_ = comparator; - } + /** + * Sets the key, relacing the custom comparator if needed. + */ + void setKey(const T key, std::function comparator = nullptr) { + key_ = key; + comparator_ = comparator; + } - /** - * Determines if the key is the same using the custom comparator - * @param other object to compare against - * @return result of the comparison - */ - inline bool isEqual(RepoValue *other) - { - return comparator_ == nullptr ? key_ == other->key_ : comparator_(key_,other->key_); - } + /** + * Determines if the key is the same using the custom comparator + * @param other object to compare against + * @return result of the comparison + */ + inline bool isEqual(RepoValue *other) { return comparator_ == nullptr ? key_ == other->key_ : comparator_(key_, other->key_); } - /** - * Determines if the key is the same using the custom comparator - * @param other object to compare against - * @return result of the comparison - */ - inline bool isKey(T other) - { - return comparator_ == nullptr ? key_ == other : comparator_(key_,other); - } + /** + * Determines if the key is the same using the custom comparator + * @param other object to compare against + * @return result of the comparison + */ + inline bool isKey(T other) { return comparator_ == nullptr ? key_ == other : comparator_(key_, other); } - /** - * Clears the buffer. - */ - void clearBuffer() { - buffer_.resize(0); - buffer_.clear(); - } + /** + * Clears the buffer. + */ + void clearBuffer() { + buffer_.resize(0); + buffer_.clear(); + } - /** - * Return the size of the memory within the key - * buffer, the size of timestamp, and the general - * system word size - */ - uint64_t size() { - return buffer_.size(); - } + /** + * Return the size of the memory within the key + * buffer, the size of timestamp, and the general + * system word size + */ + uint64_t size() { return buffer_.size(); } - size_t getBufferSize() { - return buffer_.size(); - } + size_t getBufferSize() { return buffer_.size(); } - const uint8_t *getBuffer() - { - return buffer_.data(); - } + uint8_t *getBuffer() { return &buffer_[0]; } - /** - * Places the contents of buffer into str - * @param strnig into which we are placing the memory contained in buffer. - */ - void emplace(std::string &str) { - str.insert(0, reinterpret_cast(buffer_.data()), buffer_.size()); - } + /** + * Places the contents of buffer into str + * @param strnig into which we are placing the memory contained in buffer. + */ + void emplace(std::string &str) { str.insert(0, reinterpret_cast(buffer_.data()), buffer_.size()); } - /** - * Appends ptr to the end of buffer. - * @param ptr pointer containing data to add to buffer_ - */ - void append(uint8_t *ptr, size_t size) - { - buffer_.insert(buffer_.end(), ptr, ptr + size); - } + /** + * Appends ptr to the end of buffer. + * @param ptr pointer containing data to add to buffer_ + */ + void append(uint8_t *ptr, size_t size) { buffer_.insert(buffer_.end(), ptr, ptr + size); } - RepoValue &operator=(RepoValue &&other) noexcept { - key_ = std::move(other.key_); - buffer_ = std::move(other.buffer_); - return *this; - } + /** + * Resizes buffer to the specified size. + */ + void resize(size_t size) { buffer_.resize(size); } - private: - T key_; - std::function comparator_; - std::vector buffer_; - }; - - /** - * Purpose: Atomic Entry allows us to create a statically - * sized ring buffer, with the ability to create - * - **/ -template -class AtomicEntry { + RepoValue &operator=(RepoValue &&other) noexcept { + key_ = std::move(other.key_); + buffer_ = std::move(other.buffer_); + return *this; + } + private: + T key_; + std::function comparator_; + std::vector buffer_; +}; + +/** + * Purpose: Atomic Entry allows us to create a statically + * sized ring buffer, with the ability to create + * + **/ +template +class AtomicEntry { public: /** * Constructor that accepts a max size and an atomic counter for the total * size allowd by this and other atomic entries. */ explicit AtomicEntry(std::atomic *total_size, size_t *max_size) - : accumulated_repo_size_(total_size), - max_repo_size_(max_size), - write_pending_(false), - has_value_(false), - ref_count_(0), - free_required(false) { - } + : accumulated_repo_size_(total_size), max_repo_size_(max_size), write_pending_(false), has_value_(false), ref_count_(0), free_required(false) {} /** * Sets the repo value, moving the old value into old_value. @@ -214,8 +183,7 @@ class AtomicEntry { AtomicEntry *takeOwnership() { bool lock = false; - if (!write_pending_.compare_exchange_weak(lock, true)) - return nullptr; + if (!write_pending_.compare_exchange_weak(lock, true)) return nullptr; ref_count_++; @@ -229,11 +197,11 @@ class AtomicEntry { * with said object. * A custom comparator can be provided to augment the key being added into value_ */ - bool testAndSetKey(const T str, std::function releaseTest = nullptr, std::function reclaimer = nullptr, std::function comparator = nullptr) { + bool testAndSetKey(const T str, std::function releaseTest = nullptr, std::function reclaimer = nullptr, + std::function comparator = nullptr) { bool lock = false; - if (!write_pending_.compare_exchange_weak(lock, true)) - return false; + if (!write_pending_.compare_exchange_weak(lock, true)) return false; if (has_value_) { // we either don't have a release test or we cannot release this @@ -252,7 +220,6 @@ class AtomicEntry { try_unlock(); return false; } - } ref_count_ = 1; value_.setKey(str, comparator); @@ -365,7 +332,6 @@ class AtomicEntry { if (accumulated_repo_size_ != nullptr) { *accumulated_repo_size_ -= bufferSize; } - } try_unlock(); return ref; @@ -377,7 +343,6 @@ class AtomicEntry { size = value_.getBufferSize(); try_unlock(); return size; - } /** @@ -440,7 +405,6 @@ class AtomicEntry { } private: - /** * Spin lock to unlock the current atomic entry. */ diff --git a/libminifi/include/core/repository/FileSystemRepository.h b/libminifi/include/core/repository/FileSystemRepository.h index 56a103c7cc..4dbdb41dff 100644 --- a/libminifi/include/core/repository/FileSystemRepository.h +++ b/libminifi/include/core/repository/FileSystemRepository.h @@ -18,10 +18,10 @@ #ifndef LIBMINIFI_INCLUDE_CORE_REPOSITORY_FileSystemRepository_H_ #define LIBMINIFI_INCLUDE_CORE_REPOSITORY_FileSystemRepository_H_ -#include "core/Core.h" #include "../ContentRepository.h" -#include "properties/Configure.h" +#include "core/Core.h" #include "core/logging/LoggerConfiguration.h" +#include "properties/Configure.h" namespace org { namespace apache { namespace nifi { @@ -30,18 +30,14 @@ namespace core { namespace repository { /** - * FileSystemRepository is a content repository that stores data onto the local file system. + * FileSystemRepository is a content repository that stores data onto the local + * file system. */ class FileSystemRepository : public core::ContentRepository, public core::CoreComponent { public: FileSystemRepository(std::string name = getClassName()) - : core::CoreComponent(name), - logger_(logging::LoggerFactory::getLogger()) { - - } - virtual ~FileSystemRepository() { - - } + : core::CoreComponent(name), logger_(logging::LoggerFactory::getLogger()) {} + virtual ~FileSystemRepository() {} virtual bool initialize(const std::shared_ptr &configuration); @@ -53,14 +49,12 @@ class FileSystemRepository : public core::ContentRepository, public core::CoreCo virtual std::shared_ptr read(const std::shared_ptr &claim); - virtual bool close(const std::shared_ptr &claim) { - return remove(claim); - } + virtual std::shared_ptr mmap(const std::shared_ptr &claim, size_t mapSize, bool readOnly); + virtual bool close(const std::shared_ptr &claim) { return remove(claim); } virtual bool remove(const std::shared_ptr &claim); private: - std::shared_ptr logger_; }; diff --git a/libminifi/include/core/repository/VolatileContentRepository.h b/libminifi/include/core/repository/VolatileContentRepository.h index d79232966e..bcdcf7d3db 100644 --- a/libminifi/include/core/repository/VolatileContentRepository.h +++ b/libminifi/include/core/repository/VolatileContentRepository.h @@ -18,14 +18,15 @@ #ifndef LIBMINIFI_INCLUDE_CORE_REPOSITORY_VolatileContentRepository_H_ #define LIBMINIFI_INCLUDE_CORE_REPOSITORY_VolatileContentRepository_H_ -#include "core/Core.h" -#include "AtomicRepoEntries.h" -#include "io/AtomicEntryStream.h" #include "../ContentRepository.h" -#include "core/repository/VolatileRepository.h" -#include "properties/Configure.h" +#include "AtomicRepoEntries.h" #include "core/Connectable.h" +#include "core/Core.h" #include "core/logging/LoggerConfiguration.h" +#include "core/repository/VolatileRepository.h" +#include "io/AtomicEntryStream.h" +#include "io/AtomicEntryMemoryMap.h" +#include "properties/Configure.h" namespace org { namespace apache { namespace nifi { @@ -34,12 +35,13 @@ namespace core { namespace repository { /** - * Purpose: Stages content into a volatile area of memory. Note that when the maximum number - * of entries is consumed we will rollback a session to wait for others to be freed. + * Purpose: Stages content into a volatile area of memory. Note that when the + * maximum number of entries is consumed we will rollback a session to wait for + * others to be freed. */ -class VolatileContentRepository : public core::ContentRepository, public virtual core::repository::VolatileRepository> { +class VolatileContentRepository : public core::ContentRepository, + public virtual core::repository::VolatileRepository> { public: - static const char *minimal_locking; explicit VolatileContentRepository(std::string name = getClassName()) @@ -58,7 +60,6 @@ class VolatileContentRepository : public core::ContentRepository, public virtual } master_list_.clear(); } - } /** @@ -72,17 +73,32 @@ class VolatileContentRepository : public core::ContentRepository, public virtual */ virtual void stop(); + /** + * Generic operation which mutates the state of an object in the repo. + */ + template class U, typename... V> + std::shared_ptr mutate(const std::shared_ptr &claim, V... v); + /** * Creates writable stream. * @param claim resource claim - * @return BaseStream shared pointer that represents the stream the consumer will write to. + * @return BaseStream shared pointer that represents the stream the consumer + * will write to. */ virtual std::shared_ptr write(const std::shared_ptr &claim, bool append); + /** + * Create a passthrough memory map to the memory. + * @param map_obj the object to map + * @return BaseMemoryMap shared pointer mapped directly to the memory + */ + virtual std::shared_ptr mmap(const std::shared_ptr &claim, size_t mapSize, bool readOnly); + /** * Creates readable stream. * @param claim resource claim - * @return BaseStream shared pointer that represents the stream from which the consumer will read.. + * @return BaseStream shared pointer that represents the stream from which the + * consumer will read.. */ virtual std::shared_ptr read(const std::shared_ptr &claim); @@ -90,31 +106,29 @@ class VolatileContentRepository : public core::ContentRepository, public virtual /** * Closes the claim. - * @return whether or not the claim is associated with content stored in volatile memory. + * @return whether or not the claim is associated with content stored in + * volatile memory. */ - virtual bool close(const std::shared_ptr &claim) { - return remove(claim); - } + virtual bool close(const std::shared_ptr &claim) { return remove(claim); } /** * Closes the claim. - * @return whether or not the claim is associated with content stored in volatile memory. + * @return whether or not the claim is associated with content stored in + * volatile memory. */ virtual bool remove(const std::shared_ptr &claim); protected: - virtual void start(); virtual void run(); - template + template std::shared_ptr shared_from_parent() { return std::dynamic_pointer_cast(shared_from_this()); } private: - bool minimize_locking_; // function pointers that are associated with the claims. @@ -122,11 +136,13 @@ class VolatileContentRepository : public core::ContentRepository, public virtual std::function)> resource_claim_check_; std::function)> claim_reclaimer_; - // mutex and master list that represent a cache of Atomic entries. this exists so that we don't have to walk the atomic entry list. - // The idea is to reduce the computational complexity while keeping access as maximally lock free as we can. + // mutex and master list that represent a cache of Atomic entries. this exists + // so that we don't have to walk the atomic entry list. The idea is to reduce + // the computational complexity while keeping access as maximally lock free as + // we can. std::mutex map_mutex_; - std::map>*> master_list_; + std::map> *> master_list_; // logger std::shared_ptr logger_; diff --git a/libminifi/include/io/AtomicEntryMemoryMap.h b/libminifi/include/io/AtomicEntryMemoryMap.h new file mode 100644 index 0000000000..404ed2a43d --- /dev/null +++ b/libminifi/include/io/AtomicEntryMemoryMap.h @@ -0,0 +1,92 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIBMINIFI_INCLUDE_IO_ATOMICENTRYMEMORYMAP_H_ +#define LIBMINIFI_INCLUDE_IO_ATOMICENTRYMEMORYMAP_H_ + +#include +#include +#include "BaseMemoryMap.h" +#include "Exception.h" +#include "core/logging/LoggerConfiguration.h" +#include "core/repository/AtomicRepoEntries.h" +namespace org { +namespace apache { +namespace nifi { +namespace minifi { +namespace io { + +template +class AtomicEntryMemoryMap : public BaseMemoryMap { + public: + AtomicEntryMemoryMap(const T key, core::repository::AtomicEntry *entry, size_t map_size) + : key_(key), entry_(entry), logger_(logging::LoggerFactory::getLogger()) { + if (entry_->getValue(key, &value_)) { + value_->resize(map_size); + entry_->decrementOwnership(); + invalid_stream_ = false; + } else { + invalid_stream_ = true; + } + } + + virtual ~AtomicEntryMemoryMap() { entry_->decrementOwnership(); } + + virtual void unmap() {} + + virtual size_t getSize() { + if (invalid_stream_) { + return -1; + } + + return value_->getBufferSize(); + } + + virtual void *getData() { + if (invalid_stream_) { + return nullptr; + } + + return reinterpret_cast(value_->getBuffer()); + } + + /** + * Resize the underlying file. + * @return pointer to the remapped data + */ + virtual void *resize(size_t new_size) { + value_->resize(new_size); + return reinterpret_cast(value_->getBuffer()); + } + + protected: + T key_; + core::repository::AtomicEntry *entry_; + core::repository::RepoValue *value_; + std::atomic invalid_stream_; + + // Logger + std::shared_ptr logger_; +}; + +} /* namespace io */ +} /* namespace minifi */ +} /* namespace nifi */ +} /* namespace apache */ +} /* namespace org */ + +#endif /* LIBMINIFI_INCLUDE_IO_ATOMICENTRYMEMORYMAP_H_ */ diff --git a/libminifi/include/io/BaseMemoryMap.h b/libminifi/include/io/BaseMemoryMap.h new file mode 100644 index 0000000000..0907434ff9 --- /dev/null +++ b/libminifi/include/io/BaseMemoryMap.h @@ -0,0 +1,72 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBMINIFI_INCLUDE_IO_BASEMEMORYMAP_H_ +#define LIBMINIFI_INCLUDE_IO_BASEMEMORYMAP_H_ +#include +#include +#include "Serializable.h" + +namespace org { +namespace apache { +namespace nifi { +namespace minifi { +namespace io { + +/** + * BaseMemoryMap is a generic interface to a chunk of memory mapped to an object (e.g. repository data). + * + * ** Not intended to be thread safe as it is not intended to be shared** + * + * Extensions may be thread safe and thus shareable, but that is up to the implementation. + */ +class BaseMemoryMap { + public: + virtual ~BaseMemoryMap() {} + + /** + * Gets a the address of the mapped data. + * @return pointer to the mapped data, or nullptr if not mapped + **/ + virtual void *getData() = 0; + + /** + * Gets the size of the memory map. + * @return size of memory map + */ + virtual size_t getSize() = 0; + + /** + * Resize the underlying object. + * @return pointer to the remapped data + */ + virtual void *resize(size_t newSize) = 0; + + /** + * Explicitly unmap the memory. Memory will otherwise be unmapped at destruction. + * After this is called, getData will return nullptr. + */ + virtual void unmap() = 0; +}; + +} /* namespace io */ +} /* namespace minifi */ +} /* namespace nifi */ +} /* namespace apache */ +} /* namespace org */ +#endif /* LIBMINIFI_INCLUDE_IO_BASEMEMORYMAP_H_ */ diff --git a/libminifi/include/io/DatabaseMemoryMap.h b/libminifi/include/io/DatabaseMemoryMap.h new file mode 100644 index 0000000000..66ac6700bd --- /dev/null +++ b/libminifi/include/io/DatabaseMemoryMap.h @@ -0,0 +1,99 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBMINIFI_INCLUDE_IO_PASSTHROUGHMEMORYMAP_H_ +#define LIBMINIFI_INCLUDE_IO_PASSTHROUGHMEMORYMAP_H_ +#include +#include +#include +#include "BaseMemoryMap.h" +#include "Serializable.h" + +namespace org { +namespace apache { +namespace nifi { +namespace minifi { +namespace io { + +/** + * DatabaseMemoryMap allows access to an existing underlying memory buffer. + */ +class DatabaseMemoryMap : public BaseMemoryMap { + public: + DatabaseMemoryMap(const std::shared_ptr &claim, size_t map_size, std::function(const std::shared_ptr &)> write_fn, bool read_only) : claim_(claim), write_fn_(write_fn), read_only_(read_only) { + buf.resize(map_size); + } + + virtual ~DatabaseMemoryMap() { unmap(); } + + /** + * Gets a the address of the mapped data. + * @return pointer to the mapped data, or nullptr if not mapped + **/ + virtual void *getData() { + return reinterpret_cast(&buf[0]); + } + + /** + * Gets the size of the memory map. + * @return size of memory map + */ + virtual size_t getSize() { return buf.size(); } + + /** + * Resize the underlying buffer. + * @return pointer to the remapped data + */ + virtual void *resize(size_t new_size) { + buf.resize(new_size); + return reinterpret_cast(&buf[0]); + } + + /** + * Explicitly unmap the memory. Memory will otherwise be unmapped at + * destruction. After this is called, getData will return nullptr. + */ + virtual void unmap() { + if (!read_only_) { + commit(); + } + } + + /** + * Commits the changes in memory to the underlying DB. + */ + void commit() { + auto ws = write_fn_(claim_); + if (ws->writeData(&buf[0], getSize()) != 0) { + throw std::runtime_error("Failed to write memory map data to db: " + claim_->getContentFullPath()); + } + } + + protected: + std::vector buf; + std::shared_ptr claim_; + std::function(const std::shared_ptr &)> write_fn_; + bool read_only_; +}; + +} /* namespace io */ +} /* namespace minifi */ +} /* namespace nifi */ +} /* namespace apache */ +} /* namespace org */ +#endif /* LIBMINIFI_INCLUDE_IO_PASSTHROUGHMEMORYMAP_H_ */ diff --git a/libminifi/include/io/FileMemoryMap.h b/libminifi/include/io/FileMemoryMap.h new file mode 100644 index 0000000000..1022a3a980 --- /dev/null +++ b/libminifi/include/io/FileMemoryMap.h @@ -0,0 +1,103 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIBMINIFI_INCLUDE_IO_TLS_FILEMEMORYMAP_H_ +#define LIBMINIFI_INCLUDE_IO_TLS_FILEMEMORYMAP_H_ + +#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "BaseMemoryMap.h" +#include "core/logging/LoggerConfiguration.h" + +namespace org { +namespace apache { +namespace nifi { +namespace minifi { +namespace io { + +/** + * Purpose: File Memory Map extension. + * + * Design: Memory maps file in construction, unmaps in destruction (RAII). + * Provides an interface to get the memory address and size. + */ +class FileMemoryMap : public io::BaseMemoryMap { + public: + /** + * File Memory Map constructor that opens and maps the given file with the + * given size. + */ + FileMemoryMap(const std::string &path, size_t map_size, bool read_only); + + virtual ~FileMemoryMap() { unmap(); } + + /** + * Gets a the address of the mapped data. + * @return pointer to the mapped data, or nullptr if not mapped + **/ + virtual void *getData(); + + /** + * Gets the size of the memory map. + * @return size of memory map + */ + virtual size_t getSize(); + + /** + * Resize the underlying file. + * @return pointer to the remapped data + */ + virtual void *resize(size_t new_size); + + /** + * Explicitly unmap the memory. Memory will otherwise be unmapped at + * destruction. After this is called, getData will return nullptr. + */ + virtual void unmap(); + + protected: + + void map(const std::string &path, size_t map_size, bool read_only); + +#if defined(_POSIX_VERSION) + int unix_fd_; +#endif + size_t length_; + mio::mmap_sink rw_mmap_; + mio::mmap_source ro_mmap_; + std::string path_; + bool read_only_; + + private: + std::shared_ptr logger_; +}; + +} /* namespace io */ +} /* namespace minifi */ +} /* namespace nifi */ +} /* namespace apache */ +} /* namespace org */ + +#endif /* LIBMINIFI_INCLUDE_IO_TLS_FILEMEMORYMAP_H_ */ diff --git a/libminifi/src/core/ProcessSession.cpp b/libminifi/src/core/ProcessSession.cpp index 42271b802a..814f9b67b4 100644 --- a/libminifi/src/core/ProcessSession.cpp +++ b/libminifi/src/core/ProcessSession.cpp @@ -18,26 +18,27 @@ * limitations under the License. */ #include "core/ProcessSession.h" -#include "core/ProcessSessionReadCallback.h" #include -#include -#include +#include +#include +#include #include #include -#include +#include #include -#include +#include #include -#include -#include +#include +#include "core/ProcessSessionReadCallback.h" +#include "io/BaseMemoryMap.h" /* This implementation is only for native Windows systems. */ #if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__ #define _WINSOCKAPI_ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include #include +#include #include #pragma comment(lib, "Ws2_32.lib") #include @@ -58,16 +59,15 @@ namespace core { std::shared_ptr ProcessSession::id_generator_ = utils::IdGenerator::getIdGenerator(); -ProcessSession::~ProcessSession() { - removeReferences(); -} +ProcessSession::~ProcessSession() { removeReferences(); } std::shared_ptr ProcessSession::create() { std::map empty; auto flow_version = process_context_->getProcessorNode()->getFlowIdentifier(); - std::shared_ptr record = std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); + std::shared_ptr record = + std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); record->setSize(0); if (flow_version != nullptr) { auto flow_id = flow_version->getFlowId(); @@ -84,13 +84,12 @@ std::shared_ptr ProcessSession::create() { return record; } -void ProcessSession::add(const std::shared_ptr &record) { - _addedFlowFiles[record->getUUIDStr()] = record; -} +void ProcessSession::add(const std::shared_ptr &record) { _addedFlowFiles[record->getUUIDStr()] = record; } std::shared_ptr ProcessSession::create(const std::shared_ptr &parent) { std::map empty; - std::shared_ptr record = std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); + std::shared_ptr record = + std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); if (record) { record->setSize(0); auto flow_version = process_context_->getProcessorNode()->getFlowIdentifier(); @@ -139,7 +138,8 @@ std::shared_ptr ProcessSession::clone(const std::shared_ptr ProcessSession::cloneDuringTransfer(std::shared_ptr &parent) { std::map empty; - std::shared_ptr record = std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); + std::shared_ptr record = + std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); if (record) { auto flow_version = process_context_->getProcessorNode()->getFlowIdentifier(); @@ -183,13 +183,12 @@ std::shared_ptr ProcessSession::clone(const std::shared_ptrlog_debug("Cloned parent flow files %s to %s, with %u:%u", parent->getUUIDStr(), record->getUUIDStr(), offset, size); if (parent->getResourceClaim()) { - if ((uint64_t) (offset + size) > parent->getSize()) { + if ((uint64_t)(offset + size) > parent->getSize()) { // Set offset and size logger_->log_error("clone offset %ll and size %ll exceed parent size %llu", offset, size, parent->getSize()); // Remove the Add FlowFile for the session - std::map >::iterator it = this->_addedFlowFiles.find(record->getUUIDStr()); - if (it != this->_addedFlowFiles.end()) - this->_addedFlowFiles.erase(record->getUUIDStr()); + std::map>::iterator it = this->_addedFlowFiles.find(record->getUUIDStr()); + if (it != this->_addedFlowFiles.end()) this->_addedFlowFiles.erase(record->getUUIDStr()); return nullptr; } record->setOffset(parent->getOffset() + offset); @@ -210,7 +209,8 @@ void ProcessSession::remove(const std::shared_ptr &flow) { flow->setDeleted(true); if (flow->getResourceClaim() != nullptr) { flow->getResourceClaim()->decreaseFlowFileRecordOwnedCount(); - logger_->log_debug("Auto terminated %s %llu %s", flow->getResourceClaim()->getContentFullPath(), flow->getResourceClaim()->getFlowFileRecordOwnedCount(), flow->getUUIDStr()); + logger_->log_debug("Auto terminated %s %llu %s", flow->getResourceClaim()->getContentFullPath(), + flow->getResourceClaim()->getFlowFileRecordOwnedCount(), flow->getUUIDStr()); } else { logger_->log_debug("Flow does not contain content. no resource claim to decrement."); } @@ -236,12 +236,14 @@ void ProcessSession::removeAttribute(const std::shared_ptr &flow void ProcessSession::penalize(const std::shared_ptr &flow) { uint64_t penalization_period = process_context_->getProcessorNode()->getPenalizationPeriodMsec(); - logging::LOG_INFO(logger_) << "Penalizing " << flow->getUUIDStr() << " for " << penalization_period << "ms at " << process_context_->getProcessorNode()->getName(); + logging::LOG_INFO(logger_) << "Penalizing " << flow->getUUIDStr() << " for " << penalization_period << "ms at " + << process_context_->getProcessorNode()->getName(); flow->setPenaltyExpiration(getTimeMillis() + penalization_period); } void ProcessSession::transfer(const std::shared_ptr &flow, Relationship relationship) { - logging::LOG_INFO(logger_) << "Transferring " << flow->getUUIDStr() << " from " << process_context_->getProcessorNode()->getName() << " to relationship " << relationship.getName(); + logging::LOG_INFO(logger_) << "Transferring " << flow->getUUIDStr() << " from " << process_context_->getProcessorNode()->getName() + << " to relationship " << relationship.getName(); _transferRelationship[flow->getUUIDStr()] = relationship; } @@ -296,6 +298,57 @@ void ProcessSession::write(const std::shared_ptr &flow, OutputSt } } +void ProcessSession::mmap(const std::shared_ptr &flow, MemoryMapCallback *callback, size_t map_size, bool read_only) { + std::shared_ptr claim = std::make_shared(process_context_->getContentRepository()); + + try { + uint64_t start_time = getTimeMillis(); + claim->increaseFlowFileRecordOwnedCount(); + std::shared_ptr map = process_context_->getContentRepository()->mmap(claim, map_size, read_only); + // Call the callback to map the content + if (nullptr == map) { + claim->decreaseFlowFileRecordOwnedCount(); + rollback(); + return; + } + if (!callback->process(map)) { + claim->decreaseFlowFileRecordOwnedCount(); + rollback(); + return; + } + + flow->setSize(map->getSize()); + flow->setOffset(0); + std::shared_ptr flow_claim = flow->getResourceClaim(); + if (flow_claim != nullptr) { + // Remove the old claim + flow_claim->decreaseFlowFileRecordOwnedCount(); + flow->clearResourceClaim(); + } + flow->setResourceClaim(claim); + + map->unmap(); + std::stringstream details; + details << process_context_->getProcessorNode()->getName() << " modify flow record content " << flow->getUUIDStr(); + uint64_t endTime = getTimeMillis(); + provenance_report_->modifyContent(flow, details.str(), endTime - start_time); + } catch (std::exception &exception) { + if (flow && flow->getResourceClaim() == claim) { + flow->getResourceClaim()->decreaseFlowFileRecordOwnedCount(); + flow->clearResourceClaim(); + } + logger_->log_debug("Caught Exception %s", exception.what()); + throw; + } catch (...) { + if (flow && flow->getResourceClaim() == claim) { + flow->getResourceClaim()->decreaseFlowFileRecordOwnedCount(); + flow->clearResourceClaim(); + } + logger_->log_debug("Caught Exception during process session write"); + throw; + } +} + void ProcessSession::append(const std::shared_ptr &flow, OutputStreamCallback *callback) { std::shared_ptr claim = nullptr; if (flow->getResourceClaim() == nullptr) { @@ -316,8 +369,7 @@ void ProcessSession::append(const std::shared_ptr &flow, OutputS size_t oldPos = stream->getSize(); // this prevents an issue if we write, above, with zero length. - if (oldPos > 0) - stream->seek(oldPos + 1); + if (oldPos > 0) stream->seek(oldPos + 1); if (callback->process(stream) < 0) { rollback(); return; @@ -377,7 +429,8 @@ void ProcessSession::read(const std::shared_ptr &flow, InputStre /** * Imports a file from the data stream - * @param stream incoming data stream that contains the data to store into a file + * @param stream incoming data stream that contains the data to store into a + * file * @param flow flow file * */ @@ -423,7 +476,8 @@ void ProcessSession::importFrom(io::DataStream &stream, const std::shared_ptrsetResourceClaim(claim); - logger_->log_debug("Import offset %llu length %llu into content %s for FlowFile UUID %s", flow->getOffset(), flow->getSize(), flow->getResourceClaim()->getContentFullPath(), flow->getUUIDStr()); + logger_->log_debug("Import offset %llu length %llu into content %s for FlowFile UUID %s", flow->getOffset(), flow->getSize(), + flow->getResourceClaim()->getContentFullPath(), flow->getUUIDStr()); content_stream->closeStream(); std::stringstream details; @@ -469,19 +523,22 @@ void ProcessSession::import(std::string source, const std::shared_ptrlog_error("Seeking to %d failed for file %s (does file/filesystem support seeking?)", offset, source); + logger_->log_error( + "Seeking to %d failed for file %s (does file/filesystem support " + "seeking?)", + offset, source); invalidWrite = true; } } while (input.good()) { - input.read(reinterpret_cast(charBuffer.data()), size); + input.read(reinterpret_cast(charBuffer.data()), size); if (input) { if (stream->write(charBuffer.data(), size) < 0) { invalidWrite = true; break; } } else { - if (stream->write(reinterpret_cast(charBuffer.data()), input.gcount()) < 0) { + if (stream->write(reinterpret_cast(charBuffer.data()), input.gcount()) < 0) { invalidWrite = true; break; } @@ -498,13 +555,14 @@ void ProcessSession::import(std::string source, const std::shared_ptrsetResourceClaim(claim); - logger_->log_debug("Import offset %llu length %llu into content %s for FlowFile UUID %s", flow->getOffset(), flow->getSize(), flow->getResourceClaim()->getContentFullPath(), - flow->getUUIDStr()); + logger_->log_debug( + "Import offset %llu length %llu into content %s for FlowFile UUID " + "%s", + flow->getOffset(), flow->getSize(), flow->getResourceClaim()->getContentFullPath(), flow->getUUIDStr()); stream->closeStream(); input.close(); - if (!keepSource) - std::remove(source.c_str()); + if (!keepSource) std::remove(source.c_str()); std::stringstream details; details << process_context_->getProcessorNode()->getName() << " modify flow record content " << flow->getUUIDStr(); auto endTime = getTimeMillis(); @@ -615,8 +673,8 @@ void ProcessSession::import(const std::string& source, std::vectorsetResourceClaim(claim); claim->increaseFlowFileRecordOwnedCount(); - logger_->log_debug("Import offset %u length %u into content %s for FlowFile UUID %s", flowFile->getOffset(), flowFile->getSize(), flowFile->getResourceClaim()->getContentFullPath(), - flowFile->getUUIDStr()); + logger_->log_debug("Import offset %u length %u into content %s for FlowFile UUID %s", flowFile->getOffset(), flowFile->getSize(), + flowFile->getResourceClaim()->getContentFullPath(), flowFile->getUUIDStr()); stream->closeStream(); std::string details = process_context_->getProcessorNode()->getName() + " modify flow record content " + flowFile->getUUIDStr(); uint64_t endTime = getTimeMillis(); @@ -687,38 +745,40 @@ void ProcessSession::stash(const std::string &key, const std::shared_ptrlog_debug("Stashing content from %s to key %s", flow->getUUIDStr(), key); if (!flow->getResourceClaim()) { - logger_->log_warn("Attempted to stash content of record %s when " - "there is no resource claim", - flow->getUUIDStr()); + logger_->log_warn( + "Attempted to stash content of record %s when " + "there is no resource claim", + flow->getUUIDStr()); return; } -// Stash the claim + // Stash the claim auto claim = flow->getResourceClaim(); flow->setStashClaim(key, claim); -// Clear current claim + // Clear current claim flow->clearResourceClaim(); } void ProcessSession::restore(const std::string &key, const std::shared_ptr &flow) { logger_->log_info("Restoring content to %s from key %s", flow->getUUIDStr(), key); -// Restore the claim + // Restore the claim if (!flow->hasStashClaim(key)) { logger_->log_warn("Requested restore to record %s from unknown key %s", flow->getUUIDStr(), key); return; } -// Disown current claim if existing + // Disown current claim if existing if (flow->getResourceClaim()) { - logger_->log_warn("Restoring stashed content of record %s from key %s when there is " - "existing content; existing content will be overwritten", - flow->getUUIDStr(), key); + logger_->log_warn( + "Restoring stashed content of record %s from key %s when there is " + "existing content; existing content will be overwritten", + flow->getUUIDStr(), key); flow->releaseClaim(flow->getResourceClaim()); } -// Restore the claim + // Restore the claim auto stashClaim = flow->getStashClaim(key); flow->setResourceClaim(stashClaim); flow->clearStashClaim(key); @@ -726,15 +786,16 @@ void ProcessSession::restore(const std::string &key, const std::shared_ptr record = it.second; - if (record->isDeleted()) - continue; + if (record->isDeleted()) continue; std::map::iterator itRelationship = this->_transferRelationship.find(record->getUUIDStr()); if (itRelationship != _transferRelationship.end()) { Relationship relationship = itRelationship->second; - // Find the relationship, we need to find the connections for that relationship + // Find the relationship, we need to find the connections for that + // relationship std::set> connections = process_context_->getProcessorNode()->getOutGoingConnections(relationship.getName()); if (connections.empty()) { // No connection @@ -747,8 +808,10 @@ void ProcessSession::commit() { remove(record); } } else { - // We connections, clone the flow and assign the connection accordingly - for (std::set>::iterator itConnection = connections.begin(); itConnection != connections.end(); ++itConnection) { + // We connections, clone the flow and assign the connection + // accordingly + for (std::set>::iterator itConnection = connections.begin(); itConnection != connections.end(); + ++itConnection) { std::shared_ptr connection = *itConnection; if (itConnection == connections.begin()) { // First connection which the flow need be routed to @@ -773,12 +836,12 @@ void ProcessSession::commit() { // Do the same thing for added flow file for (const auto it : _addedFlowFiles) { std::shared_ptr record = it.second; - if (record->isDeleted()) - continue; + if (record->isDeleted()) continue; std::map::iterator itRelationship = this->_transferRelationship.find(record->getUUIDStr()); if (itRelationship != _transferRelationship.end()) { Relationship relationship = itRelationship->second; - // Find the relationship, we need to find the connections for that relationship + // Find the relationship, we need to find the connections for that + // relationship std::set> connections = process_context_->getProcessorNode()->getOutGoingConnections(relationship.getName()); if (connections.empty()) { // No connection @@ -792,8 +855,10 @@ void ProcessSession::commit() { remove(record); } } else { - // We connections, clone the flow and assign the connection accordingly - for (std::set>::iterator itConnection = connections.begin(); itConnection != connections.end(); ++itConnection) { + // We connections, clone the flow and assign the connection + // accordingly + for (std::set>::iterator itConnection = connections.begin(); itConnection != connections.end(); + ++itConnection) { std::shared_ptr connection(*itConnection); if (itConnection == connections.begin()) { // First connection which the flow need be routed to @@ -816,7 +881,8 @@ void ProcessSession::commit() { } std::shared_ptr connection = nullptr; - // Complete process the added and update flow files for the session, send the flow file to its queue + // Complete process the added and update flow files for the session, send + // the flow file to its queue for (const auto &it : _updatedFlowFiles) { std::shared_ptr record = it.second; logger_->log_trace("See %s in %s", record->getUUIDStr(), "_updatedFlowFiles"); @@ -825,8 +891,7 @@ void ProcessSession::commit() { } connection = std::static_pointer_cast(record->getConnection()); - if ((connection) != nullptr) - connection->put(record); + if ((connection) != nullptr) connection->put(record); } for (const auto &it : _addedFlowFiles) { std::shared_ptr record = it.second; @@ -835,8 +900,7 @@ void ProcessSession::commit() { continue; } connection = std::static_pointer_cast(record->getConnection()); - if ((connection) != nullptr) - connection->put(record); + if ((connection) != nullptr) connection->put(record); } // Process the clone flow files for (const auto &it : _clonedFlowFiles) { @@ -846,8 +910,7 @@ void ProcessSession::commit() { continue; } connection = std::static_pointer_cast(record->getConnection()); - if ((connection) != nullptr) - connection->put(record); + if ((connection) != nullptr) connection->put(record); } // All done @@ -878,7 +941,8 @@ void ProcessSession::rollback() { if ((connection) != nullptr) { std::shared_ptr flowf = std::static_pointer_cast(record); flowf->setSnapShot(false); - logger_->log_debug("ProcessSession rollback for %s, record %s, to connection %s", process_context_->getProcessorNode()->getName(), record->getUUIDStr(), connection->getName()); + logger_->log_debug("ProcessSession rollback for %s, record %s, to connection %s", process_context_->getProcessorNode()->getName(), + record->getUUIDStr(), connection->getName()); connection->put(record); } } @@ -909,11 +973,11 @@ std::shared_ptr ProcessSession::get() { std::shared_ptr current = std::static_pointer_cast(first); do { - std::set > expired; + std::set> expired; std::shared_ptr ret = current->poll(expired); if (expired.size() > 0) { // Remove expired flow record - for (std::set >::iterator it = expired.begin(); it != expired.end(); ++it) { + for (std::set>::iterator it = expired.begin(); it != expired.end(); ++it) { std::shared_ptr record = *it; std::stringstream details; details << process_context_->getProcessorNode()->getName() << " expire flow record " << record->getUUIDStr(); @@ -925,7 +989,8 @@ std::shared_ptr ProcessSession::get() { ret->setDeleted(false); _updatedFlowFiles[ret->getUUIDStr()] = ret; std::map empty; - std::shared_ptr snapshot = std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); + std::shared_ptr snapshot = + std::make_shared(process_context_->getFlowFileRepository(), process_context_->getContentRepository(), empty); auto flow_version = process_context_->getProcessorNode()->getFlowIdentifier(); if (flow_version != nullptr) { auto flow_id = flow_version->getFlowId(); diff --git a/libminifi/src/core/repository/FileSystemRepository.cpp b/libminifi/src/core/repository/FileSystemRepository.cpp index 4607d74138..6fe4f09d90 100644 --- a/libminifi/src/core/repository/FileSystemRepository.cpp +++ b/libminifi/src/core/repository/FileSystemRepository.cpp @@ -19,6 +19,7 @@ #include "core/repository/FileSystemRepository.h" #include #include +#include "io/FileMemoryMap.h" #include "io/FileStream.h" #include "utils/file/FileUtils.h" @@ -39,13 +40,16 @@ bool FileSystemRepository::initialize(const std::shared_ptr & utils::file::FileUtils::create_dir(directory_); return true; } -void FileSystemRepository::stop() { -} +void FileSystemRepository::stop() {} std::shared_ptr FileSystemRepository::write(const std::shared_ptr &claim, bool append) { return std::make_shared(claim->getContentFullPath(), append); } +std::shared_ptr FileSystemRepository::mmap(const std::shared_ptr &claim, size_t mapSize, bool readOnly) { + return std::make_shared(claim->getContentFullPath(), mapSize, readOnly); +} + bool FileSystemRepository::exists(const std::shared_ptr &streamId) { std::ifstream file(streamId->getContentFullPath()); return file.good(); diff --git a/libminifi/src/core/repository/VolatileContentRepository.cpp b/libminifi/src/core/repository/VolatileContentRepository.cpp index 9b5f9be920..7b9569831b 100644 --- a/libminifi/src/core/repository/VolatileContentRepository.cpp +++ b/libminifi/src/core/repository/VolatileContentRepository.cpp @@ -17,13 +17,14 @@ */ #include "core/repository/VolatileContentRepository.h" -#include "core/expect.h" #include -#include #include +#include #include -#include "utils/StringUtils.h" +#include "core/expect.h" +#include "io/AtomicEntryMemoryMap.h" #include "io/FileStream.h" +#include "utils/StringUtils.h" namespace org { namespace apache { @@ -40,10 +41,11 @@ bool VolatileContentRepository::initialize(const std::shared_ptr &con if (lhsPtr == nullptr || rhsPtr == nullptr) { return false; } - return lhsPtr->getContentFullPath() == rhsPtr->getContentFullPath();}; - resource_claim_check_ = [](std::shared_ptr claim) { - return claim->getFlowFileRecordOwnedCount() <= 0;}; - claim_reclaimer_ = [&](std::shared_ptr claim) {if (claim->getFlowFileRecordOwnedCount() <= 0) { + return lhsPtr->getContentFullPath() == rhsPtr->getContentFullPath(); + }; + resource_claim_check_ = [](std::shared_ptr claim) { return claim->getFlowFileRecordOwnedCount() <= 0; }; + claim_reclaimer_ = [&](std::shared_ptr claim) { + if (claim->getFlowFileRecordOwnedCount() <= 0) { remove(claim); } }; @@ -69,26 +71,22 @@ bool VolatileContentRepository::initialize(const std::shared_ptr &con return true; } -void VolatileContentRepository::stop() { - running_ = false; -} +void VolatileContentRepository::stop() { running_ = false; } -void VolatileContentRepository::run() { -} +void VolatileContentRepository::run() {} void VolatileContentRepository::start() { - if (this->purge_period_ <= 0) - return; - if (running_) - return; + if (this->purge_period_ <= 0) return; + if (running_) return; thread_ = std::thread(&VolatileContentRepository::run, shared_from_parent()); thread_.detach(); running_ = true; logger_->log_info("%s Repository Monitor Thread Start", getName()); } -std::shared_ptr VolatileContentRepository::write(const std::shared_ptr &claim, bool append) { - logger_->log_info("enter write for %s", claim->getContentFullPath()); +template class U, typename... V> +std::shared_ptr VolatileContentRepository::mutate(const std::shared_ptr &claim, V... v) { + logger_->log_info("enter mutate for %s", claim->getContentFullPath()); { std::lock_guard lock(map_mutex_); auto claim_check = master_list_.find(claim->getContentFullPath()); @@ -98,7 +96,7 @@ std::shared_ptr VolatileContentRepository::write(const std::shar if (ent == nullptr) { return nullptr; } - return std::make_shared>>(claim, ent); + return std::make_shared>>(claim, ent, v...); } } @@ -109,7 +107,7 @@ std::shared_ptr VolatileContentRepository::write(const std::shar std::lock_guard lock(map_mutex_); master_list_[claim->getContentFullPath()] = ent; logger_->log_info("Minimize locking, return stream for %s", claim->getContentFullPath()); - return std::make_shared>>(claim, ent); + return std::make_shared>>(claim, ent, v...); } size++; } @@ -117,19 +115,31 @@ std::shared_ptr VolatileContentRepository::write(const std::shar std::lock_guard lock(map_mutex_); auto claim_check = master_list_.find(claim->getContentFullPath()); if (claim_check != master_list_.end()) { - return std::make_shared>>(claim, claim_check->second); + return std::make_shared>>(claim, claim_check->second, v...); } else { AtomicEntry> *ent = new AtomicEntry>(¤t_size_, &max_size_); if (ent->testAndSetKey(claim, nullptr, nullptr, resource_claim_comparator_)) { master_list_[claim->getContentFullPath()] = ent; - return std::make_shared>>(claim, ent); + return std::make_shared>>(claim, ent, v...); } } } - logger_->log_info("Cannot write %s %d, returning nullptr to roll back session. Repo is either full or locked", claim->getContentFullPath(), size); + logger_->log_info( + "Cannot mutate %s %d, returning nullptr to roll back " + "session. Repo is either full or locked", + claim->getContentFullPath(), size); return nullptr; } +std::shared_ptr VolatileContentRepository::write(const std::shared_ptr &claim, bool append) { + return mutate(claim); +} + +std::shared_ptr VolatileContentRepository::mmap(const std::shared_ptr &claim, size_t mapSize, + bool readOnly) { + return mutate(claim, mapSize); +} + bool VolatileContentRepository::exists(const std::shared_ptr &claim) { std::lock_guard lock(map_mutex_); auto claim_check = master_list_.find(claim->getContentFullPath()); diff --git a/libminifi/src/io/FileMemoryMap.cpp b/libminifi/src/io/FileMemoryMap.cpp new file mode 100644 index 0000000000..44c78eef03 --- /dev/null +++ b/libminifi/src/io/FileMemoryMap.cpp @@ -0,0 +1,176 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/FileMemoryMap.h" + +#include +#include +#include +#include +#include +#include + +namespace org { +namespace apache { +namespace nifi { +namespace minifi { +namespace io { + +FileMemoryMap::FileMemoryMap(const std::string &path, size_t map_size, bool read_only) + : path_(path), length_(map_size), read_only_(read_only), logger_(logging::LoggerFactory::getLogger()) { + map(path, map_size, read_only); +} + +void FileMemoryMap::map(const std::string &path, size_t map_size, bool read_only) { + std::error_code error; + + /** + * Below, we use two different methods to ensure that the file exists and it is as big + * as the requested mapping (rw mode), or error if it isn't (ro mode). Then UNIX version is + * faster as it creates and efficiently uses only one native file handle, which is passed + * to mio for mapping. + */ + +#if defined(_POSIX_VERSION) + // open the file (UNIX-optimized version, faster than generic version) + if (!read_only) { + unix_fd_ = open(path.c_str(), O_RDWR | O_CREAT, 0600); + } else { + unix_fd_ = open(path.c_str(), O_RDONLY | O_CREAT, 0600); + } + + if (unix_fd_ < 0) { + throw std::runtime_error("Failed to open for memory mapping: " + path); + } + + // ensure file is at least as big as requested map size (UNIX-optimized version, faster than generic version) + off_t cur_size = lseek(unix_fd_, 0, SEEK_END); + + if (cur_size < 0) { + throw std::runtime_error("Failed to seek end of file for mapping: " + path); + } + + if (cur_size < map_size) { + if (!read_only) { + if (lseek(unix_fd_, map_size - 1, SEEK_SET) < 0) { + throw std::runtime_error("Failed to seek " + std::to_string(map_size - 1) + " bytes for mapping: " + path); + } + + if (write(unix_fd_, "", 1) < 0) { + close(unix_fd_); + unix_fd_ = -1; + throw std::runtime_error("Failed to write 0 byte at end of file to expand file: " + path); + } + } else { + close(unix_fd_); + unix_fd_ = -1; + throw std::runtime_error("File is smaller than map size and read-only mode is set: " + path); + } + } + + // memory map the file + if (read_only) { + ro_mmap_ = mio::make_mmap_source(unix_fd_, error); + } else { + rw_mmap_ = mio::make_mmap_sink(unix_fd_, error); + } + +#else + { + // ensure file is at least as big as requested map size (generic version) + std::ifstream ifs(path, std::ifstream::in | std::ifstream::ate | std::ifstream::binary); + std::ifstream::pos_type file_size = ifs.tellg(); + ifs.close(); + + if (file_size < 0 || (size_t)file_size < map_size) { + if (!read_only) { + logger_->log_info("Resizing file '%s' to '%d' bytes", path, map_size); + std::ofstream ofs(path, std::fstream::out | std::fstream::binary); + ofs.seekp(map_size - file_size - 1, std::ios::end); + ofs << '\0'; + } else { + throw std::runtime_error("File is smaller than map size and read-only mode is set: " + path); + } + } + } + + // memory map the file + if (read_only) { + ro_mmap_ = mio::make_mmap_source(path, error); + } else { + rw_mmap_ = mio::make_mmap_sink(path, error); + } +#endif + + if (error) { + throw std::runtime_error("Failed to memory map file '" + path + "' due to: " + error.message()); + } +} + +void FileMemoryMap::unmap() { + if (read_only_) { + ro_mmap_.unmap(); + } else { + std::error_code error; + rw_mmap_.sync(error); + + if (error) { + throw std::runtime_error("Failed to unmap memory-mapped file due to: " + error.message()); + } + + rw_mmap_.unmap(); + } + +#if defined(_POSIX_VERSION) + if (unix_fd_ > 0) { + close(unix_fd_); + } + + unix_fd_ = -1; +#endif +} + +void *FileMemoryMap::getData() { + if (read_only_) { + return &ro_mmap_[0]; + } else { + return &rw_mmap_[0]; + } +} + +size_t FileMemoryMap::getSize() { + return length_; +} + +void *FileMemoryMap::resize(size_t new_size) { + if (read_only_) { + throw std::runtime_error("Cannot resize read-only mmap"); + } + + unmap(); + map(path_, new_size, false); + length_ = new_size; + + return &rw_mmap_[0]; +} + +} // namespace io +} // namespace minifi +} // namespace nifi +} // namespace apache +} // namespace org diff --git a/libminifi/test/rocksdb-tests/DBContentRepositoryTests.cpp b/libminifi/test/rocksdb-tests/DBContentRepositoryTests.cpp index 15fb81dac2..a1ef7d7fb2 100644 --- a/libminifi/test/rocksdb-tests/DBContentRepositoryTests.cpp +++ b/libminifi/test/rocksdb-tests/DBContentRepositoryTests.cpp @@ -15,15 +15,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "../TestBase.h" #include #include +#include "../TestBase.h" #include "../unit/ProvenanceTestHelper.h" -#include "provenance/Provenance.h" +#include "DatabaseContentRepository.h" #include "FlowFileRecord.h" #include "core/Core.h" -#include "DatabaseContentRepository.h" #include "properties/Configure.h" +#include "provenance/Provenance.h" TEST_CASE("Write Claim", "[TestDBCR1]") { TestController testController; @@ -35,7 +35,6 @@ TEST_CASE("Write Claim", "[TestDBCR1]") { configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default, dir); REQUIRE(true == content_repo->initialize(configuration)); - auto claim = std::make_shared(content_repo); auto stream = content_repo->write(claim); @@ -76,7 +75,6 @@ TEST_CASE("Delete Claim", "[TestDBCR2]") { configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default, dir); REQUIRE(true == content_repo->initialize(configuration)); - auto claim = std::make_shared(content_repo); auto stream = content_repo->write(claim); @@ -151,7 +149,6 @@ TEST_CASE("Test Null Claim", "[TestDBCR4]") { configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default, dir); REQUIRE(true == content_repo->initialize(configuration)); - auto claim = std::make_shared(content_repo); auto stream = content_repo->write(nullptr); diff --git a/libminifi/test/rocksdb-tests/RocksMMTests.cpp b/libminifi/test/rocksdb-tests/RocksMMTests.cpp new file mode 100644 index 0000000000..5aa2bf814b --- /dev/null +++ b/libminifi/test/rocksdb-tests/RocksMMTests.cpp @@ -0,0 +1,107 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include "../TestBase.h" +#include "../unit/ProvenanceTestHelper.h" +#include "DatabaseContentRepository.h" +#include "FlowFileRecord.h" +#include "FlowFileRepository.h" +#include "core/Core.h" +#include "core/RepositoryFactory.h" +#include "properties/Configure.h" +#include "provenance/Provenance.h" + +TEST_CASE("DatabaseContentRepository Write/Read", "[RocksMMWriteRead]") { + TestController testController; + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + + auto configuration = std::make_shared(); + configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default, dir); + + auto dbr = std::make_shared(); + REQUIRE(true == dbr->initialize(configuration)); + + std::string write_test_string("test read val"); + + auto claim = std::make_shared(test_file, dbr); + + { + auto mm = dbr->mmap(claim, 1024, false); + REQUIRE(mm != nullptr); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string.c_str(), write_test_string.length()); + } + + { + auto mm = dbr->mmap(claim, 1024, true); + REQUIRE(mm != nullptr); + std::string read_string(reinterpret_cast(mm->getData())); + REQUIRE(read_string == "test read val"); + } +} + +TEST_CASE("DatabaseContentRepository Resize", "[RocksMMResize]") { + TestController testController; + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + + auto configuration = std::make_shared(); + configuration->set(minifi::Configure::nifi_dbcontent_repository_directory_default, dir); + + std::string write_test_string("write test"); + std::string write_test_string_resized("write testtset etirw"); + + { + auto dbr = std::make_shared(); + REQUIRE(true == dbr->initialize(configuration)); + auto claim = std::make_shared(test_file, dbr); + auto mm = dbr->mmap(claim, 11, false); + REQUIRE(mm != nullptr); + REQUIRE(mm->getSize() == 11); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string.c_str(), write_test_string.length() + 1); + } + + { + auto dbr = std::make_shared(); + REQUIRE(true == dbr->initialize(configuration)); + auto claim = std::make_shared(test_file, dbr); + auto mm = dbr->mmap(claim, 11, false); + REQUIRE(mm != nullptr); + REQUIRE(mm->getSize() == 11); + mm->resize(21); + REQUIRE(mm->getSize() == 21); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string_resized.c_str(), write_test_string_resized.length() + 1); + } + + { + auto dbr = std::make_shared(); + REQUIRE(true == dbr->initialize(configuration)); + auto claim = std::make_shared(test_file, dbr); + auto mm = dbr->mmap(claim, 21, true); + REQUIRE(mm != nullptr); + REQUIRE(mm->getSize() == 21); + std::string read_string(reinterpret_cast(mm->getData())); + REQUIRE(read_string == write_test_string_resized); + } +} diff --git a/libminifi/test/unit/MemoryMapTests.cpp b/libminifi/test/unit/MemoryMapTests.cpp new file mode 100644 index 0000000000..8136a8fd36 --- /dev/null +++ b/libminifi/test/unit/MemoryMapTests.cpp @@ -0,0 +1,181 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include "../TestBase.h" +#include "ResourceClaim.h" +#include "core/Core.h" +#include "io/FileMemoryMap.h" +#include "properties/Configure.h" + +TEST_CASE("MemoryMap Test Test", "[MemoryMapTest1]") { REQUIRE(true); } + +TEST_CASE("MemoryMap FileSystemRepository Read", "[MemoryMapTestFSRead]") { + auto fsr = std::make_shared(); + TestController testController; + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + + { + std::ofstream os(test_file); + os << "hello"; + } + + auto claim = std::make_shared(test_file, fsr); + auto mm = fsr->mmap(claim, 1024, false); + std::string read_string(reinterpret_cast(mm->getData())); + REQUIRE(read_string == "hello"); +} + +TEST_CASE("MemoryMap FileSystemRepository RO Read", "[MemoryMapTestFSRORead]") { + auto fsr = std::make_shared(); + TestController testController; + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + + { + std::ofstream os(test_file); + os << "hello"; + } + + auto claim = std::make_shared(test_file, fsr); + auto mm = fsr->mmap(claim, 5, true); + std::string read_string(reinterpret_cast(mm->getData()), 5); + REQUIRE(read_string == "hello"); +} + +TEST_CASE("MemoryMap FileSystemRepository Resize", "[MemoryMapTestFSResize]") { + auto fsr = std::make_shared(); + TestController testController; + LogTestController::getInstance().setTrace(); + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + auto claim = std::make_shared(test_file, fsr); + auto mm = fsr->mmap(claim, 11, false); + std::string write_test_string("write test"); + REQUIRE(mm->getSize() == 11); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string.c_str(), write_test_string.length()); + std::string read_string(reinterpret_cast(mm->getData())); + std::stringstream iss; + { + std::ifstream is(test_file); + iss << is.rdbuf(); + } + REQUIRE(read_string == "write test"); + + mm->resize(21); + REQUIRE(mm->getSize() == 21); + std::string write_test_string_resized("write testtset etirw"); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string_resized.c_str(), write_test_string_resized.length()); + + std::string read_string_resized(reinterpret_cast(mm->getData())); + std::stringstream iss_resized; + { + std::ifstream is(test_file); + iss_resized << is.rdbuf(); + } + REQUIRE(read_string_resized == write_test_string_resized); +} + +TEST_CASE("MemoryMap VolatileContentRepository Write/Read", "[MemoryMapTestVWriteRead]") { + auto vr = std::make_shared(); + auto c = std::make_shared(); + vr->initialize(c); + TestController testController; + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + std::string write_test_string("test read val"); + + auto claim = std::make_shared(test_file, vr); + + { + auto mm = vr->mmap(claim, 1024, false); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string.c_str(), write_test_string.length()); + } + + { + auto mm = vr->mmap(claim, 1024, false); + std::string read_string(reinterpret_cast(mm->getData())); + REQUIRE(read_string == "test read val"); + } +} + +TEST_CASE("MemoryMap VolatileContentRepository Resize", "[MemoryMapTestVResize]") { + auto vr = std::make_shared(); + auto c = std::make_shared(); + vr->initialize(c); + TestController testController; + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + std::string write_test_string("write test"); + std::string write_test_string_resized("write testtset etirw"); + + auto claim = std::make_shared(test_file, vr); + + { + auto mm = vr->mmap(claim, 11, false); + REQUIRE(mm->getSize() == 11); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string.c_str(), write_test_string.length()); + } + + { + auto mm = vr->mmap(claim, 11, false); + REQUIRE(mm->getSize() == 11); + mm->resize(21); + REQUIRE(mm->getSize() == 21); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string_resized.c_str(), write_test_string_resized.length()); + } + + { + auto mm = vr->mmap(claim, 21, false); + std::string read_string(reinterpret_cast(mm->getData())); + REQUIRE(read_string == write_test_string_resized); + } +} + +TEST_CASE("MemoryMap VolatileContentRepository RO Write/Read", "[MemoryMapTestVROWriteRead]") { + auto vr = std::make_shared(); + auto c = std::make_shared(); + vr->initialize(c); + TestController testController; + char format[] = "/tmp/testRepo.XXXXXX"; + auto dir = std::string(testController.createTempDirectory(format)); + auto test_file = dir + "/testfile"; + std::string write_test_string("test read val"); + + auto claim = std::make_shared(test_file, vr); + + { + auto mm = vr->mmap(claim, 1024, true); + std::memcpy(reinterpret_cast(mm->getData()), write_test_string.c_str(), write_test_string.length()); + } + + { + auto mm = vr->mmap(claim, 1024, true); + std::string read_string(reinterpret_cast(mm->getData())); + REQUIRE(read_string == "test read val"); + } +} diff --git a/thirdparty/benchmark-1.5.0/.clang-format b/thirdparty/benchmark-1.5.0/.clang-format new file mode 100644 index 0000000000..e7d00feaa0 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Cpp +BasedOnStyle: Google +PointerAlignment: Left +... diff --git a/thirdparty/benchmark-1.5.0/.gitignore b/thirdparty/benchmark-1.5.0/.gitignore new file mode 100644 index 0000000000..806d04c6b3 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/.gitignore @@ -0,0 +1,61 @@ +*.a +*.so +*.so.?* +*.dll +*.exe +*.dylib +*.cmake +!/cmake/*.cmake +!/test/AssemblyTests.cmake +*~ +*.pyc +__pycache__ + +# lcov +*.lcov +/lcov + +# cmake files. +/Testing +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake + +# makefiles. +Makefile + +# in-source build. +bin/ +lib/ +/test/*_test + +# exuberant ctags. +tags + +# YouCompleteMe configuration. +.ycm_extra_conf.pyc + +# ninja generated files. +.ninja_deps +.ninja_log +build.ninja +install_manifest.txt +rules.ninja + +# bazel output symlinks. +bazel-* + +# out-of-source build top-level folders. +build/ +_build/ +build*/ + +# in-source dependencies +/googletest/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +CMakeSettings.json + +# Visual Studio Code cache/options directory +.vscode/ diff --git a/thirdparty/benchmark-1.5.0/.travis-libcxx-setup.sh b/thirdparty/benchmark-1.5.0/.travis-libcxx-setup.sh new file mode 100644 index 0000000000..a591743c6a --- /dev/null +++ b/thirdparty/benchmark-1.5.0/.travis-libcxx-setup.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Install a newer CMake version +curl -sSL https://cmake.org/files/v3.6/cmake-3.6.1-Linux-x86_64.sh -o install-cmake.sh +chmod +x install-cmake.sh +sudo ./install-cmake.sh --prefix=/usr/local --skip-license + +# Checkout LLVM sources +git clone --depth=1 https://github.com/llvm-mirror/llvm.git llvm-source +git clone --depth=1 https://github.com/llvm-mirror/libcxx.git llvm-source/projects/libcxx +git clone --depth=1 https://github.com/llvm-mirror/libcxxabi.git llvm-source/projects/libcxxabi + +# Setup libc++ options +if [ -z "$BUILD_32_BITS" ]; then + export BUILD_32_BITS=OFF && echo disabling 32 bit build +fi + +# Build and install libc++ (Use unstable ABI for better sanitizer coverage) +mkdir llvm-build && cd llvm-build +cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr \ + -DLIBCXX_ABI_UNSTABLE=ON \ + -DLLVM_USE_SANITIZER=${LIBCXX_SANITIZER} \ + -DLLVM_BUILD_32_BITS=${BUILD_32_BITS} \ + ../llvm-source +make cxx -j2 +sudo make install-cxxabi install-cxx +cd ../ diff --git a/thirdparty/benchmark-1.5.0/.travis.yml b/thirdparty/benchmark-1.5.0/.travis.yml new file mode 100644 index 0000000000..6b6cfc7046 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/.travis.yml @@ -0,0 +1,235 @@ +sudo: required +dist: trusty +language: cpp + +env: + global: + - /usr/local/bin:$PATH + +matrix: + include: + - compiler: gcc + addons: + apt: + packages: + - lcov + env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Coverage + - compiler: gcc + env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Debug + - compiler: gcc + env: COMPILER=g++ C_COMPILER=gcc BUILD_TYPE=Release + - compiler: gcc + addons: + apt: + packages: + - g++-multilib + - libc6:i386 + env: + - COMPILER=g++ + - C_COMPILER=gcc + - BUILD_TYPE=Debug + - BUILD_32_BITS=ON + - EXTRA_FLAGS="-m32" + - compiler: gcc + addons: + apt: + packages: + - g++-multilib + - libc6:i386 + env: + - COMPILER=g++ + - C_COMPILER=gcc + - BUILD_TYPE=Release + - BUILD_32_BITS=ON + - EXTRA_FLAGS="-m32" + - compiler: gcc + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=g++-6 C_COMPILER=gcc-6 BUILD_TYPE=Debug + - ENABLE_SANITIZER=1 + - EXTRA_FLAGS="-fno-omit-frame-pointer -g -O2 -fsanitize=undefined,address -fuse-ld=gold" + - compiler: clang + env: COMPILER=clang++ C_COMPILER=clang BUILD_TYPE=Debug + - compiler: clang + env: COMPILER=clang++ C_COMPILER=clang BUILD_TYPE=Release + # Clang w/ libc++ + - compiler: clang + dist: xenial + addons: + apt: + packages: + clang-3.8 + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug + - LIBCXX_BUILD=1 + - EXTRA_CXX_FLAGS="-stdlib=libc++" + - compiler: clang + dist: xenial + addons: + apt: + packages: + clang-3.8 + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Release + - LIBCXX_BUILD=1 + - EXTRA_CXX_FLAGS="-stdlib=libc++" + # Clang w/ 32bit libc++ + - compiler: clang + dist: xenial + addons: + apt: + packages: + - clang-3.8 + - g++-multilib + - libc6:i386 + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug + - LIBCXX_BUILD=1 + - BUILD_32_BITS=ON + - EXTRA_FLAGS="-m32" + - EXTRA_CXX_FLAGS="-stdlib=libc++" + # Clang w/ 32bit libc++ + - compiler: clang + dist: xenial + addons: + apt: + packages: + - clang-3.8 + - g++-multilib + - libc6:i386 + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Release + - LIBCXX_BUILD=1 + - BUILD_32_BITS=ON + - EXTRA_FLAGS="-m32" + - EXTRA_CXX_FLAGS="-stdlib=libc++" + # Clang w/ libc++, ASAN, UBSAN + - compiler: clang + dist: xenial + addons: + apt: + packages: + clang-3.8 + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug + - LIBCXX_BUILD=1 LIBCXX_SANITIZER="Undefined;Address" + - ENABLE_SANITIZER=1 + - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=undefined,address -fno-sanitize-recover=all" + - EXTRA_CXX_FLAGS="-stdlib=libc++" + - UBSAN_OPTIONS=print_stacktrace=1 + # Clang w/ libc++ and MSAN + - compiler: clang + dist: xenial + addons: + apt: + packages: + clang-3.8 + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=Debug + - LIBCXX_BUILD=1 LIBCXX_SANITIZER=MemoryWithOrigins + - ENABLE_SANITIZER=1 + - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=memory -fsanitize-memory-track-origins" + - EXTRA_CXX_FLAGS="-stdlib=libc++" + # Clang w/ libc++ and MSAN + - compiler: clang + dist: xenial + addons: + apt: + packages: + clang-3.8 + env: + - INSTALL_GCC6_FROM_PPA=1 + - COMPILER=clang++-3.8 C_COMPILER=clang-3.8 BUILD_TYPE=RelWithDebInfo + - LIBCXX_BUILD=1 LIBCXX_SANITIZER=Thread + - ENABLE_SANITIZER=1 + - EXTRA_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=thread -fno-sanitize-recover=all" + - EXTRA_CXX_FLAGS="-stdlib=libc++" + - os: osx + osx_image: xcode8.3 + compiler: clang + env: + - COMPILER=clang++ BUILD_TYPE=Debug + - os: osx + osx_image: xcode8.3 + compiler: clang + env: + - COMPILER=clang++ BUILD_TYPE=Release + - os: osx + osx_image: xcode8.3 + compiler: clang + env: + - COMPILER=clang++ + - BUILD_TYPE=Release + - BUILD_32_BITS=ON + - EXTRA_FLAGS="-m32" + - os: osx + osx_image: xcode8.3 + compiler: gcc + env: + - COMPILER=g++-7 C_COMPILER=gcc-7 BUILD_TYPE=Debug + +before_script: + - if [ -n "${LIBCXX_BUILD}" ]; then + source .travis-libcxx-setup.sh; + fi + - if [ -n "${ENABLE_SANITIZER}" ]; then + export EXTRA_OPTIONS="-DBENCHMARK_ENABLE_ASSEMBLY_TESTS=OFF"; + else + export EXTRA_OPTIONS=""; + fi + - mkdir -p build && cd build + +before_install: + - if [ -z "$BUILD_32_BITS" ]; then + export BUILD_32_BITS=OFF && echo disabling 32 bit build; + fi + - if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then + sudo add-apt-repository -y "ppa:ubuntu-toolchain-r/test"; + sudo apt-get update --option Acquire::Retries=100 --option Acquire::http::Timeout="60"; + fi + +install: + - if [ -n "${INSTALL_GCC6_FROM_PPA}" ]; then + travis_wait sudo -E apt-get -yq --no-install-suggests --no-install-recommends install g++-6; + fi + - if [ "${TRAVIS_OS_NAME}" == "linux" -a "${BUILD_32_BITS}" == "OFF" ]; then + travis_wait sudo -E apt-get -y --no-install-suggests --no-install-recommends install llvm-3.9-tools; + sudo cp /usr/lib/llvm-3.9/bin/FileCheck /usr/local/bin/; + fi + - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then + PATH=~/.local/bin:${PATH}; + pip install --user --upgrade pip; + travis_wait pip install --user cpp-coveralls; + fi + - if [ "${C_COMPILER}" == "gcc-7" -a "${TRAVIS_OS_NAME}" == "osx" ]; then + rm -f /usr/local/include/c++; + brew update; + travis_wait brew install gcc@7; + fi + - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then + sudo apt-get update -qq; + sudo apt-get install -qq unzip cmake3; + wget https://github.com/bazelbuild/bazel/releases/download/0.10.1/bazel-0.10.1-installer-linux-x86_64.sh --output-document bazel-installer.sh; + travis_wait sudo bash bazel-installer.sh; + fi + - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then + curl -L -o bazel-installer.sh https://github.com/bazelbuild/bazel/releases/download/0.10.1/bazel-0.10.1-installer-darwin-x86_64.sh; + travis_wait sudo bash bazel-installer.sh; + fi + +script: + - cmake -DCMAKE_C_COMPILER=${C_COMPILER} -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_C_FLAGS="${EXTRA_FLAGS}" -DCMAKE_CXX_FLAGS="${EXTRA_FLAGS} ${EXTRA_CXX_FLAGS}" -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON -DBENCHMARK_BUILD_32_BITS=${BUILD_32_BITS} ${EXTRA_OPTIONS} .. + - make + - ctest -C ${BUILD_TYPE} --output-on-failure + - bazel test -c dbg --define google_benchmark.have_regex=posix --announce_rc --verbose_failures --test_output=errors --keep_going //test/... + +after_success: + - if [ "${BUILD_TYPE}" == "Coverage" -a "${TRAVIS_OS_NAME}" == "linux" ]; then + coveralls --include src --include include --gcov-options '\-lp' --root .. --build-root .; + fi diff --git a/thirdparty/benchmark-1.5.0/.ycm_extra_conf.py b/thirdparty/benchmark-1.5.0/.ycm_extra_conf.py new file mode 100644 index 0000000000..5649ddcc74 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/.ycm_extra_conf.py @@ -0,0 +1,115 @@ +import os +import ycm_core + +# These are the compilation flags that will be used in case there's no +# compilation database set (by default, one is not set). +# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. +flags = [ +'-Wall', +'-Werror', +'-pedantic-errors', +'-std=c++0x', +'-fno-strict-aliasing', +'-O3', +'-DNDEBUG', +# ...and the same thing goes for the magic -x option which specifies the +# language that the files to be compiled are written in. This is mostly +# relevant for c++ headers. +# For a C project, you would set this to 'c' instead of 'c++'. +'-x', 'c++', +'-I', 'include', +'-isystem', '/usr/include', +'-isystem', '/usr/local/include', +] + + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = '' + +if os.path.exists( compilation_database_folder ): + database = ycm_core.CompilationDatabase( compilation_database_folder ) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cc' ] + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.abspath( __file__ ) ) + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile( filename, **kwargs ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile( filename ) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { + 'flags': final_flags, + 'do_cache': True + } diff --git a/thirdparty/benchmark-1.5.0/AUTHORS b/thirdparty/benchmark-1.5.0/AUTHORS new file mode 100644 index 0000000000..912cbbc13c --- /dev/null +++ b/thirdparty/benchmark-1.5.0/AUTHORS @@ -0,0 +1,51 @@ +# This is the official list of benchmark authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. +# +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. +# +# Please keep the list sorted. + +Albert Pretorius +Alex Steele +Andriy Berestovskyy +Arne Beer +Carto +Christopher Seymour +Daniel Harvey +David Coeurjolly +Deniz Evrenci +Dirac Research +Dominik Czarnota +Eric Fiselier +Eugene Zhuk +Evgeny Safronov +Federico Ficarelli +Felix Homann +Google Inc. +International Business Machines Corporation +Ismael Jimenez Martinez +Jern-Kuan Leong +JianXiong Zhou +Joao Paulo Magalhaes +Jussi Knuuttila +Kaito Udagawa +Kishan Kumar +Lei Xu +Matt Clarkson +Maxim Vafin +MongoDB Inc. +Nick Hutchinson +Oleksandr Sochka +Ori Livneh +Paul Redmond +Radoslav Yovchev +Roman Lebedev +Shuo Chen +Steinar H. Gunderson +Stripe, Inc. +Yixuan Qiu +Yusuke Suzuki +Zbigniew Skowron diff --git a/thirdparty/benchmark-1.5.0/BUILD.bazel b/thirdparty/benchmark-1.5.0/BUILD.bazel new file mode 100644 index 0000000000..6ee69f2907 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/BUILD.bazel @@ -0,0 +1,42 @@ +licenses(["notice"]) + +config_setting( + name = "windows", + values = { + "cpu": "x64_windows", + }, + visibility = [":__subpackages__"], +) + +cc_library( + name = "benchmark", + srcs = glob( + [ + "src/*.cc", + "src/*.h", + ], + exclude = ["src/benchmark_main.cc"], + ), + hdrs = ["include/benchmark/benchmark.h"], + linkopts = select({ + ":windows": ["-DEFAULTLIB:shlwapi.lib"], + "//conditions:default": ["-pthread"], + }), + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) + +cc_library( + name = "benchmark_main", + srcs = ["src/benchmark_main.cc"], + hdrs = ["include/benchmark/benchmark.h"], + strip_include_prefix = "include", + visibility = ["//visibility:public"], + deps = [":benchmark"], +) + +cc_library( + name = "benchmark_internal_headers", + hdrs = glob(["src/*.h"]), + visibility = ["//test:__pkg__"], +) diff --git a/thirdparty/benchmark-1.5.0/CMakeLists.txt b/thirdparty/benchmark-1.5.0/CMakeLists.txt new file mode 100644 index 0000000000..9db1361212 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/CMakeLists.txt @@ -0,0 +1,276 @@ +cmake_minimum_required (VERSION 3.5.1) + +foreach(p + CMP0048 # OK to clear PROJECT_VERSION on project() + CMP0054 # CMake 3.1 + CMP0056 # export EXE_LINKER_FLAGS to try_run + CMP0057 # Support no if() IN_LIST operator + CMP0063 # Honor visibility properties for all targets + ) + if(POLICY ${p}) + cmake_policy(SET ${p} NEW) + endif() +endforeach() + +project (benchmark CXX) + +option(BENCHMARK_ENABLE_TESTING "Enable testing of the benchmark library." ON) +option(BENCHMARK_ENABLE_EXCEPTIONS "Enable the use of exceptions in the benchmark library." ON) +option(BENCHMARK_ENABLE_LTO "Enable link time optimisation of the benchmark library." OFF) +option(BENCHMARK_USE_LIBCXX "Build and test using libc++ as the standard library." OFF) +if(NOT MSVC) + option(BENCHMARK_BUILD_32_BITS "Build a 32 bit version of the library." OFF) +else() + set(BENCHMARK_BUILD_32_BITS OFF CACHE BOOL "Build a 32 bit version of the library - unsupported when using MSVC)" FORCE) +endif() +option(BENCHMARK_ENABLE_INSTALL "Enable installation of benchmark. (Projects embedding benchmark may want to turn this OFF.)" ON) + +# Allow unmet dependencies to be met using CMake's ExternalProject mechanics, which +# may require downloading the source code. +option(BENCHMARK_DOWNLOAD_DEPENDENCIES "Allow the downloading and in-tree building of unmet dependencies" OFF) + +# This option can be used to disable building and running unit tests which depend on gtest +# in cases where it is not possible to build or find a valid version of gtest. +option(BENCHMARK_ENABLE_GTEST_TESTS "Enable building the unit tests which depend on gtest" ON) + +set(ENABLE_ASSEMBLY_TESTS_DEFAULT OFF) +function(should_enable_assembly_tests) + if(CMAKE_BUILD_TYPE) + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_LOWER) + if (${CMAKE_BUILD_TYPE_LOWER} MATCHES "coverage") + # FIXME: The --coverage flag needs to be removed when building assembly + # tests for this to work. + return() + endif() + endif() + if (MSVC) + return() + elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + return() + elseif(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) + # FIXME: Make these work on 32 bit builds + return() + elseif(BENCHMARK_BUILD_32_BITS) + # FIXME: Make these work on 32 bit builds + return() + endif() + find_program(LLVM_FILECHECK_EXE FileCheck) + if (LLVM_FILECHECK_EXE) + set(LLVM_FILECHECK_EXE "${LLVM_FILECHECK_EXE}" CACHE PATH "llvm filecheck" FORCE) + message(STATUS "LLVM FileCheck Found: ${LLVM_FILECHECK_EXE}") + else() + message(STATUS "Failed to find LLVM FileCheck") + return() + endif() + set(ENABLE_ASSEMBLY_TESTS_DEFAULT ON PARENT_SCOPE) +endfunction() +should_enable_assembly_tests() + +# This option disables the building and running of the assembly verification tests +option(BENCHMARK_ENABLE_ASSEMBLY_TESTS "Enable building and running the assembly tests" + ${ENABLE_ASSEMBLY_TESTS_DEFAULT}) + +# Make sure we can import out CMake functions +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + + +# Read the git tags to determine the project version +include(GetGitVersion) +get_git_version(GIT_VERSION) + +# Tell the user what versions we are using +string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" VERSION ${GIT_VERSION}) +message(STATUS "Version: ${VERSION}") + +# The version of the libraries +set(GENERIC_LIB_VERSION ${VERSION}) +string(SUBSTRING ${VERSION} 0 1 GENERIC_LIB_SOVERSION) + +# Import our CMake modules +include(CheckCXXCompilerFlag) +include(AddCXXCompilerFlag) +include(CXXFeatureCheck) + +if (BENCHMARK_BUILD_32_BITS) + add_required_cxx_compiler_flag(-m32) +endif() + +if (MSVC) + # Turn compiler warnings up to 11 + string(REGEX REPLACE "[-/]W[1-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + + if (NOT BENCHMARK_ENABLE_EXCEPTIONS) + add_cxx_compiler_flag(-EHs-) + add_cxx_compiler_flag(-EHa-) + add_definitions(-D_HAS_EXCEPTIONS=0) + endif() + # Link time optimisation + if (BENCHMARK_ENABLE_LTO) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") + + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /GL") + string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}") + set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}") + set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + string(REGEX REPLACE "[-/]INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG") + + set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /GL") + set(CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL "${CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL} /LTCG") + set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} /LTCG") + endif() +else() + # Try and enable C++11. Don't use C++14 because it doesn't work in some + # configurations. + add_cxx_compiler_flag(-std=c++11) + if (NOT HAVE_CXX_FLAG_STD_CXX11) + add_cxx_compiler_flag(-std=c++0x) + endif() + + # Turn compiler warnings up to 11 + add_cxx_compiler_flag(-Wall) + add_cxx_compiler_flag(-Wextra) + add_cxx_compiler_flag(-Wshadow) + add_cxx_compiler_flag(-Werror RELEASE) + add_cxx_compiler_flag(-Werror RELWITHDEBINFO) + add_cxx_compiler_flag(-Werror MINSIZEREL) + add_cxx_compiler_flag(-pedantic) + add_cxx_compiler_flag(-pedantic-errors) + add_cxx_compiler_flag(-Wshorten-64-to-32) + add_cxx_compiler_flag(-fstrict-aliasing) + # Disable warnings regarding deprecated parts of the library while building + # and testing those parts of the library. + add_cxx_compiler_flag(-Wno-deprecated-declarations) + if (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") + # Intel silently ignores '-Wno-deprecated-declarations', + # warning no. 1786 must be explicitly disabled. + # See #631 for rationale. + add_cxx_compiler_flag(-wd1786) + endif() + # Disable deprecation warnings for release builds (when -Werror is enabled). + add_cxx_compiler_flag(-Wno-deprecated RELEASE) + add_cxx_compiler_flag(-Wno-deprecated RELWITHDEBINFO) + add_cxx_compiler_flag(-Wno-deprecated MINSIZEREL) + if (NOT BENCHMARK_ENABLE_EXCEPTIONS) + add_cxx_compiler_flag(-fno-exceptions) + endif() + + if (HAVE_CXX_FLAG_FSTRICT_ALIASING) + if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel") #ICC17u2: Many false positives for Wstrict-aliasing + add_cxx_compiler_flag(-Wstrict-aliasing) + endif() + endif() + # ICC17u2: overloaded virtual function "benchmark::Fixture::SetUp" is only partially overridden + # (because of deprecated overload) + add_cxx_compiler_flag(-wd654) + add_cxx_compiler_flag(-Wthread-safety) + if (HAVE_CXX_FLAG_WTHREAD_SAFETY) + cxx_feature_check(THREAD_SAFETY_ATTRIBUTES) + endif() + + # On most UNIX like platforms g++ and clang++ define _GNU_SOURCE as a + # predefined macro, which turns on all of the wonderful libc extensions. + # However g++ doesn't do this in Cygwin so we have to define it ourselfs + # since we depend on GNU/POSIX/BSD extensions. + if (CYGWIN) + add_definitions(-D_GNU_SOURCE=1) + endif() + + if (QNXNTO) + add_definitions(-D_QNX_SOURCE) + endif() + + # Link time optimisation + if (BENCHMARK_ENABLE_LTO) + add_cxx_compiler_flag(-flto) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + find_program(GCC_AR gcc-ar) + if (GCC_AR) + set(CMAKE_AR ${GCC_AR}) + endif() + find_program(GCC_RANLIB gcc-ranlib) + if (GCC_RANLIB) + set(CMAKE_RANLIB ${GCC_RANLIB}) + endif() + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + include(llvm-toolchain) + endif() + endif() + + # Coverage build type + set(BENCHMARK_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG}" + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE) + set(BENCHMARK_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_DEBUG}" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE) + set(BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE) + mark_as_advanced( + BENCHMARK_CXX_FLAGS_COVERAGE + BENCHMARK_EXE_LINKER_FLAGS_COVERAGE + BENCHMARK_SHARED_LINKER_FLAGS_COVERAGE) + set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel Coverage.") + add_cxx_compiler_flag(--coverage COVERAGE) +endif() + +if (BENCHMARK_USE_LIBCXX) + if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + add_cxx_compiler_flag(-stdlib=libc++) + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR + "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") + add_cxx_compiler_flag(-nostdinc++) + message(WARNING "libc++ header path must be manually specified using CMAKE_CXX_FLAGS") + # Adding -nodefaultlibs directly to CMAKE__LINKER_FLAGS will break + # configuration checks such as 'find_package(Threads)' + list(APPEND BENCHMARK_CXX_LINKER_FLAGS -nodefaultlibs) + # -lc++ cannot be added directly to CMAKE__LINKER_FLAGS because + # linker flags appear before all linker inputs and -lc++ must appear after. + list(APPEND BENCHMARK_CXX_LIBRARIES c++) + else() + message(FATAL_ERROR "-DBENCHMARK_USE_LIBCXX:BOOL=ON is not supported for compiler") + endif() +endif(BENCHMARK_USE_LIBCXX) + +# C++ feature checks +# Determine the correct regular expression engine to use +cxx_feature_check(STD_REGEX) +cxx_feature_check(GNU_POSIX_REGEX) +cxx_feature_check(POSIX_REGEX) +if(NOT HAVE_STD_REGEX AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX) + message(FATAL_ERROR "Failed to determine the source files for the regular expression backend") +endif() +if (NOT BENCHMARK_ENABLE_EXCEPTIONS AND HAVE_STD_REGEX + AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX) + message(WARNING "Using std::regex with exceptions disabled is not fully supported") +endif() +cxx_feature_check(STEADY_CLOCK) +# Ensure we have pthreads +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +# Set up directories +include_directories(${PROJECT_SOURCE_DIR}/include) + +# Build the targets +add_subdirectory(src) + +if (BENCHMARK_ENABLE_TESTING) + enable_testing() + if (BENCHMARK_ENABLE_GTEST_TESTS AND + NOT (TARGET gtest AND TARGET gtest_main AND + TARGET gmock AND TARGET gmock_main)) + include(GoogleTest) + endif() + add_subdirectory(test) +endif() diff --git a/thirdparty/benchmark-1.5.0/CONTRIBUTING.md b/thirdparty/benchmark-1.5.0/CONTRIBUTING.md new file mode 100644 index 0000000000..43de4c9d47 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# How to contribute # + +We'd love to accept your patches and contributions to this project. There are +a just a few small guidelines you need to follow. + + +## Contributor License Agreement ## + +Contributions to any Google project must be accompanied by a Contributor +License Agreement. This is not a copyright **assignment**, it simply gives +Google permission to use and redistribute your contributions as part of the +project. + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual + CLA][]. + + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA][]. + +You generally only need to submit a CLA once, so if you've already submitted +one (even if it was for a different project), you probably don't need to do it +again. + +[individual CLA]: https://developers.google.com/open-source/cla/individual +[corporate CLA]: https://developers.google.com/open-source/cla/corporate + +Once your CLA is submitted (or if you already submitted one for +another Google project), make a commit adding yourself to the +[AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part +of your first [pull request][]. + +[AUTHORS]: AUTHORS +[CONTRIBUTORS]: CONTRIBUTORS + + +## Submitting a patch ## + + 1. It's generally best to start by opening a new issue describing the bug or + feature you're intending to fix. Even if you think it's relatively minor, + it's helpful to know what people are working on. Mention in the initial + issue that you are planning to work on that bug or feature so that it can + be assigned to you. + + 1. Follow the normal process of [forking][] the project, and setup a new + branch to work in. It's important that each group of changes be done in + separate branches in order to ensure that a pull request only includes the + commits related to that bug or feature. + + 1. Do your best to have [well-formed commit messages][] for each change. + This provides consistency throughout the project, and ensures that commit + messages are able to be formatted properly by various git tools. + + 1. Finally, push the commits to your fork and submit a [pull request][]. + +[forking]: https://help.github.com/articles/fork-a-repo +[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[pull request]: https://help.github.com/articles/creating-a-pull-request diff --git a/thirdparty/benchmark-1.5.0/CONTRIBUTORS b/thirdparty/benchmark-1.5.0/CONTRIBUTORS new file mode 100644 index 0000000000..b680efc8c4 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/CONTRIBUTORS @@ -0,0 +1,72 @@ +# People who have agreed to one of the CLAs and can contribute patches. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# https://developers.google.com/open-source/cla/individual +# https://developers.google.com/open-source/cla/corporate +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. +# +# Names should be added to this file as: +# Name +# +# Please keep the list sorted. + +Albert Pretorius +Alex Steele +Andriy Berestovskyy +Arne Beer +Billy Robert O'Neal III +Chris Kennelly +Christopher Seymour +Cyrille Faucheux +Daniel Harvey +David Coeurjolly +Deniz Evrenci +Dominic Hamon +Dominik Czarnota +Eric Fiselier +Eugene Zhuk +Evgeny Safronov +Federico Ficarelli +Felix Homann +Hannes Hauswedell +Ismael Jimenez Martinez +Jern-Kuan Leong +JianXiong Zhou +Joao Paulo Magalhaes +John Millikin +Jussi Knuuttila +Kai Wolf +Kishan Kumar +Kaito Udagawa +Lei Xu +Matt Clarkson +Maxim Vafin +Nick Hutchinson +Oleksandr Sochka +Ori Livneh +Pascal Leroy +Paul Redmond +Pierre Phaneuf +Radoslav Yovchev +Raul Marin +Ray Glover +Robert Guo +Roman Lebedev +Shuo Chen +Tobias Ulvgård +Tom Madams +Yixuan Qiu +Yusuke Suzuki +Zbigniew Skowron diff --git a/thirdparty/benchmark-1.5.0/LICENSE b/thirdparty/benchmark-1.5.0/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/thirdparty/benchmark-1.5.0/README.md b/thirdparty/benchmark-1.5.0/README.md new file mode 100644 index 0000000000..45e4158843 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/README.md @@ -0,0 +1,1179 @@ +# Benchmark +[![Build Status](https://travis-ci.org/google/benchmark.svg?branch=master)](https://travis-ci.org/google/benchmark) +[![Build status](https://ci.appveyor.com/api/projects/status/u0qsyp7t1tk7cpxs/branch/master?svg=true)](https://ci.appveyor.com/project/google/benchmark/branch/master) +[![Coverage Status](https://coveralls.io/repos/google/benchmark/badge.svg)](https://coveralls.io/r/google/benchmark) +[![slackin](https://slackin-iqtfqnpzxd.now.sh/badge.svg)](https://slackin-iqtfqnpzxd.now.sh/) + + +A library to benchmark code snippets, similar to unit tests. Example: + +```c++ +#include + +static void BM_SomeFunction(benchmark::State& state) { + // Perform setup here + for (auto _ : state) { + // This code gets timed + SomeFunction(); + } +} +// Register the function as a benchmark +BENCHMARK(BM_SomeFunction); +// Run the benchmark +BENCHMARK_MAIN(); +``` + +To get started, see [Requirements](#requirements) and +[Installation](#installation). See [Usage](#usage) for a full example and the +[User Guide](#user-guide) for a more comprehensive feature overview. + +It may also help to read the [Google Test documentation](https://github.com/google/googletest/blob/master/googletest/docs/primer.md) +as some of the structural aspects of the APIs are similar. + +### Resources + +[Discussion group](https://groups.google.com/d/forum/benchmark-discuss) + +IRC channel: [freenode](https://freenode.net) #googlebenchmark + +[Additional Tooling Documentation](docs/tools.md) + +[Assembly Testing Documentation](docs/AssemblyTests.md) + +## Requirements + +The library can be used with C++03. However, it requires C++11 to build, +including compiler and standard library support. + +The following minimum versions are required to build the library: + +* GCC 4.8 +* Clang 3.4 +* Visual Studio 2013 +* Intel 2015 Update 1 + +## Installation + +This describes the installation process using cmake. As pre-requisites, you'll +need git and cmake installed. + +_See [dependencies.md](dependencies.md) for more details regarding supported +versions of build tools._ + +```bash +# Check out the library. +$ git clone https://github.com/google/benchmark.git +# Benchmark requires Google Test as a dependency. Add the source tree as a subdirectory. +$ git clone https://github.com/google/googletest.git benchmark/googletest +# Make a build directory to place the build output. +$ mkdir build && cd build +# Generate a Makefile with cmake. +# Use cmake -G to generate a different file type. +$ cmake ../benchmark +# Build the library. +$ make +``` +This builds the `benchmark` and `benchmark_main` libraries and tests. +On a unix system, the build directory should now look something like this: + +``` +/benchmark +/build + /src + /libbenchmark.a + /libbenchmark_main.a + /test + ... +``` + +Next, you can run the tests to check the build. + +```bash +$ make test +``` + +If you want to install the library globally, also run: + +``` +sudo make install +``` + +Note that Google Benchmark requires Google Test to build and run the tests. This +dependency can be provided two ways: + +* Checkout the Google Test sources into `benchmark/googletest` as above. +* Otherwise, if `-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON` is specified during + configuration, the library will automatically download and build any required + dependencies. + +If you do not wish to build and run the tests, add `-DBENCHMARK_ENABLE_GTEST_TESTS=OFF` +to `CMAKE_ARGS`. + +### Debug vs Release + +By default, benchmark builds as a debug library. You will see a warning in the +output when this is the case. To build it as a release library instead, use: + +``` +cmake -DCMAKE_BUILD_TYPE=Release +``` + +To enable link-time optimisation, use + +``` +cmake -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_LTO=true +``` + +If you are using gcc, you might need to set `GCC_AR` and `GCC_RANLIB` cmake +cache variables, if autodetection fails. + +If you are using clang, you may need to set `LLVMAR_EXECUTABLE`, +`LLVMNM_EXECUTABLE` and `LLVMRANLIB_EXECUTABLE` cmake cache variables. + + +### Stable and Experimental Library Versions + +The main branch contains the latest stable version of the benchmarking library; +the API of which can be considered largely stable, with source breaking changes +being made only upon the release of a new major version. + +Newer, experimental, features are implemented and tested on the +[`v2` branch](https://github.com/google/benchmark/tree/v2). Users who wish +to use, test, and provide feedback on the new features are encouraged to try +this branch. However, this branch provides no stability guarantees and reserves +the right to change and break the API at any time. + +## Usage +### Basic usage +Define a function that executes the code to measure, register it as a benchmark +function using the `BENCHMARK` macro, and ensure an appropriate `main` function +is available: + +```c++ +#include + +static void BM_StringCreation(benchmark::State& state) { + for (auto _ : state) + std::string empty_string; +} +// Register the function as a benchmark +BENCHMARK(BM_StringCreation); + +// Define another benchmark +static void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + for (auto _ : state) + std::string copy(x); +} +BENCHMARK(BM_StringCopy); + +BENCHMARK_MAIN(); +``` + +To run the benchmark, compile and link against the `benchmark` library +(libbenchmark.a/.so). If you followed the build steps above, this +library will be under the build directory you created. + +```bash +# Example on linux after running the build steps above. Assumes the +# `benchmark` and `build` directories are under the current directory. +$ g++ -std=c++11 -isystem benchmark/include -Lbuild/src -lpthread \ + -lbenchmark mybenchmark.cc -o mybenchmark +``` + +Alternatively, link against the `benchmark_main` library and remove +`BENCHMARK_MAIN();` above to get the same behavior. + +The compiled executable will run all benchmarks by default. Pass the `--help` +flag for option information or see the guide below. + +### Platform-specific instructions + +When the library is built using GCC it is necessary to link with the pthread +library due to how GCC implements `std::thread`. Failing to link to pthread will +lead to runtime exceptions (unless you're using libc++), not linker errors. See +[issue #67](https://github.com/google/benchmark/issues/67) for more details. You +can link to pthread by adding `-pthread` to your linker command. Note, you can +also use `-lpthread`, but there are potential issues with ordering of command +line parameters if you use that. + +If you're running benchmarks on Windows, the shlwapi library (`-lshlwapi`) is +also required. + +If you're running benchmarks on solaris, you'll want the kstat library linked in +too (`-lkstat`). + +## User Guide + +### Command Line +[Output Formats](#output-formats) + +[Output Files](#output-files) + +[Running a Subset of Benchmarks](#running-a-subset-of-benchmarks) + +[Result Comparison](#result-comparison) + +### Library +[Runtime and Reporting Considerations](#runtime-and-reporting-considerations) + +[Passing Arguments](#passing-arguments) + +[Calculating Asymptotic Complexity](#asymptotic-complexity) + +[Templated Benchmarks](#templated-benchmarks) + +[Fixtures](#fixtures) + +[Custom Counters](#custom-counters) + +[Multithreaded Benchmarks](#multithreaded-benchmarks) + +[CPU Timers](#cpu-timers) + +[Manual Timing](#manual-timing) + +[Setting the Time Unit](#setting-the-time-unit) + +[Preventing Optimization](#preventing-optimization) + +[Reporting Statistics](#reporting-statistics) + +[Custom Statistics](#custom-statistics) + +[Using RegisterBenchmark](#using-register-benchmark) + +[Exiting with an Error](#exiting-with-an-error) + +[A Faster KeepRunning Loop](#a-faster-keep-running-loop) + +[Disabling CPU Frequency Scaling](#disabling-cpu-frequency-scaling) + + + +### Output Formats + +The library supports multiple output formats. Use the +`--benchmark_format=` flag to set the format type. `console` +is the default format. + +The Console format is intended to be a human readable format. By default +the format generates color output. Context is output on stderr and the +tabular data on stdout. Example tabular output looks like: +``` +Benchmark Time(ns) CPU(ns) Iterations +---------------------------------------------------------------------- +BM_SetInsert/1024/1 28928 29349 23853 133.097kB/s 33.2742k items/s +BM_SetInsert/1024/8 32065 32913 21375 949.487kB/s 237.372k items/s +BM_SetInsert/1024/10 33157 33648 21431 1.13369MB/s 290.225k items/s +``` + +The JSON format outputs human readable json split into two top level attributes. +The `context` attribute contains information about the run in general, including +information about the CPU and the date. +The `benchmarks` attribute contains a list of every benchmark run. Example json +output looks like: +```json +{ + "context": { + "date": "2015/03/17-18:40:25", + "num_cpus": 40, + "mhz_per_cpu": 2801, + "cpu_scaling_enabled": false, + "build_type": "debug" + }, + "benchmarks": [ + { + "name": "BM_SetInsert/1024/1", + "iterations": 94877, + "real_time": 29275, + "cpu_time": 29836, + "bytes_per_second": 134066, + "items_per_second": 33516 + }, + { + "name": "BM_SetInsert/1024/8", + "iterations": 21609, + "real_time": 32317, + "cpu_time": 32429, + "bytes_per_second": 986770, + "items_per_second": 246693 + }, + { + "name": "BM_SetInsert/1024/10", + "iterations": 21393, + "real_time": 32724, + "cpu_time": 33355, + "bytes_per_second": 1199226, + "items_per_second": 299807 + } + ] +} +``` + +The CSV format outputs comma-separated values. The `context` is output on stderr +and the CSV itself on stdout. Example CSV output looks like: +``` +name,iterations,real_time,cpu_time,bytes_per_second,items_per_second,label +"BM_SetInsert/1024/1",65465,17890.7,8407.45,475768,118942, +"BM_SetInsert/1024/8",116606,18810.1,9766.64,3.27646e+06,819115, +"BM_SetInsert/1024/10",106365,17238.4,8421.53,4.74973e+06,1.18743e+06, +``` + + + +### Output Files + +Write benchmark results to a file with the `--benchmark_out=` option. +Specify the output format with `--benchmark_out_format={json|console|csv}`. Note that Specifying +`--benchmark_out` does not suppress the console output. + + + +### Running a Subset of Benchmarks + +The `--benchmark_filter=` option can be used to only run the benchmarks +which match the specified ``. For example: + +```bash +$ ./run_benchmarks.x --benchmark_filter=BM_memcpy/32 +Run on (1 X 2300 MHz CPU ) +2016-06-25 19:34:24 +Benchmark Time CPU Iterations +---------------------------------------------------- +BM_memcpy/32 11 ns 11 ns 79545455 +BM_memcpy/32k 2181 ns 2185 ns 324074 +BM_memcpy/32 12 ns 12 ns 54687500 +BM_memcpy/32k 1834 ns 1837 ns 357143 +``` + + + +### Result comparison + +It is possible to compare the benchmarking results. See [Additional Tooling Documentation](docs/tools.md) + + + +### Runtime and Reporting Considerations + +When the benchmark binary is executed, each benchmark function is run serially. +The number of iterations to run is determined dynamically by running the +benchmark a few times and measuring the time taken and ensuring that the +ultimate result will be statistically stable. As such, faster benchmark +functions will be run for more iterations than slower benchmark functions, and +the number of iterations is thus reported. + +In all cases, the number of iterations for which the benchmark is run is +governed by the amount of time the benchmark takes. Concretely, the number of +iterations is at least one, not more than 1e9, until CPU time is greater than +the minimum time, or the wallclock time is 5x minimum time. The minimum time is +set per benchmark by calling `MinTime` on the registered benchmark object. + +Average timings are then reported over the iterations run. If multiple +repetitions are requested using the `--benchmark_repetitions` command-line +option, or at registration time, the benchmark function will be run several +times and statistical results across these repetitions will also be reported. + +As well as the per-benchmark entries, a preamble in the report will include +information about the machine on which the benchmarks are run. + + + +### Passing Arguments + +Sometimes a family of benchmarks can be implemented with just one routine that +takes an extra argument to specify which one of the family of benchmarks to +run. For example, the following code defines a family of benchmarks for +measuring the speed of `memcpy()` calls of different lengths: + +```c++ +static void BM_memcpy(benchmark::State& state) { + char* src = new char[state.range(0)]; + char* dst = new char[state.range(0)]; + memset(src, 'x', state.range(0)); + for (auto _ : state) + memcpy(dst, src, state.range(0)); + state.SetBytesProcessed(int64_t(state.iterations()) * + int64_t(state.range(0))); + delete[] src; + delete[] dst; +} +BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1<<10)->Arg(8<<10); +``` + +The preceding code is quite repetitive, and can be replaced with the following +short-hand. The following invocation will pick a few appropriate arguments in +the specified range and will generate a benchmark for each such argument. + +```c++ +BENCHMARK(BM_memcpy)->Range(8, 8<<10); +``` + +By default the arguments in the range are generated in multiples of eight and +the command above selects [ 8, 64, 512, 4k, 8k ]. In the following code the +range multiplier is changed to multiples of two. + +```c++ +BENCHMARK(BM_memcpy)->RangeMultiplier(2)->Range(8, 8<<10); +``` +Now arguments generated are [ 8, 16, 32, 64, 128, 256, 512, 1024, 2k, 4k, 8k ]. + +You might have a benchmark that depends on two or more inputs. For example, the +following code defines a family of benchmarks for measuring the speed of set +insertion. + +```c++ +static void BM_SetInsert(benchmark::State& state) { + std::set data; + for (auto _ : state) { + state.PauseTiming(); + data = ConstructRandomSet(state.range(0)); + state.ResumeTiming(); + for (int j = 0; j < state.range(1); ++j) + data.insert(RandomNumber()); + } +} +BENCHMARK(BM_SetInsert) + ->Args({1<<10, 128}) + ->Args({2<<10, 128}) + ->Args({4<<10, 128}) + ->Args({8<<10, 128}) + ->Args({1<<10, 512}) + ->Args({2<<10, 512}) + ->Args({4<<10, 512}) + ->Args({8<<10, 512}); +``` + +The preceding code is quite repetitive, and can be replaced with the following +short-hand. The following macro will pick a few appropriate arguments in the +product of the two specified ranges and will generate a benchmark for each such +pair. + +```c++ +BENCHMARK(BM_SetInsert)->Ranges({{1<<10, 8<<10}, {128, 512}}); +``` + +For more complex patterns of inputs, passing a custom function to `Apply` allows +programmatic specification of an arbitrary set of arguments on which to run the +benchmark. The following example enumerates a dense range on one parameter, +and a sparse range on the second. + +```c++ +static void CustomArguments(benchmark::internal::Benchmark* b) { + for (int i = 0; i <= 10; ++i) + for (int j = 32; j <= 1024*1024; j *= 8) + b->Args({i, j}); +} +BENCHMARK(BM_SetInsert)->Apply(CustomArguments); +``` + +#### Passing Arbitrary Arguments to a Benchmark + +In C++11 it is possible to define a benchmark that takes an arbitrary number +of extra arguments. The `BENCHMARK_CAPTURE(func, test_case_name, ...args)` +macro creates a benchmark that invokes `func` with the `benchmark::State` as +the first argument followed by the specified `args...`. +The `test_case_name` is appended to the name of the benchmark and +should describe the values passed. + +```c++ +template +void BM_takes_args(benchmark::State& state, ExtraArgs&&... extra_args) { + [...] +} +// Registers a benchmark named "BM_takes_args/int_string_test" that passes +// the specified values to `extra_args`. +BENCHMARK_CAPTURE(BM_takes_args, int_string_test, 42, std::string("abc")); +``` +Note that elements of `...args` may refer to global variables. Users should +avoid modifying global state inside of a benchmark. + + + +### Calculating Asymptotic Complexity (Big O) + +Asymptotic complexity might be calculated for a family of benchmarks. The +following code will calculate the coefficient for the high-order term in the +running time and the normalized root-mean square error of string comparison. + +```c++ +static void BM_StringCompare(benchmark::State& state) { + std::string s1(state.range(0), '-'); + std::string s2(state.range(0), '-'); + for (auto _ : state) { + benchmark::DoNotOptimize(s1.compare(s2)); + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_StringCompare) + ->RangeMultiplier(2)->Range(1<<10, 1<<18)->Complexity(benchmark::oN); +``` + +As shown in the following invocation, asymptotic complexity might also be +calculated automatically. + +```c++ +BENCHMARK(BM_StringCompare) + ->RangeMultiplier(2)->Range(1<<10, 1<<18)->Complexity(); +``` + +The following code will specify asymptotic complexity with a lambda function, +that might be used to customize high-order term calculation. + +```c++ +BENCHMARK(BM_StringCompare)->RangeMultiplier(2) + ->Range(1<<10, 1<<18)->Complexity([](int64_t n)->double{return n; }); +``` + + + +### Templated Benchmarks + +This example produces and consumes messages of size `sizeof(v)` `range_x` +times. It also outputs throughput in the absence of multiprogramming. + +```c++ +template void BM_Sequential(benchmark::State& state) { + Q q; + typename Q::value_type v; + for (auto _ : state) { + for (int i = state.range(0); i--; ) + q.push(v); + for (int e = state.range(0); e--; ) + q.Wait(&v); + } + // actually messages, not bytes: + state.SetBytesProcessed( + static_cast(state.iterations())*state.range(0)); +} +BENCHMARK_TEMPLATE(BM_Sequential, WaitQueue)->Range(1<<0, 1<<10); +``` + +Three macros are provided for adding benchmark templates. + +```c++ +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE(func, ...) // Takes any number of parameters. +#else // C++ < C++11 +#define BENCHMARK_TEMPLATE(func, arg1) +#endif +#define BENCHMARK_TEMPLATE1(func, arg1) +#define BENCHMARK_TEMPLATE2(func, arg1, arg2) +``` + + + +### Fixtures + +Fixture tests are created by first defining a type that derives from +`::benchmark::Fixture` and then creating/registering the tests using the +following macros: + +* `BENCHMARK_F(ClassName, Method)` +* `BENCHMARK_DEFINE_F(ClassName, Method)` +* `BENCHMARK_REGISTER_F(ClassName, Method)` + +For Example: + +```c++ +class MyFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + } + + void TearDown(const ::benchmark::State& state) { + } +}; + +BENCHMARK_F(MyFixture, FooTest)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} + +BENCHMARK_DEFINE_F(MyFixture, BarTest)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} +/* BarTest is NOT registered */ +BENCHMARK_REGISTER_F(MyFixture, BarTest)->Threads(2); +/* BarTest is now registered */ +``` + +#### Templated Fixtures + +Also you can create templated fixture by using the following macros: + +* `BENCHMARK_TEMPLATE_F(ClassName, Method, ...)` +* `BENCHMARK_TEMPLATE_DEFINE_F(ClassName, Method, ...)` + +For example: +```c++ +template +class MyFixture : public benchmark::Fixture {}; + +BENCHMARK_TEMPLATE_F(MyFixture, IntTest, int)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} + +BENCHMARK_TEMPLATE_DEFINE_F(MyFixture, DoubleTest, double)(benchmark::State& st) { + for (auto _ : st) { + ... + } +} + +BENCHMARK_REGISTER_F(MyFixture, DoubleTest)->Threads(2); +``` + + + +### Custom Counters + +You can add your own counters with user-defined names. The example below +will add columns "Foo", "Bar" and "Baz" in its output: + +```c++ +static void UserCountersExample1(benchmark::State& state) { + double numFoos = 0, numBars = 0, numBazs = 0; + for (auto _ : state) { + // ... count Foo,Bar,Baz events + } + state.counters["Foo"] = numFoos; + state.counters["Bar"] = numBars; + state.counters["Baz"] = numBazs; +} +``` + +The `state.counters` object is a `std::map` with `std::string` keys +and `Counter` values. The latter is a `double`-like class, via an implicit +conversion to `double&`. Thus you can use all of the standard arithmetic +assignment operators (`=,+=,-=,*=,/=`) to change the value of each counter. + +In multithreaded benchmarks, each counter is set on the calling thread only. +When the benchmark finishes, the counters from each thread will be summed; +the resulting sum is the value which will be shown for the benchmark. + +The `Counter` constructor accepts three parameters: the value as a `double` +; a bit flag which allows you to show counters as rates, and/or as per-thread +iteration, and/or as per-thread averages, and/or iteration invariants; +and a flag specifying the 'unit' - i.e. is 1k a 1000 (default, +`benchmark::Counter::OneK::kIs1000`), or 1024 +(`benchmark::Counter::OneK::kIs1024`)? + +```c++ + // sets a simple counter + state.counters["Foo"] = numFoos; + + // Set the counter as a rate. It will be presented divided + // by the duration of the benchmark. + state.counters["FooRate"] = Counter(numFoos, benchmark::Counter::kIsRate); + + // Set the counter as a thread-average quantity. It will + // be presented divided by the number of threads. + state.counters["FooAvg"] = Counter(numFoos, benchmark::Counter::kAvgThreads); + + // There's also a combined flag: + state.counters["FooAvgRate"] = Counter(numFoos,benchmark::Counter::kAvgThreadsRate); + + // This says that we process with the rate of state.range(0) bytes every iteration: + state.counters["BytesProcessed"] = Counter(state.range(0), benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::OneK::kIs1024); +``` + +When you're compiling in C++11 mode or later you can use `insert()` with +`std::initializer_list`: + +```c++ + // With C++11, this can be done: + state.counters.insert({{"Foo", numFoos}, {"Bar", numBars}, {"Baz", numBazs}}); + // ... instead of: + state.counters["Foo"] = numFoos; + state.counters["Bar"] = numBars; + state.counters["Baz"] = numBazs; +``` + +#### Counter Reporting + +When using the console reporter, by default, user counters are are printed at +the end after the table, the same way as ``bytes_processed`` and +``items_processed``. This is best for cases in which there are few counters, +or where there are only a couple of lines per benchmark. Here's an example of +the default output: + +``` +------------------------------------------------------------------------------ +Benchmark Time CPU Iterations UserCounters... +------------------------------------------------------------------------------ +BM_UserCounter/threads:8 2248 ns 10277 ns 68808 Bar=16 Bat=40 Baz=24 Foo=8 +BM_UserCounter/threads:1 9797 ns 9788 ns 71523 Bar=2 Bat=5 Baz=3 Foo=1024m +BM_UserCounter/threads:2 4924 ns 9842 ns 71036 Bar=4 Bat=10 Baz=6 Foo=2 +BM_UserCounter/threads:4 2589 ns 10284 ns 68012 Bar=8 Bat=20 Baz=12 Foo=4 +BM_UserCounter/threads:8 2212 ns 10287 ns 68040 Bar=16 Bat=40 Baz=24 Foo=8 +BM_UserCounter/threads:16 1782 ns 10278 ns 68144 Bar=32 Bat=80 Baz=48 Foo=16 +BM_UserCounter/threads:32 1291 ns 10296 ns 68256 Bar=64 Bat=160 Baz=96 Foo=32 +BM_UserCounter/threads:4 2615 ns 10307 ns 68040 Bar=8 Bat=20 Baz=12 Foo=4 +BM_Factorial 26 ns 26 ns 26608979 40320 +BM_Factorial/real_time 26 ns 26 ns 26587936 40320 +BM_CalculatePiRange/1 16 ns 16 ns 45704255 0 +BM_CalculatePiRange/8 73 ns 73 ns 9520927 3.28374 +BM_CalculatePiRange/64 609 ns 609 ns 1140647 3.15746 +BM_CalculatePiRange/512 4900 ns 4901 ns 142696 3.14355 +``` + +If this doesn't suit you, you can print each counter as a table column by +passing the flag `--benchmark_counters_tabular=true` to the benchmark +application. This is best for cases in which there are a lot of counters, or +a lot of lines per individual benchmark. Note that this will trigger a +reprinting of the table header any time the counter set changes between +individual benchmarks. Here's an example of corresponding output when +`--benchmark_counters_tabular=true` is passed: + +``` +--------------------------------------------------------------------------------------- +Benchmark Time CPU Iterations Bar Bat Baz Foo +--------------------------------------------------------------------------------------- +BM_UserCounter/threads:8 2198 ns 9953 ns 70688 16 40 24 8 +BM_UserCounter/threads:1 9504 ns 9504 ns 73787 2 5 3 1 +BM_UserCounter/threads:2 4775 ns 9550 ns 72606 4 10 6 2 +BM_UserCounter/threads:4 2508 ns 9951 ns 70332 8 20 12 4 +BM_UserCounter/threads:8 2055 ns 9933 ns 70344 16 40 24 8 +BM_UserCounter/threads:16 1610 ns 9946 ns 70720 32 80 48 16 +BM_UserCounter/threads:32 1192 ns 9948 ns 70496 64 160 96 32 +BM_UserCounter/threads:4 2506 ns 9949 ns 70332 8 20 12 4 +-------------------------------------------------------------- +Benchmark Time CPU Iterations +-------------------------------------------------------------- +BM_Factorial 26 ns 26 ns 26392245 40320 +BM_Factorial/real_time 26 ns 26 ns 26494107 40320 +BM_CalculatePiRange/1 15 ns 15 ns 45571597 0 +BM_CalculatePiRange/8 74 ns 74 ns 9450212 3.28374 +BM_CalculatePiRange/64 595 ns 595 ns 1173901 3.15746 +BM_CalculatePiRange/512 4752 ns 4752 ns 147380 3.14355 +BM_CalculatePiRange/4k 37970 ns 37972 ns 18453 3.14184 +BM_CalculatePiRange/32k 303733 ns 303744 ns 2305 3.14162 +BM_CalculatePiRange/256k 2434095 ns 2434186 ns 288 3.1416 +BM_CalculatePiRange/1024k 9721140 ns 9721413 ns 71 3.14159 +BM_CalculatePi/threads:8 2255 ns 9943 ns 70936 +``` +Note above the additional header printed when the benchmark changes from +``BM_UserCounter`` to ``BM_Factorial``. This is because ``BM_Factorial`` does +not have the same counter set as ``BM_UserCounter``. + + + +### Multithreaded Benchmarks + +In a multithreaded test (benchmark invoked by multiple threads simultaneously), +it is guaranteed that none of the threads will start until all have reached +the start of the benchmark loop, and all will have finished before any thread +exits the benchmark loop. (This behavior is also provided by the `KeepRunning()` +API) As such, any global setup or teardown can be wrapped in a check against the thread +index: + +```c++ +static void BM_MultiThreaded(benchmark::State& state) { + if (state.thread_index == 0) { + // Setup code here. + } + for (auto _ : state) { + // Run the test as normal. + } + if (state.thread_index == 0) { + // Teardown code here. + } +} +BENCHMARK(BM_MultiThreaded)->Threads(2); +``` + +If the benchmarked code itself uses threads and you want to compare it to +single-threaded code, you may want to use real-time ("wallclock") measurements +for latency comparisons: + +```c++ +BENCHMARK(BM_test)->Range(8, 8<<10)->UseRealTime(); +``` + +Without `UseRealTime`, CPU time is used by default. + + + +### CPU Timers + +By default, the CPU timer only measures the time spent by the main thread. +If the benchmark itself uses threads internally, this measurement may not +be what you are looking for. Instead, there is a way to measure the total +CPU usage of the process, by all the threads. + +```c++ +void callee(int i); + +static void MyMain(int size) { +#pragma omp parallel for + for(int i = 0; i < size; i++) + callee(i); +} + +static void BM_OpenMP(benchmark::State& state) { + for (auto _ : state) + MyMain(state.range(0); +} + +// Measure the time spent by the main thread, use it to decide for how long to +// run the benchmark loop. Depending on the internal implementation detail may +// measure to anywhere from near-zero (the overhead spent before/after work +// handoff to worker thread[s]) to the whole single-thread time. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10); + +// Measure the user-visible time, the wall clock (literally, the time that +// has passed on the clock on the wall), use it to decide for how long to +// run the benchmark loop. This will always be meaningful, an will match the +// time spent by the main thread in single-threaded case, in general decreasing +// with the number of internal threads doing the work. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->UseRealTime(); + +// Measure the total CPU consumption, use it to decide for how long to +// run the benchmark loop. This will always measure to no less than the +// time spent by the main thread in single-threaded case. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->MeasureProcessCPUTime(); + +// A mixture of the last two. Measure the total CPU consumption, but use the +// wall clock to decide for how long to run the benchmark loop. +BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->MeasureProcessCPUTime()->UseRealTime(); +``` + +#### Controlling Timers + +Normally, the entire duration of the work loop (`for (auto _ : state) {}`) +is measured. But sometimes, it is necessary to do some work inside of +that loop, every iteration, but without counting that time to the benchmark time. +That is possible, althought it is not recommended, since it has high overhead. + +```c++ +static void BM_SetInsert_With_Timer_Control(benchmark::State& state) { + std::set data; + for (auto _ : state) { + state.PauseTiming(); // Stop timers. They will not count until they are resumed. + data = ConstructRandomSet(state.range(0)); // Do something that should not be measured + state.ResumeTiming(); // And resume timers. They are now counting again. + // The rest will be measured. + for (int j = 0; j < state.range(1); ++j) + data.insert(RandomNumber()); + } +} +BENCHMARK(BM_SetInsert_With_Timer_Control)->Ranges({{1<<10, 8<<10}, {128, 512}}); +``` + + + +### Manual Timing + +For benchmarking something for which neither CPU time nor real-time are +correct or accurate enough, completely manual timing is supported using +the `UseManualTime` function. + +When `UseManualTime` is used, the benchmarked code must call +`SetIterationTime` once per iteration of the benchmark loop to +report the manually measured time. + +An example use case for this is benchmarking GPU execution (e.g. OpenCL +or CUDA kernels, OpenGL or Vulkan or Direct3D draw calls), which cannot +be accurately measured using CPU time or real-time. Instead, they can be +measured accurately using a dedicated API, and these measurement results +can be reported back with `SetIterationTime`. + +```c++ +static void BM_ManualTiming(benchmark::State& state) { + int microseconds = state.range(0); + std::chrono::duration sleep_duration { + static_cast(microseconds) + }; + + for (auto _ : state) { + auto start = std::chrono::high_resolution_clock::now(); + // Simulate some useful workload with a sleep + std::this_thread::sleep_for(sleep_duration); + auto end = std::chrono::high_resolution_clock::now(); + + auto elapsed_seconds = + std::chrono::duration_cast>( + end - start); + + state.SetIterationTime(elapsed_seconds.count()); + } +} +BENCHMARK(BM_ManualTiming)->Range(1, 1<<17)->UseManualTime(); +``` + + + +### Setting the Time Unit + +If a benchmark runs a few milliseconds it may be hard to visually compare the +measured times, since the output data is given in nanoseconds per default. In +order to manually set the time unit, you can specify it manually: + +```c++ +BENCHMARK(BM_test)->Unit(benchmark::kMillisecond); +``` + + + +### Preventing Optimization + +To prevent a value or expression from being optimized away by the compiler +the `benchmark::DoNotOptimize(...)` and `benchmark::ClobberMemory()` +functions can be used. + +```c++ +static void BM_test(benchmark::State& state) { + for (auto _ : state) { + int x = 0; + for (int i=0; i < 64; ++i) { + benchmark::DoNotOptimize(x += i); + } + } +} +``` + +`DoNotOptimize()` forces the *result* of `` to be stored in either +memory or a register. For GNU based compilers it acts as read/write barrier +for global memory. More specifically it forces the compiler to flush pending +writes to memory and reload any other values as necessary. + +Note that `DoNotOptimize()` does not prevent optimizations on `` +in any way. `` may even be removed entirely when the result is already +known. For example: + +```c++ + /* Example 1: `` is removed entirely. */ + int foo(int x) { return x + 42; } + while (...) DoNotOptimize(foo(0)); // Optimized to DoNotOptimize(42); + + /* Example 2: Result of '' is only reused */ + int bar(int) __attribute__((const)); + while (...) DoNotOptimize(bar(0)); // Optimized to: + // int __result__ = bar(0); + // while (...) DoNotOptimize(__result__); +``` + +The second tool for preventing optimizations is `ClobberMemory()`. In essence +`ClobberMemory()` forces the compiler to perform all pending writes to global +memory. Memory managed by block scope objects must be "escaped" using +`DoNotOptimize(...)` before it can be clobbered. In the below example +`ClobberMemory()` prevents the call to `v.push_back(42)` from being optimized +away. + +```c++ +static void BM_vector_push_back(benchmark::State& state) { + for (auto _ : state) { + std::vector v; + v.reserve(1); + benchmark::DoNotOptimize(v.data()); // Allow v.data() to be clobbered. + v.push_back(42); + benchmark::ClobberMemory(); // Force 42 to be written to memory. + } +} +``` + +Note that `ClobberMemory()` is only available for GNU or MSVC based compilers. + + + +### Statistics: Reporting the Mean, Median and Standard Deviation of Repeated Benchmarks + +By default each benchmark is run once and that single result is reported. +However benchmarks are often noisy and a single result may not be representative +of the overall behavior. For this reason it's possible to repeatedly rerun the +benchmark. + +The number of runs of each benchmark is specified globally by the +`--benchmark_repetitions` flag or on a per benchmark basis by calling +`Repetitions` on the registered benchmark object. When a benchmark is run more +than once the mean, median and standard deviation of the runs will be reported. + +Additionally the `--benchmark_report_aggregates_only={true|false}`, +`--benchmark_display_aggregates_only={true|false}` flags or +`ReportAggregatesOnly(bool)`, `DisplayAggregatesOnly(bool)` functions can be +used to change how repeated tests are reported. By default the result of each +repeated run is reported. When `report aggregates only` option is `true`, +only the aggregates (i.e. mean, median and standard deviation, maybe complexity +measurements if they were requested) of the runs is reported, to both the +reporters - standard output (console), and the file. +However when only the `display aggregates only` option is `true`, +only the aggregates are displayed in the standard output, while the file +output still contains everything. +Calling `ReportAggregatesOnly(bool)` / `DisplayAggregatesOnly(bool)` on a +registered benchmark object overrides the value of the appropriate flag for that +benchmark. + + + +### Custom Statistics + +While having mean, median and standard deviation is nice, this may not be +enough for everyone. For example you may want to know what the largest +observation is, e.g. because you have some real-time constraints. This is easy. +The following code will specify a custom statistic to be calculated, defined +by a lambda function. + +```c++ +void BM_spin_empty(benchmark::State& state) { + for (auto _ : state) { + for (int x = 0; x < state.range(0); ++x) { + benchmark::DoNotOptimize(x); + } + } +} + +BENCHMARK(BM_spin_empty) + ->ComputeStatistics("max", [](const std::vector& v) -> double { + return *(std::max_element(std::begin(v), std::end(v))); + }) + ->Arg(512); +``` + + + +### Using RegisterBenchmark(name, fn, args...) + +The `RegisterBenchmark(name, func, args...)` function provides an alternative +way to create and register benchmarks. +`RegisterBenchmark(name, func, args...)` creates, registers, and returns a +pointer to a new benchmark with the specified `name` that invokes +`func(st, args...)` where `st` is a `benchmark::State` object. + +Unlike the `BENCHMARK` registration macros, which can only be used at the global +scope, the `RegisterBenchmark` can be called anywhere. This allows for +benchmark tests to be registered programmatically. + +Additionally `RegisterBenchmark` allows any callable object to be registered +as a benchmark. Including capturing lambdas and function objects. + +For Example: +```c++ +auto BM_test = [](benchmark::State& st, auto Inputs) { /* ... */ }; + +int main(int argc, char** argv) { + for (auto& test_input : { /* ... */ }) + benchmark::RegisterBenchmark(test_input.name(), BM_test, test_input); + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} +``` + + + +### Exiting with an Error + +When errors caused by external influences, such as file I/O and network +communication, occur within a benchmark the +`State::SkipWithError(const char* msg)` function can be used to skip that run +of benchmark and report the error. Note that only future iterations of the +`KeepRunning()` are skipped. For the ranged-for version of the benchmark loop +Users must explicitly exit the loop, otherwise all iterations will be performed. +Users may explicitly return to exit the benchmark immediately. + +The `SkipWithError(...)` function may be used at any point within the benchmark, +including before and after the benchmark loop. + +For example: + +```c++ +static void BM_test(benchmark::State& state) { + auto resource = GetResource(); + if (!resource.good()) { + state.SkipWithError("Resource is not good!"); + // KeepRunning() loop will not be entered. + } + for (state.KeepRunning()) { + auto data = resource.read_data(); + if (!resource.good()) { + state.SkipWithError("Failed to read data!"); + break; // Needed to skip the rest of the iteration. + } + do_stuff(data); + } +} + +static void BM_test_ranged_fo(benchmark::State & state) { + state.SkipWithError("test will not be entered"); + for (auto _ : state) { + state.SkipWithError("Failed!"); + break; // REQUIRED to prevent all further iterations. + } +} +``` + + +### A Faster KeepRunning Loop + +In C++11 mode, a ranged-based for loop should be used in preference to +the `KeepRunning` loop for running the benchmarks. For example: + +```c++ +static void BM_Fast(benchmark::State &state) { + for (auto _ : state) { + FastOperation(); + } +} +BENCHMARK(BM_Fast); +``` + +The reason the ranged-for loop is faster than using `KeepRunning`, is +because `KeepRunning` requires a memory load and store of the iteration count +ever iteration, whereas the ranged-for variant is able to keep the iteration count +in a register. + +For example, an empty inner loop of using the ranged-based for method looks like: + +```asm +# Loop Init + mov rbx, qword ptr [r14 + 104] + call benchmark::State::StartKeepRunning() + test rbx, rbx + je .LoopEnd +.LoopHeader: # =>This Inner Loop Header: Depth=1 + add rbx, -1 + jne .LoopHeader +.LoopEnd: +``` + +Compared to an empty `KeepRunning` loop, which looks like: + +```asm +.LoopHeader: # in Loop: Header=BB0_3 Depth=1 + cmp byte ptr [rbx], 1 + jne .LoopInit +.LoopBody: # =>This Inner Loop Header: Depth=1 + mov rax, qword ptr [rbx + 8] + lea rcx, [rax + 1] + mov qword ptr [rbx + 8], rcx + cmp rax, qword ptr [rbx + 104] + jb .LoopHeader + jmp .LoopEnd +.LoopInit: + mov rdi, rbx + call benchmark::State::StartKeepRunning() + jmp .LoopBody +.LoopEnd: +``` + +Unless C++03 compatibility is required, the ranged-for variant of writing +the benchmark loop should be preferred. + + + +### Disabling CPU Frequency Scaling +If you see this error: +``` +***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. +``` +you might want to disable the CPU frequency scaling while running the benchmark: +```bash +sudo cpupower frequency-set --governor performance +./mybench +sudo cpupower frequency-set --governor powersave +``` diff --git a/thirdparty/benchmark-1.5.0/WORKSPACE b/thirdparty/benchmark-1.5.0/WORKSPACE new file mode 100644 index 0000000000..9a75f968d9 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/WORKSPACE @@ -0,0 +1,9 @@ +workspace(name = "com_github_google_benchmark") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "com_google_googletest", + urls = ["https://github.com/google/googletest/archive/3f0cf6b62ad1eb50d8736538363d3580dd640c3e.zip"], + strip_prefix = "googletest-3f0cf6b62ad1eb50d8736538363d3580dd640c3e", +) diff --git a/thirdparty/benchmark-1.5.0/_config.yml b/thirdparty/benchmark-1.5.0/_config.yml new file mode 100644 index 0000000000..18854876c6 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-midnight \ No newline at end of file diff --git a/thirdparty/benchmark-1.5.0/appveyor.yml b/thirdparty/benchmark-1.5.0/appveyor.yml new file mode 100644 index 0000000000..cf240190be --- /dev/null +++ b/thirdparty/benchmark-1.5.0/appveyor.yml @@ -0,0 +1,50 @@ +version: '{build}' + +image: Visual Studio 2017 + +configuration: + - Debug + - Release + +environment: + matrix: + - compiler: msvc-15-seh + generator: "Visual Studio 15 2017" + + - compiler: msvc-15-seh + generator: "Visual Studio 15 2017 Win64" + + - compiler: msvc-14-seh + generator: "Visual Studio 14 2015" + + - compiler: msvc-14-seh + generator: "Visual Studio 14 2015 Win64" + + - compiler: gcc-5.3.0-posix + generator: "MinGW Makefiles" + cxx_path: 'C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin' + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + +matrix: + fast_finish: true + +install: + # git bash conflicts with MinGW makefiles + - if "%generator%"=="MinGW Makefiles" (set "PATH=%PATH:C:\Program Files\Git\usr\bin;=%") + - if not "%cxx_path%"=="" (set "PATH=%PATH%;%cxx_path%") + +build_script: + - md _build -Force + - cd _build + - echo %configuration% + - cmake -G "%generator%" "-DCMAKE_BUILD_TYPE=%configuration%" -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON .. + - cmake --build . --config %configuration% + +test_script: + - ctest -c %configuration% --timeout 300 --output-on-failure + +artifacts: + - path: '_build/CMakeFiles/*.log' + name: logs + - path: '_build/Testing/**/*.xml' + name: test_results diff --git a/thirdparty/benchmark-1.5.0/cmake/AddCXXCompilerFlag.cmake b/thirdparty/benchmark-1.5.0/cmake/AddCXXCompilerFlag.cmake new file mode 100644 index 0000000000..d0d2099814 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/AddCXXCompilerFlag.cmake @@ -0,0 +1,74 @@ +# - Adds a compiler flag if it is supported by the compiler +# +# This function checks that the supplied compiler flag is supported and then +# adds it to the corresponding compiler flags +# +# add_cxx_compiler_flag( []) +# +# - Example +# +# include(AddCXXCompilerFlag) +# add_cxx_compiler_flag(-Wall) +# add_cxx_compiler_flag(-no-strict-aliasing RELEASE) +# Requires CMake 2.6+ + +if(__add_cxx_compiler_flag) + return() +endif() +set(__add_cxx_compiler_flag INCLUDED) + +include(CheckCXXCompilerFlag) + +function(mangle_compiler_flag FLAG OUTPUT) + string(TOUPPER "HAVE_CXX_FLAG_${FLAG}" SANITIZED_FLAG) + string(REPLACE "+" "X" SANITIZED_FLAG ${SANITIZED_FLAG}) + string(REGEX REPLACE "[^A-Za-z_0-9]" "_" SANITIZED_FLAG ${SANITIZED_FLAG}) + string(REGEX REPLACE "_+" "_" SANITIZED_FLAG ${SANITIZED_FLAG}) + set(${OUTPUT} "${SANITIZED_FLAG}" PARENT_SCOPE) +endfunction(mangle_compiler_flag) + +function(add_cxx_compiler_flag FLAG) + mangle_compiler_flag("${FLAG}" MANGLED_FLAG) + set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}") + check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) + set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") + if(${MANGLED_FLAG}) + set(VARIANT ${ARGV1}) + if(ARGV1) + string(TOUPPER "_${VARIANT}" VARIANT) + endif() + set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${BENCHMARK_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE) + endif() +endfunction() + +function(add_required_cxx_compiler_flag FLAG) + mangle_compiler_flag("${FLAG}" MANGLED_FLAG) + set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}") + check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) + set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") + if(${MANGLED_FLAG}) + set(VARIANT ${ARGV1}) + if(ARGV1) + string(TOUPPER "_${VARIANT}" VARIANT) + endif() + set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}" PARENT_SCOPE) + else() + message(FATAL_ERROR "Required flag '${FLAG}' is not supported by the compiler") + endif() +endfunction() + +function(check_cxx_warning_flag FLAG) + mangle_compiler_flag("${FLAG}" MANGLED_FLAG) + set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + # Add -Werror to ensure the compiler generates an error if the warning flag + # doesn't exist. + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror ${FLAG}") + check_cxx_compiler_flag("${FLAG}" ${MANGLED_FLAG}) + set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}") +endfunction() diff --git a/thirdparty/benchmark-1.5.0/cmake/CXXFeatureCheck.cmake b/thirdparty/benchmark-1.5.0/cmake/CXXFeatureCheck.cmake new file mode 100644 index 0000000000..99b56dd623 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/CXXFeatureCheck.cmake @@ -0,0 +1,64 @@ +# - Compile and run code to check for C++ features +# +# This functions compiles a source file under the `cmake` folder +# and adds the corresponding `HAVE_[FILENAME]` flag to the CMake +# environment +# +# cxx_feature_check( []) +# +# - Example +# +# include(CXXFeatureCheck) +# cxx_feature_check(STD_REGEX) +# Requires CMake 2.8.12+ + +if(__cxx_feature_check) + return() +endif() +set(__cxx_feature_check INCLUDED) + +function(cxx_feature_check FILE) + string(TOLOWER ${FILE} FILE) + string(TOUPPER ${FILE} VAR) + string(TOUPPER "HAVE_${VAR}" FEATURE) + if (DEFINED HAVE_${VAR}) + set(HAVE_${VAR} 1 PARENT_SCOPE) + add_definitions(-DHAVE_${VAR}) + return() + endif() + + if (NOT DEFINED COMPILE_${FEATURE}) + message(STATUS "Performing Test ${FEATURE}") + if(CMAKE_CROSSCOMPILING) + try_compile(COMPILE_${FEATURE} + ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${FILE}.cpp + CMAKE_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS} + LINK_LIBRARIES ${BENCHMARK_CXX_LIBRARIES}) + if(COMPILE_${FEATURE}) + message(WARNING + "If you see build failures due to cross compilation, try setting HAVE_${VAR} to 0") + set(RUN_${FEATURE} 0) + else() + set(RUN_${FEATURE} 1) + endif() + else() + message(STATUS "Performing Test ${FEATURE}") + try_run(RUN_${FEATURE} COMPILE_${FEATURE} + ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${FILE}.cpp + CMAKE_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS} + LINK_LIBRARIES ${BENCHMARK_CXX_LIBRARIES}) + endif() + endif() + + if(RUN_${FEATURE} EQUAL 0) + message(STATUS "Performing Test ${FEATURE} -- success") + set(HAVE_${VAR} 1 PARENT_SCOPE) + add_definitions(-DHAVE_${VAR}) + else() + if(NOT COMPILE_${FEATURE}) + message(STATUS "Performing Test ${FEATURE} -- failed to compile") + else() + message(STATUS "Performing Test ${FEATURE} -- compiled but failed to run") + endif() + endif() +endfunction() diff --git a/thirdparty/benchmark-1.5.0/cmake/Config.cmake.in b/thirdparty/benchmark-1.5.0/cmake/Config.cmake.in new file mode 100644 index 0000000000..6e9256eea8 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/Config.cmake.in @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") diff --git a/thirdparty/benchmark-1.5.0/cmake/GetGitVersion.cmake b/thirdparty/benchmark-1.5.0/cmake/GetGitVersion.cmake new file mode 100644 index 0000000000..4f10f226d7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/GetGitVersion.cmake @@ -0,0 +1,54 @@ +# - Returns a version string from Git tags +# +# This function inspects the annotated git tags for the project and returns a string +# into a CMake variable +# +# get_git_version() +# +# - Example +# +# include(GetGitVersion) +# get_git_version(GIT_VERSION) +# +# Requires CMake 2.8.11+ +find_package(Git) + +if(__get_git_version) + return() +endif() +set(__get_git_version INCLUDED) + +function(get_git_version var) + if(GIT_EXECUTABLE) + execute_process(COMMAND ${GIT_EXECUTABLE} describe --match "v[0-9]*.[0-9]*.[0-9]*" --abbrev=8 + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE status + OUTPUT_VARIABLE GIT_VERSION + ERROR_QUIET) + if(${status}) + set(GIT_VERSION "v0.0.0") + else() + string(STRIP ${GIT_VERSION} GIT_VERSION) + string(REGEX REPLACE "-[0-9]+-g" "-" GIT_VERSION ${GIT_VERSION}) + endif() + + # Work out if the repository is dirty + execute_process(COMMAND ${GIT_EXECUTABLE} update-index -q --refresh + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_QUIET + ERROR_QUIET) + execute_process(COMMAND ${GIT_EXECUTABLE} diff-index --name-only HEAD -- + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_DIFF_INDEX + ERROR_QUIET) + string(COMPARE NOTEQUAL "${GIT_DIFF_INDEX}" "" GIT_DIRTY) + if (${GIT_DIRTY}) + set(GIT_VERSION "${GIT_VERSION}-dirty") + endif() + else() + set(GIT_VERSION "v0.0.0") + endif() + + message(STATUS "git Version: ${GIT_VERSION}") + set(${var} ${GIT_VERSION} PARENT_SCOPE) +endfunction() diff --git a/thirdparty/benchmark-1.5.0/cmake/GoogleTest.cmake b/thirdparty/benchmark-1.5.0/cmake/GoogleTest.cmake new file mode 100644 index 0000000000..fb7c6be25e --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/GoogleTest.cmake @@ -0,0 +1,41 @@ +# Download and unpack googletest at configure time +set(GOOGLETEST_PREFIX "${benchmark_BINARY_DIR}/third_party/googletest") +configure_file(${benchmark_SOURCE_DIR}/cmake/GoogleTest.cmake.in ${GOOGLETEST_PREFIX}/CMakeLists.txt @ONLY) + +set(GOOGLETEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/googletest") # Mind the quotes +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" + -DALLOW_DOWNLOADING_GOOGLETEST=${BENCHMARK_DOWNLOAD_DEPENDENCIES} -DGOOGLETEST_PATH:PATH=${GOOGLETEST_PATH} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_PREFIX} +) + +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() + +execute_process( + COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_PREFIX} +) + +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +# Prevent overriding the parent project's compiler/linker +# settings on Windows +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +include(${GOOGLETEST_PREFIX}/googletest-paths.cmake) + +# Add googletest directly to our build. This defines +# the gtest and gtest_main targets. +add_subdirectory(${GOOGLETEST_SOURCE_DIR} + ${GOOGLETEST_BINARY_DIR} + EXCLUDE_FROM_ALL) + +set_target_properties(gtest PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $) +set_target_properties(gtest_main PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $) +set_target_properties(gmock PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $) +set_target_properties(gmock_main PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $) diff --git a/thirdparty/benchmark-1.5.0/cmake/GoogleTest.cmake.in b/thirdparty/benchmark-1.5.0/cmake/GoogleTest.cmake.in new file mode 100644 index 0000000000..28818ee293 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/GoogleTest.cmake.in @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 2.8.12) + +project(googletest-download NONE) + +# Enable ExternalProject CMake module +include(ExternalProject) + +option(ALLOW_DOWNLOADING_GOOGLETEST "If googletest src tree is not found in location specified by GOOGLETEST_PATH, do fetch the archive from internet" OFF) +set(GOOGLETEST_PATH "/usr/src/googletest" CACHE PATH + "Path to the googletest root tree. Should contain googletest and googlemock subdirs. And CMakeLists.txt in root, and in both of these subdirs") + +# Download and install GoogleTest + +message(STATUS "Looking for Google Test sources") +message(STATUS "Looking for Google Test sources in ${GOOGLETEST_PATH}") +if(EXISTS "${GOOGLETEST_PATH}" AND IS_DIRECTORY "${GOOGLETEST_PATH}" AND EXISTS "${GOOGLETEST_PATH}/CMakeLists.txt" AND + EXISTS "${GOOGLETEST_PATH}/googletest" AND IS_DIRECTORY "${GOOGLETEST_PATH}/googletest" AND EXISTS "${GOOGLETEST_PATH}/googletest/CMakeLists.txt" AND + EXISTS "${GOOGLETEST_PATH}/googlemock" AND IS_DIRECTORY "${GOOGLETEST_PATH}/googlemock" AND EXISTS "${GOOGLETEST_PATH}/googlemock/CMakeLists.txt") + message(STATUS "Found Google Test in ${GOOGLETEST_PATH}") + + ExternalProject_Add( + googletest + PREFIX "${CMAKE_BINARY_DIR}" + DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/download" + SOURCE_DIR "${GOOGLETEST_PATH}" # use existing src dir. + BINARY_DIR "${CMAKE_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) +else() + if(NOT ALLOW_DOWNLOADING_GOOGLETEST) + message(SEND_ERROR "Did not find Google Test sources! Either pass correct path in GOOGLETEST_PATH, or enable ALLOW_DOWNLOADING_GOOGLETEST, or disable BENCHMARK_ENABLE_GTEST_TESTS / BENCHMARK_ENABLE_TESTING.") + else() + message(WARNING "Did not find Google Test sources! Fetching from web...") + ExternalProject_Add( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + PREFIX "${CMAKE_BINARY_DIR}" + STAMP_DIR "${CMAKE_BINARY_DIR}/stamp" + DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/download" + SOURCE_DIR "${CMAKE_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) + endif() +endif() + +ExternalProject_Get_Property(googletest SOURCE_DIR BINARY_DIR) +file(WRITE googletest-paths.cmake +"set(GOOGLETEST_SOURCE_DIR \"${SOURCE_DIR}\") +set(GOOGLETEST_BINARY_DIR \"${BINARY_DIR}\") +") diff --git a/thirdparty/benchmark-1.5.0/cmake/benchmark.pc.in b/thirdparty/benchmark-1.5.0/cmake/benchmark.pc.in new file mode 100644 index 0000000000..43ca8f91d7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/benchmark.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: Google microbenchmark framework +Version: @VERSION@ + +Libs: -L${libdir} -lbenchmark +Libs.private: -lpthread +Cflags: -I${includedir} diff --git a/thirdparty/benchmark-1.5.0/cmake/gnu_posix_regex.cpp b/thirdparty/benchmark-1.5.0/cmake/gnu_posix_regex.cpp new file mode 100644 index 0000000000..b5b91cdab7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/gnu_posix_regex.cpp @@ -0,0 +1,12 @@ +#include +#include +int main() { + std::string str = "test0159"; + regex_t re; + int ec = regcomp(&re, "^[a-z]+[0-9]+$", REG_EXTENDED | REG_NOSUB); + if (ec != 0) { + return ec; + } + return regexec(&re, str.c_str(), 0, nullptr, 0) ? -1 : 0; +} + diff --git a/thirdparty/benchmark-1.5.0/cmake/llvm-toolchain.cmake b/thirdparty/benchmark-1.5.0/cmake/llvm-toolchain.cmake new file mode 100644 index 0000000000..fc119e52fd --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/llvm-toolchain.cmake @@ -0,0 +1,8 @@ +find_package(LLVMAr REQUIRED) +set(CMAKE_AR "${LLVMAR_EXECUTABLE}" CACHE FILEPATH "" FORCE) + +find_package(LLVMNm REQUIRED) +set(CMAKE_NM "${LLVMNM_EXECUTABLE}" CACHE FILEPATH "" FORCE) + +find_package(LLVMRanLib REQUIRED) +set(CMAKE_RANLIB "${LLVMRANLIB_EXECUTABLE}" CACHE FILEPATH "" FORCE) diff --git a/thirdparty/benchmark-1.5.0/cmake/posix_regex.cpp b/thirdparty/benchmark-1.5.0/cmake/posix_regex.cpp new file mode 100644 index 0000000000..466dc62560 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/posix_regex.cpp @@ -0,0 +1,14 @@ +#include +#include +int main() { + std::string str = "test0159"; + regex_t re; + int ec = regcomp(&re, "^[a-z]+[0-9]+$", REG_EXTENDED | REG_NOSUB); + if (ec != 0) { + return ec; + } + int ret = regexec(&re, str.c_str(), 0, nullptr, 0) ? -1 : 0; + regfree(&re); + return ret; +} + diff --git a/thirdparty/benchmark-1.5.0/cmake/split_list.cmake b/thirdparty/benchmark-1.5.0/cmake/split_list.cmake new file mode 100644 index 0000000000..67aed3fdc8 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/split_list.cmake @@ -0,0 +1,3 @@ +macro(split_list listname) + string(REPLACE ";" " " ${listname} "${${listname}}") +endmacro() diff --git a/thirdparty/benchmark-1.5.0/cmake/std_regex.cpp b/thirdparty/benchmark-1.5.0/cmake/std_regex.cpp new file mode 100644 index 0000000000..696f2a26bc --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/std_regex.cpp @@ -0,0 +1,10 @@ +#include +#include +int main() { + const std::string str = "test0159"; + std::regex re; + re = std::regex("^[a-z]+[0-9]+$", + std::regex_constants::extended | std::regex_constants::nosubs); + return std::regex_search(str, re) ? 0 : -1; +} + diff --git a/thirdparty/benchmark-1.5.0/cmake/steady_clock.cpp b/thirdparty/benchmark-1.5.0/cmake/steady_clock.cpp new file mode 100644 index 0000000000..66d50d17e9 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/steady_clock.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + typedef std::chrono::steady_clock Clock; + Clock::time_point tp = Clock::now(); + ((void)tp); +} diff --git a/thirdparty/benchmark-1.5.0/cmake/thread_safety_attributes.cpp b/thirdparty/benchmark-1.5.0/cmake/thread_safety_attributes.cpp new file mode 100644 index 0000000000..46161babdb --- /dev/null +++ b/thirdparty/benchmark-1.5.0/cmake/thread_safety_attributes.cpp @@ -0,0 +1,4 @@ +#define HAVE_THREAD_SAFETY_ATTRIBUTES +#include "../src/mutex.h" + +int main() {} diff --git a/thirdparty/benchmark-1.5.0/conan/CMakeLists.txt b/thirdparty/benchmark-1.5.0/conan/CMakeLists.txt new file mode 100644 index 0000000000..15b92ca91a --- /dev/null +++ b/thirdparty/benchmark-1.5.0/conan/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 2.8.11) +project(cmake_wrapper) + +include(conanbuildinfo.cmake) +conan_basic_setup() + +include(${CMAKE_SOURCE_DIR}/CMakeListsOriginal.txt) diff --git a/thirdparty/benchmark-1.5.0/conan/test_package/CMakeLists.txt b/thirdparty/benchmark-1.5.0/conan/test_package/CMakeLists.txt new file mode 100644 index 0000000000..089a6c729d --- /dev/null +++ b/thirdparty/benchmark-1.5.0/conan/test_package/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8.11) +project(test_package) + +set(CMAKE_VERBOSE_MAKEFILE TRUE) + +include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) +conan_basic_setup() + +add_executable(${PROJECT_NAME} test_package.cpp) +target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS}) diff --git a/thirdparty/benchmark-1.5.0/conan/test_package/conanfile.py b/thirdparty/benchmark-1.5.0/conan/test_package/conanfile.py new file mode 100644 index 0000000000..d63f4088c9 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/conan/test_package/conanfile.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from conans import ConanFile, CMake +import os + + +class TestPackageConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "cmake" + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + bin_path = os.path.join("bin", "test_package") + self.run(bin_path, run_environment=True) diff --git a/thirdparty/benchmark-1.5.0/conan/test_package/test_package.cpp b/thirdparty/benchmark-1.5.0/conan/test_package/test_package.cpp new file mode 100644 index 0000000000..4fa7ec0bf9 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/conan/test_package/test_package.cpp @@ -0,0 +1,18 @@ +#include "benchmark/benchmark.h" + +void BM_StringCreation(benchmark::State& state) { + while (state.KeepRunning()) + std::string empty_string; +} + +BENCHMARK(BM_StringCreation); + +void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + while (state.KeepRunning()) + std::string copy(x); +} + +BENCHMARK(BM_StringCopy); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/conanfile.py b/thirdparty/benchmark-1.5.0/conanfile.py new file mode 100644 index 0000000000..e31fc5268a --- /dev/null +++ b/thirdparty/benchmark-1.5.0/conanfile.py @@ -0,0 +1,79 @@ +from conans import ConanFile, CMake, tools +from conans.errors import ConanInvalidConfiguration +import shutil +import os + + +class GoogleBenchmarkConan(ConanFile): + name = "benchmark" + description = "A microbenchmark support library." + topics = ("conan", "benchmark", "google", "microbenchmark") + url = "https://github.com/google/benchmark" + homepage = "https://github.com/google/benchmark" + author = "Google Inc." + license = "Apache-2.0" + exports_sources = ["*"] + generators = "cmake" + + settings = "arch", "build_type", "compiler", "os" + options = { + "shared": [True, False], + "fPIC": [True, False], + "enable_lto": [True, False], + "enable_exceptions": [True, False] + } + default_options = {"shared": False, "fPIC": True, "enable_lto": False, "enable_exceptions": True} + + _build_subfolder = "." + + def source(self): + # Wrap the original CMake file to call conan_basic_setup + shutil.move("CMakeLists.txt", "CMakeListsOriginal.txt") + shutil.move(os.path.join("conan", "CMakeLists.txt"), "CMakeLists.txt") + + def config_options(self): + if self.settings.os == "Windows": + if self.settings.compiler == "Visual Studio" and float(self.settings.compiler.version.value) <= 12: + raise ConanInvalidConfiguration("{} {} does not support Visual Studio <= 12".format(self.name, self.version)) + del self.options.fPIC + + def configure(self): + if self.settings.os == "Windows" and self.options.shared: + raise ConanInvalidConfiguration("Windows shared builds are not supported right now, see issue #639") + + def _configure_cmake(self): + cmake = CMake(self) + + cmake.definitions["BENCHMARK_ENABLE_TESTING"] = "OFF" + cmake.definitions["BENCHMARK_ENABLE_GTEST_TESTS"] = "OFF" + cmake.definitions["BENCHMARK_ENABLE_LTO"] = "ON" if self.options.enable_lto else "OFF" + cmake.definitions["BENCHMARK_ENABLE_EXCEPTIONS"] = "ON" if self.options.enable_exceptions else "OFF" + + # See https://github.com/google/benchmark/pull/638 for Windows 32 build explanation + if self.settings.os != "Windows": + cmake.definitions["BENCHMARK_BUILD_32_BITS"] = "ON" if "64" not in str(self.settings.arch) else "OFF" + cmake.definitions["BENCHMARK_USE_LIBCXX"] = "ON" if (str(self.settings.compiler.libcxx) == "libc++") else "OFF" + else: + cmake.definitions["BENCHMARK_USE_LIBCXX"] = "OFF" + + cmake.configure(build_folder=self._build_subfolder) + return cmake + + def build(self): + cmake = self._configure_cmake() + cmake.build() + + def package(self): + cmake = self._configure_cmake() + cmake.install() + + self.copy(pattern="LICENSE", dst="licenses") + + def package_info(self): + self.cpp_info.libs = tools.collect_libs(self) + if self.settings.os == "Linux": + self.cpp_info.libs.extend(["pthread", "rt"]) + elif self.settings.os == "Windows": + self.cpp_info.libs.append("shlwapi") + elif self.settings.os == "SunOS": + self.cpp_info.libs.append("kstat") diff --git a/thirdparty/benchmark-1.5.0/dependencies.md b/thirdparty/benchmark-1.5.0/dependencies.md new file mode 100644 index 0000000000..6289b4e354 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/dependencies.md @@ -0,0 +1,18 @@ +# Build tool dependency policy + +To ensure the broadest compatibility when building the benchmark library, but +still allow forward progress, we require any build tooling to be available for: + +* Debian stable AND +* The last two Ubuntu LTS releases AND + +Currently, this means using build tool versions that are available for Ubuntu +16.04 (Xenial), Ubuntu 18.04 (Bionic), and Debian stretch. + +_Note, [travis](.travis.yml) runs under Ubuntu 14.04 (Trusty) for linux builds._ + +## cmake +The current supported version is cmake 3.5.1 as of 2018-06-06. + +_Note, this version is also available for Ubuntu 14.04, the previous Ubuntu LTS +release, as `cmake3`._ diff --git a/thirdparty/benchmark-1.5.0/docs/AssemblyTests.md b/thirdparty/benchmark-1.5.0/docs/AssemblyTests.md new file mode 100644 index 0000000000..1fbdc269b5 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/docs/AssemblyTests.md @@ -0,0 +1,147 @@ +# Assembly Tests + +The Benchmark library provides a number of functions whose primary +purpose in to affect assembly generation, including `DoNotOptimize` +and `ClobberMemory`. In addition there are other functions, +such as `KeepRunning`, for which generating good assembly is paramount. + +For these functions it's important to have tests that verify the +correctness and quality of the implementation. This requires testing +the code generated by the compiler. + +This document describes how the Benchmark library tests compiler output, +as well as how to properly write new tests. + + +## Anatomy of a Test + +Writing a test has two steps: + +* Write the code you want to generate assembly for. +* Add `// CHECK` lines to match against the verified assembly. + +Example: +```c++ + +// CHECK-LABEL: test_add: +extern "C" int test_add() { + extern int ExternInt; + return ExternInt + 1; + + // CHECK: movl ExternInt(%rip), %eax + // CHECK: addl %eax + // CHECK: ret +} + +``` + +#### LLVM Filecheck + +[LLVM's Filecheck](https://llvm.org/docs/CommandGuide/FileCheck.html) +is used to test the generated assembly against the `// CHECK` lines +specified in the tests source file. Please see the documentation +linked above for information on how to write `CHECK` directives. + +#### Tips and Tricks: + +* Tests should match the minimal amount of output required to establish +correctness. `CHECK` directives don't have to match on the exact next line +after the previous match, so tests should omit checks for unimportant +bits of assembly. ([`CHECK-NEXT`](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-next-directive) +can be used to ensure a match occurs exactly after the previous match). + +* The tests are compiled with `-O3 -g0`. So we're only testing the +optimized output. + +* The assembly output is further cleaned up using `tools/strip_asm.py`. +This removes comments, assembler directives, and unused labels before +the test is run. + +* The generated and stripped assembly file for a test is output under +`/test/.s` + +* Filecheck supports using [`CHECK` prefixes](https://llvm.org/docs/CommandGuide/FileCheck.html#cmdoption-check-prefixes) +to specify lines that should only match in certain situations. +The Benchmark tests use `CHECK-CLANG` and `CHECK-GNU` for lines that +are only expected to match Clang or GCC's output respectively. Normal +`CHECK` lines match against all compilers. (Note: `CHECK-NOT` and +`CHECK-LABEL` are NOT prefixes. They are versions of non-prefixed +`CHECK` lines) + +* Use `extern "C"` to disable name mangling for specific functions. This +makes them easier to name in the `CHECK` lines. + + +## Problems Writing Portable Tests + +Writing tests which check the code generated by a compiler are +inherently non-portable. Different compilers and even different compiler +versions may generate entirely different code. The Benchmark tests +must tolerate this. + +LLVM Filecheck provides a number of mechanisms to help write +"more portable" tests; including [matching using regular expressions](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-pattern-matching-syntax), +allowing the creation of [named variables](https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-variables) +for later matching, and [checking non-sequential matches](https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-dag-directive). + +#### Capturing Variables + +For example, say GCC stores a variable in a register but Clang stores +it in memory. To write a test that tolerates both cases we "capture" +the destination of the store, and then use the captured expression +to write the remainder of the test. + +```c++ +// CHECK-LABEL: test_div_no_op_into_shr: +extern "C" void test_div_no_op_into_shr(int value) { + int divisor = 2; + benchmark::DoNotOptimize(divisor); // hide the value from the optimizer + return value / divisor; + + // CHECK: movl $2, [[DEST:.*]] + // CHECK: idivl [[DEST]] + // CHECK: ret +} +``` + +#### Using Regular Expressions to Match Differing Output + +Often tests require testing assembly lines which may subtly differ +between compilers or compiler versions. A common example of this +is matching stack frame addresses. In this case regular expressions +can be used to match the differing bits of output. For example: + +```c++ +int ExternInt; +struct Point { int x, y, z; }; + +// CHECK-LABEL: test_store_point: +extern "C" void test_store_point() { + Point p{ExternInt, ExternInt, ExternInt}; + benchmark::DoNotOptimize(p); + + // CHECK: movl ExternInt(%rip), %eax + // CHECK: movl %eax, -{{[0-9]+}}(%rsp) + // CHECK: movl %eax, -{{[0-9]+}}(%rsp) + // CHECK: movl %eax, -{{[0-9]+}}(%rsp) + // CHECK: ret +} +``` + +## Current Requirements and Limitations + +The tests require Filecheck to be installed along the `PATH` of the +build machine. Otherwise the tests will be disabled. + +Additionally, as mentioned in the previous section, codegen tests are +inherently non-portable. Currently the tests are limited to: + +* x86_64 targets. +* Compiled with GCC or Clang + +Further work could be done, at least on a limited basis, to extend the +tests to other architectures and compilers (using `CHECK` prefixes). + +Furthermore, the tests fail for builds which specify additional flags +that modify code generation, including `--coverage` or `-fsanitize=`. + diff --git a/thirdparty/benchmark-1.5.0/docs/_config.yml b/thirdparty/benchmark-1.5.0/docs/_config.yml new file mode 100644 index 0000000000..18854876c6 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-midnight \ No newline at end of file diff --git a/thirdparty/benchmark-1.5.0/docs/tools.md b/thirdparty/benchmark-1.5.0/docs/tools.md new file mode 100644 index 0000000000..4a3b2e9bd2 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/docs/tools.md @@ -0,0 +1,199 @@ +# Benchmark Tools + +## compare.py + +The `compare.py` can be used to compare the result of benchmarks. + +**NOTE**: the utility relies on the scipy package which can be installed using [these instructions](https://www.scipy.org/install.html). + +### Displaying aggregates only + +The switch `-a` / `--display_aggregates_only` can be used to control the +displayment of the normal iterations vs the aggregates. When passed, it will +be passthrough to the benchmark binaries to be run, and will be accounted for +in the tool itself; only the aggregates will be displayed, but not normal runs. +It only affects the display, the separate runs will still be used to calculate +the U test. + +### Modes of operation + +There are three modes of operation: + +1. Just compare two benchmarks +The program is invoked like: + +``` bash +$ compare.py benchmarks [benchmark options]... +``` +Where `` and `` either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file. + +`[benchmark options]` will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal `--benchmark_*` parameters, or some custom parameters your binary takes. + +Example output: +``` +$ ./compare.py benchmarks ./a.out ./a.out +RUNNING: ./a.out --benchmark_out=/tmp/tmprBT5nW +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:16:44 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 36 ns 36 ns 19101577 211.669MB/s +BM_memcpy/64 76 ns 76 ns 9412571 800.199MB/s +BM_memcpy/512 84 ns 84 ns 8249070 5.64771GB/s +BM_memcpy/1024 116 ns 116 ns 6181763 8.19505GB/s +BM_memcpy/8192 643 ns 643 ns 1062855 11.8636GB/s +BM_copy/8 222 ns 222 ns 3137987 34.3772MB/s +BM_copy/64 1608 ns 1608 ns 432758 37.9501MB/s +BM_copy/512 12589 ns 12589 ns 54806 38.7867MB/s +BM_copy/1024 25169 ns 25169 ns 27713 38.8003MB/s +BM_copy/8192 201165 ns 201112 ns 3486 38.8466MB/s +RUNNING: ./a.out --benchmark_out=/tmp/tmpt1wwG_ +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:16:53 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 36 ns 36 ns 19397903 211.255MB/s +BM_memcpy/64 73 ns 73 ns 9691174 839.635MB/s +BM_memcpy/512 85 ns 85 ns 8312329 5.60101GB/s +BM_memcpy/1024 118 ns 118 ns 6438774 8.11608GB/s +BM_memcpy/8192 656 ns 656 ns 1068644 11.6277GB/s +BM_copy/8 223 ns 223 ns 3146977 34.2338MB/s +BM_copy/64 1611 ns 1611 ns 435340 37.8751MB/s +BM_copy/512 12622 ns 12622 ns 54818 38.6844MB/s +BM_copy/1024 25257 ns 25239 ns 27779 38.6927MB/s +BM_copy/8192 205013 ns 205010 ns 3479 38.108MB/s +Comparing ./a.out to ./a.out +Benchmark Time CPU Time Old Time New CPU Old CPU New +------------------------------------------------------------------------------------------------------ +BM_memcpy/8 +0.0020 +0.0020 36 36 36 36 +BM_memcpy/64 -0.0468 -0.0470 76 73 76 73 +BM_memcpy/512 +0.0081 +0.0083 84 85 84 85 +BM_memcpy/1024 +0.0098 +0.0097 116 118 116 118 +BM_memcpy/8192 +0.0200 +0.0203 643 656 643 656 +BM_copy/8 +0.0046 +0.0042 222 223 222 223 +BM_copy/64 +0.0020 +0.0020 1608 1611 1608 1611 +BM_copy/512 +0.0027 +0.0026 12589 12622 12589 12622 +BM_copy/1024 +0.0035 +0.0028 25169 25257 25169 25239 +BM_copy/8192 +0.0191 +0.0194 201165 205013 201112 205010 +``` + +What it does is for the every benchmark from the first run it looks for the benchmark with exactly the same name in the second run, and then compares the results. If the names differ, the benchmark is omitted from the diff. +As you can note, the values in `Time` and `CPU` columns are calculated as `(new - old) / |old|`. + +2. Compare two different filters of one benchmark +The program is invoked like: + +``` bash +$ compare.py filters [benchmark options]... +``` +Where `` either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file. + +Where `` and `` are the same regex filters that you would pass to the `[--benchmark_filter=]` parameter of the benchmark binary. + +`[benchmark options]` will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal `--benchmark_*` parameters, or some custom parameters your binary takes. + +Example output: +``` +$ ./compare.py filters ./a.out BM_memcpy BM_copy +RUNNING: ./a.out --benchmark_filter=BM_memcpy --benchmark_out=/tmp/tmpBWKk0k +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:37:28 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 36 ns 36 ns 17891491 211.215MB/s +BM_memcpy/64 74 ns 74 ns 9400999 825.646MB/s +BM_memcpy/512 87 ns 87 ns 8027453 5.46126GB/s +BM_memcpy/1024 111 ns 111 ns 6116853 8.5648GB/s +BM_memcpy/8192 657 ns 656 ns 1064679 11.6247GB/s +RUNNING: ./a.out --benchmark_filter=BM_copy --benchmark_out=/tmp/tmpAvWcOM +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:37:33 +---------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------- +BM_copy/8 227 ns 227 ns 3038700 33.6264MB/s +BM_copy/64 1640 ns 1640 ns 426893 37.2154MB/s +BM_copy/512 12804 ns 12801 ns 55417 38.1444MB/s +BM_copy/1024 25409 ns 25407 ns 27516 38.4365MB/s +BM_copy/8192 202986 ns 202990 ns 3454 38.4871MB/s +Comparing BM_memcpy to BM_copy (from ./a.out) +Benchmark Time CPU Time Old Time New CPU Old CPU New +-------------------------------------------------------------------------------------------------------------------- +[BM_memcpy vs. BM_copy]/8 +5.2829 +5.2812 36 227 36 227 +[BM_memcpy vs. BM_copy]/64 +21.1719 +21.1856 74 1640 74 1640 +[BM_memcpy vs. BM_copy]/512 +145.6487 +145.6097 87 12804 87 12801 +[BM_memcpy vs. BM_copy]/1024 +227.1860 +227.1776 111 25409 111 25407 +[BM_memcpy vs. BM_copy]/8192 +308.1664 +308.2898 657 202986 656 202990 +``` + +As you can see, it applies filter to the benchmarks, both when running the benchmark, and before doing the diff. And to make the diff work, the matches are replaced with some common string. Thus, you can compare two different benchmark families within one benchmark binary. +As you can note, the values in `Time` and `CPU` columns are calculated as `(new - old) / |old|`. + +3. Compare filter one from benchmark one to filter two from benchmark two: +The program is invoked like: + +``` bash +$ compare.py filters [benchmark options]... +``` + +Where `` and `` either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file. + +Where `` and `` are the same regex filters that you would pass to the `[--benchmark_filter=]` parameter of the benchmark binary. + +`[benchmark options]` will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal `--benchmark_*` parameters, or some custom parameters your binary takes. + +Example output: +``` +$ ./compare.py benchmarksfiltered ./a.out BM_memcpy ./a.out BM_copy +RUNNING: ./a.out --benchmark_filter=BM_memcpy --benchmark_out=/tmp/tmp_FvbYg +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:38:27 +------------------------------------------------------ +Benchmark Time CPU Iterations +------------------------------------------------------ +BM_memcpy/8 37 ns 37 ns 18953482 204.118MB/s +BM_memcpy/64 74 ns 74 ns 9206578 828.245MB/s +BM_memcpy/512 91 ns 91 ns 8086195 5.25476GB/s +BM_memcpy/1024 120 ns 120 ns 5804513 7.95662GB/s +BM_memcpy/8192 664 ns 664 ns 1028363 11.4948GB/s +RUNNING: ./a.out --benchmark_filter=BM_copy --benchmark_out=/tmp/tmpDfL5iE +Run on (8 X 4000 MHz CPU s) +2017-11-07 21:38:32 +---------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------- +BM_copy/8 230 ns 230 ns 2985909 33.1161MB/s +BM_copy/64 1654 ns 1653 ns 419408 36.9137MB/s +BM_copy/512 13122 ns 13120 ns 53403 37.2156MB/s +BM_copy/1024 26679 ns 26666 ns 26575 36.6218MB/s +BM_copy/8192 215068 ns 215053 ns 3221 36.3283MB/s +Comparing BM_memcpy (from ./a.out) to BM_copy (from ./a.out) +Benchmark Time CPU Time Old Time New CPU Old CPU New +-------------------------------------------------------------------------------------------------------------------- +[BM_memcpy vs. BM_copy]/8 +5.1649 +5.1637 37 230 37 230 +[BM_memcpy vs. BM_copy]/64 +21.4352 +21.4374 74 1654 74 1653 +[BM_memcpy vs. BM_copy]/512 +143.6022 +143.5865 91 13122 91 13120 +[BM_memcpy vs. BM_copy]/1024 +221.5903 +221.4790 120 26679 120 26666 +[BM_memcpy vs. BM_copy]/8192 +322.9059 +323.0096 664 215068 664 215053 +``` +This is a mix of the previous two modes, two (potentially different) benchmark binaries are run, and a different filter is applied to each one. +As you can note, the values in `Time` and `CPU` columns are calculated as `(new - old) / |old|`. + +### U test + +If there is a sufficient repetition count of the benchmarks, the tool can do +a [U Test](https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test), of the +null hypothesis that it is equally likely that a randomly selected value from +one sample will be less than or greater than a randomly selected value from a +second sample. + +If the calculated p-value is below this value is lower than the significance +level alpha, then the result is said to be statistically significant and the +null hypothesis is rejected. Which in other words means that the two benchmarks +aren't identical. + +**WARNING**: requires **LARGE** (no less than 9) number of repetitions to be +meaningful! diff --git a/thirdparty/benchmark-1.5.0/include/benchmark/benchmark.h b/thirdparty/benchmark-1.5.0/include/benchmark/benchmark.h new file mode 100644 index 0000000000..6cb96f546d --- /dev/null +++ b/thirdparty/benchmark-1.5.0/include/benchmark/benchmark.h @@ -0,0 +1,1583 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Support for registering benchmarks for functions. + +/* Example usage: +// Define a function that executes the code to be measured a +// specified number of times: +static void BM_StringCreation(benchmark::State& state) { + for (auto _ : state) + std::string empty_string; +} + +// Register the function as a benchmark +BENCHMARK(BM_StringCreation); + +// Define another benchmark +static void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + for (auto _ : state) + std::string copy(x); +} +BENCHMARK(BM_StringCopy); + +// Augment the main() program to invoke benchmarks if specified +// via the --benchmarks command line flag. E.g., +// my_unittest --benchmark_filter=all +// my_unittest --benchmark_filter=BM_StringCreation +// my_unittest --benchmark_filter=String +// my_unittest --benchmark_filter='Copy|Creation' +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + return 0; +} + +// Sometimes a family of microbenchmarks can be implemented with +// just one routine that takes an extra argument to specify which +// one of the family of benchmarks to run. For example, the following +// code defines a family of microbenchmarks for measuring the speed +// of memcpy() calls of different lengths: + +static void BM_memcpy(benchmark::State& state) { + char* src = new char[state.range(0)]; char* dst = new char[state.range(0)]; + memset(src, 'x', state.range(0)); + for (auto _ : state) + memcpy(dst, src, state.range(0)); + state.SetBytesProcessed(state.iterations() * state.range(0)); + delete[] src; delete[] dst; +} +BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1<<10)->Arg(8<<10); + +// The preceding code is quite repetitive, and can be replaced with the +// following short-hand. The following invocation will pick a few +// appropriate arguments in the specified range and will generate a +// microbenchmark for each such argument. +BENCHMARK(BM_memcpy)->Range(8, 8<<10); + +// You might have a microbenchmark that depends on two inputs. For +// example, the following code defines a family of microbenchmarks for +// measuring the speed of set insertion. +static void BM_SetInsert(benchmark::State& state) { + set data; + for (auto _ : state) { + state.PauseTiming(); + data = ConstructRandomSet(state.range(0)); + state.ResumeTiming(); + for (int j = 0; j < state.range(1); ++j) + data.insert(RandomNumber()); + } +} +BENCHMARK(BM_SetInsert) + ->Args({1<<10, 128}) + ->Args({2<<10, 128}) + ->Args({4<<10, 128}) + ->Args({8<<10, 128}) + ->Args({1<<10, 512}) + ->Args({2<<10, 512}) + ->Args({4<<10, 512}) + ->Args({8<<10, 512}); + +// The preceding code is quite repetitive, and can be replaced with +// the following short-hand. The following macro will pick a few +// appropriate arguments in the product of the two specified ranges +// and will generate a microbenchmark for each such pair. +BENCHMARK(BM_SetInsert)->Ranges({{1<<10, 8<<10}, {128, 512}}); + +// For more complex patterns of inputs, passing a custom function +// to Apply allows programmatic specification of an +// arbitrary set of arguments to run the microbenchmark on. +// The following example enumerates a dense range on +// one parameter, and a sparse range on the second. +static void CustomArguments(benchmark::internal::Benchmark* b) { + for (int i = 0; i <= 10; ++i) + for (int j = 32; j <= 1024*1024; j *= 8) + b->Args({i, j}); +} +BENCHMARK(BM_SetInsert)->Apply(CustomArguments); + +// Templated microbenchmarks work the same way: +// Produce then consume 'size' messages 'iters' times +// Measures throughput in the absence of multiprogramming. +template int BM_Sequential(benchmark::State& state) { + Q q; + typename Q::value_type v; + for (auto _ : state) { + for (int i = state.range(0); i--; ) + q.push(v); + for (int e = state.range(0); e--; ) + q.Wait(&v); + } + // actually messages, not bytes: + state.SetBytesProcessed(state.iterations() * state.range(0)); +} +BENCHMARK_TEMPLATE(BM_Sequential, WaitQueue)->Range(1<<0, 1<<10); + +Use `Benchmark::MinTime(double t)` to set the minimum time used to run the +benchmark. This option overrides the `benchmark_min_time` flag. + +void BM_test(benchmark::State& state) { + ... body ... +} +BENCHMARK(BM_test)->MinTime(2.0); // Run for at least 2 seconds. + +In a multithreaded test, it is guaranteed that none of the threads will start +until all have reached the loop start, and all will have finished before any +thread exits the loop body. As such, any global setup or teardown you want to +do can be wrapped in a check against the thread index: + +static void BM_MultiThreaded(benchmark::State& state) { + if (state.thread_index == 0) { + // Setup code here. + } + for (auto _ : state) { + // Run the test as normal. + } + if (state.thread_index == 0) { + // Teardown code here. + } +} +BENCHMARK(BM_MultiThreaded)->Threads(4); + + +If a benchmark runs a few milliseconds it may be hard to visually compare the +measured times, since the output data is given in nanoseconds per default. In +order to manually set the time unit, you can specify it manually: + +BENCHMARK(BM_test)->Unit(benchmark::kMillisecond); +*/ + +#ifndef BENCHMARK_BENCHMARK_H_ +#define BENCHMARK_BENCHMARK_H_ + +// The _MSVC_LANG check should detect Visual Studio 2015 Update 3 and newer. +#if __cplusplus >= 201103L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201103L) +#define BENCHMARK_HAS_CXX11 +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BENCHMARK_HAS_CXX11) +#include +#include +#include +#endif + +#if defined(_MSC_VER) +#include // for _ReadWriteBarrier +#endif + +#ifndef BENCHMARK_HAS_CXX11 +#define BENCHMARK_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + TypeName& operator=(const TypeName&) +#else +#define BENCHMARK_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete +#endif + +#if defined(__GNUC__) +#define BENCHMARK_UNUSED __attribute__((unused)) +#define BENCHMARK_ALWAYS_INLINE __attribute__((always_inline)) +#define BENCHMARK_NOEXCEPT noexcept +#define BENCHMARK_NOEXCEPT_OP(x) noexcept(x) +#elif defined(_MSC_VER) && !defined(__clang__) +#define BENCHMARK_UNUSED +#define BENCHMARK_ALWAYS_INLINE __forceinline +#if _MSC_VER >= 1900 +#define BENCHMARK_NOEXCEPT noexcept +#define BENCHMARK_NOEXCEPT_OP(x) noexcept(x) +#else +#define BENCHMARK_NOEXCEPT +#define BENCHMARK_NOEXCEPT_OP(x) +#endif +#define __func__ __FUNCTION__ +#else +#define BENCHMARK_UNUSED +#define BENCHMARK_ALWAYS_INLINE +#define BENCHMARK_NOEXCEPT +#define BENCHMARK_NOEXCEPT_OP(x) +#endif + +#define BENCHMARK_INTERNAL_TOSTRING2(x) #x +#define BENCHMARK_INTERNAL_TOSTRING(x) BENCHMARK_INTERNAL_TOSTRING2(x) + +#if defined(__GNUC__) || defined(__clang__) +#define BENCHMARK_BUILTIN_EXPECT(x, y) __builtin_expect(x, y) +#define BENCHMARK_DEPRECATED_MSG(msg) __attribute__((deprecated(msg))) +#else +#define BENCHMARK_BUILTIN_EXPECT(x, y) x +#define BENCHMARK_DEPRECATED_MSG(msg) +#define BENCHMARK_WARNING_MSG(msg) \ + __pragma(message(__FILE__ "(" BENCHMARK_INTERNAL_TOSTRING( \ + __LINE__) ") : warning note: " msg)) +#endif + +#if defined(__GNUC__) && !defined(__clang__) +#define BENCHMARK_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#if defined(__GNUC__) || __has_builtin(__builtin_unreachable) +#define BENCHMARK_UNREACHABLE() __builtin_unreachable() +#elif defined(_MSC_VER) +#define BENCHMARK_UNREACHABLE() __assume(false) +#else +#define BENCHMARK_UNREACHABLE() ((void)0) +#endif + +namespace benchmark { +class BenchmarkReporter; +class MemoryManager; + +void Initialize(int* argc, char** argv); + +// Report to stdout all arguments in 'argv' as unrecognized except the first. +// Returns true there is at least on unrecognized argument (i.e. 'argc' > 1). +bool ReportUnrecognizedArguments(int argc, char** argv); + +// Generate a list of benchmarks matching the specified --benchmark_filter flag +// and if --benchmark_list_tests is specified return after printing the name +// of each matching benchmark. Otherwise run each matching benchmark and +// report the results. +// +// The second and third overload use the specified 'display_reporter' and +// 'file_reporter' respectively. 'file_reporter' will write to the file +// specified +// by '--benchmark_output'. If '--benchmark_output' is not given the +// 'file_reporter' is ignored. +// +// RETURNS: The number of matching benchmarks. +size_t RunSpecifiedBenchmarks(); +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter); +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter); + +// Register a MemoryManager instance that will be used to collect and report +// allocation measurements for benchmark runs. +void RegisterMemoryManager(MemoryManager* memory_manager); + +namespace internal { +class Benchmark; +class BenchmarkImp; +class BenchmarkFamilies; + +void UseCharPointer(char const volatile*); + +// Take ownership of the pointer and register the benchmark. Return the +// registered benchmark. +Benchmark* RegisterBenchmarkInternal(Benchmark*); + +// Ensure that the standard streams are properly initialized in every TU. +int InitializeStreams(); +BENCHMARK_UNUSED static int stream_init_anchor = InitializeStreams(); + +} // namespace internal + +#if (!defined(__GNUC__) && !defined(__clang__)) || defined(__pnacl__) || \ + defined(__EMSCRIPTEN__) +#define BENCHMARK_HAS_NO_INLINE_ASSEMBLY +#endif + +// The DoNotOptimize(...) function can be used to prevent a value or +// expression from being optimized away by the compiler. This function is +// intended to add little to no overhead. +// See: https://youtu.be/nXaxk27zwlk?t=2441 +#ifndef BENCHMARK_HAS_NO_INLINE_ASSEMBLY +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) { + asm volatile("" : : "r,m"(value) : "memory"); +} + +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) { +#if defined(__clang__) + asm volatile("" : "+r,m"(value) : : "memory"); +#else + asm volatile("" : "+m,r"(value) : : "memory"); +#endif +} + +// Force the compiler to flush pending writes to global memory. Acts as an +// effective read/write barrier +inline BENCHMARK_ALWAYS_INLINE void ClobberMemory() { + asm volatile("" : : : "memory"); +} +#elif defined(_MSC_VER) +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) { + internal::UseCharPointer(&reinterpret_cast(value)); + _ReadWriteBarrier(); +} + +inline BENCHMARK_ALWAYS_INLINE void ClobberMemory() { _ReadWriteBarrier(); } +#else +template +inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) { + internal::UseCharPointer(&reinterpret_cast(value)); +} +// FIXME Add ClobberMemory() for non-gnu and non-msvc compilers +#endif + +// This class is used for user-defined counters. +class Counter { + public: + enum Flags { + kDefaults = 0, + // Mark the counter as a rate. It will be presented divided + // by the duration of the benchmark. + kIsRate = 1U << 0U, + // Mark the counter as a thread-average quantity. It will be + // presented divided by the number of threads. + kAvgThreads = 1U << 1U, + // Mark the counter as a thread-average rate. See above. + kAvgThreadsRate = kIsRate | kAvgThreads, + // Mark the counter as a constant value, valid/same for *every* iteration. + // When reporting, it will be *multiplied* by the iteration count. + kIsIterationInvariant = 1U << 2U, + // Mark the counter as a constant rate. + // When reporting, it will be *multiplied* by the iteration count + // and then divided by the duration of the benchmark. + kIsIterationInvariantRate = kIsRate | kIsIterationInvariant, + // Mark the counter as a iteration-average quantity. + // It will be presented divided by the number of iterations. + kAvgIterations = 1U << 3U, + // Mark the counter as a iteration-average rate. See above. + kAvgIterationsRate = kIsRate | kAvgIterations + }; + + enum OneK { + // 1'000 items per 1k + kIs1000 = 1000, + // 1'024 items per 1k + kIs1024 = 1024 + }; + + double value; + Flags flags; + OneK oneK; + + BENCHMARK_ALWAYS_INLINE + Counter(double v = 0., Flags f = kDefaults, OneK k = kIs1000) + : value(v), flags(f), oneK(k) {} + + BENCHMARK_ALWAYS_INLINE operator double const&() const { return value; } + BENCHMARK_ALWAYS_INLINE operator double&() { return value; } +}; + +// A helper for user code to create unforeseen combinations of Flags, without +// having to do this cast manually each time, or providing this operator. +Counter::Flags inline operator|(const Counter::Flags& LHS, + const Counter::Flags& RHS) { + return static_cast(static_cast(LHS) | + static_cast(RHS)); +} + +// This is the container for the user-defined counters. +typedef std::map UserCounters; + +// TimeUnit is passed to a benchmark in order to specify the order of magnitude +// for the measured time. +enum TimeUnit { kNanosecond, kMicrosecond, kMillisecond }; + +// BigO is passed to a benchmark in order to specify the asymptotic +// computational +// complexity for the benchmark. In case oAuto is selected, complexity will be +// calculated automatically to the best fit. +enum BigO { oNone, o1, oN, oNSquared, oNCubed, oLogN, oNLogN, oAuto, oLambda }; + +typedef uint64_t IterationCount; + +// BigOFunc is passed to a benchmark in order to specify the asymptotic +// computational complexity for the benchmark. +typedef double(BigOFunc)(IterationCount); + +// StatisticsFunc is passed to a benchmark in order to compute some descriptive +// statistics over all the measurements of some type +typedef double(StatisticsFunc)(const std::vector&); + +namespace internal { +struct Statistics { + std::string name_; + StatisticsFunc* compute_; + + Statistics(const std::string& name, StatisticsFunc* compute) + : name_(name), compute_(compute) {} +}; + +struct BenchmarkInstance; +class ThreadTimer; +class ThreadManager; + +enum AggregationReportMode +#if defined(BENCHMARK_HAS_CXX11) + : unsigned +#else +#endif +{ + // The mode has not been manually specified + ARM_Unspecified = 0, + // The mode is user-specified. + // This may or may not be set when the following bit-flags are set. + ARM_Default = 1U << 0U, + // File reporter should only output aggregates. + ARM_FileReportAggregatesOnly = 1U << 1U, + // Display reporter should only output aggregates + ARM_DisplayReportAggregatesOnly = 1U << 2U, + // Both reporters should only display aggregates. + ARM_ReportAggregatesOnly = + ARM_FileReportAggregatesOnly | ARM_DisplayReportAggregatesOnly +}; + +} // namespace internal + +// State is passed to a running Benchmark and contains state for the +// benchmark to use. +class State { + public: + struct StateIterator; + friend struct StateIterator; + + // Returns iterators used to run each iteration of a benchmark using a + // C++11 ranged-based for loop. These functions should not be called directly. + // + // REQUIRES: The benchmark has not started running yet. Neither begin nor end + // have been called previously. + // + // NOTE: KeepRunning may not be used after calling either of these functions. + BENCHMARK_ALWAYS_INLINE StateIterator begin(); + BENCHMARK_ALWAYS_INLINE StateIterator end(); + + // Returns true if the benchmark should continue through another iteration. + // NOTE: A benchmark may not return from the test until KeepRunning() has + // returned false. + bool KeepRunning(); + + // Returns true iff the benchmark should run n more iterations. + // REQUIRES: 'n' > 0. + // NOTE: A benchmark must not return from the test until KeepRunningBatch() + // has returned false. + // NOTE: KeepRunningBatch() may overshoot by up to 'n' iterations. + // + // Intended usage: + // while (state.KeepRunningBatch(1000)) { + // // process 1000 elements + // } + bool KeepRunningBatch(IterationCount n); + + // REQUIRES: timer is running and 'SkipWithError(...)' has not been called + // by the current thread. + // Stop the benchmark timer. If not called, the timer will be + // automatically stopped after the last iteration of the benchmark loop. + // + // For threaded benchmarks the PauseTiming() function only pauses the timing + // for the current thread. + // + // NOTE: The "real time" measurement is per-thread. If different threads + // report different measurements the largest one is reported. + // + // NOTE: PauseTiming()/ResumeTiming() are relatively + // heavyweight, and so their use should generally be avoided + // within each benchmark iteration, if possible. + void PauseTiming(); + + // REQUIRES: timer is not running and 'SkipWithError(...)' has not been called + // by the current thread. + // Start the benchmark timer. The timer is NOT running on entrance to the + // benchmark function. It begins running after control flow enters the + // benchmark loop. + // + // NOTE: PauseTiming()/ResumeTiming() are relatively + // heavyweight, and so their use should generally be avoided + // within each benchmark iteration, if possible. + void ResumeTiming(); + + // REQUIRES: 'SkipWithError(...)' has not been called previously by the + // current thread. + // Report the benchmark as resulting in an error with the specified 'msg'. + // After this call the user may explicitly 'return' from the benchmark. + // + // If the ranged-for style of benchmark loop is used, the user must explicitly + // break from the loop, otherwise all future iterations will be run. + // If the 'KeepRunning()' loop is used the current thread will automatically + // exit the loop at the end of the current iteration. + // + // For threaded benchmarks only the current thread stops executing and future + // calls to `KeepRunning()` will block until all threads have completed + // the `KeepRunning()` loop. If multiple threads report an error only the + // first error message is used. + // + // NOTE: Calling 'SkipWithError(...)' does not cause the benchmark to exit + // the current scope immediately. If the function is called from within + // the 'KeepRunning()' loop the current iteration will finish. It is the users + // responsibility to exit the scope as needed. + void SkipWithError(const char* msg); + + // REQUIRES: called exactly once per iteration of the benchmarking loop. + // Set the manually measured time for this benchmark iteration, which + // is used instead of automatically measured time if UseManualTime() was + // specified. + // + // For threaded benchmarks the final value will be set to the largest + // reported values. + void SetIterationTime(double seconds); + + // Set the number of bytes processed by the current benchmark + // execution. This routine is typically called once at the end of a + // throughput oriented benchmark. + // + // REQUIRES: a benchmark has exited its benchmarking loop. + BENCHMARK_ALWAYS_INLINE + void SetBytesProcessed(int64_t bytes) { + counters["bytes_per_second"] = + Counter(static_cast(bytes), Counter::kIsRate, Counter::kIs1024); + } + + BENCHMARK_ALWAYS_INLINE + int64_t bytes_processed() const { + if (counters.find("bytes_per_second") != counters.end()) + return static_cast(counters.at("bytes_per_second")); + return 0; + } + + // If this routine is called with complexity_n > 0 and complexity report is + // requested for the + // family benchmark, then current benchmark will be part of the computation + // and complexity_n will + // represent the length of N. + BENCHMARK_ALWAYS_INLINE + void SetComplexityN(int64_t complexity_n) { complexity_n_ = complexity_n; } + + BENCHMARK_ALWAYS_INLINE + int64_t complexity_length_n() { return complexity_n_; } + + // If this routine is called with items > 0, then an items/s + // label is printed on the benchmark report line for the currently + // executing benchmark. It is typically called at the end of a processing + // benchmark where a processing items/second output is desired. + // + // REQUIRES: a benchmark has exited its benchmarking loop. + BENCHMARK_ALWAYS_INLINE + void SetItemsProcessed(int64_t items) { + counters["items_per_second"] = + Counter(static_cast(items), benchmark::Counter::kIsRate); + } + + BENCHMARK_ALWAYS_INLINE + int64_t items_processed() const { + if (counters.find("items_per_second") != counters.end()) + return static_cast(counters.at("items_per_second")); + return 0; + } + + // If this routine is called, the specified label is printed at the + // end of the benchmark report line for the currently executing + // benchmark. Example: + // static void BM_Compress(benchmark::State& state) { + // ... + // double compress = input_size / output_size; + // state.SetLabel(StrFormat("compress:%.1f%%", 100.0*compression)); + // } + // Produces output that looks like: + // BM_Compress 50 50 14115038 compress:27.3% + // + // REQUIRES: a benchmark has exited its benchmarking loop. + void SetLabel(const char* label); + + void BENCHMARK_ALWAYS_INLINE SetLabel(const std::string& str) { + this->SetLabel(str.c_str()); + } + + // Range arguments for this run. CHECKs if the argument has been set. + BENCHMARK_ALWAYS_INLINE + int64_t range(std::size_t pos = 0) const { + assert(range_.size() > pos); + return range_[pos]; + } + + BENCHMARK_DEPRECATED_MSG("use 'range(0)' instead") + int64_t range_x() const { return range(0); } + + BENCHMARK_DEPRECATED_MSG("use 'range(1)' instead") + int64_t range_y() const { return range(1); } + + BENCHMARK_ALWAYS_INLINE + IterationCount iterations() const { + if (BENCHMARK_BUILTIN_EXPECT(!started_, false)) { + return 0; + } + return max_iterations - total_iterations_ + batch_leftover_; + } + + private + : // items we expect on the first cache line (ie 64 bytes of the struct) + // When total_iterations_ is 0, KeepRunning() and friends will return false. + // May be larger than max_iterations. + IterationCount total_iterations_; + + // When using KeepRunningBatch(), batch_leftover_ holds the number of + // iterations beyond max_iters that were run. Used to track + // completed_iterations_ accurately. + IterationCount batch_leftover_; + + public: + const IterationCount max_iterations; + + private: + bool started_; + bool finished_; + bool error_occurred_; + + private: // items we don't need on the first cache line + std::vector range_; + + int64_t complexity_n_; + + public: + // Container for user-defined counters. + UserCounters counters; + // Index of the executing thread. Values from [0, threads). + const int thread_index; + // Number of threads concurrently executing the benchmark. + const int threads; + + private: + State(IterationCount max_iters, const std::vector& ranges, + int thread_i, int n_threads, internal::ThreadTimer* timer, + internal::ThreadManager* manager); + + void StartKeepRunning(); + // Implementation of KeepRunning() and KeepRunningBatch(). + // is_batch must be true unless n is 1. + bool KeepRunningInternal(IterationCount n, bool is_batch); + void FinishKeepRunning(); + internal::ThreadTimer* timer_; + internal::ThreadManager* manager_; + + friend struct internal::BenchmarkInstance; +}; + +inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunning() { + return KeepRunningInternal(1, /*is_batch=*/false); +} + +inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningBatch(IterationCount n) { + return KeepRunningInternal(n, /*is_batch=*/true); +} + +inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningInternal(IterationCount n, + bool is_batch) { + // total_iterations_ is set to 0 by the constructor, and always set to a + // nonzero value by StartKepRunning(). + assert(n > 0); + // n must be 1 unless is_batch is true. + assert(is_batch || n == 1); + if (BENCHMARK_BUILTIN_EXPECT(total_iterations_ >= n, true)) { + total_iterations_ -= n; + return true; + } + if (!started_) { + StartKeepRunning(); + if (!error_occurred_ && total_iterations_ >= n) { + total_iterations_ -= n; + return true; + } + } + // For non-batch runs, total_iterations_ must be 0 by now. + if (is_batch && total_iterations_ != 0) { + batch_leftover_ = n - total_iterations_; + total_iterations_ = 0; + return true; + } + FinishKeepRunning(); + return false; +} + +struct State::StateIterator { + struct BENCHMARK_UNUSED Value {}; + typedef std::forward_iterator_tag iterator_category; + typedef Value value_type; + typedef Value reference; + typedef Value pointer; + typedef std::ptrdiff_t difference_type; + + private: + friend class State; + BENCHMARK_ALWAYS_INLINE + StateIterator() : cached_(0), parent_() {} + + BENCHMARK_ALWAYS_INLINE + explicit StateIterator(State* st) + : cached_(st->error_occurred_ ? 0 : st->max_iterations), parent_(st) {} + + public: + BENCHMARK_ALWAYS_INLINE + Value operator*() const { return Value(); } + + BENCHMARK_ALWAYS_INLINE + StateIterator& operator++() { + assert(cached_ > 0); + --cached_; + return *this; + } + + BENCHMARK_ALWAYS_INLINE + bool operator!=(StateIterator const&) const { + if (BENCHMARK_BUILTIN_EXPECT(cached_ != 0, true)) return true; + parent_->FinishKeepRunning(); + return false; + } + + private: + IterationCount cached_; + State* const parent_; +}; + +inline BENCHMARK_ALWAYS_INLINE State::StateIterator State::begin() { + return StateIterator(this); +} +inline BENCHMARK_ALWAYS_INLINE State::StateIterator State::end() { + StartKeepRunning(); + return StateIterator(); +} + +namespace internal { + +typedef void(Function)(State&); + +// ------------------------------------------------------ +// Benchmark registration object. The BENCHMARK() macro expands +// into an internal::Benchmark* object. Various methods can +// be called on this object to change the properties of the benchmark. +// Each method returns "this" so that multiple method calls can +// chained into one expression. +class Benchmark { + public: + virtual ~Benchmark(); + + // Note: the following methods all return "this" so that multiple + // method calls can be chained together in one expression. + + // Run this benchmark once with "x" as the extra argument passed + // to the function. + // REQUIRES: The function passed to the constructor must accept an arg1. + Benchmark* Arg(int64_t x); + + // Run this benchmark with the given time unit for the generated output report + Benchmark* Unit(TimeUnit unit); + + // Run this benchmark once for a number of values picked from the + // range [start..limit]. (start and limit are always picked.) + // REQUIRES: The function passed to the constructor must accept an arg1. + Benchmark* Range(int64_t start, int64_t limit); + + // Run this benchmark once for all values in the range [start..limit] with + // specific step + // REQUIRES: The function passed to the constructor must accept an arg1. + Benchmark* DenseRange(int64_t start, int64_t limit, int step = 1); + + // Run this benchmark once with "args" as the extra arguments passed + // to the function. + // REQUIRES: The function passed to the constructor must accept arg1, arg2 ... + Benchmark* Args(const std::vector& args); + + // Equivalent to Args({x, y}) + // NOTE: This is a legacy C++03 interface provided for compatibility only. + // New code should use 'Args'. + Benchmark* ArgPair(int64_t x, int64_t y) { + std::vector args; + args.push_back(x); + args.push_back(y); + return Args(args); + } + + // Run this benchmark once for a number of values picked from the + // ranges [start..limit]. (starts and limits are always picked.) + // REQUIRES: The function passed to the constructor must accept arg1, arg2 ... + Benchmark* Ranges(const std::vector >& ranges); + + // Equivalent to ArgNames({name}) + Benchmark* ArgName(const std::string& name); + + // Set the argument names to display in the benchmark name. If not called, + // only argument values will be shown. + Benchmark* ArgNames(const std::vector& names); + + // Equivalent to Ranges({{lo1, hi1}, {lo2, hi2}}). + // NOTE: This is a legacy C++03 interface provided for compatibility only. + // New code should use 'Ranges'. + Benchmark* RangePair(int64_t lo1, int64_t hi1, int64_t lo2, int64_t hi2) { + std::vector > ranges; + ranges.push_back(std::make_pair(lo1, hi1)); + ranges.push_back(std::make_pair(lo2, hi2)); + return Ranges(ranges); + } + + // Pass this benchmark object to *func, which can customize + // the benchmark by calling various methods like Arg, Args, + // Threads, etc. + Benchmark* Apply(void (*func)(Benchmark* benchmark)); + + // Set the range multiplier for non-dense range. If not called, the range + // multiplier kRangeMultiplier will be used. + Benchmark* RangeMultiplier(int multiplier); + + // Set the minimum amount of time to use when running this benchmark. This + // option overrides the `benchmark_min_time` flag. + // REQUIRES: `t > 0` and `Iterations` has not been called on this benchmark. + Benchmark* MinTime(double t); + + // Specify the amount of iterations that should be run by this benchmark. + // REQUIRES: 'n > 0' and `MinTime` has not been called on this benchmark. + // + // NOTE: This function should only be used when *exact* iteration control is + // needed and never to control or limit how long a benchmark runs, where + // `--benchmark_min_time=N` or `MinTime(...)` should be used instead. + Benchmark* Iterations(IterationCount n); + + // Specify the amount of times to repeat this benchmark. This option overrides + // the `benchmark_repetitions` flag. + // REQUIRES: `n > 0` + Benchmark* Repetitions(int n); + + // Specify if each repetition of the benchmark should be reported separately + // or if only the final statistics should be reported. If the benchmark + // is not repeated then the single result is always reported. + // Applies to *ALL* reporters (display and file). + Benchmark* ReportAggregatesOnly(bool value = true); + + // Same as ReportAggregatesOnly(), but applies to display reporter only. + Benchmark* DisplayAggregatesOnly(bool value = true); + + // By default, the CPU time is measured only for the main thread, which may + // be unrepresentative if the benchmark uses threads internally. If called, + // the total CPU time spent by all the threads will be measured instead. + // By default, the only the main thread CPU time will be measured. + Benchmark* MeasureProcessCPUTime(); + + // If a particular benchmark should use the Wall clock instead of the CPU time + // (be it either the CPU time of the main thread only (default), or the + // total CPU usage of the benchmark), call this method. If called, the elapsed + // (wall) time will be used to control how many iterations are run, and in the + // printing of items/second or MB/seconds values. + // If not called, the CPU time used by the benchmark will be used. + Benchmark* UseRealTime(); + + // If a benchmark must measure time manually (e.g. if GPU execution time is + // being + // measured), call this method. If called, each benchmark iteration should + // call + // SetIterationTime(seconds) to report the measured time, which will be used + // to control how many iterations are run, and in the printing of items/second + // or MB/second values. + Benchmark* UseManualTime(); + + // Set the asymptotic computational complexity for the benchmark. If called + // the asymptotic computational complexity will be shown on the output. + Benchmark* Complexity(BigO complexity = benchmark::oAuto); + + // Set the asymptotic computational complexity for the benchmark. If called + // the asymptotic computational complexity will be shown on the output. + Benchmark* Complexity(BigOFunc* complexity); + + // Add this statistics to be computed over all the values of benchmark run + Benchmark* ComputeStatistics(std::string name, StatisticsFunc* statistics); + + // Support for running multiple copies of the same benchmark concurrently + // in multiple threads. This may be useful when measuring the scaling + // of some piece of code. + + // Run one instance of this benchmark concurrently in t threads. + Benchmark* Threads(int t); + + // Pick a set of values T from [min_threads,max_threads]. + // min_threads and max_threads are always included in T. Run this + // benchmark once for each value in T. The benchmark run for a + // particular value t consists of t threads running the benchmark + // function concurrently. For example, consider: + // BENCHMARK(Foo)->ThreadRange(1,16); + // This will run the following benchmarks: + // Foo in 1 thread + // Foo in 2 threads + // Foo in 4 threads + // Foo in 8 threads + // Foo in 16 threads + Benchmark* ThreadRange(int min_threads, int max_threads); + + // For each value n in the range, run this benchmark once using n threads. + // min_threads and max_threads are always included in the range. + // stride specifies the increment. E.g. DenseThreadRange(1, 8, 3) starts + // a benchmark with 1, 4, 7 and 8 threads. + Benchmark* DenseThreadRange(int min_threads, int max_threads, int stride = 1); + + // Equivalent to ThreadRange(NumCPUs(), NumCPUs()) + Benchmark* ThreadPerCpu(); + + virtual void Run(State& state) = 0; + + protected: + explicit Benchmark(const char* name); + Benchmark(Benchmark const&); + void SetName(const char* name); + + int ArgsCnt() const; + + private: + friend class BenchmarkFamilies; + + std::string name_; + AggregationReportMode aggregation_report_mode_; + std::vector arg_names_; // Args for all benchmark runs + std::vector > args_; // Args for all benchmark runs + TimeUnit time_unit_; + int range_multiplier_; + double min_time_; + IterationCount iterations_; + int repetitions_; + bool measure_process_cpu_time_; + bool use_real_time_; + bool use_manual_time_; + BigO complexity_; + BigOFunc* complexity_lambda_; + std::vector statistics_; + std::vector thread_counts_; + + Benchmark& operator=(Benchmark const&); +}; + +} // namespace internal + +// Create and register a benchmark with the specified 'name' that invokes +// the specified functor 'fn'. +// +// RETURNS: A pointer to the registered benchmark. +internal::Benchmark* RegisterBenchmark(const char* name, + internal::Function* fn); + +#if defined(BENCHMARK_HAS_CXX11) +template +internal::Benchmark* RegisterBenchmark(const char* name, Lambda&& fn); +#endif + +// Remove all registered benchmarks. All pointers to previously registered +// benchmarks are invalidated. +void ClearRegisteredBenchmarks(); + +namespace internal { +// The class used to hold all Benchmarks created from static function. +// (ie those created using the BENCHMARK(...) macros. +class FunctionBenchmark : public Benchmark { + public: + FunctionBenchmark(const char* name, Function* func) + : Benchmark(name), func_(func) {} + + virtual void Run(State& st); + + private: + Function* func_; +}; + +#ifdef BENCHMARK_HAS_CXX11 +template +class LambdaBenchmark : public Benchmark { + public: + virtual void Run(State& st) { lambda_(st); } + + private: + template + LambdaBenchmark(const char* name, OLambda&& lam) + : Benchmark(name), lambda_(std::forward(lam)) {} + + LambdaBenchmark(LambdaBenchmark const&) = delete; + + private: + template + friend Benchmark* ::benchmark::RegisterBenchmark(const char*, Lam&&); + + Lambda lambda_; +}; +#endif + +} // namespace internal + +inline internal::Benchmark* RegisterBenchmark(const char* name, + internal::Function* fn) { + return internal::RegisterBenchmarkInternal( + ::new internal::FunctionBenchmark(name, fn)); +} + +#ifdef BENCHMARK_HAS_CXX11 +template +internal::Benchmark* RegisterBenchmark(const char* name, Lambda&& fn) { + using BenchType = + internal::LambdaBenchmark::type>; + return internal::RegisterBenchmarkInternal( + ::new BenchType(name, std::forward(fn))); +} +#endif + +#if defined(BENCHMARK_HAS_CXX11) && \ + (!defined(BENCHMARK_GCC_VERSION) || BENCHMARK_GCC_VERSION >= 409) +template +internal::Benchmark* RegisterBenchmark(const char* name, Lambda&& fn, + Args&&... args) { + return benchmark::RegisterBenchmark( + name, [=](benchmark::State& st) { fn(st, args...); }); +} +#else +#define BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK +#endif + +// The base class for all fixture tests. +class Fixture : public internal::Benchmark { + public: + Fixture() : internal::Benchmark("") {} + + virtual void Run(State& st) { + this->SetUp(st); + this->BenchmarkCase(st); + this->TearDown(st); + } + + // These will be deprecated ... + virtual void SetUp(const State&) {} + virtual void TearDown(const State&) {} + // ... In favor of these. + virtual void SetUp(State& st) { SetUp(const_cast(st)); } + virtual void TearDown(State& st) { TearDown(const_cast(st)); } + + protected: + virtual void BenchmarkCase(State&) = 0; +}; + +} // namespace benchmark + +// ------------------------------------------------------ +// Macro to register benchmarks + +// Check that __COUNTER__ is defined and that __COUNTER__ increases by 1 +// every time it is expanded. X + 1 == X + 0 is used in case X is defined to be +// empty. If X is empty the expression becomes (+1 == +0). +#if defined(__COUNTER__) && (__COUNTER__ + 1 == __COUNTER__ + 0) +#define BENCHMARK_PRIVATE_UNIQUE_ID __COUNTER__ +#else +#define BENCHMARK_PRIVATE_UNIQUE_ID __LINE__ +#endif + +// Helpers for generating unique variable names +#define BENCHMARK_PRIVATE_NAME(n) \ + BENCHMARK_PRIVATE_CONCAT(_benchmark_, BENCHMARK_PRIVATE_UNIQUE_ID, n) +#define BENCHMARK_PRIVATE_CONCAT(a, b, c) BENCHMARK_PRIVATE_CONCAT2(a, b, c) +#define BENCHMARK_PRIVATE_CONCAT2(a, b, c) a##b##c + +#define BENCHMARK_PRIVATE_DECLARE(n) \ + static ::benchmark::internal::Benchmark* BENCHMARK_PRIVATE_NAME(n) \ + BENCHMARK_UNUSED + +#define BENCHMARK(n) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark(#n, n))) + +// Old-style macros +#define BENCHMARK_WITH_ARG(n, a) BENCHMARK(n)->Arg((a)) +#define BENCHMARK_WITH_ARG2(n, a1, a2) BENCHMARK(n)->Args({(a1), (a2)}) +#define BENCHMARK_WITH_UNIT(n, t) BENCHMARK(n)->Unit((t)) +#define BENCHMARK_RANGE(n, lo, hi) BENCHMARK(n)->Range((lo), (hi)) +#define BENCHMARK_RANGE2(n, l1, h1, l2, h2) \ + BENCHMARK(n)->RangePair({{(l1), (h1)}, {(l2), (h2)}}) + +#ifdef BENCHMARK_HAS_CXX11 + +// Register a benchmark which invokes the function specified by `func` +// with the additional arguments specified by `...`. +// +// For example: +// +// template ` +// void BM_takes_args(benchmark::State& state, ExtraArgs&&... extra_args) { +// [...] +//} +// /* Registers a benchmark named "BM_takes_args/int_string_test` */ +// BENCHMARK_CAPTURE(BM_takes_args, int_string_test, 42, std::string("abc")); +#define BENCHMARK_CAPTURE(func, test_case_name, ...) \ + BENCHMARK_PRIVATE_DECLARE(func) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark( \ + #func "/" #test_case_name, \ + [](::benchmark::State& st) { func(st, __VA_ARGS__); }))) + +#endif // BENCHMARK_HAS_CXX11 + +// This will register a benchmark for a templatized function. For example: +// +// template +// void BM_Foo(int iters); +// +// BENCHMARK_TEMPLATE(BM_Foo, 1); +// +// will register BM_Foo<1> as a benchmark. +#define BENCHMARK_TEMPLATE1(n, a) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark(#n "<" #a ">", n))) + +#define BENCHMARK_TEMPLATE2(n, a, b) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark(#n "<" #a "," #b ">", \ + n))) + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE(n, ...) \ + BENCHMARK_PRIVATE_DECLARE(n) = \ + (::benchmark::internal::RegisterBenchmarkInternal( \ + new ::benchmark::internal::FunctionBenchmark( \ + #n "<" #__VA_ARGS__ ">", n<__VA_ARGS__>))) +#else +#define BENCHMARK_TEMPLATE(n, a) BENCHMARK_TEMPLATE1(n, a) +#endif + +#define BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \ + class BaseClass##_##Method##_Benchmark : public BaseClass { \ + public: \ + BaseClass##_##Method##_Benchmark() : BaseClass() { \ + this->SetName(#BaseClass "/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&); \ + }; + +#define BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(BaseClass, Method, a) \ + class BaseClass##_##Method##_Benchmark : public BaseClass { \ + public: \ + BaseClass##_##Method##_Benchmark() : BaseClass() { \ + this->SetName(#BaseClass "<" #a ">/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&); \ + }; + +#define BENCHMARK_TEMPLATE2_PRIVATE_DECLARE_F(BaseClass, Method, a, b) \ + class BaseClass##_##Method##_Benchmark : public BaseClass { \ + public: \ + BaseClass##_##Method##_Benchmark() : BaseClass() { \ + this->SetName(#BaseClass "<" #a "," #b ">/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&); \ + }; + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(BaseClass, Method, ...) \ + class BaseClass##_##Method##_Benchmark : public BaseClass<__VA_ARGS__> { \ + public: \ + BaseClass##_##Method##_Benchmark() : BaseClass<__VA_ARGS__>() { \ + this->SetName(#BaseClass "<" #__VA_ARGS__ ">/" #Method); \ + } \ + \ + protected: \ + virtual void BenchmarkCase(::benchmark::State&); \ + }; +#else +#define BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(n, a) \ + BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(n, a) +#endif + +#define BENCHMARK_DEFINE_F(BaseClass, Method) \ + BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase + +#define BENCHMARK_TEMPLATE1_DEFINE_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(BaseClass, Method, a) \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase + +#define BENCHMARK_TEMPLATE2_DEFINE_F(BaseClass, Method, a, b) \ + BENCHMARK_TEMPLATE2_PRIVATE_DECLARE_F(BaseClass, Method, a, b) \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE_DEFINE_F(BaseClass, Method, ...) \ + BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(BaseClass, Method, __VA_ARGS__) \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase +#else +#define BENCHMARK_TEMPLATE_DEFINE_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_DEFINE_F(BaseClass, Method, a) +#endif + +#define BENCHMARK_REGISTER_F(BaseClass, Method) \ + BENCHMARK_PRIVATE_REGISTER_F(BaseClass##_##Method##_Benchmark) + +#define BENCHMARK_PRIVATE_REGISTER_F(TestName) \ + BENCHMARK_PRIVATE_DECLARE(TestName) = \ + (::benchmark::internal::RegisterBenchmarkInternal(new TestName())) + +// This macro will define and register a benchmark within a fixture class. +#define BENCHMARK_F(BaseClass, Method) \ + BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase + +#define BENCHMARK_TEMPLATE1_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_PRIVATE_DECLARE_F(BaseClass, Method, a) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase + +#define BENCHMARK_TEMPLATE2_F(BaseClass, Method, a, b) \ + BENCHMARK_TEMPLATE2_PRIVATE_DECLARE_F(BaseClass, Method, a, b) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase + +#ifdef BENCHMARK_HAS_CXX11 +#define BENCHMARK_TEMPLATE_F(BaseClass, Method, ...) \ + BENCHMARK_TEMPLATE_PRIVATE_DECLARE_F(BaseClass, Method, __VA_ARGS__) \ + BENCHMARK_REGISTER_F(BaseClass, Method); \ + void BaseClass##_##Method##_Benchmark::BenchmarkCase +#else +#define BENCHMARK_TEMPLATE_F(BaseClass, Method, a) \ + BENCHMARK_TEMPLATE1_F(BaseClass, Method, a) +#endif + +// Helper macro to create a main routine in a test that runs the benchmarks +#define BENCHMARK_MAIN() \ + int main(int argc, char** argv) { \ + ::benchmark::Initialize(&argc, argv); \ + if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; \ + ::benchmark::RunSpecifiedBenchmarks(); \ + } \ + int main(int, char**) + +// ------------------------------------------------------ +// Benchmark Reporters + +namespace benchmark { + +struct CPUInfo { + struct CacheInfo { + std::string type; + int level; + int size; + int num_sharing; + }; + + int num_cpus; + double cycles_per_second; + std::vector caches; + bool scaling_enabled; + std::vector load_avg; + + static const CPUInfo& Get(); + + private: + CPUInfo(); + BENCHMARK_DISALLOW_COPY_AND_ASSIGN(CPUInfo); +}; + +// Adding Struct for System Information +struct SystemInfo { + std::string name; + static const SystemInfo& Get(); + + private: + SystemInfo(); + BENCHMARK_DISALLOW_COPY_AND_ASSIGN(SystemInfo); +}; + +// BenchmarkName contains the components of the Benchmark's name +// which allows individual fields to be modified or cleared before +// building the final name using 'str()'. +struct BenchmarkName { + std::string function_name; + std::string args; + std::string min_time; + std::string iterations; + std::string repetitions; + std::string time_type; + std::string threads; + + // Return the full name of the benchmark with each non-empty + // field separated by a '/' + std::string str() const; +}; + +// Interface for custom benchmark result printers. +// By default, benchmark reports are printed to stdout. However an application +// can control the destination of the reports by calling +// RunSpecifiedBenchmarks and passing it a custom reporter object. +// The reporter object must implement the following interface. +class BenchmarkReporter { + public: + struct Context { + CPUInfo const& cpu_info; + SystemInfo const& sys_info; + // The number of chars in the longest benchmark name. + size_t name_field_width; + static const char* executable_name; + Context(); + }; + + struct Run { + static const int64_t no_repetition_index = -1; + enum RunType { RT_Iteration, RT_Aggregate }; + + Run() + : run_type(RT_Iteration), + error_occurred(false), + iterations(1), + threads(1), + time_unit(kNanosecond), + real_accumulated_time(0), + cpu_accumulated_time(0), + max_heapbytes_used(0), + complexity(oNone), + complexity_lambda(), + complexity_n(0), + report_big_o(false), + report_rms(false), + counters(), + has_memory_result(false), + allocs_per_iter(0.0), + max_bytes_used(0) {} + + std::string benchmark_name() const; + BenchmarkName run_name; + RunType run_type; + std::string aggregate_name; + std::string report_label; // Empty if not set by benchmark. + bool error_occurred; + std::string error_message; + + IterationCount iterations; + int64_t threads; + int64_t repetition_index; + int64_t repetitions; + TimeUnit time_unit; + double real_accumulated_time; + double cpu_accumulated_time; + + // Return a value representing the real time per iteration in the unit + // specified by 'time_unit'. + // NOTE: If 'iterations' is zero the returned value represents the + // accumulated time. + double GetAdjustedRealTime() const; + + // Return a value representing the cpu time per iteration in the unit + // specified by 'time_unit'. + // NOTE: If 'iterations' is zero the returned value represents the + // accumulated time. + double GetAdjustedCPUTime() const; + + // This is set to 0.0 if memory tracing is not enabled. + double max_heapbytes_used; + + // Keep track of arguments to compute asymptotic complexity + BigO complexity; + BigOFunc* complexity_lambda; + int64_t complexity_n; + + // what statistics to compute from the measurements + const std::vector* statistics; + + // Inform print function whether the current run is a complexity report + bool report_big_o; + bool report_rms; + + UserCounters counters; + + // Memory metrics. + bool has_memory_result; + double allocs_per_iter; + int64_t max_bytes_used; + }; + + // Construct a BenchmarkReporter with the output stream set to 'std::cout' + // and the error stream set to 'std::cerr' + BenchmarkReporter(); + + // Called once for every suite of benchmarks run. + // The parameter "context" contains information that the + // reporter may wish to use when generating its report, for example the + // platform under which the benchmarks are running. The benchmark run is + // never started if this function returns false, allowing the reporter + // to skip runs based on the context information. + virtual bool ReportContext(const Context& context) = 0; + + // Called once for each group of benchmark runs, gives information about + // cpu-time and heap memory usage during the benchmark run. If the group + // of runs contained more than two entries then 'report' contains additional + // elements representing the mean and standard deviation of those runs. + // Additionally if this group of runs was the last in a family of benchmarks + // 'reports' contains additional entries representing the asymptotic + // complexity and RMS of that benchmark family. + virtual void ReportRuns(const std::vector& report) = 0; + + // Called once and only once after ever group of benchmarks is run and + // reported. + virtual void Finalize() {} + + // REQUIRES: The object referenced by 'out' is valid for the lifetime + // of the reporter. + void SetOutputStream(std::ostream* out) { + assert(out); + output_stream_ = out; + } + + // REQUIRES: The object referenced by 'err' is valid for the lifetime + // of the reporter. + void SetErrorStream(std::ostream* err) { + assert(err); + error_stream_ = err; + } + + std::ostream& GetOutputStream() const { return *output_stream_; } + + std::ostream& GetErrorStream() const { return *error_stream_; } + + virtual ~BenchmarkReporter(); + + // Write a human readable string to 'out' representing the specified + // 'context'. + // REQUIRES: 'out' is non-null. + static void PrintBasicContext(std::ostream* out, Context const& context); + + private: + std::ostream* output_stream_; + std::ostream* error_stream_; +}; + +// Simple reporter that outputs benchmark data to the console. This is the +// default reporter used by RunSpecifiedBenchmarks(). +class ConsoleReporter : public BenchmarkReporter { + public: + enum OutputOptions { + OO_None = 0, + OO_Color = 1, + OO_Tabular = 2, + OO_ColorTabular = OO_Color | OO_Tabular, + OO_Defaults = OO_ColorTabular + }; + explicit ConsoleReporter(OutputOptions opts_ = OO_Defaults) + : output_options_(opts_), + name_field_width_(0), + prev_counters_(), + printed_header_(false) {} + + virtual bool ReportContext(const Context& context); + virtual void ReportRuns(const std::vector& reports); + + protected: + virtual void PrintRunData(const Run& report); + virtual void PrintHeader(const Run& report); + + OutputOptions output_options_; + size_t name_field_width_; + UserCounters prev_counters_; + bool printed_header_; +}; + +class JSONReporter : public BenchmarkReporter { + public: + JSONReporter() : first_report_(true) {} + virtual bool ReportContext(const Context& context); + virtual void ReportRuns(const std::vector& reports); + virtual void Finalize(); + + private: + void PrintRunData(const Run& report); + + bool first_report_; +}; + +class BENCHMARK_DEPRECATED_MSG( + "The CSV Reporter will be removed in a future release") CSVReporter + : public BenchmarkReporter { + public: + CSVReporter() : printed_header_(false) {} + virtual bool ReportContext(const Context& context); + virtual void ReportRuns(const std::vector& reports); + + private: + void PrintRunData(const Run& report); + + bool printed_header_; + std::set user_counter_names_; +}; + +// If a MemoryManager is registered, it can be used to collect and report +// allocation metrics for a run of the benchmark. +class MemoryManager { + public: + struct Result { + Result() : num_allocs(0), max_bytes_used(0) {} + + // The number of allocations made in total between Start and Stop. + int64_t num_allocs; + + // The peak memory use between Start and Stop. + int64_t max_bytes_used; + }; + + virtual ~MemoryManager() {} + + // Implement this to start recording allocation information. + virtual void Start() = 0; + + // Implement this to stop recording and fill out the given Result structure. + virtual void Stop(Result* result) = 0; +}; + +inline const char* GetTimeUnitString(TimeUnit unit) { + switch (unit) { + case kMillisecond: + return "ms"; + case kMicrosecond: + return "us"; + case kNanosecond: + return "ns"; + } + BENCHMARK_UNREACHABLE(); +} + +inline double GetTimeUnitMultiplier(TimeUnit unit) { + switch (unit) { + case kMillisecond: + return 1e3; + case kMicrosecond: + return 1e6; + case kNanosecond: + return 1e9; + } + BENCHMARK_UNREACHABLE(); +} + +} // namespace benchmark + +#endif // BENCHMARK_BENCHMARK_H_ diff --git a/thirdparty/benchmark-1.5.0/mingw.py b/thirdparty/benchmark-1.5.0/mingw.py new file mode 100644 index 0000000000..706ad559db --- /dev/null +++ b/thirdparty/benchmark-1.5.0/mingw.py @@ -0,0 +1,320 @@ +#! /usr/bin/env python +# encoding: utf-8 + +import argparse +import errno +import logging +import os +import platform +import re +import sys +import subprocess +import tempfile + +try: + import winreg +except ImportError: + import _winreg as winreg +try: + import urllib.request as request +except ImportError: + import urllib as request +try: + import urllib.parse as parse +except ImportError: + import urlparse as parse + +class EmptyLogger(object): + ''' + Provides an implementation that performs no logging + ''' + def debug(self, *k, **kw): + pass + def info(self, *k, **kw): + pass + def warn(self, *k, **kw): + pass + def error(self, *k, **kw): + pass + def critical(self, *k, **kw): + pass + def setLevel(self, *k, **kw): + pass + +urls = ( + 'http://downloads.sourceforge.net/project/mingw-w64/Toolchains%20' + 'targetting%20Win32/Personal%20Builds/mingw-builds/installer/' + 'repository.txt', + 'http://downloads.sourceforge.net/project/mingwbuilds/host-windows/' + 'repository.txt' +) +''' +A list of mingw-build repositories +''' + +def repository(urls = urls, log = EmptyLogger()): + ''' + Downloads and parse mingw-build repository files and parses them + ''' + log.info('getting mingw-builds repository') + versions = {} + re_sourceforge = re.compile(r'http://sourceforge.net/projects/([^/]+)/files') + re_sub = r'http://downloads.sourceforge.net/project/\1' + for url in urls: + log.debug(' - requesting: %s', url) + socket = request.urlopen(url) + repo = socket.read() + if not isinstance(repo, str): + repo = repo.decode(); + socket.close() + for entry in repo.split('\n')[:-1]: + value = entry.split('|') + version = tuple([int(n) for n in value[0].strip().split('.')]) + version = versions.setdefault(version, {}) + arch = value[1].strip() + if arch == 'x32': + arch = 'i686' + elif arch == 'x64': + arch = 'x86_64' + arch = version.setdefault(arch, {}) + threading = arch.setdefault(value[2].strip(), {}) + exceptions = threading.setdefault(value[3].strip(), {}) + revision = exceptions.setdefault(int(value[4].strip()[3:]), + re_sourceforge.sub(re_sub, value[5].strip())) + return versions + +def find_in_path(file, path=None): + ''' + Attempts to find an executable in the path + ''' + if platform.system() == 'Windows': + file += '.exe' + if path is None: + path = os.environ.get('PATH', '') + if type(path) is type(''): + path = path.split(os.pathsep) + return list(filter(os.path.exists, + map(lambda dir, file=file: os.path.join(dir, file), path))) + +def find_7zip(log = EmptyLogger()): + ''' + Attempts to find 7zip for unpacking the mingw-build archives + ''' + log.info('finding 7zip') + path = find_in_path('7z') + if not path: + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\7-Zip') + path, _ = winreg.QueryValueEx(key, 'Path') + path = [os.path.join(path, '7z.exe')] + log.debug('found \'%s\'', path[0]) + return path[0] + +find_7zip() + +def unpack(archive, location, log = EmptyLogger()): + ''' + Unpacks a mingw-builds archive + ''' + sevenzip = find_7zip(log) + log.info('unpacking %s', os.path.basename(archive)) + cmd = [sevenzip, 'x', archive, '-o' + location, '-y'] + log.debug(' - %r', cmd) + with open(os.devnull, 'w') as devnull: + subprocess.check_call(cmd, stdout = devnull) + +def download(url, location, log = EmptyLogger()): + ''' + Downloads and unpacks a mingw-builds archive + ''' + log.info('downloading MinGW') + log.debug(' - url: %s', url) + log.debug(' - location: %s', location) + + re_content = re.compile(r'attachment;[ \t]*filename=(")?([^"]*)(")?[\r\n]*') + + stream = request.urlopen(url) + try: + content = stream.getheader('Content-Disposition') or '' + except AttributeError: + content = stream.headers.getheader('Content-Disposition') or '' + matches = re_content.match(content) + if matches: + filename = matches.group(2) + else: + parsed = parse.urlparse(stream.geturl()) + filename = os.path.basename(parsed.path) + + try: + os.makedirs(location) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(location): + pass + else: + raise + + archive = os.path.join(location, filename) + with open(archive, 'wb') as out: + while True: + buf = stream.read(1024) + if not buf: + break + out.write(buf) + unpack(archive, location, log = log) + os.remove(archive) + + possible = os.path.join(location, 'mingw64') + if not os.path.exists(possible): + possible = os.path.join(location, 'mingw32') + if not os.path.exists(possible): + raise ValueError('Failed to find unpacked MinGW: ' + possible) + return possible + +def root(location = None, arch = None, version = None, threading = None, + exceptions = None, revision = None, log = EmptyLogger()): + ''' + Returns the root folder of a specific version of the mingw-builds variant + of gcc. Will download the compiler if needed + ''' + + # Get the repository if we don't have all the information + if not (arch and version and threading and exceptions and revision): + versions = repository(log = log) + + # Determine some defaults + version = version or max(versions.keys()) + if not arch: + arch = platform.machine().lower() + if arch == 'x86': + arch = 'i686' + elif arch == 'amd64': + arch = 'x86_64' + if not threading: + keys = versions[version][arch].keys() + if 'posix' in keys: + threading = 'posix' + elif 'win32' in keys: + threading = 'win32' + else: + threading = keys[0] + if not exceptions: + keys = versions[version][arch][threading].keys() + if 'seh' in keys: + exceptions = 'seh' + elif 'sjlj' in keys: + exceptions = 'sjlj' + else: + exceptions = keys[0] + if revision == None: + revision = max(versions[version][arch][threading][exceptions].keys()) + if not location: + location = os.path.join(tempfile.gettempdir(), 'mingw-builds') + + # Get the download url + url = versions[version][arch][threading][exceptions][revision] + + # Tell the user whatzzup + log.info('finding MinGW %s', '.'.join(str(v) for v in version)) + log.debug(' - arch: %s', arch) + log.debug(' - threading: %s', threading) + log.debug(' - exceptions: %s', exceptions) + log.debug(' - revision: %s', revision) + log.debug(' - url: %s', url) + + # Store each specific revision differently + slug = '{version}-{arch}-{threading}-{exceptions}-rev{revision}' + slug = slug.format( + version = '.'.join(str(v) for v in version), + arch = arch, + threading = threading, + exceptions = exceptions, + revision = revision + ) + if arch == 'x86_64': + root_dir = os.path.join(location, slug, 'mingw64') + elif arch == 'i686': + root_dir = os.path.join(location, slug, 'mingw32') + else: + raise ValueError('Unknown MinGW arch: ' + arch) + + # Download if needed + if not os.path.exists(root_dir): + downloaded = download(url, os.path.join(location, slug), log = log) + if downloaded != root_dir: + raise ValueError('The location of mingw did not match\n%s\n%s' + % (downloaded, root_dir)) + + return root_dir + +def str2ver(string): + ''' + Converts a version string into a tuple + ''' + try: + version = tuple(int(v) for v in string.split('.')) + if len(version) is not 3: + raise ValueError() + except ValueError: + raise argparse.ArgumentTypeError( + 'please provide a three digit version string') + return version + +def main(): + ''' + Invoked when the script is run directly by the python interpreter + ''' + parser = argparse.ArgumentParser( + description = 'Downloads a specific version of MinGW', + formatter_class = argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument('--location', + help = 'the location to download the compiler to', + default = os.path.join(tempfile.gettempdir(), 'mingw-builds')) + parser.add_argument('--arch', required = True, choices = ['i686', 'x86_64'], + help = 'the target MinGW architecture string') + parser.add_argument('--version', type = str2ver, + help = 'the version of GCC to download') + parser.add_argument('--threading', choices = ['posix', 'win32'], + help = 'the threading type of the compiler') + parser.add_argument('--exceptions', choices = ['sjlj', 'seh', 'dwarf'], + help = 'the method to throw exceptions') + parser.add_argument('--revision', type=int, + help = 'the revision of the MinGW release') + group = parser.add_mutually_exclusive_group() + group.add_argument('-v', '--verbose', action='store_true', + help='increase the script output verbosity') + group.add_argument('-q', '--quiet', action='store_true', + help='only print errors and warning') + args = parser.parse_args() + + # Create the logger + logger = logging.getLogger('mingw') + handler = logging.StreamHandler() + formatter = logging.Formatter('%(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + if args.quiet: + logger.setLevel(logging.WARN) + if args.verbose: + logger.setLevel(logging.DEBUG) + + # Get MinGW + root_dir = root(location = args.location, arch = args.arch, + version = args.version, threading = args.threading, + exceptions = args.exceptions, revision = args.revision, + log = logger) + + sys.stdout.write('%s\n' % os.path.join(root_dir, 'bin')) + +if __name__ == '__main__': + try: + main() + except IOError as e: + sys.stderr.write('IO error: %s\n' % e) + sys.exit(1) + except OSError as e: + sys.stderr.write('OS error: %s\n' % e) + sys.exit(1) + except KeyboardInterrupt as e: + sys.stderr.write('Killed\n') + sys.exit(1) diff --git a/thirdparty/benchmark-1.5.0/releasing.md b/thirdparty/benchmark-1.5.0/releasing.md new file mode 100644 index 0000000000..f0cd7010e3 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/releasing.md @@ -0,0 +1,16 @@ +# How to release + +* Make sure you're on master and synced to HEAD +* Ensure the project builds and tests run (sanity check only, obviously) + * `parallel -j0 exec ::: test/*_test` can help ensure everything at least + passes +* Prepare release notes + * `git log $(git describe --abbrev=0 --tags)..HEAD` gives you the list of + commits between the last annotated tag and HEAD + * Pick the most interesting. +* Create a release through github's interface + * Note this will create a lightweight tag. + * Update this to an annotated tag: + * `git pull --tags` + * `git tag -a -f ` + * `git push --force origin` diff --git a/thirdparty/benchmark-1.5.0/src/CMakeLists.txt b/thirdparty/benchmark-1.5.0/src/CMakeLists.txt new file mode 100644 index 0000000000..b47de6791c --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/CMakeLists.txt @@ -0,0 +1,112 @@ +# Allow the source files to find headers in src/ +include(GNUInstallDirs) +include_directories(${PROJECT_SOURCE_DIR}/src) + +if (DEFINED BENCHMARK_CXX_LINKER_FLAGS) + list(APPEND CMAKE_SHARED_LINKER_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS}) + list(APPEND CMAKE_MODULE_LINKER_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS}) +endif() + +file(GLOB + SOURCE_FILES + *.cc + ${PROJECT_SOURCE_DIR}/include/benchmark/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.h) +file(GLOB BENCHMARK_MAIN "benchmark_main.cc") +foreach(item ${BENCHMARK_MAIN}) + list(REMOVE_ITEM SOURCE_FILES "${item}") +endforeach() + +add_library(benchmark ${SOURCE_FILES}) +set_target_properties(benchmark PROPERTIES + OUTPUT_NAME "benchmark" + VERSION ${GENERIC_LIB_VERSION} + SOVERSION ${GENERIC_LIB_SOVERSION} +) +target_include_directories(benchmark PUBLIC + $ + ) + +# Link threads. +target_link_libraries(benchmark ${BENCHMARK_CXX_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +find_library(LIBRT rt) +if(LIBRT) + target_link_libraries(benchmark ${LIBRT}) +endif() + +if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPER) +endif() +if(NOT CMAKE_THREAD_LIBS_INIT AND "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}" MATCHES ".*-fsanitize=[^ ]*address.*") + message(WARNING "CMake's FindThreads.cmake did not fail, but CMAKE_THREAD_LIBS_INIT ended up being empty. This was fixed in https://github.com/Kitware/CMake/commit/d53317130e84898c5328c237186dbd995aaf1c12 Let's guess that -pthread is sufficient.") + target_link_libraries(benchmark -pthread) +endif() + +# We need extra libraries on Windows +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + target_link_libraries(benchmark Shlwapi) +endif() + +# We need extra libraries on Solaris +if(${CMAKE_SYSTEM_NAME} MATCHES "SunOS") + target_link_libraries(benchmark kstat) +endif() + +# Benchmark main library +add_library(benchmark_main "benchmark_main.cc") +set_target_properties(benchmark_main PROPERTIES + OUTPUT_NAME "benchmark_main" + VERSION ${GENERIC_LIB_VERSION} + SOVERSION ${GENERIC_LIB_SOVERSION} +) +target_include_directories(benchmark PUBLIC + $ + ) +target_link_libraries(benchmark_main benchmark) + + +set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") + +set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") +set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") +set(pkg_config "${generated_dir}/${PROJECT_NAME}.pc") +set(targets_export_name "${PROJECT_NAME}Targets") + +set(namespace "${PROJECT_NAME}::") + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${version_config}" VERSION ${GENERIC_LIB_VERSION} COMPATIBILITY SameMajorVersion +) + +configure_file("${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in" "${project_config}" @ONLY) +configure_file("${PROJECT_SOURCE_DIR}/cmake/benchmark.pc.in" "${pkg_config}" @ONLY) + +if (BENCHMARK_ENABLE_INSTALL) + # Install target (will install the library to specified CMAKE_INSTALL_PREFIX variable) + install( + TARGETS benchmark benchmark_main + EXPORT ${targets_export_name} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + install( + DIRECTORY "${PROJECT_SOURCE_DIR}/include/benchmark" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.*h") + + install( + FILES "${project_config}" "${version_config}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + install( + FILES "${pkg_config}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + + install( + EXPORT "${targets_export_name}" + NAMESPACE "${namespace}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +endif() diff --git a/thirdparty/benchmark-1.5.0/src/arraysize.h b/thirdparty/benchmark-1.5.0/src/arraysize.h new file mode 100644 index 0000000000..51a50f2dff --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/arraysize.h @@ -0,0 +1,33 @@ +#ifndef BENCHMARK_ARRAYSIZE_H_ +#define BENCHMARK_ARRAYSIZE_H_ + +#include "internal_macros.h" + +namespace benchmark { +namespace internal { +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char (&ArraySizeHelper(T (&array)[N]))[N]; + +// That gcc wants both of these prototypes seems mysterious. VC, for +// its part, can't decide which to use (another mystery). Matching of +// template overloads: the final frontier. +#ifndef COMPILER_MSVC +template +char (&ArraySizeHelper(const T (&array)[N]))[N]; +#endif + +#define arraysize(array) (sizeof(::benchmark::internal::ArraySizeHelper(array))) + +} // end namespace internal +} // end namespace benchmark + +#endif // BENCHMARK_ARRAYSIZE_H_ diff --git a/thirdparty/benchmark-1.5.0/src/benchmark.cc b/thirdparty/benchmark-1.5.0/src/benchmark.cc new file mode 100644 index 0000000000..29bfa3512f --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark.cc @@ -0,0 +1,494 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" +#include "benchmark_api_internal.h" +#include "benchmark_runner.h" +#include "internal_macros.h" + +#ifndef BENCHMARK_OS_WINDOWS +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "colorprint.h" +#include "commandlineflags.h" +#include "complexity.h" +#include "counter.h" +#include "internal_macros.h" +#include "log.h" +#include "mutex.h" +#include "re.h" +#include "statistics.h" +#include "string_util.h" +#include "thread_manager.h" +#include "thread_timer.h" + +DEFINE_bool(benchmark_list_tests, false, + "Print a list of benchmarks. This option overrides all other " + "options."); + +DEFINE_string(benchmark_filter, ".", + "A regular expression that specifies the set of benchmarks " + "to execute. If this flag is empty, or if this flag is the " + "string \"all\", all benchmarks linked into the binary are " + "run."); + +DEFINE_double(benchmark_min_time, 0.5, + "Minimum number of seconds we should run benchmark before " + "results are considered significant. For cpu-time based " + "tests, this is the lower bound on the total cpu time " + "used by all threads that make up the test. For real-time " + "based tests, this is the lower bound on the elapsed time " + "of the benchmark execution, regardless of number of " + "threads."); + +DEFINE_int32(benchmark_repetitions, 1, + "The number of runs of each benchmark. If greater than 1, the " + "mean and standard deviation of the runs will be reported."); + +DEFINE_bool( + benchmark_report_aggregates_only, false, + "Report the result of each benchmark repetitions. When 'true' is specified " + "only the mean, standard deviation, and other statistics are reported for " + "repeated benchmarks. Affects all reporters."); + +DEFINE_bool( + benchmark_display_aggregates_only, false, + "Display the result of each benchmark repetitions. When 'true' is " + "specified only the mean, standard deviation, and other statistics are " + "displayed for repeated benchmarks. Unlike " + "benchmark_report_aggregates_only, only affects the display reporter, but " + "*NOT* file reporter, which will still contain all the output."); + +DEFINE_string(benchmark_format, "console", + "The format to use for console output. Valid values are " + "'console', 'json', or 'csv'."); + +DEFINE_string(benchmark_out_format, "json", + "The format to use for file output. Valid values are " + "'console', 'json', or 'csv'."); + +DEFINE_string(benchmark_out, "", "The file to write additional output to"); + +DEFINE_string(benchmark_color, "auto", + "Whether to use colors in the output. Valid values: " + "'true'/'yes'/1, 'false'/'no'/0, and 'auto'. 'auto' means to use " + "colors if the output is being sent to a terminal and the TERM " + "environment variable is set to a terminal type that supports " + "colors."); + +DEFINE_bool(benchmark_counters_tabular, false, + "Whether to use tabular format when printing user counters to " + "the console. Valid values: 'true'/'yes'/1, 'false'/'no'/0." + "Defaults to false."); + +DEFINE_int32(v, 0, "The level of verbose logging to output"); + +namespace benchmark { + +namespace internal { + +// FIXME: wouldn't LTO mess this up? +void UseCharPointer(char const volatile*) {} + +} // namespace internal + +State::State(IterationCount max_iters, const std::vector& ranges, + int thread_i, int n_threads, internal::ThreadTimer* timer, + internal::ThreadManager* manager) + : total_iterations_(0), + batch_leftover_(0), + max_iterations(max_iters), + started_(false), + finished_(false), + error_occurred_(false), + range_(ranges), + complexity_n_(0), + counters(), + thread_index(thread_i), + threads(n_threads), + timer_(timer), + manager_(manager) { + CHECK(max_iterations != 0) << "At least one iteration must be run"; + CHECK_LT(thread_index, threads) << "thread_index must be less than threads"; + + // Note: The use of offsetof below is technically undefined until C++17 + // because State is not a standard layout type. However, all compilers + // currently provide well-defined behavior as an extension (which is + // demonstrated since constexpr evaluation must diagnose all undefined + // behavior). However, GCC and Clang also warn about this use of offsetof, + // which must be suppressed. +#if defined(__INTEL_COMPILER) +#pragma warning push +#pragma warning(disable:1875) +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + // Offset tests to ensure commonly accessed data is on the first cache line. + const int cache_line_size = 64; + static_assert(offsetof(State, error_occurred_) <= + (cache_line_size - sizeof(error_occurred_)), + ""); +#if defined(__INTEL_COMPILER) +#pragma warning pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +void State::PauseTiming() { + // Add in time accumulated so far + CHECK(started_ && !finished_ && !error_occurred_); + timer_->StopTimer(); +} + +void State::ResumeTiming() { + CHECK(started_ && !finished_ && !error_occurred_); + timer_->StartTimer(); +} + +void State::SkipWithError(const char* msg) { + CHECK(msg); + error_occurred_ = true; + { + MutexLock l(manager_->GetBenchmarkMutex()); + if (manager_->results.has_error_ == false) { + manager_->results.error_message_ = msg; + manager_->results.has_error_ = true; + } + } + total_iterations_ = 0; + if (timer_->running()) timer_->StopTimer(); +} + +void State::SetIterationTime(double seconds) { + timer_->SetIterationTime(seconds); +} + +void State::SetLabel(const char* label) { + MutexLock l(manager_->GetBenchmarkMutex()); + manager_->results.report_label_ = label; +} + +void State::StartKeepRunning() { + CHECK(!started_ && !finished_); + started_ = true; + total_iterations_ = error_occurred_ ? 0 : max_iterations; + manager_->StartStopBarrier(); + if (!error_occurred_) ResumeTiming(); +} + +void State::FinishKeepRunning() { + CHECK(started_ && (!finished_ || error_occurred_)); + if (!error_occurred_) { + PauseTiming(); + } + // Total iterations has now wrapped around past 0. Fix this. + total_iterations_ = 0; + finished_ = true; + manager_->StartStopBarrier(); +} + +namespace internal { +namespace { + +void RunBenchmarks(const std::vector& benchmarks, + BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter) { + // Note the file_reporter can be null. + CHECK(display_reporter != nullptr); + + // Determine the width of the name field using a minimum width of 10. + bool might_have_aggregates = FLAGS_benchmark_repetitions > 1; + size_t name_field_width = 10; + size_t stat_field_width = 0; + for (const BenchmarkInstance& benchmark : benchmarks) { + name_field_width = + std::max(name_field_width, benchmark.name.str().size()); + might_have_aggregates |= benchmark.repetitions > 1; + + for (const auto& Stat : *benchmark.statistics) + stat_field_width = std::max(stat_field_width, Stat.name_.size()); + } + if (might_have_aggregates) name_field_width += 1 + stat_field_width; + + // Print header here + BenchmarkReporter::Context context; + context.name_field_width = name_field_width; + + // Keep track of running times of all instances of current benchmark + std::vector complexity_reports; + + // We flush streams after invoking reporter methods that write to them. This + // ensures users get timely updates even when streams are not line-buffered. + auto flushStreams = [](BenchmarkReporter* reporter) { + if (!reporter) return; + std::flush(reporter->GetOutputStream()); + std::flush(reporter->GetErrorStream()); + }; + + if (display_reporter->ReportContext(context) && + (!file_reporter || file_reporter->ReportContext(context))) { + flushStreams(display_reporter); + flushStreams(file_reporter); + + for (const auto& benchmark : benchmarks) { + RunResults run_results = RunBenchmark(benchmark, &complexity_reports); + + auto report = [&run_results](BenchmarkReporter* reporter, + bool report_aggregates_only) { + assert(reporter); + // If there are no aggregates, do output non-aggregates. + report_aggregates_only &= !run_results.aggregates_only.empty(); + if (!report_aggregates_only) + reporter->ReportRuns(run_results.non_aggregates); + if (!run_results.aggregates_only.empty()) + reporter->ReportRuns(run_results.aggregates_only); + }; + + report(display_reporter, run_results.display_report_aggregates_only); + if (file_reporter) + report(file_reporter, run_results.file_report_aggregates_only); + + flushStreams(display_reporter); + flushStreams(file_reporter); + } + } + display_reporter->Finalize(); + if (file_reporter) file_reporter->Finalize(); + flushStreams(display_reporter); + flushStreams(file_reporter); +} + +std::unique_ptr CreateReporter( + std::string const& name, ConsoleReporter::OutputOptions output_opts) { + typedef std::unique_ptr PtrType; + if (name == "console") { + return PtrType(new ConsoleReporter(output_opts)); + } else if (name == "json") { + return PtrType(new JSONReporter); + } else if (name == "csv") { + return PtrType(new CSVReporter); + } else { + std::cerr << "Unexpected format: '" << name << "'\n"; + std::exit(1); + } +} + +} // end namespace + +bool IsZero(double n) { + return std::abs(n) < std::numeric_limits::epsilon(); +} + +ConsoleReporter::OutputOptions GetOutputOptions(bool force_no_color) { + int output_opts = ConsoleReporter::OO_Defaults; + auto is_benchmark_color = [force_no_color] () -> bool { + if (force_no_color) { + return false; + } + if (FLAGS_benchmark_color == "auto") { + return IsColorTerminal(); + } + return IsTruthyFlagValue(FLAGS_benchmark_color); + }; + if (is_benchmark_color()) { + output_opts |= ConsoleReporter::OO_Color; + } else { + output_opts &= ~ConsoleReporter::OO_Color; + } + if (FLAGS_benchmark_counters_tabular) { + output_opts |= ConsoleReporter::OO_Tabular; + } else { + output_opts &= ~ConsoleReporter::OO_Tabular; + } + return static_cast(output_opts); +} + +} // end namespace internal + +size_t RunSpecifiedBenchmarks() { + return RunSpecifiedBenchmarks(nullptr, nullptr); +} + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter) { + return RunSpecifiedBenchmarks(display_reporter, nullptr); +} + +size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter, + BenchmarkReporter* file_reporter) { + std::string spec = FLAGS_benchmark_filter; + if (spec.empty() || spec == "all") + spec = "."; // Regexp that matches all benchmarks + + // Setup the reporters + std::ofstream output_file; + std::unique_ptr default_display_reporter; + std::unique_ptr default_file_reporter; + if (!display_reporter) { + default_display_reporter = internal::CreateReporter( + FLAGS_benchmark_format, internal::GetOutputOptions()); + display_reporter = default_display_reporter.get(); + } + auto& Out = display_reporter->GetOutputStream(); + auto& Err = display_reporter->GetErrorStream(); + + std::string const& fname = FLAGS_benchmark_out; + if (fname.empty() && file_reporter) { + Err << "A custom file reporter was provided but " + "--benchmark_out= was not specified." + << std::endl; + std::exit(1); + } + if (!fname.empty()) { + output_file.open(fname); + if (!output_file.is_open()) { + Err << "invalid file name: '" << fname << std::endl; + std::exit(1); + } + if (!file_reporter) { + default_file_reporter = internal::CreateReporter( + FLAGS_benchmark_out_format, ConsoleReporter::OO_None); + file_reporter = default_file_reporter.get(); + } + file_reporter->SetOutputStream(&output_file); + file_reporter->SetErrorStream(&output_file); + } + + std::vector benchmarks; + if (!FindBenchmarksInternal(spec, &benchmarks, &Err)) return 0; + + if (benchmarks.empty()) { + Err << "Failed to match any benchmarks against regex: " << spec << "\n"; + return 0; + } + + if (FLAGS_benchmark_list_tests) { + for (auto const& benchmark : benchmarks) + Out << benchmark.name.str() << "\n"; + } else { + internal::RunBenchmarks(benchmarks, display_reporter, file_reporter); + } + + return benchmarks.size(); +} + +void RegisterMemoryManager(MemoryManager* manager) { + internal::memory_manager = manager; +} + +namespace internal { + +void PrintUsageAndExit() { + fprintf(stdout, + "benchmark" + " [--benchmark_list_tests={true|false}]\n" + " [--benchmark_filter=]\n" + " [--benchmark_min_time=]\n" + " [--benchmark_repetitions=]\n" + " [--benchmark_report_aggregates_only={true|false}]\n" + " [--benchmark_display_aggregates_only={true|false}]\n" + " [--benchmark_format=]\n" + " [--benchmark_out=]\n" + " [--benchmark_out_format=]\n" + " [--benchmark_color={auto|true|false}]\n" + " [--benchmark_counters_tabular={true|false}]\n" + " [--v=]\n"); + exit(0); +} + +void ParseCommandLineFlags(int* argc, char** argv) { + using namespace benchmark; + BenchmarkReporter::Context::executable_name = + (argc && *argc > 0) ? argv[0] : "unknown"; + for (int i = 1; i < *argc; ++i) { + if (ParseBoolFlag(argv[i], "benchmark_list_tests", + &FLAGS_benchmark_list_tests) || + ParseStringFlag(argv[i], "benchmark_filter", &FLAGS_benchmark_filter) || + ParseDoubleFlag(argv[i], "benchmark_min_time", + &FLAGS_benchmark_min_time) || + ParseInt32Flag(argv[i], "benchmark_repetitions", + &FLAGS_benchmark_repetitions) || + ParseBoolFlag(argv[i], "benchmark_report_aggregates_only", + &FLAGS_benchmark_report_aggregates_only) || + ParseBoolFlag(argv[i], "benchmark_display_aggregates_only", + &FLAGS_benchmark_display_aggregates_only) || + ParseStringFlag(argv[i], "benchmark_format", &FLAGS_benchmark_format) || + ParseStringFlag(argv[i], "benchmark_out", &FLAGS_benchmark_out) || + ParseStringFlag(argv[i], "benchmark_out_format", + &FLAGS_benchmark_out_format) || + ParseStringFlag(argv[i], "benchmark_color", &FLAGS_benchmark_color) || + // "color_print" is the deprecated name for "benchmark_color". + // TODO: Remove this. + ParseStringFlag(argv[i], "color_print", &FLAGS_benchmark_color) || + ParseBoolFlag(argv[i], "benchmark_counters_tabular", + &FLAGS_benchmark_counters_tabular) || + ParseInt32Flag(argv[i], "v", &FLAGS_v)) { + for (int j = i; j != *argc - 1; ++j) argv[j] = argv[j + 1]; + + --(*argc); + --i; + } else if (IsFlag(argv[i], "help")) { + PrintUsageAndExit(); + } + } + for (auto const* flag : + {&FLAGS_benchmark_format, &FLAGS_benchmark_out_format}) + if (*flag != "console" && *flag != "json" && *flag != "csv") { + PrintUsageAndExit(); + } + if (FLAGS_benchmark_color.empty()) { + PrintUsageAndExit(); + } +} + +int InitializeStreams() { + static std::ios_base::Init init; + return 0; +} + +} // end namespace internal + +void Initialize(int* argc, char** argv) { + internal::ParseCommandLineFlags(argc, argv); + internal::LogLevel() = FLAGS_v; +} + +bool ReportUnrecognizedArguments(int argc, char** argv) { + for (int i = 1; i < argc; ++i) { + fprintf(stderr, "%s: error: unrecognized command-line flag: %s\n", argv[0], + argv[i]); + } + return argc > 1; +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_api_internal.cc b/thirdparty/benchmark-1.5.0/src/benchmark_api_internal.cc new file mode 100644 index 0000000000..d468a257e3 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_api_internal.cc @@ -0,0 +1,15 @@ +#include "benchmark_api_internal.h" + +namespace benchmark { +namespace internal { + +State BenchmarkInstance::Run(IterationCount iters, int thread_id, + internal::ThreadTimer* timer, + internal::ThreadManager* manager) const { + State st(iters, arg, thread_id, threads, timer, manager); + benchmark->Run(st); + return st; +} + +} // internal +} // benchmark diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_api_internal.h b/thirdparty/benchmark-1.5.0/src/benchmark_api_internal.h new file mode 100644 index 0000000000..264eff95c5 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_api_internal.h @@ -0,0 +1,53 @@ +#ifndef BENCHMARK_API_INTERNAL_H +#define BENCHMARK_API_INTERNAL_H + +#include "benchmark/benchmark.h" +#include "commandlineflags.h" + +#include +#include +#include +#include +#include +#include + +namespace benchmark { +namespace internal { + +// Information kept per benchmark we may want to run +struct BenchmarkInstance { + BenchmarkName name; + Benchmark* benchmark; + AggregationReportMode aggregation_report_mode; + std::vector arg; + TimeUnit time_unit; + int range_multiplier; + bool measure_process_cpu_time; + bool use_real_time; + bool use_manual_time; + BigO complexity; + BigOFunc* complexity_lambda; + UserCounters counters; + const std::vector* statistics; + bool last_benchmark_instance; + int repetitions; + double min_time; + IterationCount iterations; + int threads; // Number of concurrent threads to us + + State Run(IterationCount iters, int thread_id, internal::ThreadTimer* timer, + internal::ThreadManager* manager) const; +}; + +bool FindBenchmarksInternal(const std::string& re, + std::vector* benchmarks, + std::ostream* Err); + +bool IsZero(double n); + +ConsoleReporter::OutputOptions GetOutputOptions(bool force_no_color = false); + +} // end namespace internal +} // end namespace benchmark + +#endif // BENCHMARK_API_INTERNAL_H diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_main.cc b/thirdparty/benchmark-1.5.0/src/benchmark_main.cc new file mode 100644 index 0000000000..b3b2478314 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_main.cc @@ -0,0 +1,17 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_name.cc b/thirdparty/benchmark-1.5.0/src/benchmark_name.cc new file mode 100644 index 0000000000..2a17ebce27 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_name.cc @@ -0,0 +1,58 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +namespace benchmark { + +namespace { + +// Compute the total size of a pack of std::strings +size_t size_impl() { return 0; } + +template +size_t size_impl(const Head& head, const Tail&... tail) { + return head.size() + size_impl(tail...); +} + +// Join a pack of std::strings using a delimiter +// TODO: use absl::StrJoin +void join_impl(std::string&, char) {} + +template +void join_impl(std::string& s, const char delimiter, const Head& head, + const Tail&... tail) { + if (!s.empty() && !head.empty()) { + s += delimiter; + } + + s += head; + + join_impl(s, delimiter, tail...); +} + +template +std::string join(char delimiter, const Ts&... ts) { + std::string s; + s.reserve(sizeof...(Ts) + size_impl(ts...)); + join_impl(s, delimiter, ts...); + return s; +} +} // namespace + +std::string BenchmarkName::str() const { + return join('/', function_name, args, min_time, iterations, repetitions, + time_type, threads); +} +} // namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_register.cc b/thirdparty/benchmark-1.5.0/src/benchmark_register.cc new file mode 100644 index 0000000000..6696c382b8 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_register.cc @@ -0,0 +1,504 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark_register.h" + +#ifndef BENCHMARK_OS_WINDOWS +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define __STDC_FORMAT_MACROS +#include + +#include "benchmark/benchmark.h" +#include "benchmark_api_internal.h" +#include "check.h" +#include "commandlineflags.h" +#include "complexity.h" +#include "internal_macros.h" +#include "log.h" +#include "mutex.h" +#include "re.h" +#include "statistics.h" +#include "string_util.h" +#include "timers.h" + +namespace benchmark { + +namespace { +// For non-dense Range, intermediate values are powers of kRangeMultiplier. +static const int kRangeMultiplier = 8; +// The size of a benchmark family determines is the number of inputs to repeat +// the benchmark on. If this is "large" then warn the user during configuration. +static const size_t kMaxFamilySize = 100; +} // end namespace + +namespace internal { + +//=============================================================================// +// BenchmarkFamilies +//=============================================================================// + +// Class for managing registered benchmarks. Note that each registered +// benchmark identifies a family of related benchmarks to run. +class BenchmarkFamilies { + public: + static BenchmarkFamilies* GetInstance(); + + // Registers a benchmark family and returns the index assigned to it. + size_t AddBenchmark(std::unique_ptr family); + + // Clear all registered benchmark families. + void ClearBenchmarks(); + + // Extract the list of benchmark instances that match the specified + // regular expression. + bool FindBenchmarks(std::string re, + std::vector* benchmarks, + std::ostream* Err); + + private: + BenchmarkFamilies() {} + + std::vector> families_; + Mutex mutex_; +}; + +BenchmarkFamilies* BenchmarkFamilies::GetInstance() { + static BenchmarkFamilies instance; + return &instance; +} + +size_t BenchmarkFamilies::AddBenchmark(std::unique_ptr family) { + MutexLock l(mutex_); + size_t index = families_.size(); + families_.push_back(std::move(family)); + return index; +} + +void BenchmarkFamilies::ClearBenchmarks() { + MutexLock l(mutex_); + families_.clear(); + families_.shrink_to_fit(); +} + +bool BenchmarkFamilies::FindBenchmarks( + std::string spec, std::vector* benchmarks, + std::ostream* ErrStream) { + CHECK(ErrStream); + auto& Err = *ErrStream; + // Make regular expression out of command-line flag + std::string error_msg; + Regex re; + bool isNegativeFilter = false; + if (spec[0] == '-') { + spec.replace(0, 1, ""); + isNegativeFilter = true; + } + if (!re.Init(spec, &error_msg)) { + Err << "Could not compile benchmark re: " << error_msg << std::endl; + return false; + } + + // Special list of thread counts to use when none are specified + const std::vector one_thread = {1}; + + MutexLock l(mutex_); + for (std::unique_ptr& family : families_) { + // Family was deleted or benchmark doesn't match + if (!family) continue; + + if (family->ArgsCnt() == -1) { + family->Args({}); + } + const std::vector* thread_counts = + (family->thread_counts_.empty() + ? &one_thread + : &static_cast&>(family->thread_counts_)); + const size_t family_size = family->args_.size() * thread_counts->size(); + // The benchmark will be run at least 'family_size' different inputs. + // If 'family_size' is very large warn the user. + if (family_size > kMaxFamilySize) { + Err << "The number of inputs is very large. " << family->name_ + << " will be repeated at least " << family_size << " times.\n"; + } + // reserve in the special case the regex ".", since we know the final + // family size. + if (spec == ".") benchmarks->reserve(family_size); + + for (auto const& args : family->args_) { + for (int num_threads : *thread_counts) { + BenchmarkInstance instance; + instance.name.function_name = family->name_; + instance.benchmark = family.get(); + instance.aggregation_report_mode = family->aggregation_report_mode_; + instance.arg = args; + instance.time_unit = family->time_unit_; + instance.range_multiplier = family->range_multiplier_; + instance.min_time = family->min_time_; + instance.iterations = family->iterations_; + instance.repetitions = family->repetitions_; + instance.measure_process_cpu_time = family->measure_process_cpu_time_; + instance.use_real_time = family->use_real_time_; + instance.use_manual_time = family->use_manual_time_; + instance.complexity = family->complexity_; + instance.complexity_lambda = family->complexity_lambda_; + instance.statistics = &family->statistics_; + instance.threads = num_threads; + + // Add arguments to instance name + size_t arg_i = 0; + for (auto const& arg : args) { + if (!instance.name.args.empty()) { + instance.name.args += '/'; + } + + if (arg_i < family->arg_names_.size()) { + const auto& arg_name = family->arg_names_[arg_i]; + if (!arg_name.empty()) { + instance.name.args += StrFormat("%s:", arg_name.c_str()); + } + } + + instance.name.args += StrFormat("%" PRId64, arg); + ++arg_i; + } + + if (!IsZero(family->min_time_)) + instance.name.min_time = + StrFormat("min_time:%0.3f", family->min_time_); + if (family->iterations_ != 0) { + instance.name.iterations = + StrFormat("iterations:%lu", + static_cast(family->iterations_)); + } + if (family->repetitions_ != 0) + instance.name.repetitions = + StrFormat("repeats:%d", family->repetitions_); + + if (family->measure_process_cpu_time_) { + instance.name.time_type = "process_time"; + } + + if (family->use_manual_time_) { + if (!instance.name.time_type.empty()) { + instance.name.time_type += '/'; + } + instance.name.time_type += "manual_time"; + } else if (family->use_real_time_) { + if (!instance.name.time_type.empty()) { + instance.name.time_type += '/'; + } + instance.name.time_type += "real_time"; + } + + // Add the number of threads used to the name + if (!family->thread_counts_.empty()) { + instance.name.threads = StrFormat("threads:%d", instance.threads); + } + + const auto full_name = instance.name.str(); + if ((re.Match(full_name) && !isNegativeFilter) || + (!re.Match(full_name) && isNegativeFilter)) { + instance.last_benchmark_instance = (&args == &family->args_.back()); + benchmarks->push_back(std::move(instance)); + } + } + } + } + return true; +} + +Benchmark* RegisterBenchmarkInternal(Benchmark* bench) { + std::unique_ptr bench_ptr(bench); + BenchmarkFamilies* families = BenchmarkFamilies::GetInstance(); + families->AddBenchmark(std::move(bench_ptr)); + return bench; +} + +// FIXME: This function is a hack so that benchmark.cc can access +// `BenchmarkFamilies` +bool FindBenchmarksInternal(const std::string& re, + std::vector* benchmarks, + std::ostream* Err) { + return BenchmarkFamilies::GetInstance()->FindBenchmarks(re, benchmarks, Err); +} + +//=============================================================================// +// Benchmark +//=============================================================================// + +Benchmark::Benchmark(const char* name) + : name_(name), + aggregation_report_mode_(ARM_Unspecified), + time_unit_(kNanosecond), + range_multiplier_(kRangeMultiplier), + min_time_(0), + iterations_(0), + repetitions_(0), + measure_process_cpu_time_(false), + use_real_time_(false), + use_manual_time_(false), + complexity_(oNone), + complexity_lambda_(nullptr) { + ComputeStatistics("mean", StatisticsMean); + ComputeStatistics("median", StatisticsMedian); + ComputeStatistics("stddev", StatisticsStdDev); +} + +Benchmark::~Benchmark() {} + +Benchmark* Benchmark::Arg(int64_t x) { + CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + args_.push_back({x}); + return this; +} + +Benchmark* Benchmark::Unit(TimeUnit unit) { + time_unit_ = unit; + return this; +} + +Benchmark* Benchmark::Range(int64_t start, int64_t limit) { + CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + std::vector arglist; + AddRange(&arglist, start, limit, range_multiplier_); + + for (int64_t i : arglist) { + args_.push_back({i}); + } + return this; +} + +Benchmark* Benchmark::Ranges( + const std::vector>& ranges) { + CHECK(ArgsCnt() == -1 || ArgsCnt() == static_cast(ranges.size())); + std::vector> arglists(ranges.size()); + std::size_t total = 1; + for (std::size_t i = 0; i < ranges.size(); i++) { + AddRange(&arglists[i], ranges[i].first, ranges[i].second, + range_multiplier_); + total *= arglists[i].size(); + } + + std::vector ctr(arglists.size(), 0); + + for (std::size_t i = 0; i < total; i++) { + std::vector tmp; + tmp.reserve(arglists.size()); + + for (std::size_t j = 0; j < arglists.size(); j++) { + tmp.push_back(arglists[j].at(ctr[j])); + } + + args_.push_back(std::move(tmp)); + + for (std::size_t j = 0; j < arglists.size(); j++) { + if (ctr[j] + 1 < arglists[j].size()) { + ++ctr[j]; + break; + } + ctr[j] = 0; + } + } + return this; +} + +Benchmark* Benchmark::ArgName(const std::string& name) { + CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + arg_names_ = {name}; + return this; +} + +Benchmark* Benchmark::ArgNames(const std::vector& names) { + CHECK(ArgsCnt() == -1 || ArgsCnt() == static_cast(names.size())); + arg_names_ = names; + return this; +} + +Benchmark* Benchmark::DenseRange(int64_t start, int64_t limit, int step) { + CHECK(ArgsCnt() == -1 || ArgsCnt() == 1); + CHECK_LE(start, limit); + for (int64_t arg = start; arg <= limit; arg += step) { + args_.push_back({arg}); + } + return this; +} + +Benchmark* Benchmark::Args(const std::vector& args) { + CHECK(ArgsCnt() == -1 || ArgsCnt() == static_cast(args.size())); + args_.push_back(args); + return this; +} + +Benchmark* Benchmark::Apply(void (*custom_arguments)(Benchmark* benchmark)) { + custom_arguments(this); + return this; +} + +Benchmark* Benchmark::RangeMultiplier(int multiplier) { + CHECK(multiplier > 1); + range_multiplier_ = multiplier; + return this; +} + +Benchmark* Benchmark::MinTime(double t) { + CHECK(t > 0.0); + CHECK(iterations_ == 0); + min_time_ = t; + return this; +} + +Benchmark* Benchmark::Iterations(IterationCount n) { + CHECK(n > 0); + CHECK(IsZero(min_time_)); + iterations_ = n; + return this; +} + +Benchmark* Benchmark::Repetitions(int n) { + CHECK(n > 0); + repetitions_ = n; + return this; +} + +Benchmark* Benchmark::ReportAggregatesOnly(bool value) { + aggregation_report_mode_ = value ? ARM_ReportAggregatesOnly : ARM_Default; + return this; +} + +Benchmark* Benchmark::DisplayAggregatesOnly(bool value) { + // If we were called, the report mode is no longer 'unspecified', in any case. + aggregation_report_mode_ = static_cast( + aggregation_report_mode_ | ARM_Default); + + if (value) { + aggregation_report_mode_ = static_cast( + aggregation_report_mode_ | ARM_DisplayReportAggregatesOnly); + } else { + aggregation_report_mode_ = static_cast( + aggregation_report_mode_ & ~ARM_DisplayReportAggregatesOnly); + } + + return this; +} + +Benchmark* Benchmark::MeasureProcessCPUTime() { + // Can be used together with UseRealTime() / UseManualTime(). + measure_process_cpu_time_ = true; + return this; +} + +Benchmark* Benchmark::UseRealTime() { + CHECK(!use_manual_time_) + << "Cannot set UseRealTime and UseManualTime simultaneously."; + use_real_time_ = true; + return this; +} + +Benchmark* Benchmark::UseManualTime() { + CHECK(!use_real_time_) + << "Cannot set UseRealTime and UseManualTime simultaneously."; + use_manual_time_ = true; + return this; +} + +Benchmark* Benchmark::Complexity(BigO complexity) { + complexity_ = complexity; + return this; +} + +Benchmark* Benchmark::Complexity(BigOFunc* complexity) { + complexity_lambda_ = complexity; + complexity_ = oLambda; + return this; +} + +Benchmark* Benchmark::ComputeStatistics(std::string name, + StatisticsFunc* statistics) { + statistics_.emplace_back(name, statistics); + return this; +} + +Benchmark* Benchmark::Threads(int t) { + CHECK_GT(t, 0); + thread_counts_.push_back(t); + return this; +} + +Benchmark* Benchmark::ThreadRange(int min_threads, int max_threads) { + CHECK_GT(min_threads, 0); + CHECK_GE(max_threads, min_threads); + + AddRange(&thread_counts_, min_threads, max_threads, 2); + return this; +} + +Benchmark* Benchmark::DenseThreadRange(int min_threads, int max_threads, + int stride) { + CHECK_GT(min_threads, 0); + CHECK_GE(max_threads, min_threads); + CHECK_GE(stride, 1); + + for (auto i = min_threads; i < max_threads; i += stride) { + thread_counts_.push_back(i); + } + thread_counts_.push_back(max_threads); + return this; +} + +Benchmark* Benchmark::ThreadPerCpu() { + thread_counts_.push_back(CPUInfo::Get().num_cpus); + return this; +} + +void Benchmark::SetName(const char* name) { name_ = name; } + +int Benchmark::ArgsCnt() const { + if (args_.empty()) { + if (arg_names_.empty()) return -1; + return static_cast(arg_names_.size()); + } + return static_cast(args_.front().size()); +} + +//=============================================================================// +// FunctionBenchmark +//=============================================================================// + +void FunctionBenchmark::Run(State& st) { func_(st); } + +} // end namespace internal + +void ClearRegisteredBenchmarks() { + internal::BenchmarkFamilies::GetInstance()->ClearBenchmarks(); +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_register.h b/thirdparty/benchmark-1.5.0/src/benchmark_register.h new file mode 100644 index 0000000000..61377d7423 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_register.h @@ -0,0 +1,107 @@ +#ifndef BENCHMARK_REGISTER_H +#define BENCHMARK_REGISTER_H + +#include + +#include "check.h" + +namespace benchmark { +namespace internal { + +// Append the powers of 'mult' in the closed interval [lo, hi]. +// Returns iterator to the start of the inserted range. +template +typename std::vector::iterator +AddPowers(std::vector* dst, T lo, T hi, int mult) { + CHECK_GE(lo, 0); + CHECK_GE(hi, lo); + CHECK_GE(mult, 2); + + const size_t start_offset = dst->size(); + + static const T kmax = std::numeric_limits::max(); + + // Space out the values in multiples of "mult" + for (T i = 1; i <= hi; i *= mult) { + if (i >= lo) { + dst->push_back(i); + } + // Break the loop here since multiplying by + // 'mult' would move outside of the range of T + if (i > kmax / mult) break; + } + + return dst->begin() + start_offset; +} + +template +void AddNegatedPowers(std::vector* dst, T lo, T hi, int mult) { + // We negate lo and hi so we require that they cannot be equal to 'min'. + CHECK_GT(lo, std::numeric_limits::min()); + CHECK_GT(hi, std::numeric_limits::min()); + CHECK_GE(hi, lo); + CHECK_LE(hi, 0); + + // Add positive powers, then negate and reverse. + // Casts necessary since small integers get promoted + // to 'int' when negating. + const auto lo_complement = static_cast(-lo); + const auto hi_complement = static_cast(-hi); + + const auto it = AddPowers(dst, hi_complement, lo_complement, mult); + + std::for_each(it, dst->end(), [](T& t) { t *= -1; }); + std::reverse(it, dst->end()); +} + +template +void AddRange(std::vector* dst, T lo, T hi, int mult) { + static_assert(std::is_integral::value && std::is_signed::value, + "Args type must be a signed integer"); + + CHECK_GE(hi, lo); + CHECK_GE(mult, 2); + + // Add "lo" + dst->push_back(lo); + + // Handle lo == hi as a special case, so we then know + // lo < hi and so it is safe to add 1 to lo and subtract 1 + // from hi without falling outside of the range of T. + if (lo == hi) return; + + // Ensure that lo_inner <= hi_inner below. + if (lo + 1 == hi) { + dst->push_back(hi); + return; + } + + // Add all powers of 'mult' in the range [lo+1, hi-1] (inclusive). + const auto lo_inner = static_cast(lo + 1); + const auto hi_inner = static_cast(hi - 1); + + // Insert negative values + if (lo_inner < 0) { + AddNegatedPowers(dst, lo_inner, std::min(hi_inner, T{-1}), mult); + } + + // Treat 0 as a special case (see discussion on #762). + if (lo <= 0 && hi >= 0) { + dst->push_back(0); + } + + // Insert positive values + if (hi_inner > 0) { + AddPowers(dst, std::max(lo_inner, T{1}), hi_inner, mult); + } + + // Add "hi" (if different from last value). + if (hi != dst->back()) { + dst->push_back(hi); + } +} + +} // namespace internal +} // namespace benchmark + +#endif // BENCHMARK_REGISTER_H diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_runner.cc b/thirdparty/benchmark-1.5.0/src/benchmark_runner.cc new file mode 100644 index 0000000000..0bae6a545e --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_runner.cc @@ -0,0 +1,361 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark_runner.h" +#include "benchmark/benchmark.h" +#include "benchmark_api_internal.h" +#include "internal_macros.h" + +#ifndef BENCHMARK_OS_WINDOWS +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "colorprint.h" +#include "commandlineflags.h" +#include "complexity.h" +#include "counter.h" +#include "internal_macros.h" +#include "log.h" +#include "mutex.h" +#include "re.h" +#include "statistics.h" +#include "string_util.h" +#include "thread_manager.h" +#include "thread_timer.h" + +namespace benchmark { + +namespace internal { + +MemoryManager* memory_manager = nullptr; + +namespace { + +static constexpr IterationCount kMaxIterations = 1000000000; + +BenchmarkReporter::Run CreateRunReport( + const benchmark::internal::BenchmarkInstance& b, + const internal::ThreadManager::Result& results, + IterationCount memory_iterations, + const MemoryManager::Result& memory_result, double seconds, + int64_t repetition_index) { + // Create report about this benchmark run. + BenchmarkReporter::Run report; + + report.run_name = b.name; + report.error_occurred = results.has_error_; + report.error_message = results.error_message_; + report.report_label = results.report_label_; + // This is the total iterations across all threads. + report.iterations = results.iterations; + report.time_unit = b.time_unit; + report.threads = b.threads; + report.repetition_index = repetition_index; + report.repetitions = b.repetitions; + + if (!report.error_occurred) { + if (b.use_manual_time) { + report.real_accumulated_time = results.manual_time_used; + } else { + report.real_accumulated_time = results.real_time_used; + } + report.cpu_accumulated_time = results.cpu_time_used; + report.complexity_n = results.complexity_n; + report.complexity = b.complexity; + report.complexity_lambda = b.complexity_lambda; + report.statistics = b.statistics; + report.counters = results.counters; + + if (memory_iterations > 0) { + report.has_memory_result = true; + report.allocs_per_iter = + memory_iterations ? static_cast(memory_result.num_allocs) / + memory_iterations + : 0; + report.max_bytes_used = memory_result.max_bytes_used; + } + + internal::Finish(&report.counters, results.iterations, seconds, b.threads); + } + return report; +} + +// Execute one thread of benchmark b for the specified number of iterations. +// Adds the stats collected for the thread into *total. +void RunInThread(const BenchmarkInstance* b, IterationCount iters, + int thread_id, ThreadManager* manager) { + internal::ThreadTimer timer( + b->measure_process_cpu_time + ? internal::ThreadTimer::CreateProcessCpuTime() + : internal::ThreadTimer::Create()); + State st = b->Run(iters, thread_id, &timer, manager); + CHECK(st.iterations() >= st.max_iterations) + << "Benchmark returned before State::KeepRunning() returned false!"; + { + MutexLock l(manager->GetBenchmarkMutex()); + internal::ThreadManager::Result& results = manager->results; + results.iterations += st.iterations(); + results.cpu_time_used += timer.cpu_time_used(); + results.real_time_used += timer.real_time_used(); + results.manual_time_used += timer.manual_time_used(); + results.complexity_n += st.complexity_length_n(); + internal::Increment(&results.counters, st.counters); + } + manager->NotifyThreadComplete(); +} + +class BenchmarkRunner { + public: + BenchmarkRunner(const benchmark::internal::BenchmarkInstance& b_, + std::vector* complexity_reports_) + : b(b_), + complexity_reports(*complexity_reports_), + min_time(!IsZero(b.min_time) ? b.min_time : FLAGS_benchmark_min_time), + repeats(b.repetitions != 0 ? b.repetitions + : FLAGS_benchmark_repetitions), + has_explicit_iteration_count(b.iterations != 0), + pool(b.threads - 1), + iters(has_explicit_iteration_count ? b.iterations : 1) { + run_results.display_report_aggregates_only = + (FLAGS_benchmark_report_aggregates_only || + FLAGS_benchmark_display_aggregates_only); + run_results.file_report_aggregates_only = + FLAGS_benchmark_report_aggregates_only; + if (b.aggregation_report_mode != internal::ARM_Unspecified) { + run_results.display_report_aggregates_only = + (b.aggregation_report_mode & + internal::ARM_DisplayReportAggregatesOnly); + run_results.file_report_aggregates_only = + (b.aggregation_report_mode & internal::ARM_FileReportAggregatesOnly); + } + + for (int repetition_num = 0; repetition_num < repeats; repetition_num++) { + DoOneRepetition(repetition_num); + } + + // Calculate additional statistics + run_results.aggregates_only = ComputeStats(run_results.non_aggregates); + + // Maybe calculate complexity report + if ((b.complexity != oNone) && b.last_benchmark_instance) { + auto additional_run_stats = ComputeBigO(complexity_reports); + run_results.aggregates_only.insert(run_results.aggregates_only.end(), + additional_run_stats.begin(), + additional_run_stats.end()); + complexity_reports.clear(); + } + } + + RunResults&& get_results() { return std::move(run_results); } + + private: + RunResults run_results; + + const benchmark::internal::BenchmarkInstance& b; + std::vector& complexity_reports; + + const double min_time; + const int repeats; + const bool has_explicit_iteration_count; + + std::vector pool; + + IterationCount iters; // preserved between repetitions! + // So only the first repetition has to find/calculate it, + // the other repetitions will just use that precomputed iteration count. + + struct IterationResults { + internal::ThreadManager::Result results; + IterationCount iters; + double seconds; + }; + IterationResults DoNIterations() { + VLOG(2) << "Running " << b.name.str() << " for " << iters << "\n"; + + std::unique_ptr manager; + manager.reset(new internal::ThreadManager(b.threads)); + + // Run all but one thread in separate threads + for (std::size_t ti = 0; ti < pool.size(); ++ti) { + pool[ti] = std::thread(&RunInThread, &b, iters, static_cast(ti + 1), + manager.get()); + } + // And run one thread here directly. + // (If we were asked to run just one thread, we don't create new threads.) + // Yes, we need to do this here *after* we start the separate threads. + RunInThread(&b, iters, 0, manager.get()); + + // The main thread has finished. Now let's wait for the other threads. + manager->WaitForAllThreads(); + for (std::thread& thread : pool) thread.join(); + + IterationResults i; + // Acquire the measurements/counters from the manager, UNDER THE LOCK! + { + MutexLock l(manager->GetBenchmarkMutex()); + i.results = manager->results; + } + + // And get rid of the manager. + manager.reset(); + + // Adjust real/manual time stats since they were reported per thread. + i.results.real_time_used /= b.threads; + i.results.manual_time_used /= b.threads; + // If we were measuring whole-process CPU usage, adjust the CPU time too. + if (b.measure_process_cpu_time) i.results.cpu_time_used /= b.threads; + + VLOG(2) << "Ran in " << i.results.cpu_time_used << "/" + << i.results.real_time_used << "\n"; + + // So for how long were we running? + i.iters = iters; + // Base decisions off of real time if requested by this benchmark. + i.seconds = i.results.cpu_time_used; + if (b.use_manual_time) { + i.seconds = i.results.manual_time_used; + } else if (b.use_real_time) { + i.seconds = i.results.real_time_used; + } + + return i; + } + + IterationCount PredictNumItersNeeded(const IterationResults& i) const { + // See how much iterations should be increased by. + // Note: Avoid division by zero with max(seconds, 1ns). + double multiplier = min_time * 1.4 / std::max(i.seconds, 1e-9); + // If our last run was at least 10% of FLAGS_benchmark_min_time then we + // use the multiplier directly. + // Otherwise we use at most 10 times expansion. + // NOTE: When the last run was at least 10% of the min time the max + // expansion should be 14x. + bool is_significant = (i.seconds / min_time) > 0.1; + multiplier = is_significant ? multiplier : std::min(10.0, multiplier); + if (multiplier <= 1.0) multiplier = 2.0; + + // So what seems to be the sufficiently-large iteration count? Round up. + const IterationCount max_next_iters = + 0.5 + std::max(multiplier * i.iters, i.iters + 1.0); + // But we do have *some* sanity limits though.. + const IterationCount next_iters = std::min(max_next_iters, kMaxIterations); + + VLOG(3) << "Next iters: " << next_iters << ", " << multiplier << "\n"; + return next_iters; // round up before conversion to integer. + } + + bool ShouldReportIterationResults(const IterationResults& i) const { + // Determine if this run should be reported; + // Either it has run for a sufficient amount of time + // or because an error was reported. + return i.results.has_error_ || + i.iters >= kMaxIterations || // Too many iterations already. + i.seconds >= min_time || // The elapsed time is large enough. + // CPU time is specified but the elapsed real time greatly exceeds + // the minimum time. + // Note that user provided timers are except from this sanity check. + ((i.results.real_time_used >= 5 * min_time) && !b.use_manual_time); + } + + void DoOneRepetition(int64_t repetition_index) { + const bool is_the_first_repetition = repetition_index == 0; + IterationResults i; + + // We *may* be gradually increasing the length (iteration count) + // of the benchmark until we decide the results are significant. + // And once we do, we report those last results and exit. + // Please do note that the if there are repetitions, the iteration count + // is *only* calculated for the *first* repetition, and other repetitions + // simply use that precomputed iteration count. + for (;;) { + i = DoNIterations(); + + // Do we consider the results to be significant? + // If we are doing repetitions, and the first repetition was already done, + // it has calculated the correct iteration time, so we have run that very + // iteration count just now. No need to calculate anything. Just report. + // Else, the normal rules apply. + const bool results_are_significant = !is_the_first_repetition || + has_explicit_iteration_count || + ShouldReportIterationResults(i); + + if (results_are_significant) break; // Good, let's report them! + + // Nope, bad iteration. Let's re-estimate the hopefully-sufficient + // iteration count, and run the benchmark again... + + iters = PredictNumItersNeeded(i); + assert(iters > i.iters && + "if we did more iterations than we want to do the next time, " + "then we should have accepted the current iteration run."); + } + + // Oh, one last thing, we need to also produce the 'memory measurements'.. + MemoryManager::Result memory_result; + IterationCount memory_iterations = 0; + if (memory_manager != nullptr) { + // Only run a few iterations to reduce the impact of one-time + // allocations in benchmarks that are not properly managed. + memory_iterations = std::min(16, iters); + memory_manager->Start(); + std::unique_ptr manager; + manager.reset(new internal::ThreadManager(1)); + RunInThread(&b, memory_iterations, 0, manager.get()); + manager->WaitForAllThreads(); + manager.reset(); + + memory_manager->Stop(&memory_result); + } + + // Ok, now actualy report. + BenchmarkReporter::Run report = + CreateRunReport(b, i.results, memory_iterations, memory_result, + i.seconds, repetition_index); + + if (!report.error_occurred && b.complexity != oNone) + complexity_reports.push_back(report); + + run_results.non_aggregates.push_back(report); + } +}; + +} // end namespace + +RunResults RunBenchmark( + const benchmark::internal::BenchmarkInstance& b, + std::vector* complexity_reports) { + internal::BenchmarkRunner r(b, complexity_reports); + return r.get_results(); +} + +} // end namespace internal + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/benchmark_runner.h b/thirdparty/benchmark-1.5.0/src/benchmark_runner.h new file mode 100644 index 0000000000..96e8282a11 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/benchmark_runner.h @@ -0,0 +1,51 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BENCHMARK_RUNNER_H_ +#define BENCHMARK_RUNNER_H_ + +#include "benchmark_api_internal.h" +#include "internal_macros.h" + +DECLARE_double(benchmark_min_time); + +DECLARE_int32(benchmark_repetitions); + +DECLARE_bool(benchmark_report_aggregates_only); + +DECLARE_bool(benchmark_display_aggregates_only); + +namespace benchmark { + +namespace internal { + +extern MemoryManager* memory_manager; + +struct RunResults { + std::vector non_aggregates; + std::vector aggregates_only; + + bool display_report_aggregates_only = false; + bool file_report_aggregates_only = false; +}; + +RunResults RunBenchmark( + const benchmark::internal::BenchmarkInstance& b, + std::vector* complexity_reports); + +} // namespace internal + +} // end namespace benchmark + +#endif // BENCHMARK_RUNNER_H_ diff --git a/thirdparty/benchmark-1.5.0/src/check.h b/thirdparty/benchmark-1.5.0/src/check.h new file mode 100644 index 0000000000..f5f8253f80 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/check.h @@ -0,0 +1,82 @@ +#ifndef CHECK_H_ +#define CHECK_H_ + +#include +#include +#include + +#include "internal_macros.h" +#include "log.h" + +namespace benchmark { +namespace internal { + +typedef void(AbortHandlerT)(); + +inline AbortHandlerT*& GetAbortHandler() { + static AbortHandlerT* handler = &std::abort; + return handler; +} + +BENCHMARK_NORETURN inline void CallAbortHandler() { + GetAbortHandler()(); + std::abort(); // fallback to enforce noreturn +} + +// CheckHandler is the class constructed by failing CHECK macros. CheckHandler +// will log information about the failures and abort when it is destructed. +class CheckHandler { + public: + CheckHandler(const char* check, const char* file, const char* func, int line) + : log_(GetErrorLogInstance()) { + log_ << file << ":" << line << ": " << func << ": Check `" << check + << "' failed. "; + } + + LogType& GetLog() { return log_; } + + BENCHMARK_NORETURN ~CheckHandler() BENCHMARK_NOEXCEPT_OP(false) { + log_ << std::endl; + CallAbortHandler(); + } + + CheckHandler& operator=(const CheckHandler&) = delete; + CheckHandler(const CheckHandler&) = delete; + CheckHandler() = delete; + + private: + LogType& log_; +}; + +} // end namespace internal +} // end namespace benchmark + +// The CHECK macro returns a std::ostream object that can have extra information +// written to it. +#ifndef NDEBUG +#define CHECK(b) \ + (b ? ::benchmark::internal::GetNullLogInstance() \ + : ::benchmark::internal::CheckHandler(#b, __FILE__, __func__, __LINE__) \ + .GetLog()) +#else +#define CHECK(b) ::benchmark::internal::GetNullLogInstance() +#endif + +// clang-format off +// preserve whitespacing between operators for alignment +#define CHECK_EQ(a, b) CHECK((a) == (b)) +#define CHECK_NE(a, b) CHECK((a) != (b)) +#define CHECK_GE(a, b) CHECK((a) >= (b)) +#define CHECK_LE(a, b) CHECK((a) <= (b)) +#define CHECK_GT(a, b) CHECK((a) > (b)) +#define CHECK_LT(a, b) CHECK((a) < (b)) + +#define CHECK_FLOAT_EQ(a, b, eps) CHECK(std::fabs((a) - (b)) < (eps)) +#define CHECK_FLOAT_NE(a, b, eps) CHECK(std::fabs((a) - (b)) >= (eps)) +#define CHECK_FLOAT_GE(a, b, eps) CHECK((a) - (b) > -(eps)) +#define CHECK_FLOAT_LE(a, b, eps) CHECK((b) - (a) > -(eps)) +#define CHECK_FLOAT_GT(a, b, eps) CHECK((a) - (b) > (eps)) +#define CHECK_FLOAT_LT(a, b, eps) CHECK((b) - (a) > (eps)) +//clang-format on + +#endif // CHECK_H_ diff --git a/thirdparty/benchmark-1.5.0/src/colorprint.cc b/thirdparty/benchmark-1.5.0/src/colorprint.cc new file mode 100644 index 0000000000..fff6a98818 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/colorprint.cc @@ -0,0 +1,188 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "colorprint.h" + +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#include +#else +#include +#endif // BENCHMARK_OS_WINDOWS + +namespace benchmark { +namespace { +#ifdef BENCHMARK_OS_WINDOWS +typedef WORD PlatformColorCode; +#else +typedef const char* PlatformColorCode; +#endif + +PlatformColorCode GetPlatformColorCode(LogColor color) { +#ifdef BENCHMARK_OS_WINDOWS + switch (color) { + case COLOR_RED: + return FOREGROUND_RED; + case COLOR_GREEN: + return FOREGROUND_GREEN; + case COLOR_YELLOW: + return FOREGROUND_RED | FOREGROUND_GREEN; + case COLOR_BLUE: + return FOREGROUND_BLUE; + case COLOR_MAGENTA: + return FOREGROUND_BLUE | FOREGROUND_RED; + case COLOR_CYAN: + return FOREGROUND_BLUE | FOREGROUND_GREEN; + case COLOR_WHITE: // fall through to default + default: + return 0; + } +#else + switch (color) { + case COLOR_RED: + return "1"; + case COLOR_GREEN: + return "2"; + case COLOR_YELLOW: + return "3"; + case COLOR_BLUE: + return "4"; + case COLOR_MAGENTA: + return "5"; + case COLOR_CYAN: + return "6"; + case COLOR_WHITE: + return "7"; + default: + return nullptr; + }; +#endif +} + +} // end namespace + +std::string FormatString(const char* msg, va_list args) { + // we might need a second shot at this, so pre-emptivly make a copy + va_list args_cp; + va_copy(args_cp, args); + + std::size_t size = 256; + char local_buff[256]; + auto ret = vsnprintf(local_buff, size, msg, args_cp); + + va_end(args_cp); + + // currently there is no error handling for failure, so this is hack. + CHECK(ret >= 0); + + if (ret == 0) // handle empty expansion + return {}; + else if (static_cast(ret) < size) + return local_buff; + else { + // we did not provide a long enough buffer on our first attempt. + size = (size_t)ret + 1; // + 1 for the null byte + std::unique_ptr buff(new char[size]); + ret = vsnprintf(buff.get(), size, msg, args); + CHECK(ret > 0 && ((size_t)ret) < size); + return buff.get(); + } +} + +std::string FormatString(const char* msg, ...) { + va_list args; + va_start(args, msg); + auto tmp = FormatString(msg, args); + va_end(args); + return tmp; +} + +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + ColorPrintf(out, color, fmt, args); + va_end(args); +} + +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, + va_list args) { +#ifdef BENCHMARK_OS_WINDOWS + ((void)out); // suppress unused warning + + const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stdout); + SetConsoleTextAttribute(stdout_handle, + GetPlatformColorCode(color) | FOREGROUND_INTENSITY); + vprintf(fmt, args); + + fflush(stdout); + // Restores the text color. + SetConsoleTextAttribute(stdout_handle, old_color_attrs); +#else + const char* color_code = GetPlatformColorCode(color); + if (color_code) out << FormatString("\033[0;3%sm", color_code); + out << FormatString(fmt, args) << "\033[m"; +#endif +} + +bool IsColorTerminal() { +#if BENCHMARK_OS_WINDOWS + // On Windows the TERM variable is usually not set, but the + // console there does support colors. + return 0 != _isatty(_fileno(stdout)); +#else + // On non-Windows platforms, we rely on the TERM variable. This list of + // supported TERM values is copied from Google Test: + // . + const char* const SUPPORTED_TERM_VALUES[] = { + "xterm", "xterm-color", "xterm-256color", + "screen", "screen-256color", "tmux", + "tmux-256color", "rxvt-unicode", "rxvt-unicode-256color", + "linux", "cygwin", + }; + + const char* const term = getenv("TERM"); + + bool term_supports_color = false; + for (const char* candidate : SUPPORTED_TERM_VALUES) { + if (term && 0 == strcmp(term, candidate)) { + term_supports_color = true; + break; + } + } + + return 0 != isatty(fileno(stdout)) && term_supports_color; +#endif // BENCHMARK_OS_WINDOWS +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/colorprint.h b/thirdparty/benchmark-1.5.0/src/colorprint.h new file mode 100644 index 0000000000..9f6fab9b34 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/colorprint.h @@ -0,0 +1,33 @@ +#ifndef BENCHMARK_COLORPRINT_H_ +#define BENCHMARK_COLORPRINT_H_ + +#include +#include +#include + +namespace benchmark { +enum LogColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW, + COLOR_BLUE, + COLOR_MAGENTA, + COLOR_CYAN, + COLOR_WHITE +}; + +std::string FormatString(const char* msg, va_list args); +std::string FormatString(const char* msg, ...); + +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, + va_list args); +void ColorPrintf(std::ostream& out, LogColor color, const char* fmt, ...); + +// Returns true if stdout appears to be a terminal that supports colored +// output, false otherwise. +bool IsColorTerminal(); + +} // end namespace benchmark + +#endif // BENCHMARK_COLORPRINT_H_ diff --git a/thirdparty/benchmark-1.5.0/src/commandlineflags.cc b/thirdparty/benchmark-1.5.0/src/commandlineflags.cc new file mode 100644 index 0000000000..6bd65c5ae7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/commandlineflags.cc @@ -0,0 +1,222 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "commandlineflags.h" + +#include +#include +#include +#include +#include + +namespace benchmark { +namespace { + +// Parses 'str' for a 32-bit signed integer. If successful, writes +// the result to *value and returns true; otherwise leaves *value +// unchanged and returns false. +bool ParseInt32(const std::string& src_text, const char* str, int32_t* value) { + // Parses the environment variable as a decimal integer. + char* end = nullptr; + const long long_value = strtol(str, &end, 10); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + std::cerr << src_text << " is expected to be a 32-bit integer, " + << "but actually has value \"" << str << "\".\n"; + return false; + } + + // Is the parsed value in the range of an Int32? + const int32_t result = static_cast(long_value); + if (long_value == std::numeric_limits::max() || + long_value == std::numeric_limits::min() || + // The parsed value overflows as a long. (strtol() returns + // LONG_MAX or LONG_MIN when the input overflows.) + result != long_value + // The parsed value overflows as an Int32. + ) { + std::cerr << src_text << " is expected to be a 32-bit integer, " + << "but actually has value \"" << str << "\", " + << "which overflows.\n"; + return false; + } + + *value = result; + return true; +} + +// Parses 'str' for a double. If successful, writes the result to *value and +// returns true; otherwise leaves *value unchanged and returns false. +bool ParseDouble(const std::string& src_text, const char* str, double* value) { + // Parses the environment variable as a decimal integer. + char* end = nullptr; + const double double_value = strtod(str, &end); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + std::cerr << src_text << " is expected to be a double, " + << "but actually has value \"" << str << "\".\n"; + return false; + } + + *value = double_value; + return true; +} + +// Returns the name of the environment variable corresponding to the +// given flag. For example, FlagToEnvVar("foo") will return +// "BENCHMARK_FOO" in the open-source version. +static std::string FlagToEnvVar(const char* flag) { + const std::string flag_str(flag); + + std::string env_var; + for (size_t i = 0; i != flag_str.length(); ++i) + env_var += static_cast(::toupper(flag_str.c_str()[i])); + + return "BENCHMARK_" + env_var; +} + +} // namespace + +// Reads and returns the Boolean environment variable corresponding to +// the given flag; if it's not set, returns default_value. +// +// The value is considered true iff it's not "0". +bool BoolFromEnv(const char* flag, bool default_value) { + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = getenv(env_var.c_str()); + return string_value == nullptr ? default_value + : strcmp(string_value, "0") != 0; +} + +// Reads and returns a 32-bit integer stored in the environment +// variable corresponding to the given flag; if it isn't set or +// doesn't represent a valid 32-bit integer, returns default_value. +int32_t Int32FromEnv(const char* flag, int32_t default_value) { + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = getenv(env_var.c_str()); + if (string_value == nullptr) { + // The environment variable is not set. + return default_value; + } + + int32_t result = default_value; + if (!ParseInt32(std::string("Environment variable ") + env_var, string_value, + &result)) { + std::cout << "The default value " << default_value << " is used.\n"; + return default_value; + } + + return result; +} + +// Reads and returns the string environment variable corresponding to +// the given flag; if it's not set, returns default_value. +const char* StringFromEnv(const char* flag, const char* default_value) { + const std::string env_var = FlagToEnvVar(flag); + const char* const value = getenv(env_var.c_str()); + return value == nullptr ? default_value : value; +} + +// Parses a string as a command line flag. The string should have +// the format "--flag=value". When def_optional is true, the "=value" +// part can be omitted. +// +// Returns the value of the flag, or nullptr if the parsing failed. +const char* ParseFlagValue(const char* str, const char* flag, + bool def_optional) { + // str and flag must not be nullptr. + if (str == nullptr || flag == nullptr) return nullptr; + + // The flag must start with "--". + const std::string flag_str = std::string("--") + std::string(flag); + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) return flag_end; + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return nullptr; + + // Returns the string after "=". + return flag_end + 1; +} + +bool ParseBoolFlag(const char* str, const char* flag, bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Converts the string value to a bool. + *value = IsTruthyFlagValue(value_str); + return true; +} + +bool ParseInt32Flag(const char* str, const char* flag, int32_t* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + return ParseInt32(std::string("The value of flag --") + flag, value_str, + value); +} + +bool ParseDoubleFlag(const char* str, const char* flag, double* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + // Sets *value to the value of the flag. + return ParseDouble(std::string("The value of flag --") + flag, value_str, + value); +} + +bool ParseStringFlag(const char* str, const char* flag, std::string* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == nullptr) return false; + + *value = value_str; + return true; +} + +bool IsFlag(const char* str, const char* flag) { + return (ParseFlagValue(str, flag, true) != nullptr); +} + +bool IsTruthyFlagValue(const std::string& value) { + if (value.empty()) return true; + char ch = value[0]; + return isalnum(ch) && + !(ch == '0' || ch == 'f' || ch == 'F' || ch == 'n' || ch == 'N'); +} +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/commandlineflags.h b/thirdparty/benchmark-1.5.0/src/commandlineflags.h new file mode 100644 index 0000000000..5eaea82a59 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/commandlineflags.h @@ -0,0 +1,73 @@ +#ifndef BENCHMARK_COMMANDLINEFLAGS_H_ +#define BENCHMARK_COMMANDLINEFLAGS_H_ + +#include +#include + +// Macro for referencing flags. +#define FLAG(name) FLAGS_##name + +// Macros for declaring flags. +#define DECLARE_bool(name) extern bool FLAG(name) +#define DECLARE_int32(name) extern int32_t FLAG(name) +#define DECLARE_int64(name) extern int64_t FLAG(name) +#define DECLARE_double(name) extern double FLAG(name) +#define DECLARE_string(name) extern std::string FLAG(name) + +// Macros for defining flags. +#define DEFINE_bool(name, default_val, doc) bool FLAG(name) = (default_val) +#define DEFINE_int32(name, default_val, doc) int32_t FLAG(name) = (default_val) +#define DEFINE_int64(name, default_val, doc) int64_t FLAG(name) = (default_val) +#define DEFINE_double(name, default_val, doc) double FLAG(name) = (default_val) +#define DEFINE_string(name, default_val, doc) \ + std::string FLAG(name) = (default_val) + +namespace benchmark { +// Parses a bool/Int32/string from the environment variable +// corresponding to the given Google Test flag. +bool BoolFromEnv(const char* flag, bool default_val); +int32_t Int32FromEnv(const char* flag, int32_t default_val); +const char* StringFromEnv(const char* flag, const char* default_val); + +// Parses a string for a bool flag, in the form of either +// "--flag=value" or "--flag". +// +// In the former case, the value is taken as true if it passes IsTruthyValue(). +// +// In the latter case, the value is taken as true. +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseBoolFlag(const char* str, const char* flag, bool* value); + +// Parses a string for an Int32 flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseInt32Flag(const char* str, const char* flag, int32_t* value); + +// Parses a string for a Double flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseDoubleFlag(const char* str, const char* flag, double* value); + +// Parses a string for a string flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseStringFlag(const char* str, const char* flag, std::string* value); + +// Returns true if the string matches the flag. +bool IsFlag(const char* str, const char* flag); + +// Returns true unless value starts with one of: '0', 'f', 'F', 'n' or 'N', or +// some non-alphanumeric character. As a special case, also returns true if +// value is the empty string. +bool IsTruthyFlagValue(const std::string& value); +} // end namespace benchmark + +#endif // BENCHMARK_COMMANDLINEFLAGS_H_ diff --git a/thirdparty/benchmark-1.5.0/src/complexity.cc b/thirdparty/benchmark-1.5.0/src/complexity.cc new file mode 100644 index 0000000000..aeed67f0c7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/complexity.cc @@ -0,0 +1,238 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Source project : https://github.com/ismaelJimenez/cpp.leastsq +// Adapted to be used with google benchmark + +#include "benchmark/benchmark.h" + +#include +#include +#include "check.h" +#include "complexity.h" + +namespace benchmark { + +// Internal function to calculate the different scalability forms +BigOFunc* FittingCurve(BigO complexity) { + static const double kLog2E = 1.44269504088896340736; + switch (complexity) { + case oN: + return [](IterationCount n) -> double { return static_cast(n); }; + case oNSquared: + return [](IterationCount n) -> double { return std::pow(n, 2); }; + case oNCubed: + return [](IterationCount n) -> double { return std::pow(n, 3); }; + case oLogN: + /* Note: can't use log2 because Android's GNU STL lacks it */ + return + [](IterationCount n) { return kLog2E * log(static_cast(n)); }; + case oNLogN: + /* Note: can't use log2 because Android's GNU STL lacks it */ + return [](IterationCount n) { + return kLog2E * n * log(static_cast(n)); + }; + case o1: + default: + return [](IterationCount) { return 1.0; }; + } +} + +// Function to return an string for the calculated complexity +std::string GetBigOString(BigO complexity) { + switch (complexity) { + case oN: + return "N"; + case oNSquared: + return "N^2"; + case oNCubed: + return "N^3"; + case oLogN: + return "lgN"; + case oNLogN: + return "NlgN"; + case o1: + return "(1)"; + default: + return "f(N)"; + } +} + +// Find the coefficient for the high-order term in the running time, by +// minimizing the sum of squares of relative error, for the fitting curve +// given by the lambda expression. +// - n : Vector containing the size of the benchmark tests. +// - time : Vector containing the times for the benchmark tests. +// - fitting_curve : lambda expression (e.g. [](int64_t n) {return n; };). + +// For a deeper explanation on the algorithm logic, please refer to +// https://en.wikipedia.org/wiki/Least_squares#Least_squares,_regression_analysis_and_statistics + +LeastSq MinimalLeastSq(const std::vector& n, + const std::vector& time, + BigOFunc* fitting_curve) { + double sigma_gn = 0.0; + double sigma_gn_squared = 0.0; + double sigma_time = 0.0; + double sigma_time_gn = 0.0; + + // Calculate least square fitting parameter + for (size_t i = 0; i < n.size(); ++i) { + double gn_i = fitting_curve(n[i]); + sigma_gn += gn_i; + sigma_gn_squared += gn_i * gn_i; + sigma_time += time[i]; + sigma_time_gn += time[i] * gn_i; + } + + LeastSq result; + result.complexity = oLambda; + + // Calculate complexity. + result.coef = sigma_time_gn / sigma_gn_squared; + + // Calculate RMS + double rms = 0.0; + for (size_t i = 0; i < n.size(); ++i) { + double fit = result.coef * fitting_curve(n[i]); + rms += pow((time[i] - fit), 2); + } + + // Normalized RMS by the mean of the observed values + double mean = sigma_time / n.size(); + result.rms = sqrt(rms / n.size()) / mean; + + return result; +} + +// Find the coefficient for the high-order term in the running time, by +// minimizing the sum of squares of relative error. +// - n : Vector containing the size of the benchmark tests. +// - time : Vector containing the times for the benchmark tests. +// - complexity : If different than oAuto, the fitting curve will stick to +// this one. If it is oAuto, it will be calculated the best +// fitting curve. +LeastSq MinimalLeastSq(const std::vector& n, + const std::vector& time, const BigO complexity) { + CHECK_EQ(n.size(), time.size()); + CHECK_GE(n.size(), 2); // Do not compute fitting curve is less than two + // benchmark runs are given + CHECK_NE(complexity, oNone); + + LeastSq best_fit; + + if (complexity == oAuto) { + std::vector fit_curves = {oLogN, oN, oNLogN, oNSquared, oNCubed}; + + // Take o1 as default best fitting curve + best_fit = MinimalLeastSq(n, time, FittingCurve(o1)); + best_fit.complexity = o1; + + // Compute all possible fitting curves and stick to the best one + for (const auto& fit : fit_curves) { + LeastSq current_fit = MinimalLeastSq(n, time, FittingCurve(fit)); + if (current_fit.rms < best_fit.rms) { + best_fit = current_fit; + best_fit.complexity = fit; + } + } + } else { + best_fit = MinimalLeastSq(n, time, FittingCurve(complexity)); + best_fit.complexity = complexity; + } + + return best_fit; +} + +std::vector ComputeBigO( + const std::vector& reports) { + typedef BenchmarkReporter::Run Run; + std::vector results; + + if (reports.size() < 2) return results; + + // Accumulators. + std::vector n; + std::vector real_time; + std::vector cpu_time; + + // Populate the accumulators. + for (const Run& run : reports) { + CHECK_GT(run.complexity_n, 0) << "Did you forget to call SetComplexityN?"; + n.push_back(run.complexity_n); + real_time.push_back(run.real_accumulated_time / run.iterations); + cpu_time.push_back(run.cpu_accumulated_time / run.iterations); + } + + LeastSq result_cpu; + LeastSq result_real; + + if (reports[0].complexity == oLambda) { + result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity_lambda); + result_real = MinimalLeastSq(n, real_time, reports[0].complexity_lambda); + } else { + result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity); + result_real = MinimalLeastSq(n, real_time, result_cpu.complexity); + } + + // Drop the 'args' when reporting complexity. + auto run_name = reports[0].run_name; + run_name.args.clear(); + + // Get the data from the accumulator to BenchmarkReporter::Run's. + Run big_o; + big_o.run_name = run_name; + big_o.run_type = BenchmarkReporter::Run::RT_Aggregate; + big_o.repetitions = reports[0].repetitions; + big_o.repetition_index = Run::no_repetition_index; + big_o.threads = reports[0].threads; + big_o.aggregate_name = "BigO"; + big_o.report_label = reports[0].report_label; + big_o.iterations = 0; + big_o.real_accumulated_time = result_real.coef; + big_o.cpu_accumulated_time = result_cpu.coef; + big_o.report_big_o = true; + big_o.complexity = result_cpu.complexity; + + // All the time results are reported after being multiplied by the + // time unit multiplier. But since RMS is a relative quantity it + // should not be multiplied at all. So, here, we _divide_ it by the + // multiplier so that when it is multiplied later the result is the + // correct one. + double multiplier = GetTimeUnitMultiplier(reports[0].time_unit); + + // Only add label to mean/stddev if it is same for all runs + Run rms; + rms.run_name = run_name; + rms.run_type = BenchmarkReporter::Run::RT_Aggregate; + rms.aggregate_name = "RMS"; + rms.report_label = big_o.report_label; + rms.iterations = 0; + rms.repetition_index = Run::no_repetition_index; + rms.repetitions = reports[0].repetitions; + rms.threads = reports[0].threads; + rms.real_accumulated_time = result_real.rms / multiplier; + rms.cpu_accumulated_time = result_cpu.rms / multiplier; + rms.report_rms = true; + rms.complexity = result_cpu.complexity; + // don't forget to keep the time unit, or we won't be able to + // recover the correct value. + rms.time_unit = reports[0].time_unit; + + results.push_back(big_o); + results.push_back(rms); + return results; +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/complexity.h b/thirdparty/benchmark-1.5.0/src/complexity.h new file mode 100644 index 0000000000..df29b48d29 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/complexity.h @@ -0,0 +1,55 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Source project : https://github.com/ismaelJimenez/cpp.leastsq +// Adapted to be used with google benchmark + +#ifndef COMPLEXITY_H_ +#define COMPLEXITY_H_ + +#include +#include + +#include "benchmark/benchmark.h" + +namespace benchmark { + +// Return a vector containing the bigO and RMS information for the specified +// list of reports. If 'reports.size() < 2' an empty vector is returned. +std::vector ComputeBigO( + const std::vector& reports); + +// This data structure will contain the result returned by MinimalLeastSq +// - coef : Estimated coeficient for the high-order term as +// interpolated from data. +// - rms : Normalized Root Mean Squared Error. +// - complexity : Scalability form (e.g. oN, oNLogN). In case a scalability +// form has been provided to MinimalLeastSq this will return +// the same value. In case BigO::oAuto has been selected, this +// parameter will return the best fitting curve detected. + +struct LeastSq { + LeastSq() : coef(0.0), rms(0.0), complexity(oNone) {} + + double coef; + double rms; + BigO complexity; +}; + +// Function to return an string for the calculated complexity +std::string GetBigOString(BigO complexity); + +} // end namespace benchmark + +#endif // COMPLEXITY_H_ diff --git a/thirdparty/benchmark-1.5.0/src/console_reporter.cc b/thirdparty/benchmark-1.5.0/src/console_reporter.cc new file mode 100644 index 0000000000..cc8ae276f6 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/console_reporter.cc @@ -0,0 +1,179 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" +#include "complexity.h" +#include "counter.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "colorprint.h" +#include "commandlineflags.h" +#include "internal_macros.h" +#include "string_util.h" +#include "timers.h" + +namespace benchmark { + +bool ConsoleReporter::ReportContext(const Context& context) { + name_field_width_ = context.name_field_width; + printed_header_ = false; + prev_counters_.clear(); + + PrintBasicContext(&GetErrorStream(), context); + +#ifdef BENCHMARK_OS_WINDOWS + if ((output_options_ & OO_Color) && &std::cout != &GetOutputStream()) { + GetErrorStream() + << "Color printing is only supported for stdout on windows." + " Disabling color printing\n"; + output_options_ = static_cast< OutputOptions >(output_options_ & ~OO_Color); + } +#endif + + return true; +} + +void ConsoleReporter::PrintHeader(const Run& run) { + std::string str = FormatString("%-*s %13s %15s %12s", static_cast(name_field_width_), + "Benchmark", "Time", "CPU", "Iterations"); + if(!run.counters.empty()) { + if(output_options_ & OO_Tabular) { + for(auto const& c : run.counters) { + str += FormatString(" %10s", c.first.c_str()); + } + } else { + str += " UserCounters..."; + } + } + std::string line = std::string(str.length(), '-'); + GetOutputStream() << line << "\n" << str << "\n" << line << "\n"; +} + +void ConsoleReporter::ReportRuns(const std::vector& reports) { + for (const auto& run : reports) { + // print the header: + // --- if none was printed yet + bool print_header = !printed_header_; + // --- or if the format is tabular and this run + // has different fields from the prev header + print_header |= (output_options_ & OO_Tabular) && + (!internal::SameNames(run.counters, prev_counters_)); + if (print_header) { + printed_header_ = true; + prev_counters_ = run.counters; + PrintHeader(run); + } + // As an alternative to printing the headers like this, we could sort + // the benchmarks by header and then print. But this would require + // waiting for the full results before printing, or printing twice. + PrintRunData(run); + } +} + +static void IgnoreColorPrint(std::ostream& out, LogColor, const char* fmt, + ...) { + va_list args; + va_start(args, fmt); + out << FormatString(fmt, args); + va_end(args); +} + + +static std::string FormatTime(double time) { + // Align decimal places... + if (time < 1.0) { + return FormatString("%10.3f", time); + } + if (time < 10.0) { + return FormatString("%10.2f", time); + } + if (time < 100.0) { + return FormatString("%10.1f", time); + } + return FormatString("%10.0f", time); +} + +void ConsoleReporter::PrintRunData(const Run& result) { + typedef void(PrinterFn)(std::ostream&, LogColor, const char*, ...); + auto& Out = GetOutputStream(); + PrinterFn* printer = (output_options_ & OO_Color) ? + (PrinterFn*)ColorPrintf : IgnoreColorPrint; + auto name_color = + (result.report_big_o || result.report_rms) ? COLOR_BLUE : COLOR_GREEN; + printer(Out, name_color, "%-*s ", name_field_width_, + result.benchmark_name().c_str()); + + if (result.error_occurred) { + printer(Out, COLOR_RED, "ERROR OCCURRED: \'%s\'", + result.error_message.c_str()); + printer(Out, COLOR_DEFAULT, "\n"); + return; + } + + const double real_time = result.GetAdjustedRealTime(); + const double cpu_time = result.GetAdjustedCPUTime(); + const std::string real_time_str = FormatTime(real_time); + const std::string cpu_time_str = FormatTime(cpu_time); + + + if (result.report_big_o) { + std::string big_o = GetBigOString(result.complexity); + printer(Out, COLOR_YELLOW, "%10.2f %-4s %10.2f %-4s ", real_time, big_o.c_str(), + cpu_time, big_o.c_str()); + } else if (result.report_rms) { + printer(Out, COLOR_YELLOW, "%10.0f %-4s %10.0f %-4s ", real_time * 100, "%", + cpu_time * 100, "%"); + } else { + const char* timeLabel = GetTimeUnitString(result.time_unit); + printer(Out, COLOR_YELLOW, "%s %-4s %s %-4s ", real_time_str.c_str(), timeLabel, + cpu_time_str.c_str(), timeLabel); + } + + if (!result.report_big_o && !result.report_rms) { + printer(Out, COLOR_CYAN, "%10lld", result.iterations); + } + + for (auto& c : result.counters) { + const std::size_t cNameLen = std::max(std::string::size_type(10), + c.first.length()); + auto const& s = HumanReadableNumber(c.second.value, c.second.oneK); + if (output_options_ & OO_Tabular) { + if (c.second.flags & Counter::kIsRate) { + printer(Out, COLOR_DEFAULT, " %*s/s", cNameLen - 2, s.c_str()); + } else { + printer(Out, COLOR_DEFAULT, " %*s", cNameLen, s.c_str()); + } + } else { + const char* unit = (c.second.flags & Counter::kIsRate) ? "/s" : ""; + printer(Out, COLOR_DEFAULT, " %s=%s%s", c.first.c_str(), s.c_str(), + unit); + } + } + + if (!result.report_label.empty()) { + printer(Out, COLOR_DEFAULT, " %s", result.report_label.c_str()); + } + + printer(Out, COLOR_DEFAULT, "\n"); +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/counter.cc b/thirdparty/benchmark-1.5.0/src/counter.cc new file mode 100644 index 0000000000..c248ea110b --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/counter.cc @@ -0,0 +1,76 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "counter.h" + +namespace benchmark { +namespace internal { + +double Finish(Counter const& c, IterationCount iterations, double cpu_time, + double num_threads) { + double v = c.value; + if (c.flags & Counter::kIsRate) { + v /= cpu_time; + } + if (c.flags & Counter::kAvgThreads) { + v /= num_threads; + } + if (c.flags & Counter::kIsIterationInvariant) { + v *= iterations; + } + if (c.flags & Counter::kAvgIterations) { + v /= iterations; + } + return v; +} + +void Finish(UserCounters* l, IterationCount iterations, double cpu_time, + double num_threads) { + for (auto& c : *l) { + c.second.value = Finish(c.second, iterations, cpu_time, num_threads); + } +} + +void Increment(UserCounters* l, UserCounters const& r) { + // add counters present in both or just in *l + for (auto& c : *l) { + auto it = r.find(c.first); + if (it != r.end()) { + c.second.value = c.second + it->second; + } + } + // add counters present in r, but not in *l + for (auto const& tc : r) { + auto it = l->find(tc.first); + if (it == l->end()) { + (*l)[tc.first] = tc.second; + } + } +} + +bool SameNames(UserCounters const& l, UserCounters const& r) { + if (&l == &r) return true; + if (l.size() != r.size()) { + return false; + } + for (auto const& c : l) { + if (r.find(c.first) == r.end()) { + return false; + } + } + return true; +} + +} // end namespace internal +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/counter.h b/thirdparty/benchmark-1.5.0/src/counter.h new file mode 100644 index 0000000000..1ad46d4940 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/counter.h @@ -0,0 +1,27 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" + +namespace benchmark { + +// these counter-related functions are hidden to reduce API surface. +namespace internal { +void Finish(UserCounters* l, IterationCount iterations, double time, + double num_threads); +void Increment(UserCounters* l, UserCounters const& r); +bool SameNames(UserCounters const& l, UserCounters const& r); +} // end namespace internal + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/csv_reporter.cc b/thirdparty/benchmark-1.5.0/src/csv_reporter.cc new file mode 100644 index 0000000000..af2c18fc8a --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/csv_reporter.cc @@ -0,0 +1,154 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" +#include "complexity.h" + +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "string_util.h" +#include "timers.h" + +// File format reference: http://edoceo.com/utilitas/csv-file-format. + +namespace benchmark { + +namespace { +std::vector elements = { + "name", "iterations", "real_time", "cpu_time", + "time_unit", "bytes_per_second", "items_per_second", "label", + "error_occurred", "error_message"}; +} // namespace + +std::string CsvEscape(const std::string & s) { + std::string tmp; + tmp.reserve(s.size() + 2); + for (char c : s) { + switch (c) { + case '"' : tmp += "\"\""; break; + default : tmp += c; break; + } + } + return '"' + tmp + '"'; +} + +bool CSVReporter::ReportContext(const Context& context) { + PrintBasicContext(&GetErrorStream(), context); + return true; +} + +void CSVReporter::ReportRuns(const std::vector& reports) { + std::ostream& Out = GetOutputStream(); + + if (!printed_header_) { + // save the names of all the user counters + for (const auto& run : reports) { + for (const auto& cnt : run.counters) { + if (cnt.first == "bytes_per_second" || cnt.first == "items_per_second") + continue; + user_counter_names_.insert(cnt.first); + } + } + + // print the header + for (auto B = elements.begin(); B != elements.end();) { + Out << *B++; + if (B != elements.end()) Out << ","; + } + for (auto B = user_counter_names_.begin(); + B != user_counter_names_.end();) { + Out << ",\"" << *B++ << "\""; + } + Out << "\n"; + + printed_header_ = true; + } else { + // check that all the current counters are saved in the name set + for (const auto& run : reports) { + for (const auto& cnt : run.counters) { + if (cnt.first == "bytes_per_second" || cnt.first == "items_per_second") + continue; + CHECK(user_counter_names_.find(cnt.first) != user_counter_names_.end()) + << "All counters must be present in each run. " + << "Counter named \"" << cnt.first + << "\" was not in a run after being added to the header"; + } + } + } + + // print results for each run + for (const auto& run : reports) { + PrintRunData(run); + } +} + +void CSVReporter::PrintRunData(const Run& run) { + std::ostream& Out = GetOutputStream(); + Out << CsvEscape(run.benchmark_name()) << ","; + if (run.error_occurred) { + Out << std::string(elements.size() - 3, ','); + Out << "true,"; + Out << CsvEscape(run.error_message) << "\n"; + return; + } + + // Do not print iteration on bigO and RMS report + if (!run.report_big_o && !run.report_rms) { + Out << run.iterations; + } + Out << ","; + + Out << run.GetAdjustedRealTime() << ","; + Out << run.GetAdjustedCPUTime() << ","; + + // Do not print timeLabel on bigO and RMS report + if (run.report_big_o) { + Out << GetBigOString(run.complexity); + } else if (!run.report_rms) { + Out << GetTimeUnitString(run.time_unit); + } + Out << ","; + + if (run.counters.find("bytes_per_second") != run.counters.end()) { + Out << run.counters.at("bytes_per_second"); + } + Out << ","; + if (run.counters.find("items_per_second") != run.counters.end()) { + Out << run.counters.at("items_per_second"); + } + Out << ","; + if (!run.report_label.empty()) { + Out << CsvEscape(run.report_label); + } + Out << ",,"; // for error_occurred and error_message + + // Print user counters + for (const auto& ucn : user_counter_names_) { + auto it = run.counters.find(ucn); + if (it == run.counters.end()) { + Out << ","; + } else { + Out << "," << it->second; + } + } + Out << '\n'; +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/cycleclock.h b/thirdparty/benchmark-1.5.0/src/cycleclock.h new file mode 100644 index 0000000000..f5e37b011b --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/cycleclock.h @@ -0,0 +1,177 @@ +// ---------------------------------------------------------------------- +// CycleClock +// A CycleClock tells you the current time in Cycles. The "time" +// is actually time since power-on. This is like time() but doesn't +// involve a system call and is much more precise. +// +// NOTE: Not all cpu/platform/kernel combinations guarantee that this +// clock increments at a constant rate or is synchronized across all logical +// cpus in a system. +// +// If you need the above guarantees, please consider using a different +// API. There are efforts to provide an interface which provides a millisecond +// granularity and implemented as a memory read. A memory read is generally +// cheaper than the CycleClock for many architectures. +// +// Also, in some out of order CPU implementations, the CycleClock is not +// serializing. So if you're trying to count at cycles granularity, your +// data might be inaccurate due to out of order instruction execution. +// ---------------------------------------------------------------------- + +#ifndef BENCHMARK_CYCLECLOCK_H_ +#define BENCHMARK_CYCLECLOCK_H_ + +#include + +#include "benchmark/benchmark.h" +#include "internal_macros.h" + +#if defined(BENCHMARK_OS_MACOSX) +#include +#endif +// For MSVC, we want to use '_asm rdtsc' when possible (since it works +// with even ancient MSVC compilers), and when not possible the +// __rdtsc intrinsic, declared in . Unfortunately, in some +// environments, and have conflicting +// declarations of some other intrinsics, breaking compilation. +// Therefore, we simply declare __rdtsc ourselves. See also +// http://connect.microsoft.com/VisualStudio/feedback/details/262047 +#if defined(COMPILER_MSVC) && !defined(_M_IX86) +extern "C" uint64_t __rdtsc(); +#pragma intrinsic(__rdtsc) +#endif + +#if !defined(BENCHMARK_OS_WINDOWS) || defined(BENCHMARK_OS_MINGW) +#include +#include +#endif + +#ifdef BENCHMARK_OS_EMSCRIPTEN +#include +#endif + +namespace benchmark { +// NOTE: only i386 and x86_64 have been well tested. +// PPC, sparc, alpha, and ia64 are based on +// http://peter.kuscsik.com/wordpress/?p=14 +// with modifications by m3b. See also +// https://setisvn.ssl.berkeley.edu/svn/lib/fftw-3.0.1/kernel/cycle.h +namespace cycleclock { +// This should return the number of cycles since power-on. Thread-safe. +inline BENCHMARK_ALWAYS_INLINE int64_t Now() { +#if defined(BENCHMARK_OS_MACOSX) + // this goes at the top because we need ALL Macs, regardless of + // architecture, to return the number of "mach time units" that + // have passed since startup. See sysinfo.cc where + // InitializeSystemInfo() sets the supposed cpu clock frequency of + // macs to the number of mach time units per second, not actual + // CPU clock frequency (which can change in the face of CPU + // frequency scaling). Also note that when the Mac sleeps, this + // counter pauses; it does not continue counting, nor does it + // reset to zero. + return mach_absolute_time(); +#elif defined(BENCHMARK_OS_EMSCRIPTEN) + // this goes above x86-specific code because old versions of Emscripten + // define __x86_64__, although they have nothing to do with it. + return static_cast(emscripten_get_now() * 1e+6); +#elif defined(__i386__) + int64_t ret; + __asm__ volatile("rdtsc" : "=A"(ret)); + return ret; +#elif defined(__x86_64__) || defined(__amd64__) + uint64_t low, high; + __asm__ volatile("rdtsc" : "=a"(low), "=d"(high)); + return (high << 32) | low; +#elif defined(__powerpc__) || defined(__ppc__) + // This returns a time-base, which is not always precisely a cycle-count. + int64_t tbl, tbu0, tbu1; + asm("mftbu %0" : "=r"(tbu0)); + asm("mftb %0" : "=r"(tbl)); + asm("mftbu %0" : "=r"(tbu1)); + tbl &= -static_cast(tbu0 == tbu1); + // high 32 bits in tbu1; low 32 bits in tbl (tbu0 is garbage) + return (tbu1 << 32) | tbl; +#elif defined(__sparc__) + int64_t tick; + asm(".byte 0x83, 0x41, 0x00, 0x00"); + asm("mov %%g1, %0" : "=r"(tick)); + return tick; +#elif defined(__ia64__) + int64_t itc; + asm("mov %0 = ar.itc" : "=r"(itc)); + return itc; +#elif defined(COMPILER_MSVC) && defined(_M_IX86) + // Older MSVC compilers (like 7.x) don't seem to support the + // __rdtsc intrinsic properly, so I prefer to use _asm instead + // when I know it will work. Otherwise, I'll use __rdtsc and hope + // the code is being compiled with a non-ancient compiler. + _asm rdtsc +#elif defined(COMPILER_MSVC) + return __rdtsc(); +#elif defined(BENCHMARK_OS_NACL) + // Native Client validator on x86/x86-64 allows RDTSC instructions, + // and this case is handled above. Native Client validator on ARM + // rejects MRC instructions (used in the ARM-specific sequence below), + // so we handle it here. Portable Native Client compiles to + // architecture-agnostic bytecode, which doesn't provide any + // cycle counter access mnemonics. + + // Native Client does not provide any API to access cycle counter. + // Use clock_gettime(CLOCK_MONOTONIC, ...) instead of gettimeofday + // because is provides nanosecond resolution (which is noticable at + // least for PNaCl modules running on x86 Mac & Linux). + // Initialize to always return 0 if clock_gettime fails. + struct timespec ts = {0, 0}; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) * 1000000000 + ts.tv_nsec; +#elif defined(__aarch64__) + // System timer of ARMv8 runs at a different frequency than the CPU's. + // The frequency is fixed, typically in the range 1-50MHz. It can be + // read at CNTFRQ special register. We assume the OS has set up + // the virtual timer properly. + int64_t virtual_timer_value; + asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); + return virtual_timer_value; +#elif defined(__ARM_ARCH) + // V6 is the earliest arch that has a standard cyclecount + // Native Client validator doesn't allow MRC instructions. +#if (__ARM_ARCH >= 6) + uint32_t pmccntr; + uint32_t pmuseren; + uint32_t pmcntenset; + // Read the user mode perf monitor counter access permissions. + asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r"(pmuseren)); + if (pmuseren & 1) { // Allows reading perfmon counters for user mode code. + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcntenset)); + if (pmcntenset & 0x80000000ul) { // Is it counting? + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(pmccntr)); + // The counter is set up to count every 64th cycle + return static_cast(pmccntr) * 64; // Should optimize to << 6 + } + } +#endif + struct timeval tv; + gettimeofday(&tv, nullptr); + return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; +#elif defined(__mips__) + // mips apparently only allows rdtsc for superusers, so we fall + // back to gettimeofday. It's possible clock_gettime would be better. + struct timeval tv; + gettimeofday(&tv, nullptr); + return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; +#elif defined(__s390__) // Covers both s390 and s390x. + // Return the CPU clock. + uint64_t tsc; + asm("stck %0" : "=Q"(tsc) : : "cc"); + return tsc; +#else +// The soft failover to a generic implementation is automatic only for ARM. +// For other platforms the developer is expected to make an attempt to create +// a fast implementation and use generic version if nothing better is available. +#error You need to define CycleTimer for your OS and CPU +#endif +} +} // end namespace cycleclock +} // end namespace benchmark + +#endif // BENCHMARK_CYCLECLOCK_H_ diff --git a/thirdparty/benchmark-1.5.0/src/internal_macros.h b/thirdparty/benchmark-1.5.0/src/internal_macros.h new file mode 100644 index 0000000000..6adf00d056 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/internal_macros.h @@ -0,0 +1,94 @@ +#ifndef BENCHMARK_INTERNAL_MACROS_H_ +#define BENCHMARK_INTERNAL_MACROS_H_ + +#include "benchmark/benchmark.h" + +/* Needed to detect STL */ +#include + +// clang-format off + +#ifndef __has_feature +#define __has_feature(x) 0 +#endif + +#if defined(__clang__) + #if !defined(COMPILER_CLANG) + #define COMPILER_CLANG + #endif +#elif defined(_MSC_VER) + #if !defined(COMPILER_MSVC) + #define COMPILER_MSVC + #endif +#elif defined(__GNUC__) + #if !defined(COMPILER_GCC) + #define COMPILER_GCC + #endif +#endif + +#if __has_feature(cxx_attributes) + #define BENCHMARK_NORETURN [[noreturn]] +#elif defined(__GNUC__) + #define BENCHMARK_NORETURN __attribute__((noreturn)) +#elif defined(COMPILER_MSVC) + #define BENCHMARK_NORETURN __declspec(noreturn) +#else + #define BENCHMARK_NORETURN +#endif + +#if defined(__CYGWIN__) + #define BENCHMARK_OS_CYGWIN 1 +#elif defined(_WIN32) + #define BENCHMARK_OS_WINDOWS 1 + #if defined(__MINGW32__) + #define BENCHMARK_OS_MINGW 1 + #endif +#elif defined(__APPLE__) + #define BENCHMARK_OS_APPLE 1 + #include "TargetConditionals.h" + #if defined(TARGET_OS_MAC) + #define BENCHMARK_OS_MACOSX 1 + #if defined(TARGET_OS_IPHONE) + #define BENCHMARK_OS_IOS 1 + #endif + #endif +#elif defined(__FreeBSD__) + #define BENCHMARK_OS_FREEBSD 1 +#elif defined(__NetBSD__) + #define BENCHMARK_OS_NETBSD 1 +#elif defined(__OpenBSD__) + #define BENCHMARK_OS_OPENBSD 1 +#elif defined(__linux__) + #define BENCHMARK_OS_LINUX 1 +#elif defined(__native_client__) + #define BENCHMARK_OS_NACL 1 +#elif defined(__EMSCRIPTEN__) + #define BENCHMARK_OS_EMSCRIPTEN 1 +#elif defined(__rtems__) + #define BENCHMARK_OS_RTEMS 1 +#elif defined(__Fuchsia__) +#define BENCHMARK_OS_FUCHSIA 1 +#elif defined (__SVR4) && defined (__sun) +#define BENCHMARK_OS_SOLARIS 1 +#elif defined(__QNX__) +#define BENCHMARK_OS_QNX 1 +#endif + +#if defined(__ANDROID__) && defined(__GLIBCXX__) +#define BENCHMARK_STL_ANDROID_GNUSTL 1 +#endif + +#if !__has_feature(cxx_exceptions) && !defined(__cpp_exceptions) \ + && !defined(__EXCEPTIONS) + #define BENCHMARK_HAS_NO_EXCEPTIONS +#endif + +#if defined(COMPILER_CLANG) || defined(COMPILER_GCC) + #define BENCHMARK_MAYBE_UNUSED __attribute__((unused)) +#else + #define BENCHMARK_MAYBE_UNUSED +#endif + +// clang-format on + +#endif // BENCHMARK_INTERNAL_MACROS_H_ diff --git a/thirdparty/benchmark-1.5.0/src/json_reporter.cc b/thirdparty/benchmark-1.5.0/src/json_reporter.cc new file mode 100644 index 0000000000..11db2b99d5 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/json_reporter.cc @@ -0,0 +1,253 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" +#include "complexity.h" + +#include +#include +#include +#include // for setprecision +#include +#include +#include +#include +#include + +#include "string_util.h" +#include "timers.h" + +namespace benchmark { + +namespace { + +std::string StrEscape(const std::string & s) { + std::string tmp; + tmp.reserve(s.size()); + for (char c : s) { + switch (c) { + case '\b': tmp += "\\b"; break; + case '\f': tmp += "\\f"; break; + case '\n': tmp += "\\n"; break; + case '\r': tmp += "\\r"; break; + case '\t': tmp += "\\t"; break; + case '\\': tmp += "\\\\"; break; + case '"' : tmp += "\\\""; break; + default : tmp += c; break; + } + } + return tmp; +} + +std::string FormatKV(std::string const& key, std::string const& value) { + return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str()); +} + +std::string FormatKV(std::string const& key, const char* value) { + return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str()); +} + +std::string FormatKV(std::string const& key, bool value) { + return StrFormat("\"%s\": %s", StrEscape(key).c_str(), value ? "true" : "false"); +} + +std::string FormatKV(std::string const& key, int64_t value) { + std::stringstream ss; + ss << '"' << StrEscape(key) << "\": " << value; + return ss.str(); +} + +std::string FormatKV(std::string const& key, IterationCount value) { + std::stringstream ss; + ss << '"' << StrEscape(key) << "\": " << value; + return ss.str(); +} + +std::string FormatKV(std::string const& key, double value) { + std::stringstream ss; + ss << '"' << StrEscape(key) << "\": "; + + if (std::isnan(value)) + ss << (value < 0 ? "-" : "") << "NaN"; + else if (std::isinf(value)) + ss << (value < 0 ? "-" : "") << "Infinity"; + else { + const auto max_digits10 = + std::numeric_limits::max_digits10; + const auto max_fractional_digits10 = max_digits10 - 1; + ss << std::scientific << std::setprecision(max_fractional_digits10) + << value; + } + return ss.str(); +} + +int64_t RoundDouble(double v) { return static_cast(v + 0.5); } + +} // end namespace + +bool JSONReporter::ReportContext(const Context& context) { + std::ostream& out = GetOutputStream(); + + out << "{\n"; + std::string inner_indent(2, ' '); + + // Open context block and print context information. + out << inner_indent << "\"context\": {\n"; + std::string indent(4, ' '); + + std::string walltime_value = LocalDateTimeString(); + out << indent << FormatKV("date", walltime_value) << ",\n"; + + out << indent << FormatKV("host_name", context.sys_info.name) << ",\n"; + + if (Context::executable_name) { + out << indent << FormatKV("executable", Context::executable_name) << ",\n"; + } + + CPUInfo const& info = context.cpu_info; + out << indent << FormatKV("num_cpus", static_cast(info.num_cpus)) + << ",\n"; + out << indent + << FormatKV("mhz_per_cpu", + RoundDouble(info.cycles_per_second / 1000000.0)) + << ",\n"; + out << indent << FormatKV("cpu_scaling_enabled", info.scaling_enabled) + << ",\n"; + + out << indent << "\"caches\": [\n"; + indent = std::string(6, ' '); + std::string cache_indent(8, ' '); + for (size_t i = 0; i < info.caches.size(); ++i) { + auto& CI = info.caches[i]; + out << indent << "{\n"; + out << cache_indent << FormatKV("type", CI.type) << ",\n"; + out << cache_indent << FormatKV("level", static_cast(CI.level)) + << ",\n"; + out << cache_indent + << FormatKV("size", static_cast(CI.size) * 1000u) << ",\n"; + out << cache_indent + << FormatKV("num_sharing", static_cast(CI.num_sharing)) + << "\n"; + out << indent << "}"; + if (i != info.caches.size() - 1) out << ","; + out << "\n"; + } + indent = std::string(4, ' '); + out << indent << "],\n"; + out << indent << "\"load_avg\": ["; + for (auto it = info.load_avg.begin(); it != info.load_avg.end();) { + out << *it++; + if (it != info.load_avg.end()) out << ","; + } + out << "],\n"; + +#if defined(NDEBUG) + const char build_type[] = "release"; +#else + const char build_type[] = "debug"; +#endif + out << indent << FormatKV("library_build_type", build_type) << "\n"; + // Close context block and open the list of benchmarks. + out << inner_indent << "},\n"; + out << inner_indent << "\"benchmarks\": [\n"; + return true; +} + +void JSONReporter::ReportRuns(std::vector const& reports) { + if (reports.empty()) { + return; + } + std::string indent(4, ' '); + std::ostream& out = GetOutputStream(); + if (!first_report_) { + out << ",\n"; + } + first_report_ = false; + + for (auto it = reports.begin(); it != reports.end(); ++it) { + out << indent << "{\n"; + PrintRunData(*it); + out << indent << '}'; + auto it_cp = it; + if (++it_cp != reports.end()) { + out << ",\n"; + } + } +} + +void JSONReporter::Finalize() { + // Close the list of benchmarks and the top level object. + GetOutputStream() << "\n ]\n}\n"; +} + +void JSONReporter::PrintRunData(Run const& run) { + std::string indent(6, ' '); + std::ostream& out = GetOutputStream(); + out << indent << FormatKV("name", run.benchmark_name()) << ",\n"; + out << indent << FormatKV("run_name", run.run_name.str()) << ",\n"; + out << indent << FormatKV("run_type", [&run]() -> const char* { + switch (run.run_type) { + case BenchmarkReporter::Run::RT_Iteration: + return "iteration"; + case BenchmarkReporter::Run::RT_Aggregate: + return "aggregate"; + } + BENCHMARK_UNREACHABLE(); + }()) << ",\n"; + out << indent << FormatKV("repetitions", run.repetitions) << ",\n"; + if (run.run_type != BenchmarkReporter::Run::RT_Aggregate) { + out << indent << FormatKV("repetition_index", run.repetition_index) + << ",\n"; + } + out << indent << FormatKV("threads", run.threads) << ",\n"; + if (run.run_type == BenchmarkReporter::Run::RT_Aggregate) { + out << indent << FormatKV("aggregate_name", run.aggregate_name) << ",\n"; + } + if (run.error_occurred) { + out << indent << FormatKV("error_occurred", run.error_occurred) << ",\n"; + out << indent << FormatKV("error_message", run.error_message) << ",\n"; + } + if (!run.report_big_o && !run.report_rms) { + out << indent << FormatKV("iterations", run.iterations) << ",\n"; + out << indent << FormatKV("real_time", run.GetAdjustedRealTime()) << ",\n"; + out << indent << FormatKV("cpu_time", run.GetAdjustedCPUTime()); + out << ",\n" + << indent << FormatKV("time_unit", GetTimeUnitString(run.time_unit)); + } else if (run.report_big_o) { + out << indent << FormatKV("cpu_coefficient", run.GetAdjustedCPUTime()) + << ",\n"; + out << indent << FormatKV("real_coefficient", run.GetAdjustedRealTime()) + << ",\n"; + out << indent << FormatKV("big_o", GetBigOString(run.complexity)) << ",\n"; + out << indent << FormatKV("time_unit", GetTimeUnitString(run.time_unit)); + } else if (run.report_rms) { + out << indent << FormatKV("rms", run.GetAdjustedCPUTime()); + } + + for (auto& c : run.counters) { + out << ",\n" << indent << FormatKV(c.first, c.second); + } + + if (run.has_memory_result) { + out << ",\n" << indent << FormatKV("allocs_per_iter", run.allocs_per_iter); + out << ",\n" << indent << FormatKV("max_bytes_used", run.max_bytes_used); + } + + if (!run.report_label.empty()) { + out << ",\n" << indent << FormatKV("label", run.report_label); + } + out << '\n'; +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/log.h b/thirdparty/benchmark-1.5.0/src/log.h new file mode 100644 index 0000000000..47d0c35c01 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/log.h @@ -0,0 +1,74 @@ +#ifndef BENCHMARK_LOG_H_ +#define BENCHMARK_LOG_H_ + +#include +#include + +#include "benchmark/benchmark.h" + +namespace benchmark { +namespace internal { + +typedef std::basic_ostream&(EndLType)(std::basic_ostream&); + +class LogType { + friend LogType& GetNullLogInstance(); + friend LogType& GetErrorLogInstance(); + + // FIXME: Add locking to output. + template + friend LogType& operator<<(LogType&, Tp const&); + friend LogType& operator<<(LogType&, EndLType*); + + private: + LogType(std::ostream* out) : out_(out) {} + std::ostream* out_; + BENCHMARK_DISALLOW_COPY_AND_ASSIGN(LogType); +}; + +template +LogType& operator<<(LogType& log, Tp const& value) { + if (log.out_) { + *log.out_ << value; + } + return log; +} + +inline LogType& operator<<(LogType& log, EndLType* m) { + if (log.out_) { + *log.out_ << m; + } + return log; +} + +inline int& LogLevel() { + static int log_level = 0; + return log_level; +} + +inline LogType& GetNullLogInstance() { + static LogType log(nullptr); + return log; +} + +inline LogType& GetErrorLogInstance() { + static LogType log(&std::clog); + return log; +} + +inline LogType& GetLogInstanceForLevel(int level) { + if (level <= LogLevel()) { + return GetErrorLogInstance(); + } + return GetNullLogInstance(); +} + +} // end namespace internal +} // end namespace benchmark + +// clang-format off +#define VLOG(x) \ + (::benchmark::internal::GetLogInstanceForLevel(x) << "-- LOG(" << x << "):" \ + " ") +// clang-format on +#endif diff --git a/thirdparty/benchmark-1.5.0/src/mutex.h b/thirdparty/benchmark-1.5.0/src/mutex.h new file mode 100644 index 0000000000..5f461d05a0 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/mutex.h @@ -0,0 +1,155 @@ +#ifndef BENCHMARK_MUTEX_H_ +#define BENCHMARK_MUTEX_H_ + +#include +#include + +#include "check.h" + +// Enable thread safety attributes only with clang. +// The attributes can be safely erased when compiling with other compilers. +#if defined(HAVE_THREAD_SAFETY_ATTRIBUTES) +#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define SCOPED_CAPABILITY THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define ACQUIRED_BEFORE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) + +#define ACQUIRED_AFTER(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) + +#define REQUIRES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define REQUIRES_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) + +#define ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) + +#define ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) + +#define RELEASE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) + +#define RELEASE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) + +#define EXCLUDES(...) THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +#define ASSERT_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define ASSERT_SHARED_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define RETURN_CAPABILITY(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define NO_THREAD_SAFETY_ANALYSIS \ + THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) + +namespace benchmark { + +typedef std::condition_variable Condition; + +// NOTE: Wrappers for std::mutex and std::unique_lock are provided so that +// we can annotate them with thread safety attributes and use the +// -Wthread-safety warning with clang. The standard library types cannot be +// used directly because they do not provided the required annotations. +class CAPABILITY("mutex") Mutex { + public: + Mutex() {} + + void lock() ACQUIRE() { mut_.lock(); } + void unlock() RELEASE() { mut_.unlock(); } + std::mutex& native_handle() { return mut_; } + + private: + std::mutex mut_; +}; + +class SCOPED_CAPABILITY MutexLock { + typedef std::unique_lock MutexLockImp; + + public: + MutexLock(Mutex& m) ACQUIRE(m) : ml_(m.native_handle()) {} + ~MutexLock() RELEASE() {} + MutexLockImp& native_handle() { return ml_; } + + private: + MutexLockImp ml_; +}; + +class Barrier { + public: + Barrier(int num_threads) : running_threads_(num_threads) {} + + // Called by each thread + bool wait() EXCLUDES(lock_) { + bool last_thread = false; + { + MutexLock ml(lock_); + last_thread = createBarrier(ml); + } + if (last_thread) phase_condition_.notify_all(); + return last_thread; + } + + void removeThread() EXCLUDES(lock_) { + MutexLock ml(lock_); + --running_threads_; + if (entered_ != 0) phase_condition_.notify_all(); + } + + private: + Mutex lock_; + Condition phase_condition_; + int running_threads_; + + // State for barrier management + int phase_number_ = 0; + int entered_ = 0; // Number of threads that have entered this barrier + + // Enter the barrier and wait until all other threads have also + // entered the barrier. Returns iff this is the last thread to + // enter the barrier. + bool createBarrier(MutexLock& ml) REQUIRES(lock_) { + CHECK_LT(entered_, running_threads_); + entered_++; + if (entered_ < running_threads_) { + // Wait for all threads to enter + int phase_number_cp = phase_number_; + auto cb = [this, phase_number_cp]() { + return this->phase_number_ > phase_number_cp || + entered_ == running_threads_; // A thread has aborted in error + }; + phase_condition_.wait(ml.native_handle(), cb); + if (phase_number_ > phase_number_cp) return false; + // else (running_threads_ == entered_) and we are the last thread. + } + // Last thread has reached the barrier + phase_number_++; + entered_ = 0; + return true; + } +}; + +} // end namespace benchmark + +#endif // BENCHMARK_MUTEX_H_ diff --git a/thirdparty/benchmark-1.5.0/src/re.h b/thirdparty/benchmark-1.5.0/src/re.h new file mode 100644 index 0000000000..fbe25037b4 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/re.h @@ -0,0 +1,158 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BENCHMARK_RE_H_ +#define BENCHMARK_RE_H_ + +#include "internal_macros.h" + +// clang-format off + +#if !defined(HAVE_STD_REGEX) && \ + !defined(HAVE_GNU_POSIX_REGEX) && \ + !defined(HAVE_POSIX_REGEX) + // No explicit regex selection; detect based on builtin hints. + #if defined(BENCHMARK_OS_LINUX) || defined(BENCHMARK_OS_APPLE) + #define HAVE_POSIX_REGEX 1 + #elif __cplusplus >= 199711L + #define HAVE_STD_REGEX 1 + #endif +#endif + +// Prefer C regex libraries when compiling w/o exceptions so that we can +// correctly report errors. +#if defined(BENCHMARK_HAS_NO_EXCEPTIONS) && \ + defined(BENCHMARK_HAVE_STD_REGEX) && \ + (defined(HAVE_GNU_POSIX_REGEX) || defined(HAVE_POSIX_REGEX)) + #undef HAVE_STD_REGEX +#endif + +#if defined(HAVE_STD_REGEX) + #include +#elif defined(HAVE_GNU_POSIX_REGEX) + #include +#elif defined(HAVE_POSIX_REGEX) + #include +#else +#error No regular expression backend was found! +#endif + +// clang-format on + +#include + +#include "check.h" + +namespace benchmark { + +// A wrapper around the POSIX regular expression API that provides automatic +// cleanup +class Regex { + public: + Regex() : init_(false) {} + + ~Regex(); + + // Compile a regular expression matcher from spec. Returns true on success. + // + // On failure (and if error is not nullptr), error is populated with a human + // readable error message if an error occurs. + bool Init(const std::string& spec, std::string* error); + + // Returns whether str matches the compiled regular expression. + bool Match(const std::string& str); + + private: + bool init_; +// Underlying regular expression object +#if defined(HAVE_STD_REGEX) + std::regex re_; +#elif defined(HAVE_POSIX_REGEX) || defined(HAVE_GNU_POSIX_REGEX) + regex_t re_; +#else +#error No regular expression backend implementation available +#endif +}; + +#if defined(HAVE_STD_REGEX) + +inline bool Regex::Init(const std::string& spec, std::string* error) { +#ifdef BENCHMARK_HAS_NO_EXCEPTIONS + ((void)error); // suppress unused warning +#else + try { +#endif + re_ = std::regex(spec, std::regex_constants::extended); + init_ = true; +#ifndef BENCHMARK_HAS_NO_EXCEPTIONS +} +catch (const std::regex_error& e) { + if (error) { + *error = e.what(); + } +} +#endif +return init_; +} + +inline Regex::~Regex() {} + +inline bool Regex::Match(const std::string& str) { + if (!init_) { + return false; + } + return std::regex_search(str, re_); +} + +#else +inline bool Regex::Init(const std::string& spec, std::string* error) { + int ec = regcomp(&re_, spec.c_str(), REG_EXTENDED | REG_NOSUB); + if (ec != 0) { + if (error) { + size_t needed = regerror(ec, &re_, nullptr, 0); + char* errbuf = new char[needed]; + regerror(ec, &re_, errbuf, needed); + + // regerror returns the number of bytes necessary to null terminate + // the string, so we move that when assigning to error. + CHECK_NE(needed, 0); + error->assign(errbuf, needed - 1); + + delete[] errbuf; + } + + return false; + } + + init_ = true; + return true; +} + +inline Regex::~Regex() { + if (init_) { + regfree(&re_); + } +} + +inline bool Regex::Match(const std::string& str) { + if (!init_) { + return false; + } + return regexec(&re_, str.c_str(), 0, nullptr, 0) == 0; +} +#endif + +} // end namespace benchmark + +#endif // BENCHMARK_RE_H_ diff --git a/thirdparty/benchmark-1.5.0/src/reporter.cc b/thirdparty/benchmark-1.5.0/src/reporter.cc new file mode 100644 index 0000000000..4d3e477d44 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/reporter.cc @@ -0,0 +1,105 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" +#include "timers.h" + +#include + +#include +#include +#include + +#include "check.h" +#include "string_util.h" + +namespace benchmark { + +BenchmarkReporter::BenchmarkReporter() + : output_stream_(&std::cout), error_stream_(&std::cerr) {} + +BenchmarkReporter::~BenchmarkReporter() {} + +void BenchmarkReporter::PrintBasicContext(std::ostream *out, + Context const &context) { + CHECK(out) << "cannot be null"; + auto &Out = *out; + + Out << LocalDateTimeString() << "\n"; + + if (context.executable_name) + Out << "Running " << context.executable_name << "\n"; + + const CPUInfo &info = context.cpu_info; + Out << "Run on (" << info.num_cpus << " X " + << (info.cycles_per_second / 1000000.0) << " MHz CPU " + << ((info.num_cpus > 1) ? "s" : "") << ")\n"; + if (info.caches.size() != 0) { + Out << "CPU Caches:\n"; + for (auto &CInfo : info.caches) { + Out << " L" << CInfo.level << " " << CInfo.type << " " + << (CInfo.size / 1000) << "K"; + if (CInfo.num_sharing != 0) + Out << " (x" << (info.num_cpus / CInfo.num_sharing) << ")"; + Out << "\n"; + } + } + if (!info.load_avg.empty()) { + Out << "Load Average: "; + for (auto It = info.load_avg.begin(); It != info.load_avg.end();) { + Out << StrFormat("%.2f", *It++); + if (It != info.load_avg.end()) Out << ", "; + } + Out << "\n"; + } + + if (info.scaling_enabled) { + Out << "***WARNING*** CPU scaling is enabled, the benchmark " + "real time measurements may be noisy and will incur extra " + "overhead.\n"; + } + +#ifndef NDEBUG + Out << "***WARNING*** Library was built as DEBUG. Timings may be " + "affected.\n"; +#endif +} + +// No initializer because it's already initialized to NULL. +const char *BenchmarkReporter::Context::executable_name; + +BenchmarkReporter::Context::Context() + : cpu_info(CPUInfo::Get()), sys_info(SystemInfo::Get()) {} + +std::string BenchmarkReporter::Run::benchmark_name() const { + std::string name = run_name.str(); + if (run_type == RT_Aggregate) { + name += "_" + aggregate_name; + } + return name; +} + +double BenchmarkReporter::Run::GetAdjustedRealTime() const { + double new_time = real_accumulated_time * GetTimeUnitMultiplier(time_unit); + if (iterations != 0) new_time /= static_cast(iterations); + return new_time; +} + +double BenchmarkReporter::Run::GetAdjustedCPUTime() const { + double new_time = cpu_accumulated_time * GetTimeUnitMultiplier(time_unit); + if (iterations != 0) new_time /= static_cast(iterations); + return new_time; +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/sleep.cc b/thirdparty/benchmark-1.5.0/src/sleep.cc new file mode 100644 index 0000000000..1512ac90f7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/sleep.cc @@ -0,0 +1,51 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sleep.h" + +#include +#include +#include + +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#endif + +namespace benchmark { +#ifdef BENCHMARK_OS_WINDOWS +// Window's Sleep takes milliseconds argument. +void SleepForMilliseconds(int milliseconds) { Sleep(milliseconds); } +void SleepForSeconds(double seconds) { + SleepForMilliseconds(static_cast(kNumMillisPerSecond * seconds)); +} +#else // BENCHMARK_OS_WINDOWS +void SleepForMicroseconds(int microseconds) { + struct timespec sleep_time; + sleep_time.tv_sec = microseconds / kNumMicrosPerSecond; + sleep_time.tv_nsec = (microseconds % kNumMicrosPerSecond) * kNumNanosPerMicro; + while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) + ; // Ignore signals and wait for the full interval to elapse. +} + +void SleepForMilliseconds(int milliseconds) { + SleepForMicroseconds(milliseconds * kNumMicrosPerMilli); +} + +void SleepForSeconds(double seconds) { + SleepForMicroseconds(static_cast(seconds * kNumMicrosPerSecond)); +} +#endif // BENCHMARK_OS_WINDOWS +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/sleep.h b/thirdparty/benchmark-1.5.0/src/sleep.h new file mode 100644 index 0000000000..f98551afe2 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/sleep.h @@ -0,0 +1,15 @@ +#ifndef BENCHMARK_SLEEP_H_ +#define BENCHMARK_SLEEP_H_ + +namespace benchmark { +const int kNumMillisPerSecond = 1000; +const int kNumMicrosPerMilli = 1000; +const int kNumMicrosPerSecond = kNumMillisPerSecond * 1000; +const int kNumNanosPerMicro = 1000; +const int kNumNanosPerSecond = kNumNanosPerMicro * kNumMicrosPerSecond; + +void SleepForMilliseconds(int milliseconds); +void SleepForSeconds(double seconds); +} // end namespace benchmark + +#endif // BENCHMARK_SLEEP_H_ diff --git a/thirdparty/benchmark-1.5.0/src/statistics.cc b/thirdparty/benchmark-1.5.0/src/statistics.cc new file mode 100644 index 0000000000..bd5a3d6597 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/statistics.cc @@ -0,0 +1,193 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// Copyright 2017 Roman Lebedev. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "benchmark/benchmark.h" + +#include +#include +#include +#include +#include +#include "check.h" +#include "statistics.h" + +namespace benchmark { + +auto StatisticsSum = [](const std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0.0); +}; + +double StatisticsMean(const std::vector& v) { + if (v.empty()) return 0.0; + return StatisticsSum(v) * (1.0 / v.size()); +} + +double StatisticsMedian(const std::vector& v) { + if (v.size() < 3) return StatisticsMean(v); + std::vector copy(v); + + auto center = copy.begin() + v.size() / 2; + std::nth_element(copy.begin(), center, copy.end()); + + // did we have an odd number of samples? + // if yes, then center is the median + // it no, then we are looking for the average between center and the value + // before + if (v.size() % 2 == 1) return *center; + auto center2 = copy.begin() + v.size() / 2 - 1; + std::nth_element(copy.begin(), center2, copy.end()); + return (*center + *center2) / 2.0; +} + +// Return the sum of the squares of this sample set +auto SumSquares = [](const std::vector& v) { + return std::inner_product(v.begin(), v.end(), v.begin(), 0.0); +}; + +auto Sqr = [](const double dat) { return dat * dat; }; +auto Sqrt = [](const double dat) { + // Avoid NaN due to imprecision in the calculations + if (dat < 0.0) return 0.0; + return std::sqrt(dat); +}; + +double StatisticsStdDev(const std::vector& v) { + const auto mean = StatisticsMean(v); + if (v.empty()) return mean; + + // Sample standard deviation is undefined for n = 1 + if (v.size() == 1) return 0.0; + + const double avg_squares = SumSquares(v) * (1.0 / v.size()); + return Sqrt(v.size() / (v.size() - 1.0) * (avg_squares - Sqr(mean))); +} + +std::vector ComputeStats( + const std::vector& reports) { + typedef BenchmarkReporter::Run Run; + std::vector results; + + auto error_count = + std::count_if(reports.begin(), reports.end(), + [](Run const& run) { return run.error_occurred; }); + + if (reports.size() - error_count < 2) { + // We don't report aggregated data if there was a single run. + return results; + } + + // Accumulators. + std::vector real_accumulated_time_stat; + std::vector cpu_accumulated_time_stat; + + real_accumulated_time_stat.reserve(reports.size()); + cpu_accumulated_time_stat.reserve(reports.size()); + + // All repetitions should be run with the same number of iterations so we + // can take this information from the first benchmark. + const IterationCount run_iterations = reports.front().iterations; + // create stats for user counters + struct CounterStat { + Counter c; + std::vector s; + }; + std::map counter_stats; + for (Run const& r : reports) { + for (auto const& cnt : r.counters) { + auto it = counter_stats.find(cnt.first); + if (it == counter_stats.end()) { + counter_stats.insert({cnt.first, {cnt.second, std::vector{}}}); + it = counter_stats.find(cnt.first); + it->second.s.reserve(reports.size()); + } else { + CHECK_EQ(counter_stats[cnt.first].c.flags, cnt.second.flags); + } + } + } + + // Populate the accumulators. + for (Run const& run : reports) { + CHECK_EQ(reports[0].benchmark_name(), run.benchmark_name()); + CHECK_EQ(run_iterations, run.iterations); + if (run.error_occurred) continue; + real_accumulated_time_stat.emplace_back(run.real_accumulated_time); + cpu_accumulated_time_stat.emplace_back(run.cpu_accumulated_time); + // user counters + for (auto const& cnt : run.counters) { + auto it = counter_stats.find(cnt.first); + CHECK_NE(it, counter_stats.end()); + it->second.s.emplace_back(cnt.second); + } + } + + // Only add label if it is same for all runs + std::string report_label = reports[0].report_label; + for (std::size_t i = 1; i < reports.size(); i++) { + if (reports[i].report_label != report_label) { + report_label = ""; + break; + } + } + + const double iteration_rescale_factor = + double(reports.size()) / double(run_iterations); + + for (const auto& Stat : *reports[0].statistics) { + // Get the data from the accumulator to BenchmarkReporter::Run's. + Run data; + data.run_name = reports[0].run_name; + data.run_type = BenchmarkReporter::Run::RT_Aggregate; + data.threads = reports[0].threads; + data.repetitions = reports[0].repetitions; + data.repetition_index = Run::no_repetition_index; + data.aggregate_name = Stat.name_; + data.report_label = report_label; + + // It is incorrect to say that an aggregate is computed over + // run's iterations, because those iterations already got averaged. + // Similarly, if there are N repetitions with 1 iterations each, + // an aggregate will be computed over N measurements, not 1. + // Thus it is best to simply use the count of separate reports. + data.iterations = reports.size(); + + data.real_accumulated_time = Stat.compute_(real_accumulated_time_stat); + data.cpu_accumulated_time = Stat.compute_(cpu_accumulated_time_stat); + + // We will divide these times by data.iterations when reporting, but the + // data.iterations is not nessesairly the scale of these measurements, + // because in each repetition, these timers are sum over all the iterations. + // And if we want to say that the stats are over N repetitions and not + // M iterations, we need to multiply these by (N/M). + data.real_accumulated_time *= iteration_rescale_factor; + data.cpu_accumulated_time *= iteration_rescale_factor; + + data.time_unit = reports[0].time_unit; + + // user counters + for (auto const& kv : counter_stats) { + // Do *NOT* rescale the custom counters. They are already properly scaled. + const auto uc_stat = Stat.compute_(kv.second.s); + auto c = Counter(uc_stat, counter_stats[kv.first].c.flags, + counter_stats[kv.first].c.oneK); + data.counters[kv.first] = c; + } + + results.push_back(data); + } + + return results; +} + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/statistics.h b/thirdparty/benchmark-1.5.0/src/statistics.h new file mode 100644 index 0000000000..7eccc85536 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/statistics.h @@ -0,0 +1,37 @@ +// Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +// Copyright 2017 Roman Lebedev. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STATISTICS_H_ +#define STATISTICS_H_ + +#include + +#include "benchmark/benchmark.h" + +namespace benchmark { + +// Return a vector containing the mean, median and standard devation information +// (and any user-specified info) for the specified list of reports. If 'reports' +// contains less than two non-errored runs an empty vector is returned +std::vector ComputeStats( + const std::vector& reports); + +double StatisticsMean(const std::vector& v); +double StatisticsMedian(const std::vector& v); +double StatisticsStdDev(const std::vector& v); + +} // end namespace benchmark + +#endif // STATISTICS_H_ diff --git a/thirdparty/benchmark-1.5.0/src/string_util.cc b/thirdparty/benchmark-1.5.0/src/string_util.cc new file mode 100644 index 0000000000..39b01a1719 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/string_util.cc @@ -0,0 +1,252 @@ +#include "string_util.h" + +#include +#include +#include +#include +#include +#include + +#include "arraysize.h" + +namespace benchmark { +namespace { + +// kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta. +const char kBigSIUnits[] = "kMGTPEZY"; +// Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi. +const char kBigIECUnits[] = "KMGTPEZY"; +// milli, micro, nano, pico, femto, atto, zepto, yocto. +const char kSmallSIUnits[] = "munpfazy"; + +// We require that all three arrays have the same size. +static_assert(arraysize(kBigSIUnits) == arraysize(kBigIECUnits), + "SI and IEC unit arrays must be the same size"); +static_assert(arraysize(kSmallSIUnits) == arraysize(kBigSIUnits), + "Small SI and Big SI unit arrays must be the same size"); + +static const int64_t kUnitsSize = arraysize(kBigSIUnits); + +void ToExponentAndMantissa(double val, double thresh, int precision, + double one_k, std::string* mantissa, + int64_t* exponent) { + std::stringstream mantissa_stream; + + if (val < 0) { + mantissa_stream << "-"; + val = -val; + } + + // Adjust threshold so that it never excludes things which can't be rendered + // in 'precision' digits. + const double adjusted_threshold = + std::max(thresh, 1.0 / std::pow(10.0, precision)); + const double big_threshold = adjusted_threshold * one_k; + const double small_threshold = adjusted_threshold; + // Values in ]simple_threshold,small_threshold[ will be printed as-is + const double simple_threshold = 0.01; + + if (val > big_threshold) { + // Positive powers + double scaled = val; + for (size_t i = 0; i < arraysize(kBigSIUnits); ++i) { + scaled /= one_k; + if (scaled <= big_threshold) { + mantissa_stream << scaled; + *exponent = i + 1; + *mantissa = mantissa_stream.str(); + return; + } + } + mantissa_stream << val; + *exponent = 0; + } else if (val < small_threshold) { + // Negative powers + if (val < simple_threshold) { + double scaled = val; + for (size_t i = 0; i < arraysize(kSmallSIUnits); ++i) { + scaled *= one_k; + if (scaled >= small_threshold) { + mantissa_stream << scaled; + *exponent = -static_cast(i + 1); + *mantissa = mantissa_stream.str(); + return; + } + } + } + mantissa_stream << val; + *exponent = 0; + } else { + mantissa_stream << val; + *exponent = 0; + } + *mantissa = mantissa_stream.str(); +} + +std::string ExponentToPrefix(int64_t exponent, bool iec) { + if (exponent == 0) return ""; + + const int64_t index = (exponent > 0 ? exponent - 1 : -exponent - 1); + if (index >= kUnitsSize) return ""; + + const char* array = + (exponent > 0 ? (iec ? kBigIECUnits : kBigSIUnits) : kSmallSIUnits); + if (iec) + return array[index] + std::string("i"); + else + return std::string(1, array[index]); +} + +std::string ToBinaryStringFullySpecified(double value, double threshold, + int precision, double one_k = 1024.0) { + std::string mantissa; + int64_t exponent; + ToExponentAndMantissa(value, threshold, precision, one_k, &mantissa, + &exponent); + return mantissa + ExponentToPrefix(exponent, false); +} + +} // end namespace + +void AppendHumanReadable(int n, std::string* str) { + std::stringstream ss; + // Round down to the nearest SI prefix. + ss << ToBinaryStringFullySpecified(n, 1.0, 0); + *str += ss.str(); +} + +std::string HumanReadableNumber(double n, double one_k) { + // 1.1 means that figures up to 1.1k should be shown with the next unit down; + // this softens edge effects. + // 1 means that we should show one decimal place of precision. + return ToBinaryStringFullySpecified(n, 1.1, 1, one_k); +} + +std::string StrFormatImp(const char* msg, va_list args) { + // we might need a second shot at this, so pre-emptivly make a copy + va_list args_cp; + va_copy(args_cp, args); + + // TODO(ericwf): use std::array for first attempt to avoid one memory + // allocation guess what the size might be + std::array local_buff; + std::size_t size = local_buff.size(); + // 2015-10-08: vsnprintf is used instead of snd::vsnprintf due to a limitation + // in the android-ndk + auto ret = vsnprintf(local_buff.data(), size, msg, args_cp); + + va_end(args_cp); + + // handle empty expansion + if (ret == 0) return std::string{}; + if (static_cast(ret) < size) + return std::string(local_buff.data()); + + // we did not provide a long enough buffer on our first attempt. + // add 1 to size to account for null-byte in size cast to prevent overflow + size = static_cast(ret) + 1; + auto buff_ptr = std::unique_ptr(new char[size]); + // 2015-10-08: vsnprintf is used instead of snd::vsnprintf due to a limitation + // in the android-ndk + ret = vsnprintf(buff_ptr.get(), size, msg, args); + return std::string(buff_ptr.get()); +} + +std::string StrFormat(const char* format, ...) { + va_list args; + va_start(args, format); + std::string tmp = StrFormatImp(format, args); + va_end(args); + return tmp; +} + +#ifdef BENCHMARK_STL_ANDROID_GNUSTL +/* + * GNU STL in Android NDK lacks support for some C++11 functions, including + * stoul, stoi, stod. We reimplement them here using C functions strtoul, + * strtol, strtod. Note that reimplemented functions are in benchmark:: + * namespace, not std:: namespace. + */ +unsigned long stoul(const std::string& str, size_t* pos, int base) { + /* Record previous errno */ + const int oldErrno = errno; + errno = 0; + + const char* strStart = str.c_str(); + char* strEnd = const_cast(strStart); + const unsigned long result = strtoul(strStart, &strEnd, base); + + const int strtoulErrno = errno; + /* Restore previous errno */ + errno = oldErrno; + + /* Check for errors and return */ + if (strtoulErrno == ERANGE) { + throw std::out_of_range( + "stoul failed: " + str + " is outside of range of unsigned long"); + } else if (strEnd == strStart || strtoulErrno != 0) { + throw std::invalid_argument( + "stoul failed: " + str + " is not an integer"); + } + if (pos != nullptr) { + *pos = static_cast(strEnd - strStart); + } + return result; +} + +int stoi(const std::string& str, size_t* pos, int base) { + /* Record previous errno */ + const int oldErrno = errno; + errno = 0; + + const char* strStart = str.c_str(); + char* strEnd = const_cast(strStart); + const long result = strtol(strStart, &strEnd, base); + + const int strtolErrno = errno; + /* Restore previous errno */ + errno = oldErrno; + + /* Check for errors and return */ + if (strtolErrno == ERANGE || long(int(result)) != result) { + throw std::out_of_range( + "stoul failed: " + str + " is outside of range of int"); + } else if (strEnd == strStart || strtolErrno != 0) { + throw std::invalid_argument( + "stoul failed: " + str + " is not an integer"); + } + if (pos != nullptr) { + *pos = static_cast(strEnd - strStart); + } + return int(result); +} + +double stod(const std::string& str, size_t* pos) { + /* Record previous errno */ + const int oldErrno = errno; + errno = 0; + + const char* strStart = str.c_str(); + char* strEnd = const_cast(strStart); + const double result = strtod(strStart, &strEnd); + + /* Restore previous errno */ + const int strtodErrno = errno; + errno = oldErrno; + + /* Check for errors and return */ + if (strtodErrno == ERANGE) { + throw std::out_of_range( + "stoul failed: " + str + " is outside of range of int"); + } else if (strEnd == strStart || strtodErrno != 0) { + throw std::invalid_argument( + "stoul failed: " + str + " is not an integer"); + } + if (pos != nullptr) { + *pos = static_cast(strEnd - strStart); + } + return result; +} +#endif + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/string_util.h b/thirdparty/benchmark-1.5.0/src/string_util.h new file mode 100644 index 0000000000..09d7b4bd2a --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/string_util.h @@ -0,0 +1,59 @@ +#ifndef BENCHMARK_STRING_UTIL_H_ +#define BENCHMARK_STRING_UTIL_H_ + +#include +#include +#include +#include "internal_macros.h" + +namespace benchmark { + +void AppendHumanReadable(int n, std::string* str); + +std::string HumanReadableNumber(double n, double one_k = 1024.0); + +#if defined(__MINGW32__) +__attribute__((format(__MINGW_PRINTF_FORMAT, 1, 2))) +#elif defined(__GNUC__) +__attribute__((format(printf, 1, 2))) +#endif +std::string +StrFormat(const char* format, ...); + +inline std::ostream& StrCatImp(std::ostream& out) BENCHMARK_NOEXCEPT { + return out; +} + +template +inline std::ostream& StrCatImp(std::ostream& out, First&& f, Rest&&... rest) { + out << std::forward(f); + return StrCatImp(out, std::forward(rest)...); +} + +template +inline std::string StrCat(Args&&... args) { + std::ostringstream ss; + StrCatImp(ss, std::forward(args)...); + return ss.str(); +} + +#ifdef BENCHMARK_STL_ANDROID_GNUSTL +/* + * GNU STL in Android NDK lacks support for some C++11 functions, including + * stoul, stoi, stod. We reimplement them here using C functions strtoul, + * strtol, strtod. Note that reimplemented functions are in benchmark:: + * namespace, not std:: namespace. + */ +unsigned long stoul(const std::string& str, size_t* pos = nullptr, + int base = 10); +int stoi(const std::string& str, size_t* pos = nullptr, int base = 10); +double stod(const std::string& str, size_t* pos = nullptr); +#else +using std::stoul; +using std::stoi; +using std::stod; +#endif + +} // end namespace benchmark + +#endif // BENCHMARK_STRING_UTIL_H_ diff --git a/thirdparty/benchmark-1.5.0/src/sysinfo.cc b/thirdparty/benchmark-1.5.0/src/sysinfo.cc new file mode 100644 index 0000000000..28126470ba --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/sysinfo.cc @@ -0,0 +1,699 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#undef StrCat // Don't let StrCat in string_util.h be renamed to lstrcatA +#include +#include +#include +#else +#include +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include // this header must be included before 'sys/sysctl.h' to avoid compilation error on FreeBSD +#include +#if defined BENCHMARK_OS_FREEBSD || defined BENCHMARK_OS_MACOSX || \ + defined BENCHMARK_OS_NETBSD || defined BENCHMARK_OS_OPENBSD +#define BENCHMARK_HAS_SYSCTL +#include +#endif +#endif +#if defined(BENCHMARK_OS_SOLARIS) +#include +#endif +#if defined(BENCHMARK_OS_QNX) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "cycleclock.h" +#include "internal_macros.h" +#include "log.h" +#include "sleep.h" +#include "string_util.h" + +namespace benchmark { +namespace { + +void PrintImp(std::ostream& out) { out << std::endl; } + +template +void PrintImp(std::ostream& out, First&& f, Rest&&... rest) { + out << std::forward(f); + PrintImp(out, std::forward(rest)...); +} + +template +BENCHMARK_NORETURN void PrintErrorAndDie(Args&&... args) { + PrintImp(std::cerr, std::forward(args)...); + std::exit(EXIT_FAILURE); +} + +#ifdef BENCHMARK_HAS_SYSCTL + +/// ValueUnion - A type used to correctly alias the byte-for-byte output of +/// `sysctl` with the result type it's to be interpreted as. +struct ValueUnion { + union DataT { + uint32_t uint32_value; + uint64_t uint64_value; + // For correct aliasing of union members from bytes. + char bytes[8]; + }; + using DataPtr = std::unique_ptr; + + // The size of the data union member + its trailing array size. + size_t Size; + DataPtr Buff; + + public: + ValueUnion() : Size(0), Buff(nullptr, &std::free) {} + + explicit ValueUnion(size_t BuffSize) + : Size(sizeof(DataT) + BuffSize), + Buff(::new (std::malloc(Size)) DataT(), &std::free) {} + + ValueUnion(ValueUnion&& other) = default; + + explicit operator bool() const { return bool(Buff); } + + char* data() const { return Buff->bytes; } + + std::string GetAsString() const { return std::string(data()); } + + int64_t GetAsInteger() const { + if (Size == sizeof(Buff->uint32_value)) + return static_cast(Buff->uint32_value); + else if (Size == sizeof(Buff->uint64_value)) + return static_cast(Buff->uint64_value); + BENCHMARK_UNREACHABLE(); + } + + uint64_t GetAsUnsigned() const { + if (Size == sizeof(Buff->uint32_value)) + return Buff->uint32_value; + else if (Size == sizeof(Buff->uint64_value)) + return Buff->uint64_value; + BENCHMARK_UNREACHABLE(); + } + + template + std::array GetAsArray() { + const int ArrSize = sizeof(T) * N; + CHECK_LE(ArrSize, Size); + std::array Arr; + std::memcpy(Arr.data(), data(), ArrSize); + return Arr; + } +}; + +ValueUnion GetSysctlImp(std::string const& Name) { +#if defined BENCHMARK_OS_OPENBSD + int mib[2]; + + mib[0] = CTL_HW; + if ((Name == "hw.ncpu") || (Name == "hw.cpuspeed")){ + ValueUnion buff(sizeof(int)); + + if (Name == "hw.ncpu") { + mib[1] = HW_NCPU; + } else { + mib[1] = HW_CPUSPEED; + } + + if (sysctl(mib, 2, buff.data(), &buff.Size, nullptr, 0) == -1) { + return ValueUnion(); + } + return buff; + } + return ValueUnion(); +#else + size_t CurBuffSize = 0; + if (sysctlbyname(Name.c_str(), nullptr, &CurBuffSize, nullptr, 0) == -1) + return ValueUnion(); + + ValueUnion buff(CurBuffSize); + if (sysctlbyname(Name.c_str(), buff.data(), &buff.Size, nullptr, 0) == 0) + return buff; + return ValueUnion(); +#endif +} + +BENCHMARK_MAYBE_UNUSED +bool GetSysctl(std::string const& Name, std::string* Out) { + Out->clear(); + auto Buff = GetSysctlImp(Name); + if (!Buff) return false; + Out->assign(Buff.data()); + return true; +} + +template ::value>::type> +bool GetSysctl(std::string const& Name, Tp* Out) { + *Out = 0; + auto Buff = GetSysctlImp(Name); + if (!Buff) return false; + *Out = static_cast(Buff.GetAsUnsigned()); + return true; +} + +template +bool GetSysctl(std::string const& Name, std::array* Out) { + auto Buff = GetSysctlImp(Name); + if (!Buff) return false; + *Out = Buff.GetAsArray(); + return true; +} +#endif + +template +bool ReadFromFile(std::string const& fname, ArgT* arg) { + *arg = ArgT(); + std::ifstream f(fname.c_str()); + if (!f.is_open()) return false; + f >> *arg; + return f.good(); +} + +bool CpuScalingEnabled(int num_cpus) { + // We don't have a valid CPU count, so don't even bother. + if (num_cpus <= 0) return false; +#ifdef BENCHMARK_OS_QNX + return false; +#endif +#ifndef BENCHMARK_OS_WINDOWS + // On Linux, the CPUfreq subsystem exposes CPU information as files on the + // local file system. If reading the exported files fails, then we may not be + // running on Linux, so we silently ignore all the read errors. + std::string res; + for (int cpu = 0; cpu < num_cpus; ++cpu) { + std::string governor_file = + StrCat("/sys/devices/system/cpu/cpu", cpu, "/cpufreq/scaling_governor"); + if (ReadFromFile(governor_file, &res) && res != "performance") return true; + } +#endif + return false; +} + +int CountSetBitsInCPUMap(std::string Val) { + auto CountBits = [](std::string Part) { + using CPUMask = std::bitset; + Part = "0x" + Part; + CPUMask Mask(benchmark::stoul(Part, nullptr, 16)); + return static_cast(Mask.count()); + }; + size_t Pos; + int total = 0; + while ((Pos = Val.find(',')) != std::string::npos) { + total += CountBits(Val.substr(0, Pos)); + Val = Val.substr(Pos + 1); + } + if (!Val.empty()) { + total += CountBits(Val); + } + return total; +} + +BENCHMARK_MAYBE_UNUSED +std::vector GetCacheSizesFromKVFS() { + std::vector res; + std::string dir = "/sys/devices/system/cpu/cpu0/cache/"; + int Idx = 0; + while (true) { + CPUInfo::CacheInfo info; + std::string FPath = StrCat(dir, "index", Idx++, "/"); + std::ifstream f(StrCat(FPath, "size").c_str()); + if (!f.is_open()) break; + std::string suffix; + f >> info.size; + if (f.fail()) + PrintErrorAndDie("Failed while reading file '", FPath, "size'"); + if (f.good()) { + f >> suffix; + if (f.bad()) + PrintErrorAndDie( + "Invalid cache size format: failed to read size suffix"); + else if (f && suffix != "K") + PrintErrorAndDie("Invalid cache size format: Expected bytes ", suffix); + else if (suffix == "K") + info.size *= 1000; + } + if (!ReadFromFile(StrCat(FPath, "type"), &info.type)) + PrintErrorAndDie("Failed to read from file ", FPath, "type"); + if (!ReadFromFile(StrCat(FPath, "level"), &info.level)) + PrintErrorAndDie("Failed to read from file ", FPath, "level"); + std::string map_str; + if (!ReadFromFile(StrCat(FPath, "shared_cpu_map"), &map_str)) + PrintErrorAndDie("Failed to read from file ", FPath, "shared_cpu_map"); + info.num_sharing = CountSetBitsInCPUMap(map_str); + res.push_back(info); + } + + return res; +} + +#ifdef BENCHMARK_OS_MACOSX +std::vector GetCacheSizesMacOSX() { + std::vector res; + std::array CacheCounts{{0, 0, 0, 0}}; + GetSysctl("hw.cacheconfig", &CacheCounts); + + struct { + std::string name; + std::string type; + int level; + uint64_t num_sharing; + } Cases[] = {{"hw.l1dcachesize", "Data", 1, CacheCounts[1]}, + {"hw.l1icachesize", "Instruction", 1, CacheCounts[1]}, + {"hw.l2cachesize", "Unified", 2, CacheCounts[2]}, + {"hw.l3cachesize", "Unified", 3, CacheCounts[3]}}; + for (auto& C : Cases) { + int val; + if (!GetSysctl(C.name, &val)) continue; + CPUInfo::CacheInfo info; + info.type = C.type; + info.level = C.level; + info.size = val; + info.num_sharing = static_cast(C.num_sharing); + res.push_back(std::move(info)); + } + return res; +} +#elif defined(BENCHMARK_OS_WINDOWS) +std::vector GetCacheSizesWindows() { + std::vector res; + DWORD buffer_size = 0; + using PInfo = SYSTEM_LOGICAL_PROCESSOR_INFORMATION; + using CInfo = CACHE_DESCRIPTOR; + + using UPtr = std::unique_ptr; + GetLogicalProcessorInformation(nullptr, &buffer_size); + UPtr buff((PInfo*)malloc(buffer_size), &std::free); + if (!GetLogicalProcessorInformation(buff.get(), &buffer_size)) + PrintErrorAndDie("Failed during call to GetLogicalProcessorInformation: ", + GetLastError()); + + PInfo* it = buff.get(); + PInfo* end = buff.get() + (buffer_size / sizeof(PInfo)); + + for (; it != end; ++it) { + if (it->Relationship != RelationCache) continue; + using BitSet = std::bitset; + BitSet B(it->ProcessorMask); + // To prevent duplicates, only consider caches where CPU 0 is specified + if (!B.test(0)) continue; + CInfo* Cache = &it->Cache; + CPUInfo::CacheInfo C; + C.num_sharing = static_cast(B.count()); + C.level = Cache->Level; + C.size = Cache->Size; + switch (Cache->Type) { + case CacheUnified: + C.type = "Unified"; + break; + case CacheInstruction: + C.type = "Instruction"; + break; + case CacheData: + C.type = "Data"; + break; + case CacheTrace: + C.type = "Trace"; + break; + default: + C.type = "Unknown"; + break; + } + res.push_back(C); + } + return res; +} +#elif BENCHMARK_OS_QNX +std::vector GetCacheSizesQNX() { + std::vector res; + struct cacheattr_entry *cache = SYSPAGE_ENTRY(cacheattr); + uint32_t const elsize = SYSPAGE_ELEMENT_SIZE(cacheattr); + int num = SYSPAGE_ENTRY_SIZE(cacheattr) / elsize ; + for(int i = 0; i < num; ++i ) { + CPUInfo::CacheInfo info; + switch (cache->flags){ + case CACHE_FLAG_INSTR : + info.type = "Instruction"; + info.level = 1; + break; + case CACHE_FLAG_DATA : + info.type = "Data"; + info.level = 1; + break; + case CACHE_FLAG_UNIFIED : + info.type = "Unified"; + info.level = 2; + case CACHE_FLAG_SHARED : + info.type = "Shared"; + info.level = 3; + default : + continue; + break; + } + info.size = cache->line_size * cache->num_lines; + info.num_sharing = 0; + res.push_back(std::move(info)); + cache = SYSPAGE_ARRAY_ADJ_OFFSET(cacheattr, cache, elsize); + } + return res; +} +#endif + +std::vector GetCacheSizes() { +#ifdef BENCHMARK_OS_MACOSX + return GetCacheSizesMacOSX(); +#elif defined(BENCHMARK_OS_WINDOWS) + return GetCacheSizesWindows(); +#elif defined(BENCHMARK_OS_QNX) + return GetCacheSizesQNX(); +#else + return GetCacheSizesFromKVFS(); +#endif +} + +std::string GetSystemName() { +#if defined(BENCHMARK_OS_WINDOWS) + std::string str; + const unsigned COUNT = MAX_COMPUTERNAME_LENGTH+1; + TCHAR hostname[COUNT] = {'\0'}; + DWORD DWCOUNT = COUNT; + if (!GetComputerName(hostname, &DWCOUNT)) + return std::string(""); +#ifndef UNICODE + str = std::string(hostname, DWCOUNT); +#else + //Using wstring_convert, Is deprecated in C++17 + using convert_type = std::codecvt_utf8; + std::wstring_convert converter; + std::wstring wStr(hostname, DWCOUNT); + str = converter.to_bytes(wStr); +#endif + return str; +#else // defined(BENCHMARK_OS_WINDOWS) +#ifdef BENCHMARK_HAS_SYSCTL // BSD/Mac Doesnt have HOST_NAME_MAX defined +#define HOST_NAME_MAX 64 +#elif defined(BENCHMARK_OS_QNX) +#define HOST_NAME_MAX 154 +#endif + char hostname[HOST_NAME_MAX]; + int retVal = gethostname(hostname, HOST_NAME_MAX); + if (retVal != 0) return std::string(""); + return std::string(hostname); +#endif // Catch-all POSIX block. +} + +int GetNumCPUs() { +#ifdef BENCHMARK_HAS_SYSCTL + int NumCPU = -1; + if (GetSysctl("hw.ncpu", &NumCPU)) return NumCPU; + fprintf(stderr, "Err: %s\n", strerror(errno)); + std::exit(EXIT_FAILURE); +#elif defined(BENCHMARK_OS_WINDOWS) + SYSTEM_INFO sysinfo; + // Use memset as opposed to = {} to avoid GCC missing initializer false + // positives. + std::memset(&sysinfo, 0, sizeof(SYSTEM_INFO)); + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; // number of logical + // processors in the current + // group +#elif defined(BENCHMARK_OS_SOLARIS) + // Returns -1 in case of a failure. + int NumCPU = sysconf(_SC_NPROCESSORS_ONLN); + if (NumCPU < 0) { + fprintf(stderr, + "sysconf(_SC_NPROCESSORS_ONLN) failed with error: %s\n", + strerror(errno)); + } + return NumCPU; +#elif defined(BENCHMARK_OS_QNX) + return static_cast(_syspage_ptr->num_cpu); +#else + int NumCPUs = 0; + int MaxID = -1; + std::ifstream f("/proc/cpuinfo"); + if (!f.is_open()) { + std::cerr << "failed to open /proc/cpuinfo\n"; + return -1; + } + const std::string Key = "processor"; + std::string ln; + while (std::getline(f, ln)) { + if (ln.empty()) continue; + size_t SplitIdx = ln.find(':'); + std::string value; +#if defined(__s390__) + // s390 has another format in /proc/cpuinfo + // it needs to be parsed differently + if (SplitIdx != std::string::npos) value = ln.substr(Key.size()+1,SplitIdx-Key.size()-1); +#else + if (SplitIdx != std::string::npos) value = ln.substr(SplitIdx + 1); +#endif + if (ln.size() >= Key.size() && ln.compare(0, Key.size(), Key) == 0) { + NumCPUs++; + if (!value.empty()) { + int CurID = benchmark::stoi(value); + MaxID = std::max(CurID, MaxID); + } + } + } + if (f.bad()) { + std::cerr << "Failure reading /proc/cpuinfo\n"; + return -1; + } + if (!f.eof()) { + std::cerr << "Failed to read to end of /proc/cpuinfo\n"; + return -1; + } + f.close(); + + if ((MaxID + 1) != NumCPUs) { + fprintf(stderr, + "CPU ID assignments in /proc/cpuinfo seem messed up." + " This is usually caused by a bad BIOS.\n"); + } + return NumCPUs; +#endif + BENCHMARK_UNREACHABLE(); +} + +double GetCPUCyclesPerSecond() { +#if defined BENCHMARK_OS_LINUX || defined BENCHMARK_OS_CYGWIN + long freq; + + // If the kernel is exporting the tsc frequency use that. There are issues + // where cpuinfo_max_freq cannot be relied on because the BIOS may be + // exporintg an invalid p-state (on x86) or p-states may be used to put the + // processor in a new mode (turbo mode). Essentially, those frequencies + // cannot always be relied upon. The same reasons apply to /proc/cpuinfo as + // well. + if (ReadFromFile("/sys/devices/system/cpu/cpu0/tsc_freq_khz", &freq) + // If CPU scaling is in effect, we want to use the *maximum* frequency, + // not whatever CPU speed some random processor happens to be using now. + || ReadFromFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", + &freq)) { + // The value is in kHz (as the file name suggests). For example, on a + // 2GHz warpstation, the file contains the value "2000000". + return freq * 1000.0; + } + + const double error_value = -1; + double bogo_clock = error_value; + + std::ifstream f("/proc/cpuinfo"); + if (!f.is_open()) { + std::cerr << "failed to open /proc/cpuinfo\n"; + return error_value; + } + + auto startsWithKey = [](std::string const& Value, std::string const& Key) { + if (Key.size() > Value.size()) return false; + auto Cmp = [&](char X, char Y) { + return std::tolower(X) == std::tolower(Y); + }; + return std::equal(Key.begin(), Key.end(), Value.begin(), Cmp); + }; + + std::string ln; + while (std::getline(f, ln)) { + if (ln.empty()) continue; + size_t SplitIdx = ln.find(':'); + std::string value; + if (SplitIdx != std::string::npos) value = ln.substr(SplitIdx + 1); + // When parsing the "cpu MHz" and "bogomips" (fallback) entries, we only + // accept positive values. Some environments (virtual machines) report zero, + // which would cause infinite looping in WallTime_Init. + if (startsWithKey(ln, "cpu MHz")) { + if (!value.empty()) { + double cycles_per_second = benchmark::stod(value) * 1000000.0; + if (cycles_per_second > 0) return cycles_per_second; + } + } else if (startsWithKey(ln, "bogomips")) { + if (!value.empty()) { + bogo_clock = benchmark::stod(value) * 1000000.0; + if (bogo_clock < 0.0) bogo_clock = error_value; + } + } + } + if (f.bad()) { + std::cerr << "Failure reading /proc/cpuinfo\n"; + return error_value; + } + if (!f.eof()) { + std::cerr << "Failed to read to end of /proc/cpuinfo\n"; + return error_value; + } + f.close(); + // If we found the bogomips clock, but nothing better, we'll use it (but + // we're not happy about it); otherwise, fallback to the rough estimation + // below. + if (bogo_clock >= 0.0) return bogo_clock; + +#elif defined BENCHMARK_HAS_SYSCTL + constexpr auto* FreqStr = +#if defined(BENCHMARK_OS_FREEBSD) || defined(BENCHMARK_OS_NETBSD) + "machdep.tsc_freq"; +#elif defined BENCHMARK_OS_OPENBSD + "hw.cpuspeed"; +#else + "hw.cpufrequency"; +#endif + unsigned long long hz = 0; +#if defined BENCHMARK_OS_OPENBSD + if (GetSysctl(FreqStr, &hz)) return hz * 1000000; +#else + if (GetSysctl(FreqStr, &hz)) return hz; +#endif + fprintf(stderr, "Unable to determine clock rate from sysctl: %s: %s\n", + FreqStr, strerror(errno)); + +#elif defined BENCHMARK_OS_WINDOWS + // In NT, read MHz from the registry. If we fail to do so or we're in win9x + // then make a crude estimate. + DWORD data, data_size = sizeof(data); + if (IsWindowsXPOrGreater() && + SUCCEEDED( + SHGetValueA(HKEY_LOCAL_MACHINE, + "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", + "~MHz", nullptr, &data, &data_size))) + return static_cast((int64_t)data * + (int64_t)(1000 * 1000)); // was mhz +#elif defined (BENCHMARK_OS_SOLARIS) + kstat_ctl_t *kc = kstat_open(); + if (!kc) { + std::cerr << "failed to open /dev/kstat\n"; + return -1; + } + kstat_t *ksp = kstat_lookup(kc, (char*)"cpu_info", -1, (char*)"cpu_info0"); + if (!ksp) { + std::cerr << "failed to lookup in /dev/kstat\n"; + return -1; + } + if (kstat_read(kc, ksp, NULL) < 0) { + std::cerr << "failed to read from /dev/kstat\n"; + return -1; + } + kstat_named_t *knp = + (kstat_named_t*)kstat_data_lookup(ksp, (char*)"current_clock_Hz"); + if (!knp) { + std::cerr << "failed to lookup data in /dev/kstat\n"; + return -1; + } + if (knp->data_type != KSTAT_DATA_UINT64) { + std::cerr << "current_clock_Hz is of unexpected data type: " + << knp->data_type << "\n"; + return -1; + } + double clock_hz = knp->value.ui64; + kstat_close(kc); + return clock_hz; +#elif defined (BENCHMARK_OS_QNX) + return static_cast((int64_t)(SYSPAGE_ENTRY(cpuinfo)->speed) * + (int64_t)(1000 * 1000)); +#endif + // If we've fallen through, attempt to roughly estimate the CPU clock rate. + const int estimate_time_ms = 1000; + const auto start_ticks = cycleclock::Now(); + SleepForMilliseconds(estimate_time_ms); + return static_cast(cycleclock::Now() - start_ticks); +} + +std::vector GetLoadAvg() { +#if (defined BENCHMARK_OS_FREEBSD || defined(BENCHMARK_OS_LINUX) || \ + defined BENCHMARK_OS_MACOSX || defined BENCHMARK_OS_NETBSD || \ + defined BENCHMARK_OS_OPENBSD) && !defined(__ANDROID__) + constexpr int kMaxSamples = 3; + std::vector res(kMaxSamples, 0.0); + const int nelem = getloadavg(res.data(), kMaxSamples); + if (nelem < 1) { + res.clear(); + } else { + res.resize(nelem); + } + return res; +#else + return {}; +#endif +} + +} // end namespace + +const CPUInfo& CPUInfo::Get() { + static const CPUInfo* info = new CPUInfo(); + return *info; +} + +CPUInfo::CPUInfo() + : num_cpus(GetNumCPUs()), + cycles_per_second(GetCPUCyclesPerSecond()), + caches(GetCacheSizes()), + scaling_enabled(CpuScalingEnabled(num_cpus)), + load_avg(GetLoadAvg()) {} + + +const SystemInfo& SystemInfo::Get() { + static const SystemInfo* info = new SystemInfo(); + return *info; +} + +SystemInfo::SystemInfo() : name(GetSystemName()) {} +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/thread_manager.h b/thirdparty/benchmark-1.5.0/src/thread_manager.h new file mode 100644 index 0000000000..1720281f0a --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/thread_manager.h @@ -0,0 +1,64 @@ +#ifndef BENCHMARK_THREAD_MANAGER_H +#define BENCHMARK_THREAD_MANAGER_H + +#include + +#include "benchmark/benchmark.h" +#include "mutex.h" + +namespace benchmark { +namespace internal { + +class ThreadManager { + public: + ThreadManager(int num_threads) + : alive_threads_(num_threads), start_stop_barrier_(num_threads) {} + + Mutex& GetBenchmarkMutex() const RETURN_CAPABILITY(benchmark_mutex_) { + return benchmark_mutex_; + } + + bool StartStopBarrier() EXCLUDES(end_cond_mutex_) { + return start_stop_barrier_.wait(); + } + + void NotifyThreadComplete() EXCLUDES(end_cond_mutex_) { + start_stop_barrier_.removeThread(); + if (--alive_threads_ == 0) { + MutexLock lock(end_cond_mutex_); + end_condition_.notify_all(); + } + } + + void WaitForAllThreads() EXCLUDES(end_cond_mutex_) { + MutexLock lock(end_cond_mutex_); + end_condition_.wait(lock.native_handle(), + [this]() { return alive_threads_ == 0; }); + } + + public: + struct Result { + IterationCount iterations = 0; + double real_time_used = 0; + double cpu_time_used = 0; + double manual_time_used = 0; + int64_t complexity_n = 0; + std::string report_label_; + std::string error_message_; + bool has_error_ = false; + UserCounters counters; + }; + GUARDED_BY(GetBenchmarkMutex()) Result results; + + private: + mutable Mutex benchmark_mutex_; + std::atomic alive_threads_; + Barrier start_stop_barrier_; + Mutex end_cond_mutex_; + Condition end_condition_; +}; + +} // namespace internal +} // namespace benchmark + +#endif // BENCHMARK_THREAD_MANAGER_H diff --git a/thirdparty/benchmark-1.5.0/src/thread_timer.h b/thirdparty/benchmark-1.5.0/src/thread_timer.h new file mode 100644 index 0000000000..fbd298d3bd --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/thread_timer.h @@ -0,0 +1,86 @@ +#ifndef BENCHMARK_THREAD_TIMER_H +#define BENCHMARK_THREAD_TIMER_H + +#include "check.h" +#include "timers.h" + +namespace benchmark { +namespace internal { + +class ThreadTimer { + explicit ThreadTimer(bool measure_process_cpu_time_) + : measure_process_cpu_time(measure_process_cpu_time_) {} + + public: + static ThreadTimer Create() { + return ThreadTimer(/*measure_process_cpu_time_=*/false); + } + static ThreadTimer CreateProcessCpuTime() { + return ThreadTimer(/*measure_process_cpu_time_=*/true); + } + + // Called by each thread + void StartTimer() { + running_ = true; + start_real_time_ = ChronoClockNow(); + start_cpu_time_ = ReadCpuTimerOfChoice(); + } + + // Called by each thread + void StopTimer() { + CHECK(running_); + running_ = false; + real_time_used_ += ChronoClockNow() - start_real_time_; + // Floating point error can result in the subtraction producing a negative + // time. Guard against that. + cpu_time_used_ += + std::max(ReadCpuTimerOfChoice() - start_cpu_time_, 0); + } + + // Called by each thread + void SetIterationTime(double seconds) { manual_time_used_ += seconds; } + + bool running() const { return running_; } + + // REQUIRES: timer is not running + double real_time_used() { + CHECK(!running_); + return real_time_used_; + } + + // REQUIRES: timer is not running + double cpu_time_used() { + CHECK(!running_); + return cpu_time_used_; + } + + // REQUIRES: timer is not running + double manual_time_used() { + CHECK(!running_); + return manual_time_used_; + } + + private: + double ReadCpuTimerOfChoice() const { + if (measure_process_cpu_time) return ProcessCPUUsage(); + return ThreadCPUUsage(); + } + + // should the thread, or the process, time be measured? + const bool measure_process_cpu_time; + + bool running_ = false; // Is the timer running + double start_real_time_ = 0; // If running_ + double start_cpu_time_ = 0; // If running_ + + // Accumulated time so far (does not contain current slice if running_) + double real_time_used_ = 0; + double cpu_time_used_ = 0; + // Manually set iteration time. User sets this with SetIterationTime(seconds). + double manual_time_used_ = 0; +}; + +} // namespace internal +} // namespace benchmark + +#endif // BENCHMARK_THREAD_TIMER_H diff --git a/thirdparty/benchmark-1.5.0/src/timers.cc b/thirdparty/benchmark-1.5.0/src/timers.cc new file mode 100644 index 0000000000..7613ff92c6 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/timers.cc @@ -0,0 +1,217 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "timers.h" +#include "internal_macros.h" + +#ifdef BENCHMARK_OS_WINDOWS +#include +#undef StrCat // Don't let StrCat in string_util.h be renamed to lstrcatA +#include +#include +#else +#include +#ifndef BENCHMARK_OS_FUCHSIA +#include +#endif +#include +#include // this header must be included before 'sys/sysctl.h' to avoid compilation error on FreeBSD +#include +#if defined BENCHMARK_OS_FREEBSD || defined BENCHMARK_OS_MACOSX +#include +#endif +#if defined(BENCHMARK_OS_MACOSX) +#include +#include +#include +#endif +#endif + +#ifdef BENCHMARK_OS_EMSCRIPTEN +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "check.h" +#include "log.h" +#include "sleep.h" +#include "string_util.h" + +namespace benchmark { + +// Suppress unused warnings on helper functions. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +namespace { +#if defined(BENCHMARK_OS_WINDOWS) +double MakeTime(FILETIME const& kernel_time, FILETIME const& user_time) { + ULARGE_INTEGER kernel; + ULARGE_INTEGER user; + kernel.HighPart = kernel_time.dwHighDateTime; + kernel.LowPart = kernel_time.dwLowDateTime; + user.HighPart = user_time.dwHighDateTime; + user.LowPart = user_time.dwLowDateTime; + return (static_cast(kernel.QuadPart) + + static_cast(user.QuadPart)) * + 1e-7; +} +#elif !defined(BENCHMARK_OS_FUCHSIA) +double MakeTime(struct rusage const& ru) { + return (static_cast(ru.ru_utime.tv_sec) + + static_cast(ru.ru_utime.tv_usec) * 1e-6 + + static_cast(ru.ru_stime.tv_sec) + + static_cast(ru.ru_stime.tv_usec) * 1e-6); +} +#endif +#if defined(BENCHMARK_OS_MACOSX) +double MakeTime(thread_basic_info_data_t const& info) { + return (static_cast(info.user_time.seconds) + + static_cast(info.user_time.microseconds) * 1e-6 + + static_cast(info.system_time.seconds) + + static_cast(info.system_time.microseconds) * 1e-6); +} +#endif +#if defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_THREAD_CPUTIME_ID) +double MakeTime(struct timespec const& ts) { + return ts.tv_sec + (static_cast(ts.tv_nsec) * 1e-9); +} +#endif + +BENCHMARK_NORETURN static void DiagnoseAndExit(const char* msg) { + std::cerr << "ERROR: " << msg << std::endl; + std::exit(EXIT_FAILURE); +} + +} // end namespace + +double ProcessCPUUsage() { +#if defined(BENCHMARK_OS_WINDOWS) + HANDLE proc = GetCurrentProcess(); + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + if (GetProcessTimes(proc, &creation_time, &exit_time, &kernel_time, + &user_time)) + return MakeTime(kernel_time, user_time); + DiagnoseAndExit("GetProccessTimes() failed"); +#elif defined(BENCHMARK_OS_EMSCRIPTEN) + // clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...) returns 0 on Emscripten. + // Use Emscripten-specific API. Reported CPU time would be exactly the + // same as total time, but this is ok because there aren't long-latency + // syncronous system calls in Emscripten. + return emscripten_get_now() * 1e-3; +#elif defined(CLOCK_PROCESS_CPUTIME_ID) && !defined(BENCHMARK_OS_MACOSX) + // FIXME We want to use clock_gettime, but its not available in MacOS 10.11. See + // https://github.com/google/benchmark/pull/292 + struct timespec spec; + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &spec) == 0) + return MakeTime(spec); + DiagnoseAndExit("clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...) failed"); +#else + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) == 0) return MakeTime(ru); + DiagnoseAndExit("getrusage(RUSAGE_SELF, ...) failed"); +#endif +} + +double ThreadCPUUsage() { +#if defined(BENCHMARK_OS_WINDOWS) + HANDLE this_thread = GetCurrentThread(); + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + GetThreadTimes(this_thread, &creation_time, &exit_time, &kernel_time, + &user_time); + return MakeTime(kernel_time, user_time); +#elif defined(BENCHMARK_OS_MACOSX) + // FIXME We want to use clock_gettime, but its not available in MacOS 10.11. See + // https://github.com/google/benchmark/pull/292 + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + thread_basic_info_data_t info; + mach_port_t thread = pthread_mach_thread_np(pthread_self()); + if (thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)&info, &count) == + KERN_SUCCESS) { + return MakeTime(info); + } + DiagnoseAndExit("ThreadCPUUsage() failed when evaluating thread_info"); +#elif defined(BENCHMARK_OS_EMSCRIPTEN) + // Emscripten doesn't support traditional threads + return ProcessCPUUsage(); +#elif defined(BENCHMARK_OS_RTEMS) + // RTEMS doesn't support CLOCK_THREAD_CPUTIME_ID. See + // https://github.com/RTEMS/rtems/blob/master/cpukit/posix/src/clockgettime.c + return ProcessCPUUsage(); +#elif defined(BENCHMARK_OS_SOLARIS) + struct rusage ru; + if (getrusage(RUSAGE_LWP, &ru) == 0) return MakeTime(ru); + DiagnoseAndExit("getrusage(RUSAGE_LWP, ...) failed"); +#elif defined(CLOCK_THREAD_CPUTIME_ID) + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0) return MakeTime(ts); + DiagnoseAndExit("clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) failed"); +#else +#error Per-thread timing is not available on your system. +#endif +} + +namespace { + +std::string DateTimeString(bool local) { + typedef std::chrono::system_clock Clock; + std::time_t now = Clock::to_time_t(Clock::now()); + const std::size_t kStorageSize = 128; + char storage[kStorageSize]; + std::size_t written; + + if (local) { +#if defined(BENCHMARK_OS_WINDOWS) + written = + std::strftime(storage, sizeof(storage), "%x %X", ::localtime(&now)); +#else + std::tm timeinfo; + ::localtime_r(&now, &timeinfo); + written = std::strftime(storage, sizeof(storage), "%F %T", &timeinfo); +#endif + } else { +#if defined(BENCHMARK_OS_WINDOWS) + written = std::strftime(storage, sizeof(storage), "%x %X", ::gmtime(&now)); +#else + std::tm timeinfo; + ::gmtime_r(&now, &timeinfo); + written = std::strftime(storage, sizeof(storage), "%F %T", &timeinfo); +#endif + } + CHECK(written < kStorageSize); + ((void)written); // prevent unused variable in optimized mode. + return std::string(storage); +} + +} // end namespace + +std::string LocalDateTimeString() { return DateTimeString(true); } + +} // end namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/src/timers.h b/thirdparty/benchmark-1.5.0/src/timers.h new file mode 100644 index 0000000000..65606ccd93 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/src/timers.h @@ -0,0 +1,48 @@ +#ifndef BENCHMARK_TIMERS_H +#define BENCHMARK_TIMERS_H + +#include +#include + +namespace benchmark { + +// Return the CPU usage of the current process +double ProcessCPUUsage(); + +// Return the CPU usage of the children of the current process +double ChildrenCPUUsage(); + +// Return the CPU usage of the current thread +double ThreadCPUUsage(); + +#if defined(HAVE_STEADY_CLOCK) +template +struct ChooseSteadyClock { + typedef std::chrono::high_resolution_clock type; +}; + +template <> +struct ChooseSteadyClock { + typedef std::chrono::steady_clock type; +}; +#endif + +struct ChooseClockType { +#if defined(HAVE_STEADY_CLOCK) + typedef ChooseSteadyClock<>::type type; +#else + typedef std::chrono::high_resolution_clock type; +#endif +}; + +inline double ChronoClockNow() { + typedef ChooseClockType::type ClockType; + using FpSeconds = std::chrono::duration; + return FpSeconds(ClockType::now().time_since_epoch()).count(); +} + +std::string LocalDateTimeString(); + +} // end namespace benchmark + +#endif // BENCHMARK_TIMERS_H diff --git a/thirdparty/benchmark-1.5.0/test/AssemblyTests.cmake b/thirdparty/benchmark-1.5.0/test/AssemblyTests.cmake new file mode 100644 index 0000000000..3d078586f1 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/AssemblyTests.cmake @@ -0,0 +1,46 @@ + +include(split_list) + +set(ASM_TEST_FLAGS "") +check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG) +if (BENCHMARK_HAS_O3_FLAG) + list(APPEND ASM_TEST_FLAGS -O3) +endif() + +check_cxx_compiler_flag(-g0 BENCHMARK_HAS_G0_FLAG) +if (BENCHMARK_HAS_G0_FLAG) + list(APPEND ASM_TEST_FLAGS -g0) +endif() + +check_cxx_compiler_flag(-fno-stack-protector BENCHMARK_HAS_FNO_STACK_PROTECTOR_FLAG) +if (BENCHMARK_HAS_FNO_STACK_PROTECTOR_FLAG) + list(APPEND ASM_TEST_FLAGS -fno-stack-protector) +endif() + +split_list(ASM_TEST_FLAGS) +string(TOUPPER "${CMAKE_CXX_COMPILER_ID}" ASM_TEST_COMPILER) + +macro(add_filecheck_test name) + cmake_parse_arguments(ARG "" "" "CHECK_PREFIXES" ${ARGV}) + add_library(${name} OBJECT ${name}.cc) + set_target_properties(${name} PROPERTIES COMPILE_FLAGS "-S ${ASM_TEST_FLAGS}") + set(ASM_OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/${name}.s") + add_custom_target(copy_${name} ALL + COMMAND ${PROJECT_SOURCE_DIR}/tools/strip_asm.py + $ + ${ASM_OUTPUT_FILE} + BYPRODUCTS ${ASM_OUTPUT_FILE}) + add_dependencies(copy_${name} ${name}) + if (NOT ARG_CHECK_PREFIXES) + set(ARG_CHECK_PREFIXES "CHECK") + endif() + foreach(prefix ${ARG_CHECK_PREFIXES}) + add_test(NAME run_${name}_${prefix} + COMMAND + ${LLVM_FILECHECK_EXE} ${name}.cc + --input-file=${ASM_OUTPUT_FILE} + --check-prefixes=CHECK,CHECK-${ASM_TEST_COMPILER} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + endforeach() +endmacro() + diff --git a/thirdparty/benchmark-1.5.0/test/BUILD b/thirdparty/benchmark-1.5.0/test/BUILD new file mode 100644 index 0000000000..3f174c486f --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/BUILD @@ -0,0 +1,65 @@ +TEST_COPTS = [ + "-pedantic", + "-pedantic-errors", + "-std=c++11", + "-Wall", + "-Wextra", + "-Wshadow", +# "-Wshorten-64-to-32", + "-Wfloat-equal", + "-fstrict-aliasing", +] + +PER_SRC_COPTS = ({ + "cxx03_test.cc": ["-std=c++03"], + # Some of the issues with DoNotOptimize only occur when optimization is enabled + "donotoptimize_test.cc": ["-O3"], +}) + + +TEST_ARGS = ["--benchmark_min_time=0.01"] + +PER_SRC_TEST_ARGS = ({ + "user_counters_tabular_test.cc": ["--benchmark_counters_tabular=true"], +}) + +cc_library( + name = "output_test_helper", + testonly = 1, + srcs = ["output_test_helper.cc"], + hdrs = ["output_test.h"], + copts = TEST_COPTS, + deps = [ + "//:benchmark", + "//:benchmark_internal_headers", + ], +) + +[ + cc_test( + name = test_src[:-len(".cc")], + size = "small", + srcs = [test_src], + args = TEST_ARGS + PER_SRC_TEST_ARGS.get(test_src, []), + copts = TEST_COPTS + PER_SRC_COPTS.get(test_src, []), + deps = [ + ":output_test_helper", + "//:benchmark", + "//:benchmark_internal_headers", + "@com_google_googletest//:gtest", + ] + ( + ["@com_google_googletest//:gtest_main"] if (test_src[-len("gtest.cc"):] == "gtest.cc") else [] + ), + # FIXME: Add support for assembly tests to bazel. + # See Issue #556 + # https://github.com/google/benchmark/issues/556 + ) for test_src in glob(["*test.cc"], exclude = ["*_assembly_test.cc", "link_main_test.cc"]) +] + +cc_test( + name = "link_main_test", + size = "small", + srcs = ["link_main_test.cc"], + copts = TEST_COPTS, + deps = ["//:benchmark_main"], +) diff --git a/thirdparty/benchmark-1.5.0/test/CMakeLists.txt b/thirdparty/benchmark-1.5.0/test/CMakeLists.txt new file mode 100644 index 0000000000..030f35aae3 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/CMakeLists.txt @@ -0,0 +1,259 @@ +# Enable the tests + +find_package(Threads REQUIRED) +include(CheckCXXCompilerFlag) + +# NOTE: Some tests use `` to perform the test. Therefore we must +# strip -DNDEBUG from the default CMake flags in DEBUG mode. +string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) +if( NOT uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG" ) + add_definitions( -UNDEBUG ) + add_definitions(-DTEST_BENCHMARK_LIBRARY_HAS_NO_ASSERTIONS) + # Also remove /D NDEBUG to avoid MSVC warnings about conflicting defines. + foreach (flags_var_to_scrub + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_RELWITHDEBINFO + CMAKE_C_FLAGS_MINSIZEREL) + string (REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " + "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") + endforeach() +endif() + +check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG) +set(BENCHMARK_O3_FLAG "") +if (BENCHMARK_HAS_O3_FLAG) + set(BENCHMARK_O3_FLAG "-O3") +endif() + +# NOTE: These flags must be added after find_package(Threads REQUIRED) otherwise +# they will break the configuration check. +if (DEFINED BENCHMARK_CXX_LINKER_FLAGS) + list(APPEND CMAKE_EXE_LINKER_FLAGS ${BENCHMARK_CXX_LINKER_FLAGS}) +endif() + +add_library(output_test_helper STATIC output_test_helper.cc output_test.h) + +macro(compile_benchmark_test name) + add_executable(${name} "${name}.cc") + target_link_libraries(${name} benchmark ${CMAKE_THREAD_LIBS_INIT}) +endmacro(compile_benchmark_test) + +macro(compile_benchmark_test_with_main name) + add_executable(${name} "${name}.cc") + target_link_libraries(${name} benchmark_main) +endmacro(compile_benchmark_test_with_main) + +macro(compile_output_test name) + add_executable(${name} "${name}.cc" output_test.h) + target_link_libraries(${name} output_test_helper benchmark + ${BENCHMARK_CXX_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +endmacro(compile_output_test) + +# Demonstration executable +compile_benchmark_test(benchmark_test) +add_test(benchmark benchmark_test --benchmark_min_time=0.01) + +compile_benchmark_test(filter_test) +macro(add_filter_test name filter expect) + add_test(${name} filter_test --benchmark_min_time=0.01 --benchmark_filter=${filter} ${expect}) + add_test(${name}_list_only filter_test --benchmark_list_tests --benchmark_filter=${filter} ${expect}) +endmacro(add_filter_test) + +add_filter_test(filter_simple "Foo" 3) +add_filter_test(filter_simple_negative "-Foo" 2) +add_filter_test(filter_suffix "BM_.*" 4) +add_filter_test(filter_suffix_negative "-BM_.*" 1) +add_filter_test(filter_regex_all ".*" 5) +add_filter_test(filter_regex_all_negative "-.*" 0) +add_filter_test(filter_regex_blank "" 5) +add_filter_test(filter_regex_blank_negative "-" 0) +add_filter_test(filter_regex_none "monkey" 0) +add_filter_test(filter_regex_none_negative "-monkey" 5) +add_filter_test(filter_regex_wildcard ".*Foo.*" 3) +add_filter_test(filter_regex_wildcard_negative "-.*Foo.*" 2) +add_filter_test(filter_regex_begin "^BM_.*" 4) +add_filter_test(filter_regex_begin_negative "-^BM_.*" 1) +add_filter_test(filter_regex_begin2 "^N" 1) +add_filter_test(filter_regex_begin2_negative "-^N" 4) +add_filter_test(filter_regex_end ".*Ba$" 1) +add_filter_test(filter_regex_end_negative "-.*Ba$" 4) + +compile_benchmark_test(options_test) +add_test(options_benchmarks options_test --benchmark_min_time=0.01) + +compile_benchmark_test(basic_test) +add_test(basic_benchmark basic_test --benchmark_min_time=0.01) + +compile_benchmark_test(diagnostics_test) +add_test(diagnostics_test diagnostics_test --benchmark_min_time=0.01) + +compile_benchmark_test(skip_with_error_test) +add_test(skip_with_error_test skip_with_error_test --benchmark_min_time=0.01) + +compile_benchmark_test(donotoptimize_test) +# Some of the issues with DoNotOptimize only occur when optimization is enabled +check_cxx_compiler_flag(-O3 BENCHMARK_HAS_O3_FLAG) +if (BENCHMARK_HAS_O3_FLAG) + set_target_properties(donotoptimize_test PROPERTIES COMPILE_FLAGS "-O3") +endif() +add_test(donotoptimize_test donotoptimize_test --benchmark_min_time=0.01) + +compile_benchmark_test(fixture_test) +add_test(fixture_test fixture_test --benchmark_min_time=0.01) + +compile_benchmark_test(register_benchmark_test) +add_test(register_benchmark_test register_benchmark_test --benchmark_min_time=0.01) + +compile_benchmark_test(map_test) +add_test(map_test map_test --benchmark_min_time=0.01) + +compile_benchmark_test(multiple_ranges_test) +add_test(multiple_ranges_test multiple_ranges_test --benchmark_min_time=0.01) + +compile_benchmark_test_with_main(link_main_test) +add_test(link_main_test link_main_test --benchmark_min_time=0.01) + +compile_output_test(reporter_output_test) +add_test(reporter_output_test reporter_output_test --benchmark_min_time=0.01) + +compile_output_test(templated_fixture_test) +add_test(templated_fixture_test templated_fixture_test --benchmark_min_time=0.01) + +compile_output_test(user_counters_test) +add_test(user_counters_test user_counters_test --benchmark_min_time=0.01) + +compile_output_test(internal_threading_test) +add_test(internal_threading_test internal_threading_test --benchmark_min_time=0.01) + +compile_output_test(report_aggregates_only_test) +add_test(report_aggregates_only_test report_aggregates_only_test --benchmark_min_time=0.01) + +compile_output_test(display_aggregates_only_test) +add_test(display_aggregates_only_test display_aggregates_only_test --benchmark_min_time=0.01) + +compile_output_test(user_counters_tabular_test) +add_test(user_counters_tabular_test user_counters_tabular_test --benchmark_counters_tabular=true --benchmark_min_time=0.01) + +compile_output_test(user_counters_thousands_test) +add_test(user_counters_thousands_test user_counters_thousands_test --benchmark_min_time=0.01) + +compile_output_test(memory_manager_test) +add_test(memory_manager_test memory_manager_test --benchmark_min_time=0.01) + +check_cxx_compiler_flag(-std=c++03 BENCHMARK_HAS_CXX03_FLAG) +if (BENCHMARK_HAS_CXX03_FLAG) + compile_benchmark_test(cxx03_test) + set_target_properties(cxx03_test + PROPERTIES + COMPILE_FLAGS "-std=c++03") + # libstdc++ provides different definitions within between dialects. When + # LTO is enabled and -Werror is specified GCC diagnoses this ODR violation + # causing the test to fail to compile. To prevent this we explicitly disable + # the warning. + check_cxx_compiler_flag(-Wno-odr BENCHMARK_HAS_WNO_ODR) + if (BENCHMARK_ENABLE_LTO AND BENCHMARK_HAS_WNO_ODR) + set_target_properties(cxx03_test + PROPERTIES + LINK_FLAGS "-Wno-odr") + endif() + add_test(cxx03 cxx03_test --benchmark_min_time=0.01) +endif() + +# Attempt to work around flaky test failures when running on Appveyor servers. +if (DEFINED ENV{APPVEYOR}) + set(COMPLEXITY_MIN_TIME "0.5") +else() + set(COMPLEXITY_MIN_TIME "0.01") +endif() +compile_output_test(complexity_test) +add_test(complexity_benchmark complexity_test --benchmark_min_time=${COMPLEXITY_MIN_TIME}) + +############################################################################### +# GoogleTest Unit Tests +############################################################################### + +if (BENCHMARK_ENABLE_GTEST_TESTS) + macro(compile_gtest name) + add_executable(${name} "${name}.cc") + target_link_libraries(${name} benchmark + gmock_main ${CMAKE_THREAD_LIBS_INIT}) + endmacro(compile_gtest) + + macro(add_gtest name) + compile_gtest(${name}) + add_test(${name} ${name}) + endmacro() + + add_gtest(benchmark_gtest) + add_gtest(benchmark_name_gtest) + add_gtest(commandlineflags_gtest) + add_gtest(statistics_gtest) + add_gtest(string_util_gtest) +endif(BENCHMARK_ENABLE_GTEST_TESTS) + +############################################################################### +# Assembly Unit Tests +############################################################################### + +if (BENCHMARK_ENABLE_ASSEMBLY_TESTS) + if (NOT LLVM_FILECHECK_EXE) + message(FATAL_ERROR "LLVM FileCheck is required when including this file") + endif() + include(AssemblyTests.cmake) + add_filecheck_test(donotoptimize_assembly_test) + add_filecheck_test(state_assembly_test) + add_filecheck_test(clobber_memory_assembly_test) +endif() + + + +############################################################################### +# Code Coverage Configuration +############################################################################### + +# Add the coverage command(s) +if(CMAKE_BUILD_TYPE) + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_LOWER) +endif() +if (${CMAKE_BUILD_TYPE_LOWER} MATCHES "coverage") + find_program(GCOV gcov) + find_program(LCOV lcov) + find_program(GENHTML genhtml) + find_program(CTEST ctest) + if (GCOV AND LCOV AND GENHTML AND CTEST AND HAVE_CXX_FLAG_COVERAGE) + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/lcov/index.html + COMMAND ${LCOV} -q -z -d . + COMMAND ${LCOV} -q --no-external -c -b "${CMAKE_SOURCE_DIR}" -d . -o before.lcov -i + COMMAND ${CTEST} --force-new-ctest-process + COMMAND ${LCOV} -q --no-external -c -b "${CMAKE_SOURCE_DIR}" -d . -o after.lcov + COMMAND ${LCOV} -q -a before.lcov -a after.lcov --output-file final.lcov + COMMAND ${LCOV} -q -r final.lcov "'${CMAKE_SOURCE_DIR}/test/*'" -o final.lcov + COMMAND ${GENHTML} final.lcov -o lcov --demangle-cpp --sort -p "${CMAKE_BINARY_DIR}" -t benchmark + DEPENDS filter_test benchmark_test options_test basic_test fixture_test cxx03_test complexity_test + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running LCOV" + ) + add_custom_target(coverage + DEPENDS ${CMAKE_BINARY_DIR}/lcov/index.html + COMMENT "LCOV report at lcov/index.html" + ) + message(STATUS "Coverage command added") + else() + if (HAVE_CXX_FLAG_COVERAGE) + set(CXX_FLAG_COVERAGE_MESSAGE supported) + else() + set(CXX_FLAG_COVERAGE_MESSAGE unavailable) + endif() + message(WARNING + "Coverage not available:\n" + " gcov: ${GCOV}\n" + " lcov: ${LCOV}\n" + " genhtml: ${GENHTML}\n" + " ctest: ${CTEST}\n" + " --coverage flag: ${CXX_FLAG_COVERAGE_MESSAGE}") + endif() +endif() diff --git a/thirdparty/benchmark-1.5.0/test/basic_test.cc b/thirdparty/benchmark-1.5.0/test/basic_test.cc new file mode 100644 index 0000000000..5f3dd1a3ee --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/basic_test.cc @@ -0,0 +1,136 @@ + +#include "benchmark/benchmark.h" + +#define BASIC_BENCHMARK_TEST(x) BENCHMARK(x)->Arg(8)->Arg(512)->Arg(8192) + +void BM_empty(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } +} +BENCHMARK(BM_empty); +BENCHMARK(BM_empty)->ThreadPerCpu(); + +void BM_spin_empty(benchmark::State& state) { + for (auto _ : state) { + for (int x = 0; x < state.range(0); ++x) { + benchmark::DoNotOptimize(x); + } + } +} +BASIC_BENCHMARK_TEST(BM_spin_empty); +BASIC_BENCHMARK_TEST(BM_spin_empty)->ThreadPerCpu(); + +void BM_spin_pause_before(benchmark::State& state) { + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_before); +BASIC_BENCHMARK_TEST(BM_spin_pause_before)->ThreadPerCpu(); + +void BM_spin_pause_during(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + state.ResumeTiming(); + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_during); +BASIC_BENCHMARK_TEST(BM_spin_pause_during)->ThreadPerCpu(); + +void BM_pause_during(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + state.ResumeTiming(); + } +} +BENCHMARK(BM_pause_during); +BENCHMARK(BM_pause_during)->ThreadPerCpu(); +BENCHMARK(BM_pause_during)->UseRealTime(); +BENCHMARK(BM_pause_during)->UseRealTime()->ThreadPerCpu(); + +void BM_spin_pause_after(benchmark::State& state) { + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_after); +BASIC_BENCHMARK_TEST(BM_spin_pause_after)->ThreadPerCpu(); + +void BM_spin_pause_before_and_after(benchmark::State& state) { + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } + } + for (int i = 0; i < state.range(0); ++i) { + benchmark::DoNotOptimize(i); + } +} +BASIC_BENCHMARK_TEST(BM_spin_pause_before_and_after); +BASIC_BENCHMARK_TEST(BM_spin_pause_before_and_after)->ThreadPerCpu(); + +void BM_empty_stop_start(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_empty_stop_start); +BENCHMARK(BM_empty_stop_start)->ThreadPerCpu(); + + +void BM_KeepRunning(benchmark::State& state) { + benchmark::IterationCount iter_count = 0; + assert(iter_count == state.iterations()); + while (state.KeepRunning()) { + ++iter_count; + } + assert(iter_count == state.iterations()); +} +BENCHMARK(BM_KeepRunning); + +void BM_KeepRunningBatch(benchmark::State& state) { + // Choose a prime batch size to avoid evenly dividing max_iterations. + const benchmark::IterationCount batch_size = 101; + benchmark::IterationCount iter_count = 0; + while (state.KeepRunningBatch(batch_size)) { + iter_count += batch_size; + } + assert(state.iterations() == iter_count); +} +BENCHMARK(BM_KeepRunningBatch); + +void BM_RangedFor(benchmark::State& state) { + benchmark::IterationCount iter_count = 0; + for (auto _ : state) { + ++iter_count; + } + assert(iter_count == state.max_iterations); +} +BENCHMARK(BM_RangedFor); + +// Ensure that StateIterator provides all the necessary typedefs required to +// instantiate std::iterator_traits. +static_assert(std::is_same< + typename std::iterator_traits::value_type, + typename benchmark::State::StateIterator::value_type>::value, ""); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/benchmark_gtest.cc b/thirdparty/benchmark-1.5.0/test/benchmark_gtest.cc new file mode 100644 index 0000000000..9557b20ec7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/benchmark_gtest.cc @@ -0,0 +1,128 @@ +#include + +#include "../src/benchmark_register.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace benchmark { +namespace internal { +namespace { + +TEST(AddRangeTest, Simple) { + std::vector dst; + AddRange(&dst, 1, 2, 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2)); +} + +TEST(AddRangeTest, Simple64) { + std::vector dst; + AddRange(&dst, static_cast(1), static_cast(2), 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2)); +} + +TEST(AddRangeTest, Advanced) { + std::vector dst; + AddRange(&dst, 5, 15, 2); + EXPECT_THAT(dst, testing::ElementsAre(5, 8, 15)); +} + +TEST(AddRangeTest, Advanced64) { + std::vector dst; + AddRange(&dst, static_cast(5), static_cast(15), 2); + EXPECT_THAT(dst, testing::ElementsAre(5, 8, 15)); +} + +TEST(AddRangeTest, FullRange8) { + std::vector dst; + AddRange(&dst, int8_t{1}, std::numeric_limits::max(), 8); + EXPECT_THAT(dst, testing::ElementsAre(1, 8, 64, 127)); +} + +TEST(AddRangeTest, FullRange64) { + std::vector dst; + AddRange(&dst, int64_t{1}, std::numeric_limits::max(), 1024); + EXPECT_THAT( + dst, testing::ElementsAre(1LL, 1024LL, 1048576LL, 1073741824LL, + 1099511627776LL, 1125899906842624LL, + 1152921504606846976LL, 9223372036854775807LL)); +} + +TEST(AddRangeTest, NegativeRanges) { + std::vector dst; + AddRange(&dst, -8, 0, 2); + EXPECT_THAT(dst, testing::ElementsAre(-8, -4, -2, -1, 0)); +} + +TEST(AddRangeTest, StrictlyNegative) { + std::vector dst; + AddRange(&dst, -8, -1, 2); + EXPECT_THAT(dst, testing::ElementsAre(-8, -4, -2, -1)); +} + +TEST(AddRangeTest, SymmetricNegativeRanges) { + std::vector dst; + AddRange(&dst, -8, 8, 2); + EXPECT_THAT(dst, testing::ElementsAre(-8, -4, -2, -1, 0, 1, 2, 4, 8)); +} + +TEST(AddRangeTest, SymmetricNegativeRangesOddMult) { + std::vector dst; + AddRange(&dst, -30, 32, 5); + EXPECT_THAT(dst, testing::ElementsAre(-30, -25, -5, -1, 0, 1, 5, 25, 32)); +} + +TEST(AddRangeTest, NegativeRangesAsymmetric) { + std::vector dst; + AddRange(&dst, -3, 5, 2); + EXPECT_THAT(dst, testing::ElementsAre(-3, -2, -1, 0, 1, 2, 4, 5)); +} + +TEST(AddRangeTest, NegativeRangesLargeStep) { + // Always include -1, 0, 1 when crossing zero. + std::vector dst; + AddRange(&dst, -8, 8, 10); + EXPECT_THAT(dst, testing::ElementsAre(-8, -1, 0, 1, 8)); +} + +TEST(AddRangeTest, ZeroOnlyRange) { + std::vector dst; + AddRange(&dst, 0, 0, 2); + EXPECT_THAT(dst, testing::ElementsAre(0)); +} + +TEST(AddRangeTest, NegativeRange64) { + std::vector dst; + AddRange(&dst, -4, 4, 2); + EXPECT_THAT(dst, testing::ElementsAre(-4, -2, -1, 0, 1, 2, 4)); +} + +TEST(AddRangeTest, NegativeRangePreservesExistingOrder) { + // If elements already exist in the range, ensure we don't change + // their ordering by adding negative values. + std::vector dst = {1, 2, 3}; + AddRange(&dst, -2, 2, 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2, 3, -2, -1, 0, 1, 2)); +} + +TEST(AddRangeTest, FullNegativeRange64) { + std::vector dst; + const auto min = std::numeric_limits::min(); + const auto max = std::numeric_limits::max(); + AddRange(&dst, min, max, 1024); + EXPECT_THAT( + dst, testing::ElementsAreArray(std::vector{ + min, -1152921504606846976LL, -1125899906842624LL, + -1099511627776LL, -1073741824LL, -1048576LL, -1024LL, -1LL, 0LL, + 1LL, 1024LL, 1048576LL, 1073741824LL, 1099511627776LL, + 1125899906842624LL, 1152921504606846976LL, max})); +} + +TEST(AddRangeTest, Simple8) { + std::vector dst; + AddRange(&dst, 1, 8, 2); + EXPECT_THAT(dst, testing::ElementsAre(1, 2, 4, 8)); +} + +} // namespace +} // namespace internal +} // namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/test/benchmark_name_gtest.cc b/thirdparty/benchmark-1.5.0/test/benchmark_name_gtest.cc new file mode 100644 index 0000000000..afb401c1f5 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/benchmark_name_gtest.cc @@ -0,0 +1,74 @@ +#include "benchmark/benchmark.h" +#include "gtest/gtest.h" + +namespace { + +using namespace benchmark; +using namespace benchmark::internal; + +TEST(BenchmarkNameTest, Empty) { + const auto name = BenchmarkName(); + EXPECT_EQ(name.str(), std::string()); +} + +TEST(BenchmarkNameTest, FunctionName) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + EXPECT_EQ(name.str(), "function_name"); +} + +TEST(BenchmarkNameTest, FunctionNameAndArgs) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.args = "some_args:3/4/5"; + EXPECT_EQ(name.str(), "function_name/some_args:3/4/5"); +} + +TEST(BenchmarkNameTest, MinTime) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.args = "some_args:3/4"; + name.min_time = "min_time:3.4s"; + EXPECT_EQ(name.str(), "function_name/some_args:3/4/min_time:3.4s"); +} + +TEST(BenchmarkNameTest, Iterations) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.iterations = "iterations:42"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/iterations:42"); +} + +TEST(BenchmarkNameTest, Repetitions) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.repetitions = "repetitions:24"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/repetitions:24"); +} + +TEST(BenchmarkNameTest, TimeType) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.time_type = "hammer_time"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/hammer_time"); +} + +TEST(BenchmarkNameTest, Threads) { + auto name = BenchmarkName(); + name.function_name = "function_name"; + name.min_time = "min_time:3.4s"; + name.threads = "threads:256"; + EXPECT_EQ(name.str(), "function_name/min_time:3.4s/threads:256"); +} + +TEST(BenchmarkNameTest, TestEmptyFunctionName) { + auto name = BenchmarkName(); + name.args = "first:3/second:4"; + name.threads = "threads:22"; + EXPECT_EQ(name.str(), "first:3/second:4/threads:22"); +} + +} // end namespace diff --git a/thirdparty/benchmark-1.5.0/test/benchmark_test.cc b/thirdparty/benchmark-1.5.0/test/benchmark_test.cc new file mode 100644 index 0000000000..3cd4f5565f --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/benchmark_test.cc @@ -0,0 +1,245 @@ +#include "benchmark/benchmark.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__GNUC__) +#define BENCHMARK_NOINLINE __attribute__((noinline)) +#else +#define BENCHMARK_NOINLINE +#endif + +namespace { + +int BENCHMARK_NOINLINE Factorial(uint32_t n) { + return (n == 1) ? 1 : n * Factorial(n - 1); +} + +double CalculatePi(int depth) { + double pi = 0.0; + for (int i = 0; i < depth; ++i) { + double numerator = static_cast(((i % 2) * 2) - 1); + double denominator = static_cast((2 * i) - 1); + pi += numerator / denominator; + } + return (pi - 1.0) * 4; +} + +std::set ConstructRandomSet(int64_t size) { + std::set s; + for (int i = 0; i < size; ++i) s.insert(s.end(), i); + return s; +} + +std::mutex test_vector_mu; +std::vector* test_vector = nullptr; + +} // end namespace + +static void BM_Factorial(benchmark::State& state) { + int fac_42 = 0; + for (auto _ : state) fac_42 = Factorial(8); + // Prevent compiler optimizations + std::stringstream ss; + ss << fac_42; + state.SetLabel(ss.str()); +} +BENCHMARK(BM_Factorial); +BENCHMARK(BM_Factorial)->UseRealTime(); + +static void BM_CalculatePiRange(benchmark::State& state) { + double pi = 0.0; + for (auto _ : state) pi = CalculatePi(static_cast(state.range(0))); + std::stringstream ss; + ss << pi; + state.SetLabel(ss.str()); +} +BENCHMARK_RANGE(BM_CalculatePiRange, 1, 1024 * 1024); + +static void BM_CalculatePi(benchmark::State& state) { + static const int depth = 1024; + for (auto _ : state) { + benchmark::DoNotOptimize(CalculatePi(static_cast(depth))); + } +} +BENCHMARK(BM_CalculatePi)->Threads(8); +BENCHMARK(BM_CalculatePi)->ThreadRange(1, 32); +BENCHMARK(BM_CalculatePi)->ThreadPerCpu(); + +static void BM_SetInsert(benchmark::State& state) { + std::set data; + for (auto _ : state) { + state.PauseTiming(); + data = ConstructRandomSet(state.range(0)); + state.ResumeTiming(); + for (int j = 0; j < state.range(1); ++j) data.insert(rand()); + } + state.SetItemsProcessed(state.iterations() * state.range(1)); + state.SetBytesProcessed(state.iterations() * state.range(1) * sizeof(int)); +} + +// Test many inserts at once to reduce the total iterations needed. Otherwise, the slower, +// non-timed part of each iteration will make the benchmark take forever. +BENCHMARK(BM_SetInsert)->Ranges({{1 << 10, 8 << 10}, {128, 512}}); + +template +static void BM_Sequential(benchmark::State& state) { + ValueType v = 42; + for (auto _ : state) { + Container c; + for (int64_t i = state.range(0); --i;) c.push_back(v); + } + const int64_t items_processed = state.iterations() * state.range(0); + state.SetItemsProcessed(items_processed); + state.SetBytesProcessed(items_processed * sizeof(v)); +} +BENCHMARK_TEMPLATE2(BM_Sequential, std::vector, int) + ->Range(1 << 0, 1 << 10); +BENCHMARK_TEMPLATE(BM_Sequential, std::list)->Range(1 << 0, 1 << 10); +// Test the variadic version of BENCHMARK_TEMPLATE in C++11 and beyond. +#ifdef BENCHMARK_HAS_CXX11 +BENCHMARK_TEMPLATE(BM_Sequential, std::vector, int)->Arg(512); +#endif + +static void BM_StringCompare(benchmark::State& state) { + size_t len = static_cast(state.range(0)); + std::string s1(len, '-'); + std::string s2(len, '-'); + for (auto _ : state) benchmark::DoNotOptimize(s1.compare(s2)); +} +BENCHMARK(BM_StringCompare)->Range(1, 1 << 20); + +static void BM_SetupTeardown(benchmark::State& state) { + if (state.thread_index == 0) { + // No need to lock test_vector_mu here as this is running single-threaded. + test_vector = new std::vector(); + } + int i = 0; + for (auto _ : state) { + std::lock_guard l(test_vector_mu); + if (i % 2 == 0) + test_vector->push_back(i); + else + test_vector->pop_back(); + ++i; + } + if (state.thread_index == 0) { + delete test_vector; + } +} +BENCHMARK(BM_SetupTeardown)->ThreadPerCpu(); + +static void BM_LongTest(benchmark::State& state) { + double tracker = 0.0; + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + benchmark::DoNotOptimize(tracker += i); + } +} +BENCHMARK(BM_LongTest)->Range(1 << 16, 1 << 28); + +static void BM_ParallelMemset(benchmark::State& state) { + int64_t size = state.range(0) / static_cast(sizeof(int)); + int thread_size = static_cast(size) / state.threads; + int from = thread_size * state.thread_index; + int to = from + thread_size; + + if (state.thread_index == 0) { + test_vector = new std::vector(static_cast(size)); + } + + for (auto _ : state) { + for (int i = from; i < to; i++) { + // No need to lock test_vector_mu as ranges + // do not overlap between threads. + benchmark::DoNotOptimize(test_vector->at(i) = 1); + } + } + + if (state.thread_index == 0) { + delete test_vector; + } +} +BENCHMARK(BM_ParallelMemset)->Arg(10 << 20)->ThreadRange(1, 4); + +static void BM_ManualTiming(benchmark::State& state) { + int64_t slept_for = 0; + int64_t microseconds = state.range(0); + std::chrono::duration sleep_duration{ + static_cast(microseconds)}; + + for (auto _ : state) { + auto start = std::chrono::high_resolution_clock::now(); + // Simulate some useful workload with a sleep + std::this_thread::sleep_for( + std::chrono::duration_cast(sleep_duration)); + auto end = std::chrono::high_resolution_clock::now(); + + auto elapsed = + std::chrono::duration_cast>(end - start); + + state.SetIterationTime(elapsed.count()); + slept_for += microseconds; + } + state.SetItemsProcessed(slept_for); +} +BENCHMARK(BM_ManualTiming)->Range(1, 1 << 14)->UseRealTime(); +BENCHMARK(BM_ManualTiming)->Range(1, 1 << 14)->UseManualTime(); + +#ifdef BENCHMARK_HAS_CXX11 + +template +void BM_with_args(benchmark::State& state, Args&&...) { + for (auto _ : state) { + } +} +BENCHMARK_CAPTURE(BM_with_args, int_test, 42, 43, 44); +BENCHMARK_CAPTURE(BM_with_args, string_and_pair_test, std::string("abc"), + std::pair(42, 3.8)); + +void BM_non_template_args(benchmark::State& state, int, double) { + while(state.KeepRunning()) {} +} +BENCHMARK_CAPTURE(BM_non_template_args, basic_test, 0, 0); + +#endif // BENCHMARK_HAS_CXX11 + +static void BM_DenseThreadRanges(benchmark::State& st) { + switch (st.range(0)) { + case 1: + assert(st.threads == 1 || st.threads == 2 || st.threads == 3); + break; + case 2: + assert(st.threads == 1 || st.threads == 3 || st.threads == 4); + break; + case 3: + assert(st.threads == 5 || st.threads == 8 || st.threads == 11 || + st.threads == 14); + break; + default: + assert(false && "Invalid test case number"); + } + while (st.KeepRunning()) { + } +} +BENCHMARK(BM_DenseThreadRanges)->Arg(1)->DenseThreadRange(1, 3); +BENCHMARK(BM_DenseThreadRanges)->Arg(2)->DenseThreadRange(1, 4, 2); +BENCHMARK(BM_DenseThreadRanges)->Arg(3)->DenseThreadRange(5, 14, 3); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/clobber_memory_assembly_test.cc b/thirdparty/benchmark-1.5.0/test/clobber_memory_assembly_test.cc new file mode 100644 index 0000000000..f41911a39c --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/clobber_memory_assembly_test.cc @@ -0,0 +1,64 @@ +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wreturn-type" +#endif + +extern "C" { + +extern int ExternInt; +extern int ExternInt2; +extern int ExternInt3; + +} + +// CHECK-LABEL: test_basic: +extern "C" void test_basic() { + int x; + benchmark::DoNotOptimize(&x); + x = 101; + benchmark::ClobberMemory(); + // CHECK: leaq [[DEST:[^,]+]], %rax + // CHECK: movl $101, [[DEST]] + // CHECK: ret +} + +// CHECK-LABEL: test_redundant_store: +extern "C" void test_redundant_store() { + ExternInt = 3; + benchmark::ClobberMemory(); + ExternInt = 51; + // CHECK-DAG: ExternInt + // CHECK-DAG: movl $3 + // CHECK: movl $51 +} + +// CHECK-LABEL: test_redundant_read: +extern "C" void test_redundant_read() { + int x; + benchmark::DoNotOptimize(&x); + x = ExternInt; + benchmark::ClobberMemory(); + x = ExternInt2; + // CHECK: leaq [[DEST:[^,]+]], %rax + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, [[DEST]] + // CHECK-NOT: ExternInt2 + // CHECK: ret +} + +// CHECK-LABEL: test_redundant_read2: +extern "C" void test_redundant_read2() { + int x; + benchmark::DoNotOptimize(&x); + x = ExternInt; + benchmark::ClobberMemory(); + x = ExternInt2; + benchmark::ClobberMemory(); + // CHECK: leaq [[DEST:[^,]+]], %rax + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, [[DEST]] + // CHECK: ExternInt2(%rip) + // CHECK: movl %eax, [[DEST]] + // CHECK: ret +} diff --git a/thirdparty/benchmark-1.5.0/test/commandlineflags_gtest.cc b/thirdparty/benchmark-1.5.0/test/commandlineflags_gtest.cc new file mode 100644 index 0000000000..5460778c48 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/commandlineflags_gtest.cc @@ -0,0 +1,78 @@ +#include + +#include "../src/commandlineflags.h" +#include "../src/internal_macros.h" +#include "gtest/gtest.h" + +namespace benchmark { +namespace { + +#if defined(BENCHMARK_OS_WINDOWS) +int setenv(const char* name, const char* value, int overwrite) { + if (!overwrite) { + // NOTE: getenv_s is far superior but not available under mingw. + char* env_value = getenv(name); + if (env_value == nullptr) { + return -1; + } + } + return _putenv_s(name, value); +} + +int unsetenv(const char* name) { + return _putenv_s(name, ""); +} + +#endif // BENCHMARK_OS_WINDOWS + +TEST(BoolFromEnv, Default) { + ASSERT_EQ(unsetenv("BENCHMARK_NOT_IN_ENV"), 0); + EXPECT_EQ(BoolFromEnv("not_in_env", true), true); +} + +TEST(BoolFromEnv, False) { + ASSERT_EQ(setenv("BENCHMARK_IN_ENV", "0", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", true), false); + unsetenv("BENCHMARK_IN_ENV"); +} + +TEST(BoolFromEnv, True) { + ASSERT_EQ(setenv("BENCHMARK_IN_ENV", "1", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("BENCHMARK_IN_ENV"); + + ASSERT_EQ(setenv("BENCHMARK_IN_ENV", "foo", 1), 0); + EXPECT_EQ(BoolFromEnv("in_env", false), true); + unsetenv("BENCHMARK_IN_ENV"); +} + +TEST(Int32FromEnv, NotInEnv) { + ASSERT_EQ(unsetenv("BENCHMARK_NOT_IN_ENV"), 0); + EXPECT_EQ(Int32FromEnv("not_in_env", 42), 42); +} + +TEST(Int32FromEnv, InvalidInteger) { + ASSERT_EQ(setenv("BENCHMARK_IN_ENV", "foo", 1), 0); + EXPECT_EQ(Int32FromEnv("in_env", 42), 42); + ASSERT_EQ(unsetenv("BENCHMARK_IN_ENV"), 0); +} + +TEST(Int32FromEnv, ValidInteger) { + ASSERT_EQ(setenv("BENCHMARK_IN_ENV", "42", 1), 0); + EXPECT_EQ(Int32FromEnv("in_env", 64), 42); + unsetenv("BENCHMARK_IN_ENV"); +} + +TEST(StringFromEnv, Default) { + ASSERT_EQ(unsetenv("BENCHMARK_NOT_IN_ENV"), 0); + EXPECT_STREQ(StringFromEnv("not_in_env", "foo"), "foo"); +} + +TEST(StringFromEnv, Valid) { + ASSERT_EQ(setenv("BENCHMARK_IN_ENV", "foo", 1), 0); + EXPECT_STREQ(StringFromEnv("in_env", "bar"), "foo"); + unsetenv("BENCHMARK_IN_ENV"); +} + +} // namespace +} // namespace benchmark diff --git a/thirdparty/benchmark-1.5.0/test/complexity_test.cc b/thirdparty/benchmark-1.5.0/test/complexity_test.cc new file mode 100644 index 0000000000..d4febbbc15 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/complexity_test.cc @@ -0,0 +1,211 @@ +#undef NDEBUG +#include +#include +#include +#include +#include +#include "benchmark/benchmark.h" +#include "output_test.h" + +namespace { + +#define ADD_COMPLEXITY_CASES(...) \ + int CONCAT(dummy, __LINE__) = AddComplexityTest(__VA_ARGS__) + +int AddComplexityTest(std::string test_name, std::string big_o_test_name, + std::string rms_test_name, std::string big_o) { + SetSubstitutions({{"%name", test_name}, + {"%bigo_name", big_o_test_name}, + {"%rms_name", rms_test_name}, + {"%bigo_str", "[ ]* %float " + big_o}, + {"%bigo", big_o}, + {"%rms", "[ ]*[0-9]+ %"}}); + AddCases( + TC_ConsoleOut, + {{"^%bigo_name %bigo_str %bigo_str[ ]*$"}, + {"^%bigo_name", MR_Not}, // Assert we we didn't only matched a name. + {"^%rms_name %rms %rms[ ]*$", MR_Next}}); + AddCases(TC_JSONOut, {{"\"name\": \"%bigo_name\",$"}, + {"\"run_name\": \"%name\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": %int,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"BigO\",$", MR_Next}, + {"\"cpu_coefficient\": %float,$", MR_Next}, + {"\"real_coefficient\": %float,$", MR_Next}, + {"\"big_o\": \"%bigo\",$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}, + {"\"name\": \"%rms_name\",$"}, + {"\"run_name\": \"%name\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": %int,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"RMS\",$", MR_Next}, + {"\"rms\": %float$", MR_Next}, + {"}", MR_Next}}); + AddCases(TC_CSVOut, {{"^\"%bigo_name\",,%float,%float,%bigo,,,,,$"}, + {"^\"%bigo_name\"", MR_Not}, + {"^\"%rms_name\",,%float,%float,,,,,,$", MR_Next}}); + return 0; +} + +} // end namespace + +// ========================================================================= // +// --------------------------- Testing BigO O(1) --------------------------- // +// ========================================================================= // + +void BM_Complexity_O1(benchmark::State& state) { + for (auto _ : state) { + for (int i = 0; i < 1024; ++i) { + benchmark::DoNotOptimize(&i); + } + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_Complexity_O1)->Range(1, 1 << 18)->Complexity(benchmark::o1); +BENCHMARK(BM_Complexity_O1)->Range(1, 1 << 18)->Complexity(); +BENCHMARK(BM_Complexity_O1) + ->Range(1, 1 << 18) + ->Complexity([](benchmark::IterationCount) { return 1.0; }); + +const char *one_test_name = "BM_Complexity_O1"; +const char *big_o_1_test_name = "BM_Complexity_O1_BigO"; +const char *rms_o_1_test_name = "BM_Complexity_O1_RMS"; +const char *enum_big_o_1 = "\\([0-9]+\\)"; +// FIXME: Tolerate both '(1)' and 'lgN' as output when the complexity is auto +// deduced. +// See https://github.com/google/benchmark/issues/272 +const char *auto_big_o_1 = "(\\([0-9]+\\))|(lgN)"; +const char *lambda_big_o_1 = "f\\(N\\)"; + +// Add enum tests +ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name, + enum_big_o_1); + +// Add auto enum tests +ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name, + auto_big_o_1); + +// Add lambda tests +ADD_COMPLEXITY_CASES(one_test_name, big_o_1_test_name, rms_o_1_test_name, + lambda_big_o_1); + +// ========================================================================= // +// --------------------------- Testing BigO O(N) --------------------------- // +// ========================================================================= // + +std::vector ConstructRandomVector(int64_t size) { + std::vector v; + v.reserve(static_cast(size)); + for (int i = 0; i < size; ++i) { + v.push_back(static_cast(std::rand() % size)); + } + return v; +} + +void BM_Complexity_O_N(benchmark::State& state) { + auto v = ConstructRandomVector(state.range(0)); + // Test worst case scenario (item not in vector) + const int64_t item_not_in_vector = state.range(0) * 2; + for (auto _ : state) { + benchmark::DoNotOptimize(std::find(v.begin(), v.end(), item_not_in_vector)); + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_Complexity_O_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(benchmark::oN); +BENCHMARK(BM_Complexity_O_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity([](benchmark::IterationCount n) -> double { + return static_cast(n); + }); +BENCHMARK(BM_Complexity_O_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(); + +const char *n_test_name = "BM_Complexity_O_N"; +const char *big_o_n_test_name = "BM_Complexity_O_N_BigO"; +const char *rms_o_n_test_name = "BM_Complexity_O_N_RMS"; +const char *enum_auto_big_o_n = "N"; +const char *lambda_big_o_n = "f\\(N\\)"; + +// Add enum tests +ADD_COMPLEXITY_CASES(n_test_name, big_o_n_test_name, rms_o_n_test_name, + enum_auto_big_o_n); + +// Add lambda tests +ADD_COMPLEXITY_CASES(n_test_name, big_o_n_test_name, rms_o_n_test_name, + lambda_big_o_n); + +// ========================================================================= // +// ------------------------- Testing BigO O(N*lgN) ------------------------- // +// ========================================================================= // + +static void BM_Complexity_O_N_log_N(benchmark::State& state) { + auto v = ConstructRandomVector(state.range(0)); + for (auto _ : state) { + std::sort(v.begin(), v.end()); + } + state.SetComplexityN(state.range(0)); +} +static const double kLog2E = 1.44269504088896340736; +BENCHMARK(BM_Complexity_O_N_log_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(benchmark::oNLogN); +BENCHMARK(BM_Complexity_O_N_log_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity([](benchmark::IterationCount n) { + return kLog2E * n * log(static_cast(n)); + }); +BENCHMARK(BM_Complexity_O_N_log_N) + ->RangeMultiplier(2) + ->Range(1 << 10, 1 << 16) + ->Complexity(); + +const char *n_lg_n_test_name = "BM_Complexity_O_N_log_N"; +const char *big_o_n_lg_n_test_name = "BM_Complexity_O_N_log_N_BigO"; +const char *rms_o_n_lg_n_test_name = "BM_Complexity_O_N_log_N_RMS"; +const char *enum_auto_big_o_n_lg_n = "NlgN"; +const char *lambda_big_o_n_lg_n = "f\\(N\\)"; + +// Add enum tests +ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name, + rms_o_n_lg_n_test_name, enum_auto_big_o_n_lg_n); + +// Add lambda tests +ADD_COMPLEXITY_CASES(n_lg_n_test_name, big_o_n_lg_n_test_name, + rms_o_n_lg_n_test_name, lambda_big_o_n_lg_n); + +// ========================================================================= // +// -------- Testing formatting of Complexity with captured args ------------ // +// ========================================================================= // + +void BM_ComplexityCaptureArgs(benchmark::State& state, int n) { + for (auto _ : state) { + } + state.SetComplexityN(n); +} + +BENCHMARK_CAPTURE(BM_ComplexityCaptureArgs, capture_test, 100) + ->Complexity(benchmark::oN) + ->Ranges({{1, 2}, {3, 4}}); + +const std::string complexity_capture_name = + "BM_ComplexityCaptureArgs/capture_test"; + +ADD_COMPLEXITY_CASES(complexity_capture_name, complexity_capture_name + "_BigO", + complexity_capture_name + "_RMS", "N"); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char *argv[]) { RunOutputTests(argc, argv); } diff --git a/thirdparty/benchmark-1.5.0/test/cxx03_test.cc b/thirdparty/benchmark-1.5.0/test/cxx03_test.cc new file mode 100644 index 0000000000..c4c9a52273 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/cxx03_test.cc @@ -0,0 +1,63 @@ +#undef NDEBUG +#include +#include + +#include "benchmark/benchmark.h" + +#if __cplusplus >= 201103L +#error C++11 or greater detected. Should be C++03. +#endif + +#ifdef BENCHMARK_HAS_CXX11 +#error C++11 or greater detected by the library. BENCHMARK_HAS_CXX11 is defined. +#endif + +void BM_empty(benchmark::State& state) { + while (state.KeepRunning()) { + volatile benchmark::IterationCount x = state.iterations(); + ((void)x); + } +} +BENCHMARK(BM_empty); + +// The new C++11 interface for args/ranges requires initializer list support. +// Therefore we provide the old interface to support C++03. +void BM_old_arg_range_interface(benchmark::State& state) { + assert((state.range(0) == 1 && state.range(1) == 2) || + (state.range(0) == 5 && state.range(1) == 6)); + while (state.KeepRunning()) { + } +} +BENCHMARK(BM_old_arg_range_interface)->ArgPair(1, 2)->RangePair(5, 5, 6, 6); + +template +void BM_template2(benchmark::State& state) { + BM_empty(state); +} +BENCHMARK_TEMPLATE2(BM_template2, int, long); + +template +void BM_template1(benchmark::State& state) { + BM_empty(state); +} +BENCHMARK_TEMPLATE(BM_template1, long); +BENCHMARK_TEMPLATE1(BM_template1, int); + +template +struct BM_Fixture : public ::benchmark::Fixture { +}; + +BENCHMARK_TEMPLATE_F(BM_Fixture, BM_template1, long)(benchmark::State& state) { + BM_empty(state); +} +BENCHMARK_TEMPLATE1_F(BM_Fixture, BM_template2, int)(benchmark::State& state) { + BM_empty(state); +} + +void BM_counters(benchmark::State& state) { + BM_empty(state); + state.counters["Foo"] = 2; +} +BENCHMARK(BM_counters); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/diagnostics_test.cc b/thirdparty/benchmark-1.5.0/test/diagnostics_test.cc new file mode 100644 index 0000000000..dd64a33655 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/diagnostics_test.cc @@ -0,0 +1,80 @@ +// Testing: +// State::PauseTiming() +// State::ResumeTiming() +// Test that CHECK's within these function diagnose when they are called +// outside of the KeepRunning() loop. +// +// NOTE: Users should NOT include or use src/check.h. This is only done in +// order to test library internals. + +#include +#include + +#include "../src/check.h" +#include "benchmark/benchmark.h" + +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +#define TEST_HAS_NO_EXCEPTIONS +#endif + +void TestHandler() { +#ifndef TEST_HAS_NO_EXCEPTIONS + throw std::logic_error(""); +#else + std::abort(); +#endif +} + +void try_invalid_pause_resume(benchmark::State& state) { +#if !defined(TEST_BENCHMARK_LIBRARY_HAS_NO_ASSERTIONS) && !defined(TEST_HAS_NO_EXCEPTIONS) + try { + state.PauseTiming(); + std::abort(); + } catch (std::logic_error const&) { + } + try { + state.ResumeTiming(); + std::abort(); + } catch (std::logic_error const&) { + } +#else + (void)state; // avoid unused warning +#endif +} + +void BM_diagnostic_test(benchmark::State& state) { + static bool called_once = false; + + if (called_once == false) try_invalid_pause_resume(state); + + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } + + if (called_once == false) try_invalid_pause_resume(state); + + called_once = true; +} +BENCHMARK(BM_diagnostic_test); + + +void BM_diagnostic_test_keep_running(benchmark::State& state) { + static bool called_once = false; + + if (called_once == false) try_invalid_pause_resume(state); + + while(state.KeepRunning()) { + benchmark::DoNotOptimize(state.iterations()); + } + + if (called_once == false) try_invalid_pause_resume(state); + + called_once = true; +} +BENCHMARK(BM_diagnostic_test_keep_running); + +int main(int argc, char* argv[]) { + benchmark::internal::GetAbortHandler() = &TestHandler; + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/thirdparty/benchmark-1.5.0/test/display_aggregates_only_test.cc b/thirdparty/benchmark-1.5.0/test/display_aggregates_only_test.cc new file mode 100644 index 0000000000..3c36d3f03c --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/display_aggregates_only_test.cc @@ -0,0 +1,43 @@ + +#undef NDEBUG +#include +#include + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// Ok this test is super ugly. We want to check what happens with the file +// reporter in the presence of DisplayAggregatesOnly(). +// We do not care about console output, the normal tests check that already. + +void BM_SummaryRepeat(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryRepeat)->Repetitions(3)->DisplayAggregatesOnly(); + +int main(int argc, char* argv[]) { + const std::string output = GetFileReporterOutput(argc, argv); + + if (SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3") != 6 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3\"") != 3 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_mean\"") != 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_median\"") != + 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\"") != + 1) { + std::cout << "Precondition mismatch. Expected to only find 6 " + "occurrences of \"BM_SummaryRepeat/repeats:3\" substring:\n" + "\"name\": \"BM_SummaryRepeat/repeats:3\", " + "\"name\": \"BM_SummaryRepeat/repeats:3\", " + "\"name\": \"BM_SummaryRepeat/repeats:3\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_mean\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_median\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\"\nThe entire " + "output:\n"; + std::cout << output; + return 1; + } + + return 0; +} diff --git a/thirdparty/benchmark-1.5.0/test/donotoptimize_assembly_test.cc b/thirdparty/benchmark-1.5.0/test/donotoptimize_assembly_test.cc new file mode 100644 index 0000000000..d4b0bab70e --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/donotoptimize_assembly_test.cc @@ -0,0 +1,163 @@ +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wreturn-type" +#endif + +extern "C" { + +extern int ExternInt; +extern int ExternInt2; +extern int ExternInt3; + +inline int Add42(int x) { return x + 42; } + +struct NotTriviallyCopyable { + NotTriviallyCopyable(); + explicit NotTriviallyCopyable(int x) : value(x) {} + NotTriviallyCopyable(NotTriviallyCopyable const&); + int value; +}; + +struct Large { + int value; + int data[2]; +}; + +} +// CHECK-LABEL: test_with_rvalue: +extern "C" void test_with_rvalue() { + benchmark::DoNotOptimize(Add42(0)); + // CHECK: movl $42, %eax + // CHECK: ret +} + +// CHECK-LABEL: test_with_large_rvalue: +extern "C" void test_with_large_rvalue() { + benchmark::DoNotOptimize(Large{ExternInt, {ExternInt, ExternInt}}); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]] + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_non_trivial_rvalue: +extern "C" void test_with_non_trivial_rvalue() { + benchmark::DoNotOptimize(NotTriviallyCopyable(ExternInt)); + // CHECK: mov{{l|q}} ExternInt(%rip) + // CHECK: ret +} + +// CHECK-LABEL: test_with_lvalue: +extern "C" void test_with_lvalue() { + int x = 101; + benchmark::DoNotOptimize(x); + // CHECK-GNU: movl $101, %eax + // CHECK-CLANG: movl $101, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_large_lvalue: +extern "C" void test_with_large_lvalue() { + Large L{ExternInt, {ExternInt, ExternInt}}; + benchmark::DoNotOptimize(L); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_non_trivial_lvalue: +extern "C" void test_with_non_trivial_lvalue() { + NotTriviallyCopyable NTC(ExternInt); + benchmark::DoNotOptimize(NTC); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_const_lvalue: +extern "C" void test_with_const_lvalue() { + const int x = 123; + benchmark::DoNotOptimize(x); + // CHECK: movl $123, %eax + // CHECK: ret +} + +// CHECK-LABEL: test_with_large_const_lvalue: +extern "C" void test_with_large_const_lvalue() { + const Large L{ExternInt, {ExternInt, ExternInt}}; + benchmark::DoNotOptimize(L); + // CHECK: ExternInt(%rip) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: movl %eax, -{{[0-9]+}}(%[[REG]]) + // CHECK: ret +} + +// CHECK-LABEL: test_with_non_trivial_const_lvalue: +extern "C" void test_with_non_trivial_const_lvalue() { + const NotTriviallyCopyable Obj(ExternInt); + benchmark::DoNotOptimize(Obj); + // CHECK: mov{{q|l}} ExternInt(%rip) + // CHECK: ret +} + +// CHECK-LABEL: test_div_by_two: +extern "C" int test_div_by_two(int input) { + int divisor = 2; + benchmark::DoNotOptimize(divisor); + return input / divisor; + // CHECK: movl $2, [[DEST:.*]] + // CHECK: idivl [[DEST]] + // CHECK: ret +} + +// CHECK-LABEL: test_inc_integer: +extern "C" int test_inc_integer() { + int x = 0; + for (int i=0; i < 5; ++i) + benchmark::DoNotOptimize(++x); + // CHECK: movl $1, [[DEST:.*]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK: {{(addl \$1,|incl)}} [[DEST]] + // CHECK-CLANG: movl [[DEST]], %eax + // CHECK: ret + return x; +} + +// CHECK-LABEL: test_pointer_rvalue +extern "C" void test_pointer_rvalue() { + // CHECK: movl $42, [[DEST:.*]] + // CHECK: leaq [[DEST]], %rax + // CHECK-CLANG: movq %rax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret + int x = 42; + benchmark::DoNotOptimize(&x); +} + +// CHECK-LABEL: test_pointer_const_lvalue: +extern "C" void test_pointer_const_lvalue() { + // CHECK: movl $42, [[DEST:.*]] + // CHECK: leaq [[DEST]], %rax + // CHECK-CLANG: movq %rax, -{{[0-9]+}}(%[[REG:[a-z]+]]) + // CHECK: ret + int x = 42; + int * const xp = &x; + benchmark::DoNotOptimize(xp); +} + +// CHECK-LABEL: test_pointer_lvalue: +extern "C" void test_pointer_lvalue() { + // CHECK: movl $42, [[DEST:.*]] + // CHECK: leaq [[DEST]], %rax + // CHECK-CLANG: movq %rax, -{{[0-9]+}}(%[[REG:[a-z+]+]]) + // CHECK: ret + int x = 42; + int *xp = &x; + benchmark::DoNotOptimize(xp); +} diff --git a/thirdparty/benchmark-1.5.0/test/donotoptimize_test.cc b/thirdparty/benchmark-1.5.0/test/donotoptimize_test.cc new file mode 100644 index 0000000000..2ce92d1c72 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/donotoptimize_test.cc @@ -0,0 +1,52 @@ +#include "benchmark/benchmark.h" + +#include + +namespace { +#if defined(__GNUC__) +std::uint64_t double_up(const std::uint64_t x) __attribute__((const)); +#endif +std::uint64_t double_up(const std::uint64_t x) { return x * 2; } +} + +// Using DoNotOptimize on types like BitRef seem to cause a lot of problems +// with the inline assembly on both GCC and Clang. +struct BitRef { + int index; + unsigned char &byte; + +public: + static BitRef Make() { + static unsigned char arr[2] = {}; + BitRef b(1, arr[0]); + return b; + } +private: + BitRef(int i, unsigned char& b) : index(i), byte(b) {} +}; + +int main(int, char*[]) { + // this test verifies compilation of DoNotOptimize() for some types + + char buffer8[8] = ""; + benchmark::DoNotOptimize(buffer8); + + char buffer20[20] = ""; + benchmark::DoNotOptimize(buffer20); + + char buffer1024[1024] = ""; + benchmark::DoNotOptimize(buffer1024); + benchmark::DoNotOptimize(&buffer1024[0]); + + int x = 123; + benchmark::DoNotOptimize(x); + benchmark::DoNotOptimize(&x); + benchmark::DoNotOptimize(x += 42); + + benchmark::DoNotOptimize(double_up(x)); + + // These tests are to e + benchmark::DoNotOptimize(BitRef::Make()); + BitRef lval = BitRef::Make(); + benchmark::DoNotOptimize(lval); +} diff --git a/thirdparty/benchmark-1.5.0/test/filter_test.cc b/thirdparty/benchmark-1.5.0/test/filter_test.cc new file mode 100644 index 0000000000..0e27065c15 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/filter_test.cc @@ -0,0 +1,104 @@ +#include "benchmark/benchmark.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +class TestReporter : public benchmark::ConsoleReporter { + public: + virtual bool ReportContext(const Context& context) { + return ConsoleReporter::ReportContext(context); + }; + + virtual void ReportRuns(const std::vector& report) { + ++count_; + ConsoleReporter::ReportRuns(report); + }; + + TestReporter() : count_(0) {} + + virtual ~TestReporter() {} + + size_t GetCount() const { return count_; } + + private: + mutable size_t count_; +}; + +} // end namespace + +static void NoPrefix(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(NoPrefix); + +static void BM_Foo(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_Foo); + +static void BM_Bar(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_Bar); + +static void BM_FooBar(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_FooBar); + +static void BM_FooBa(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_FooBa); + +int main(int argc, char **argv) { + bool list_only = false; + for (int i = 0; i < argc; ++i) + list_only |= std::string(argv[i]).find("--benchmark_list_tests") != + std::string::npos; + + benchmark::Initialize(&argc, argv); + + TestReporter test_reporter; + const size_t returned_count = + benchmark::RunSpecifiedBenchmarks(&test_reporter); + + if (argc == 2) { + // Make sure we ran all of the tests + std::stringstream ss(argv[1]); + size_t expected_return; + ss >> expected_return; + + if (returned_count != expected_return) { + std::cerr << "ERROR: Expected " << expected_return + << " tests to match the filter but returned_count = " + << returned_count << std::endl; + return -1; + } + + const size_t expected_reports = list_only ? 0 : expected_return; + const size_t reports_count = test_reporter.GetCount(); + if (reports_count != expected_reports) { + std::cerr << "ERROR: Expected " << expected_reports + << " tests to be run but reported_count = " << reports_count + << std::endl; + return -1; + } + } + + return 0; +} diff --git a/thirdparty/benchmark-1.5.0/test/fixture_test.cc b/thirdparty/benchmark-1.5.0/test/fixture_test.cc new file mode 100644 index 0000000000..1462b10f02 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/fixture_test.cc @@ -0,0 +1,49 @@ + +#include "benchmark/benchmark.h" + +#include +#include + +class MyFixture : public ::benchmark::Fixture { + public: + void SetUp(const ::benchmark::State& state) { + if (state.thread_index == 0) { + assert(data.get() == nullptr); + data.reset(new int(42)); + } + } + + void TearDown(const ::benchmark::State& state) { + if (state.thread_index == 0) { + assert(data.get() != nullptr); + data.reset(); + } + } + + ~MyFixture() { assert(data == nullptr); } + + std::unique_ptr data; +}; + +BENCHMARK_F(MyFixture, Foo)(benchmark::State &st) { + assert(data.get() != nullptr); + assert(*data == 42); + for (auto _ : st) { + } +} + +BENCHMARK_DEFINE_F(MyFixture, Bar)(benchmark::State& st) { + if (st.thread_index == 0) { + assert(data.get() != nullptr); + assert(*data == 42); + } + for (auto _ : st) { + assert(data.get() != nullptr); + assert(*data == 42); + } + st.SetItemsProcessed(st.range(0)); +} +BENCHMARK_REGISTER_F(MyFixture, Bar)->Arg(42); +BENCHMARK_REGISTER_F(MyFixture, Bar)->Arg(42)->ThreadPerCpu(); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/internal_threading_test.cc b/thirdparty/benchmark-1.5.0/test/internal_threading_test.cc new file mode 100644 index 0000000000..039d7c14a8 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/internal_threading_test.cc @@ -0,0 +1,184 @@ + +#undef NDEBUG + +#include +#include +#include "../src/timers.h" +#include "benchmark/benchmark.h" +#include "output_test.h" + +static const std::chrono::duration time_frame(50); +static const double time_frame_in_sec( + std::chrono::duration_cast>>( + time_frame) + .count()); + +void MyBusySpinwait() { + const auto start = benchmark::ChronoClockNow(); + + while (true) { + const auto now = benchmark::ChronoClockNow(); + const auto elapsed = now - start; + + if (std::chrono::duration(elapsed) >= + time_frame) + return; + } +} + +// ========================================================================= // +// --------------------------- TEST CASES BEGIN ---------------------------- // +// ========================================================================= // + +// ========================================================================= // +// BM_MainThread + +void BM_MainThread(benchmark::State& state) { + for (auto _ : state) { + MyBusySpinwait(); + state.SetIterationTime(time_frame_in_sec); + } + state.counters["invtime"] = + benchmark::Counter{1, benchmark::Counter::kIsRate}; +} + +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1)->UseRealTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1)->UseManualTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(1)->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2)->UseRealTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2)->UseManualTime(); +BENCHMARK(BM_MainThread)->Iterations(1)->Threads(2)->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +// ========================================================================= // +// BM_WorkerThread + +void BM_WorkerThread(benchmark::State& state) { + for (auto _ : state) { + std::thread Worker(&MyBusySpinwait); + Worker.join(); + state.SetIterationTime(time_frame_in_sec); + } + state.counters["invtime"] = + benchmark::Counter{1, benchmark::Counter::kIsRate}; +} + +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1)->UseRealTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1)->UseManualTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(1)->MeasureProcessCPUTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2)->UseRealTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2)->UseManualTime(); +BENCHMARK(BM_WorkerThread)->Iterations(1)->Threads(2)->MeasureProcessCPUTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_WorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +// ========================================================================= // +// BM_MainThreadAndWorkerThread + +void BM_MainThreadAndWorkerThread(benchmark::State& state) { + for (auto _ : state) { + std::thread Worker(&MyBusySpinwait); + MyBusySpinwait(); + Worker.join(); + state.SetIterationTime(time_frame_in_sec); + } + state.counters["invtime"] = + benchmark::Counter{1, benchmark::Counter::kIsRate}; +} + +BENCHMARK(BM_MainThreadAndWorkerThread)->Iterations(1)->Threads(1); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->UseManualTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(1) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +BENCHMARK(BM_MainThreadAndWorkerThread)->Iterations(1)->Threads(2); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->UseManualTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK(BM_MainThreadAndWorkerThread) + ->Iterations(1) + ->Threads(2) + ->MeasureProcessCPUTime() + ->UseManualTime(); + +// ========================================================================= // +// ---------------------------- TEST CASES END ----------------------------- // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/thirdparty/benchmark-1.5.0/test/link_main_test.cc b/thirdparty/benchmark-1.5.0/test/link_main_test.cc new file mode 100644 index 0000000000..241ad5c390 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/link_main_test.cc @@ -0,0 +1,8 @@ +#include "benchmark/benchmark.h" + +void BM_empty(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } +} +BENCHMARK(BM_empty); diff --git a/thirdparty/benchmark-1.5.0/test/map_test.cc b/thirdparty/benchmark-1.5.0/test/map_test.cc new file mode 100644 index 0000000000..dbf7982a36 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/map_test.cc @@ -0,0 +1,57 @@ +#include "benchmark/benchmark.h" + +#include +#include + +namespace { + +std::map ConstructRandomMap(int size) { + std::map m; + for (int i = 0; i < size; ++i) { + m.insert(std::make_pair(std::rand() % size, std::rand() % size)); + } + return m; +} + +} // namespace + +// Basic version. +static void BM_MapLookup(benchmark::State& state) { + const int size = static_cast(state.range(0)); + std::map m; + for (auto _ : state) { + state.PauseTiming(); + m = ConstructRandomMap(size); + state.ResumeTiming(); + for (int i = 0; i < size; ++i) { + benchmark::DoNotOptimize(m.find(std::rand() % size)); + } + } + state.SetItemsProcessed(state.iterations() * size); +} +BENCHMARK(BM_MapLookup)->Range(1 << 3, 1 << 12); + +// Using fixtures. +class MapFixture : public ::benchmark::Fixture { + public: + void SetUp(const ::benchmark::State& st) { + m = ConstructRandomMap(static_cast(st.range(0))); + } + + void TearDown(const ::benchmark::State&) { m.clear(); } + + std::map m; +}; + +BENCHMARK_DEFINE_F(MapFixture, Lookup)(benchmark::State& state) { + const int size = static_cast(state.range(0)); + for (auto _ : state) { + for (int i = 0; i < size; ++i) { + benchmark::DoNotOptimize(m.find(std::rand() % size)); + } + } + state.SetItemsProcessed(state.iterations() * size); +} +BENCHMARK_REGISTER_F(MapFixture, Lookup)->Range(1 << 3, 1 << 12); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/memory_manager_test.cc b/thirdparty/benchmark-1.5.0/test/memory_manager_test.cc new file mode 100644 index 0000000000..90bed16cff --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/memory_manager_test.cc @@ -0,0 +1,44 @@ +#include + +#include "../src/check.h" +#include "benchmark/benchmark.h" +#include "output_test.h" + +class TestMemoryManager : public benchmark::MemoryManager { + void Start() {} + void Stop(Result* result) { + result->num_allocs = 42; + result->max_bytes_used = 42000; + } +}; + +void BM_empty(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } +} +BENCHMARK(BM_empty); + +ADD_CASES(TC_ConsoleOut, {{"^BM_empty %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_empty\",$"}, + {"\"run_name\": \"BM_empty\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"allocs_per_iter\": %float,$", MR_Next}, + {"\"max_bytes_used\": 42000$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_empty\",%csv_report$"}}); + +int main(int argc, char* argv[]) { + std::unique_ptr mm(new TestMemoryManager()); + + benchmark::RegisterMemoryManager(mm.get()); + RunOutputTests(argc, argv); + benchmark::RegisterMemoryManager(nullptr); +} diff --git a/thirdparty/benchmark-1.5.0/test/multiple_ranges_test.cc b/thirdparty/benchmark-1.5.0/test/multiple_ranges_test.cc new file mode 100644 index 0000000000..b25f40eb52 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/multiple_ranges_test.cc @@ -0,0 +1,96 @@ +#include "benchmark/benchmark.h" + +#include +#include +#include +#include + +class MultipleRangesFixture : public ::benchmark::Fixture { + public: + MultipleRangesFixture() + : expectedValues({{1, 3, 5}, + {1, 3, 8}, + {1, 3, 15}, + {2, 3, 5}, + {2, 3, 8}, + {2, 3, 15}, + {1, 4, 5}, + {1, 4, 8}, + {1, 4, 15}, + {2, 4, 5}, + {2, 4, 8}, + {2, 4, 15}, + {1, 7, 5}, + {1, 7, 8}, + {1, 7, 15}, + {2, 7, 5}, + {2, 7, 8}, + {2, 7, 15}, + {7, 6, 3}}) {} + + void SetUp(const ::benchmark::State& state) { + std::vector ranges = {state.range(0), state.range(1), + state.range(2)}; + + assert(expectedValues.find(ranges) != expectedValues.end()); + + actualValues.insert(ranges); + } + + // NOTE: This is not TearDown as we want to check after _all_ runs are + // complete. + virtual ~MultipleRangesFixture() { + if (actualValues != expectedValues) { + std::cout << "EXPECTED\n"; + for (auto v : expectedValues) { + std::cout << "{"; + for (int64_t iv : v) { + std::cout << iv << ", "; + } + std::cout << "}\n"; + } + std::cout << "ACTUAL\n"; + for (auto v : actualValues) { + std::cout << "{"; + for (int64_t iv : v) { + std::cout << iv << ", "; + } + std::cout << "}\n"; + } + } + } + + std::set> expectedValues; + std::set> actualValues; +}; + +BENCHMARK_DEFINE_F(MultipleRangesFixture, Empty)(benchmark::State& state) { + for (auto _ : state) { + int64_t product = state.range(0) * state.range(1) * state.range(2); + for (int64_t x = 0; x < product; x++) { + benchmark::DoNotOptimize(x); + } + } +} + +BENCHMARK_REGISTER_F(MultipleRangesFixture, Empty) + ->RangeMultiplier(2) + ->Ranges({{1, 2}, {3, 7}, {5, 15}}) + ->Args({7, 6, 3}); + +void BM_CheckDefaultArgument(benchmark::State& state) { + // Test that the 'range()' without an argument is the same as 'range(0)'. + assert(state.range() == state.range(0)); + assert(state.range() != state.range(1)); + for (auto _ : state) { + } +} +BENCHMARK(BM_CheckDefaultArgument)->Ranges({{1, 5}, {6, 10}}); + +static void BM_MultipleRanges(benchmark::State& st) { + for (auto _ : st) { + } +} +BENCHMARK(BM_MultipleRanges)->Ranges({{5, 5}, {6, 6}}); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/options_test.cc b/thirdparty/benchmark-1.5.0/test/options_test.cc new file mode 100644 index 0000000000..7bfc235465 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/options_test.cc @@ -0,0 +1,75 @@ +#include "benchmark/benchmark.h" +#include +#include + +#if defined(NDEBUG) +#undef NDEBUG +#endif +#include + +void BM_basic(benchmark::State& state) { + for (auto _ : state) { + } +} + +void BM_basic_slow(benchmark::State& state) { + std::chrono::milliseconds sleep_duration(state.range(0)); + for (auto _ : state) { + std::this_thread::sleep_for( + std::chrono::duration_cast(sleep_duration)); + } +} + +BENCHMARK(BM_basic); +BENCHMARK(BM_basic)->Arg(42); +BENCHMARK(BM_basic_slow)->Arg(10)->Unit(benchmark::kNanosecond); +BENCHMARK(BM_basic_slow)->Arg(100)->Unit(benchmark::kMicrosecond); +BENCHMARK(BM_basic_slow)->Arg(1000)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_basic)->Range(1, 8); +BENCHMARK(BM_basic)->RangeMultiplier(2)->Range(1, 8); +BENCHMARK(BM_basic)->DenseRange(10, 15); +BENCHMARK(BM_basic)->Args({42, 42}); +BENCHMARK(BM_basic)->Ranges({{64, 512}, {64, 512}}); +BENCHMARK(BM_basic)->MinTime(0.7); +BENCHMARK(BM_basic)->UseRealTime(); +BENCHMARK(BM_basic)->ThreadRange(2, 4); +BENCHMARK(BM_basic)->ThreadPerCpu(); +BENCHMARK(BM_basic)->Repetitions(3); +BENCHMARK(BM_basic) + ->RangeMultiplier(std::numeric_limits::max()) + ->Range(std::numeric_limits::min(), + std::numeric_limits::max()); + +// Negative ranges +BENCHMARK(BM_basic)->Range(-64, -1); +BENCHMARK(BM_basic)->RangeMultiplier(4)->Range(-8, 8); +BENCHMARK(BM_basic)->DenseRange(-2, 2, 1); +BENCHMARK(BM_basic)->Ranges({{-64, 1}, {-8, -1}}); + +void CustomArgs(benchmark::internal::Benchmark* b) { + for (int i = 0; i < 10; ++i) { + b->Arg(i); + } +} + +BENCHMARK(BM_basic)->Apply(CustomArgs); + +void BM_explicit_iteration_count(benchmark::State& state) { + // Test that benchmarks specified with an explicit iteration count are + // only run once. + static bool invoked_before = false; + assert(!invoked_before); + invoked_before = true; + + // Test that the requested iteration count is respected. + assert(state.max_iterations == 42); + size_t actual_iterations = 0; + for (auto _ : state) + ++actual_iterations; + assert(state.iterations() == state.max_iterations); + assert(state.iterations() == 42); + +} +BENCHMARK(BM_explicit_iteration_count)->Iterations(42); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/output_test.h b/thirdparty/benchmark-1.5.0/test/output_test.h new file mode 100644 index 0000000000..9385761b21 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/output_test.h @@ -0,0 +1,213 @@ +#ifndef TEST_OUTPUT_TEST_H +#define TEST_OUTPUT_TEST_H + +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include + +#include "../src/re.h" +#include "benchmark/benchmark.h" + +#define CONCAT2(x, y) x##y +#define CONCAT(x, y) CONCAT2(x, y) + +#define ADD_CASES(...) int CONCAT(dummy, __LINE__) = ::AddCases(__VA_ARGS__) + +#define SET_SUBSTITUTIONS(...) \ + int CONCAT(dummy, __LINE__) = ::SetSubstitutions(__VA_ARGS__) + +enum MatchRules { + MR_Default, // Skip non-matching lines until a match is found. + MR_Next, // Match must occur on the next line. + MR_Not // No line between the current position and the next match matches + // the regex +}; + +struct TestCase { + TestCase(std::string re, int rule = MR_Default); + + std::string regex_str; + int match_rule; + std::string substituted_regex; + std::shared_ptr regex; +}; + +enum TestCaseID { + TC_ConsoleOut, + TC_ConsoleErr, + TC_JSONOut, + TC_JSONErr, + TC_CSVOut, + TC_CSVErr, + + TC_NumID // PRIVATE +}; + +// Add a list of test cases to be run against the output specified by +// 'ID' +int AddCases(TestCaseID ID, std::initializer_list il); + +// Add or set a list of substitutions to be performed on constructed regex's +// See 'output_test_helper.cc' for a list of default substitutions. +int SetSubstitutions( + std::initializer_list> il); + +// Run all output tests. +void RunOutputTests(int argc, char* argv[]); + +// Count the number of 'pat' substrings in the 'haystack' string. +int SubstrCnt(const std::string& haystack, const std::string& pat); + +// Run registered benchmarks with file reporter enabled, and return the content +// outputted by the file reporter. +std::string GetFileReporterOutput(int argc, char* argv[]); + +// ========================================================================= // +// ------------------------- Results checking ------------------------------ // +// ========================================================================= // + +// Call this macro to register a benchmark for checking its results. This +// should be all that's needed. It subscribes a function to check the (CSV) +// results of a benchmark. This is done only after verifying that the output +// strings are really as expected. +// bm_name_pattern: a name or a regex pattern which will be matched against +// all the benchmark names. Matching benchmarks +// will be the subject of a call to checker_function +// checker_function: should be of type ResultsCheckFn (see below) +#define CHECK_BENCHMARK_RESULTS(bm_name_pattern, checker_function) \ + size_t CONCAT(dummy, __LINE__) = AddChecker(bm_name_pattern, checker_function) + +struct Results; +typedef std::function ResultsCheckFn; + +size_t AddChecker(const char* bm_name_pattern, ResultsCheckFn fn); + +// Class holding the results of a benchmark. +// It is passed in calls to checker functions. +struct Results { + // the benchmark name + std::string name; + // the benchmark fields + std::map values; + + Results(const std::string& n) : name(n) {} + + int NumThreads() const; + + double NumIterations() const; + + typedef enum { kCpuTime, kRealTime } BenchmarkTime; + + // get cpu_time or real_time in seconds + double GetTime(BenchmarkTime which) const; + + // get the real_time duration of the benchmark in seconds. + // it is better to use fuzzy float checks for this, as the float + // ASCII formatting is lossy. + double DurationRealTime() const { + return NumIterations() * GetTime(kRealTime); + } + // get the cpu_time duration of the benchmark in seconds + double DurationCPUTime() const { + return NumIterations() * GetTime(kCpuTime); + } + + // get the string for a result by name, or nullptr if the name + // is not found + const std::string* Get(const char* entry_name) const { + auto it = values.find(entry_name); + if (it == values.end()) return nullptr; + return &it->second; + } + + // get a result by name, parsed as a specific type. + // NOTE: for counters, use GetCounterAs instead. + template + T GetAs(const char* entry_name) const; + + // counters are written as doubles, so they have to be read first + // as a double, and only then converted to the asked type. + template + T GetCounterAs(const char* entry_name) const { + double dval = GetAs(entry_name); + T tval = static_cast(dval); + return tval; + } +}; + +template +T Results::GetAs(const char* entry_name) const { + auto* sv = Get(entry_name); + CHECK(sv != nullptr && !sv->empty()); + std::stringstream ss; + ss << *sv; + T out; + ss >> out; + CHECK(!ss.fail()); + return out; +} + +//---------------------------------- +// Macros to help in result checking. Do not use them with arguments causing +// side-effects. + +// clang-format off + +#define _CHECK_RESULT_VALUE(entry, getfn, var_type, var_name, relationship, value) \ + CONCAT(CHECK_, relationship) \ + (entry.getfn< var_type >(var_name), (value)) << "\n" \ + << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ + << __FILE__ << ":" << __LINE__ << ": " \ + << "expected (" << #var_type << ")" << (var_name) \ + << "=" << (entry).getfn< var_type >(var_name) \ + << " to be " #relationship " to " << (value) << "\n" + +// check with tolerance. eps_factor is the tolerance window, which is +// interpreted relative to value (eg, 0.1 means 10% of value). +#define _CHECK_FLOAT_RESULT_VALUE(entry, getfn, var_type, var_name, relationship, value, eps_factor) \ + CONCAT(CHECK_FLOAT_, relationship) \ + (entry.getfn< var_type >(var_name), (value), (eps_factor) * (value)) << "\n" \ + << __FILE__ << ":" << __LINE__ << ": " << (entry).name << ":\n" \ + << __FILE__ << ":" << __LINE__ << ": " \ + << "expected (" << #var_type << ")" << (var_name) \ + << "=" << (entry).getfn< var_type >(var_name) \ + << " to be " #relationship " to " << (value) << "\n" \ + << __FILE__ << ":" << __LINE__ << ": " \ + << "with tolerance of " << (eps_factor) * (value) \ + << " (" << (eps_factor)*100. << "%), " \ + << "but delta was " << ((entry).getfn< var_type >(var_name) - (value)) \ + << " (" << (((entry).getfn< var_type >(var_name) - (value)) \ + / \ + ((value) > 1.e-5 || value < -1.e-5 ? value : 1.e-5)*100.) \ + << "%)" + +#define CHECK_RESULT_VALUE(entry, var_type, var_name, relationship, value) \ + _CHECK_RESULT_VALUE(entry, GetAs, var_type, var_name, relationship, value) + +#define CHECK_COUNTER_VALUE(entry, var_type, var_name, relationship, value) \ + _CHECK_RESULT_VALUE(entry, GetCounterAs, var_type, var_name, relationship, value) + +#define CHECK_FLOAT_RESULT_VALUE(entry, var_name, relationship, value, eps_factor) \ + _CHECK_FLOAT_RESULT_VALUE(entry, GetAs, double, var_name, relationship, value, eps_factor) + +#define CHECK_FLOAT_COUNTER_VALUE(entry, var_name, relationship, value, eps_factor) \ + _CHECK_FLOAT_RESULT_VALUE(entry, GetCounterAs, double, var_name, relationship, value, eps_factor) + +// clang-format on + +// ========================================================================= // +// --------------------------- Misc Utilities ------------------------------ // +// ========================================================================= // + +namespace { + +const char* const dec_re = "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"; + +} // end namespace + +#endif // TEST_OUTPUT_TEST_H diff --git a/thirdparty/benchmark-1.5.0/test/output_test_helper.cc b/thirdparty/benchmark-1.5.0/test/output_test_helper.cc new file mode 100644 index 0000000000..5dc951d2bc --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/output_test_helper.cc @@ -0,0 +1,505 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/benchmark_api_internal.h" +#include "../src/check.h" // NOTE: check.h is for internal use only! +#include "../src/re.h" // NOTE: re.h is for internal use only +#include "output_test.h" + +// ========================================================================= // +// ------------------------------ Internals -------------------------------- // +// ========================================================================= // +namespace internal { +namespace { + +using TestCaseList = std::vector; + +// Use a vector because the order elements are added matters during iteration. +// std::map/unordered_map don't guarantee that. +// For example: +// SetSubstitutions({{"%HelloWorld", "Hello"}, {"%Hello", "Hi"}}); +// Substitute("%HelloWorld") // Always expands to Hello. +using SubMap = std::vector>; + +TestCaseList& GetTestCaseList(TestCaseID ID) { + // Uses function-local statics to ensure initialization occurs + // before first use. + static TestCaseList lists[TC_NumID]; + return lists[ID]; +} + +SubMap& GetSubstitutions() { + // Don't use 'dec_re' from header because it may not yet be initialized. + // clang-format off + static std::string safe_dec_re = "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"; + static std::string time_re = "([0-9]+[.])?[0-9]+"; + static SubMap map = { + {"%float", "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?"}, + // human-readable float + {"%hrfloat", "[0-9]*[.]?[0-9]+([eE][-+][0-9]+)?[kMGTPEZYmunpfazy]?"}, + {"%int", "[ ]*[0-9]+"}, + {" %s ", "[ ]+"}, + {"%time", "[ ]*" + time_re + "[ ]+ns"}, + {"%console_report", "[ ]*" + time_re + "[ ]+ns [ ]*" + time_re + "[ ]+ns [ ]*[0-9]+"}, + {"%console_time_only_report", "[ ]*" + time_re + "[ ]+ns [ ]*" + time_re + "[ ]+ns"}, + {"%console_us_report", "[ ]*" + time_re + "[ ]+us [ ]*" + time_re + "[ ]+us [ ]*[0-9]+"}, + {"%console_us_time_only_report", "[ ]*" + time_re + "[ ]+us [ ]*" + time_re + "[ ]+us"}, + {"%csv_header", + "name,iterations,real_time,cpu_time,time_unit,bytes_per_second," + "items_per_second,label,error_occurred,error_message"}, + {"%csv_report", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns,,,,,"}, + {"%csv_us_report", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",us,,,,,"}, + {"%csv_bytes_report", + "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns," + safe_dec_re + ",,,,"}, + {"%csv_items_report", + "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns,," + safe_dec_re + ",,,"}, + {"%csv_bytes_items_report", + "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns," + safe_dec_re + + "," + safe_dec_re + ",,,"}, + {"%csv_label_report_begin", "[0-9]+," + safe_dec_re + "," + safe_dec_re + ",ns,,,"}, + {"%csv_label_report_end", ",,"}}; + // clang-format on + return map; +} + +std::string PerformSubstitutions(std::string source) { + SubMap const& subs = GetSubstitutions(); + using SizeT = std::string::size_type; + for (auto const& KV : subs) { + SizeT pos; + SizeT next_start = 0; + while ((pos = source.find(KV.first, next_start)) != std::string::npos) { + next_start = pos + KV.second.size(); + source.replace(pos, KV.first.size(), KV.second); + } + } + return source; +} + +void CheckCase(std::stringstream& remaining_output, TestCase const& TC, + TestCaseList const& not_checks) { + std::string first_line; + bool on_first = true; + std::string line; + while (remaining_output.eof() == false) { + CHECK(remaining_output.good()); + std::getline(remaining_output, line); + if (on_first) { + first_line = line; + on_first = false; + } + for (const auto& NC : not_checks) { + CHECK(!NC.regex->Match(line)) + << "Unexpected match for line \"" << line << "\" for MR_Not regex \"" + << NC.regex_str << "\"" + << "\n actual regex string \"" << TC.substituted_regex << "\"" + << "\n started matching near: " << first_line; + } + if (TC.regex->Match(line)) return; + CHECK(TC.match_rule != MR_Next) + << "Expected line \"" << line << "\" to match regex \"" << TC.regex_str + << "\"" + << "\n actual regex string \"" << TC.substituted_regex << "\"" + << "\n started matching near: " << first_line; + } + CHECK(remaining_output.eof() == false) + << "End of output reached before match for regex \"" << TC.regex_str + << "\" was found" + << "\n actual regex string \"" << TC.substituted_regex << "\"" + << "\n started matching near: " << first_line; +} + +void CheckCases(TestCaseList const& checks, std::stringstream& output) { + std::vector not_checks; + for (size_t i = 0; i < checks.size(); ++i) { + const auto& TC = checks[i]; + if (TC.match_rule == MR_Not) { + not_checks.push_back(TC); + continue; + } + CheckCase(output, TC, not_checks); + not_checks.clear(); + } +} + +class TestReporter : public benchmark::BenchmarkReporter { + public: + TestReporter(std::vector reps) + : reporters_(reps) {} + + virtual bool ReportContext(const Context& context) { + bool last_ret = false; + bool first = true; + for (auto rep : reporters_) { + bool new_ret = rep->ReportContext(context); + CHECK(first || new_ret == last_ret) + << "Reports return different values for ReportContext"; + first = false; + last_ret = new_ret; + } + (void)first; + return last_ret; + } + + void ReportRuns(const std::vector& report) { + for (auto rep : reporters_) rep->ReportRuns(report); + } + void Finalize() { + for (auto rep : reporters_) rep->Finalize(); + } + + private: + std::vector reporters_; +}; +} // namespace + +} // end namespace internal + +// ========================================================================= // +// -------------------------- Results checking ----------------------------- // +// ========================================================================= // + +namespace internal { + +// Utility class to manage subscribers for checking benchmark results. +// It works by parsing the CSV output to read the results. +class ResultsChecker { + public: + struct PatternAndFn : public TestCase { // reusing TestCase for its regexes + PatternAndFn(const std::string& rx, ResultsCheckFn fn_) + : TestCase(rx), fn(fn_) {} + ResultsCheckFn fn; + }; + + std::vector check_patterns; + std::vector results; + std::vector field_names; + + void Add(const std::string& entry_pattern, ResultsCheckFn fn); + + void CheckResults(std::stringstream& output); + + private: + void SetHeader_(const std::string& csv_header); + void SetValues_(const std::string& entry_csv_line); + + std::vector SplitCsv_(const std::string& line); +}; + +// store the static ResultsChecker in a function to prevent initialization +// order problems +ResultsChecker& GetResultsChecker() { + static ResultsChecker rc; + return rc; +} + +// add a results checker for a benchmark +void ResultsChecker::Add(const std::string& entry_pattern, ResultsCheckFn fn) { + check_patterns.emplace_back(entry_pattern, fn); +} + +// check the results of all subscribed benchmarks +void ResultsChecker::CheckResults(std::stringstream& output) { + // first reset the stream to the start + { + auto start = std::stringstream::pos_type(0); + // clear before calling tellg() + output.clear(); + // seek to zero only when needed + if (output.tellg() > start) output.seekg(start); + // and just in case + output.clear(); + } + // now go over every line and publish it to the ResultsChecker + std::string line; + bool on_first = true; + while (output.eof() == false) { + CHECK(output.good()); + std::getline(output, line); + if (on_first) { + SetHeader_(line); // this is important + on_first = false; + continue; + } + SetValues_(line); + } + // finally we can call the subscribed check functions + for (const auto& p : check_patterns) { + VLOG(2) << "--------------------------------\n"; + VLOG(2) << "checking for benchmarks matching " << p.regex_str << "...\n"; + for (const auto& r : results) { + if (!p.regex->Match(r.name)) { + VLOG(2) << p.regex_str << " is not matched by " << r.name << "\n"; + continue; + } else { + VLOG(2) << p.regex_str << " is matched by " << r.name << "\n"; + } + VLOG(1) << "Checking results of " << r.name << ": ... \n"; + p.fn(r); + VLOG(1) << "Checking results of " << r.name << ": OK.\n"; + } + } +} + +// prepare for the names in this header +void ResultsChecker::SetHeader_(const std::string& csv_header) { + field_names = SplitCsv_(csv_header); +} + +// set the values for a benchmark +void ResultsChecker::SetValues_(const std::string& entry_csv_line) { + if (entry_csv_line.empty()) return; // some lines are empty + CHECK(!field_names.empty()); + auto vals = SplitCsv_(entry_csv_line); + CHECK_EQ(vals.size(), field_names.size()); + results.emplace_back(vals[0]); // vals[0] is the benchmark name + auto& entry = results.back(); + for (size_t i = 1, e = vals.size(); i < e; ++i) { + entry.values[field_names[i]] = vals[i]; + } +} + +// a quick'n'dirty csv splitter (eliminating quotes) +std::vector ResultsChecker::SplitCsv_(const std::string& line) { + std::vector out; + if (line.empty()) return out; + if (!field_names.empty()) out.reserve(field_names.size()); + size_t prev = 0, pos = line.find_first_of(','), curr = pos; + while (pos != line.npos) { + CHECK(curr > 0); + if (line[prev] == '"') ++prev; + if (line[curr - 1] == '"') --curr; + out.push_back(line.substr(prev, curr - prev)); + prev = pos + 1; + pos = line.find_first_of(',', pos + 1); + curr = pos; + } + curr = line.size(); + if (line[prev] == '"') ++prev; + if (line[curr - 1] == '"') --curr; + out.push_back(line.substr(prev, curr - prev)); + return out; +} + +} // end namespace internal + +size_t AddChecker(const char* bm_name, ResultsCheckFn fn) { + auto& rc = internal::GetResultsChecker(); + rc.Add(bm_name, fn); + return rc.results.size(); +} + +int Results::NumThreads() const { + auto pos = name.find("/threads:"); + if (pos == name.npos) return 1; + auto end = name.find('/', pos + 9); + std::stringstream ss; + ss << name.substr(pos + 9, end); + int num = 1; + ss >> num; + CHECK(!ss.fail()); + return num; +} + +double Results::NumIterations() const { + return GetAs("iterations"); +} + +double Results::GetTime(BenchmarkTime which) const { + CHECK(which == kCpuTime || which == kRealTime); + const char* which_str = which == kCpuTime ? "cpu_time" : "real_time"; + double val = GetAs(which_str); + auto unit = Get("time_unit"); + CHECK(unit); + if (*unit == "ns") { + return val * 1.e-9; + } else if (*unit == "us") { + return val * 1.e-6; + } else if (*unit == "ms") { + return val * 1.e-3; + } else if (*unit == "s") { + return val; + } else { + CHECK(1 == 0) << "unknown time unit: " << *unit; + return 0; + } +} + +// ========================================================================= // +// -------------------------- Public API Definitions------------------------ // +// ========================================================================= // + +TestCase::TestCase(std::string re, int rule) + : regex_str(std::move(re)), + match_rule(rule), + substituted_regex(internal::PerformSubstitutions(regex_str)), + regex(std::make_shared()) { + std::string err_str; + regex->Init(substituted_regex, &err_str); + CHECK(err_str.empty()) << "Could not construct regex \"" << substituted_regex + << "\"" + << "\n originally \"" << regex_str << "\"" + << "\n got error: " << err_str; +} + +int AddCases(TestCaseID ID, std::initializer_list il) { + auto& L = internal::GetTestCaseList(ID); + L.insert(L.end(), il); + return 0; +} + +int SetSubstitutions( + std::initializer_list> il) { + auto& subs = internal::GetSubstitutions(); + for (auto KV : il) { + bool exists = false; + KV.second = internal::PerformSubstitutions(KV.second); + for (auto& EKV : subs) { + if (EKV.first == KV.first) { + EKV.second = std::move(KV.second); + exists = true; + break; + } + } + if (!exists) subs.push_back(std::move(KV)); + } + return 0; +} + +void RunOutputTests(int argc, char* argv[]) { + using internal::GetTestCaseList; + benchmark::Initialize(&argc, argv); + auto options = benchmark::internal::GetOutputOptions(/*force_no_color*/ true); + benchmark::ConsoleReporter CR(options); + benchmark::JSONReporter JR; + benchmark::CSVReporter CSVR; + struct ReporterTest { + const char* name; + std::vector& output_cases; + std::vector& error_cases; + benchmark::BenchmarkReporter& reporter; + std::stringstream out_stream; + std::stringstream err_stream; + + ReporterTest(const char* n, std::vector& out_tc, + std::vector& err_tc, + benchmark::BenchmarkReporter& br) + : name(n), output_cases(out_tc), error_cases(err_tc), reporter(br) { + reporter.SetOutputStream(&out_stream); + reporter.SetErrorStream(&err_stream); + } + } TestCases[] = { + {"ConsoleReporter", GetTestCaseList(TC_ConsoleOut), + GetTestCaseList(TC_ConsoleErr), CR}, + {"JSONReporter", GetTestCaseList(TC_JSONOut), GetTestCaseList(TC_JSONErr), + JR}, + {"CSVReporter", GetTestCaseList(TC_CSVOut), GetTestCaseList(TC_CSVErr), + CSVR}, + }; + + // Create the test reporter and run the benchmarks. + std::cout << "Running benchmarks...\n"; + internal::TestReporter test_rep({&CR, &JR, &CSVR}); + benchmark::RunSpecifiedBenchmarks(&test_rep); + + for (auto& rep_test : TestCases) { + std::string msg = std::string("\nTesting ") + rep_test.name + " Output\n"; + std::string banner(msg.size() - 1, '-'); + std::cout << banner << msg << banner << "\n"; + + std::cerr << rep_test.err_stream.str(); + std::cout << rep_test.out_stream.str(); + + internal::CheckCases(rep_test.error_cases, rep_test.err_stream); + internal::CheckCases(rep_test.output_cases, rep_test.out_stream); + + std::cout << "\n"; + } + + // now that we know the output is as expected, we can dispatch + // the checks to subscribees. + auto& csv = TestCases[2]; + // would use == but gcc spits a warning + CHECK(std::strcmp(csv.name, "CSVReporter") == 0); + internal::GetResultsChecker().CheckResults(csv.out_stream); +} + +int SubstrCnt(const std::string& haystack, const std::string& pat) { + if (pat.length() == 0) return 0; + int count = 0; + for (size_t offset = haystack.find(pat); offset != std::string::npos; + offset = haystack.find(pat, offset + pat.length())) + ++count; + return count; +} + +static char ToHex(int ch) { + return ch < 10 ? static_cast('0' + ch) + : static_cast('a' + (ch - 10)); +} + +static char RandomHexChar() { + static std::mt19937 rd{std::random_device{}()}; + static std::uniform_int_distribution mrand{0, 15}; + return ToHex(mrand(rd)); +} + +static std::string GetRandomFileName() { + std::string model = "test.%%%%%%"; + for (auto & ch : model) { + if (ch == '%') + ch = RandomHexChar(); + } + return model; +} + +static bool FileExists(std::string const& name) { + std::ifstream in(name.c_str()); + return in.good(); +} + +static std::string GetTempFileName() { + // This function attempts to avoid race conditions where two tests + // create the same file at the same time. However, it still introduces races + // similar to tmpnam. + int retries = 3; + while (--retries) { + std::string name = GetRandomFileName(); + if (!FileExists(name)) + return name; + } + std::cerr << "Failed to create unique temporary file name" << std::endl; + std::abort(); +} + +std::string GetFileReporterOutput(int argc, char* argv[]) { + std::vector new_argv(argv, argv + argc); + assert(static_cast(argc) == new_argv.size()); + + std::string tmp_file_name = GetTempFileName(); + std::cout << "Will be using this as the tmp file: " << tmp_file_name << '\n'; + + std::string tmp = "--benchmark_out="; + tmp += tmp_file_name; + new_argv.emplace_back(const_cast(tmp.c_str())); + + argc = int(new_argv.size()); + + benchmark::Initialize(&argc, new_argv.data()); + benchmark::RunSpecifiedBenchmarks(); + + // Read the output back from the file, and delete the file. + std::ifstream tmp_stream(tmp_file_name); + std::string output = std::string((std::istreambuf_iterator(tmp_stream)), + std::istreambuf_iterator()); + std::remove(tmp_file_name.c_str()); + + return output; +} diff --git a/thirdparty/benchmark-1.5.0/test/register_benchmark_test.cc b/thirdparty/benchmark-1.5.0/test/register_benchmark_test.cc new file mode 100644 index 0000000000..3ac5b21fb3 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/register_benchmark_test.cc @@ -0,0 +1,184 @@ + +#undef NDEBUG +#include +#include + +#include "../src/check.h" // NOTE: check.h is for internal use only! +#include "benchmark/benchmark.h" + +namespace { + +class TestReporter : public benchmark::ConsoleReporter { + public: + virtual void ReportRuns(const std::vector& report) { + all_runs_.insert(all_runs_.end(), begin(report), end(report)); + ConsoleReporter::ReportRuns(report); + } + + std::vector all_runs_; +}; + +struct TestCase { + std::string name; + const char* label; + // Note: not explicit as we rely on it being converted through ADD_CASES. + TestCase(const char* xname) : TestCase(xname, nullptr) {} + TestCase(const char* xname, const char* xlabel) + : name(xname), label(xlabel) {} + + typedef benchmark::BenchmarkReporter::Run Run; + + void CheckRun(Run const& run) const { + // clang-format off + CHECK(name == run.benchmark_name()) << "expected " << name << " got " + << run.benchmark_name(); + if (label) { + CHECK(run.report_label == label) << "expected " << label << " got " + << run.report_label; + } else { + CHECK(run.report_label == ""); + } + // clang-format on + } +}; + +std::vector ExpectedResults; + +int AddCases(std::initializer_list const& v) { + for (auto N : v) { + ExpectedResults.push_back(N); + } + return 0; +} + +#define CONCAT(x, y) CONCAT2(x, y) +#define CONCAT2(x, y) x##y +#define ADD_CASES(...) int CONCAT(dummy, __LINE__) = AddCases({__VA_ARGS__}) + +} // end namespace + +typedef benchmark::internal::Benchmark* ReturnVal; + +//----------------------------------------------------------------------------// +// Test RegisterBenchmark with no additional arguments +//----------------------------------------------------------------------------// +void BM_function(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_function); +ReturnVal dummy = benchmark::RegisterBenchmark( + "BM_function_manual_registration", BM_function); +ADD_CASES({"BM_function"}, {"BM_function_manual_registration"}); + +//----------------------------------------------------------------------------// +// Test RegisterBenchmark with additional arguments +// Note: GCC <= 4.8 do not support this form of RegisterBenchmark because they +// reject the variadic pack expansion of lambda captures. +//----------------------------------------------------------------------------// +#ifndef BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK + +void BM_extra_args(benchmark::State& st, const char* label) { + for (auto _ : st) { + } + st.SetLabel(label); +} +int RegisterFromFunction() { + std::pair cases[] = { + {"test1", "One"}, {"test2", "Two"}, {"test3", "Three"}}; + for (auto const& c : cases) + benchmark::RegisterBenchmark(c.first, &BM_extra_args, c.second); + return 0; +} +int dummy2 = RegisterFromFunction(); +ADD_CASES({"test1", "One"}, {"test2", "Two"}, {"test3", "Three"}); + +#endif // BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK + +//----------------------------------------------------------------------------// +// Test RegisterBenchmark with different callable types +//----------------------------------------------------------------------------// + +struct CustomFixture { + void operator()(benchmark::State& st) { + for (auto _ : st) { + } + } +}; + +void TestRegistrationAtRuntime() { +#ifdef BENCHMARK_HAS_CXX11 + { + CustomFixture fx; + benchmark::RegisterBenchmark("custom_fixture", fx); + AddCases({"custom_fixture"}); + } +#endif +#ifndef BENCHMARK_HAS_NO_VARIADIC_REGISTER_BENCHMARK + { + const char* x = "42"; + auto capturing_lam = [=](benchmark::State& st) { + for (auto _ : st) { + } + st.SetLabel(x); + }; + benchmark::RegisterBenchmark("lambda_benchmark", capturing_lam); + AddCases({{"lambda_benchmark", x}}); + } +#endif +} + +// Test that all benchmarks, registered at either during static init or runtime, +// are run and the results are passed to the reported. +void RunTestOne() { + TestRegistrationAtRuntime(); + + TestReporter test_reporter; + benchmark::RunSpecifiedBenchmarks(&test_reporter); + + typedef benchmark::BenchmarkReporter::Run Run; + auto EB = ExpectedResults.begin(); + + for (Run const& run : test_reporter.all_runs_) { + assert(EB != ExpectedResults.end()); + EB->CheckRun(run); + ++EB; + } + assert(EB == ExpectedResults.end()); +} + +// Test that ClearRegisteredBenchmarks() clears all previously registered +// benchmarks. +// Also test that new benchmarks can be registered and ran afterwards. +void RunTestTwo() { + assert(ExpectedResults.size() != 0 && + "must have at least one registered benchmark"); + ExpectedResults.clear(); + benchmark::ClearRegisteredBenchmarks(); + + TestReporter test_reporter; + size_t num_ran = benchmark::RunSpecifiedBenchmarks(&test_reporter); + assert(num_ran == 0); + assert(test_reporter.all_runs_.begin() == test_reporter.all_runs_.end()); + + TestRegistrationAtRuntime(); + num_ran = benchmark::RunSpecifiedBenchmarks(&test_reporter); + assert(num_ran == ExpectedResults.size()); + + typedef benchmark::BenchmarkReporter::Run Run; + auto EB = ExpectedResults.begin(); + + for (Run const& run : test_reporter.all_runs_) { + assert(EB != ExpectedResults.end()); + EB->CheckRun(run); + ++EB; + } + assert(EB == ExpectedResults.end()); +} + +int main(int argc, char* argv[]) { + benchmark::Initialize(&argc, argv); + + RunTestOne(); + RunTestTwo(); +} diff --git a/thirdparty/benchmark-1.5.0/test/report_aggregates_only_test.cc b/thirdparty/benchmark-1.5.0/test/report_aggregates_only_test.cc new file mode 100644 index 0000000000..9646b9be53 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/report_aggregates_only_test.cc @@ -0,0 +1,39 @@ + +#undef NDEBUG +#include +#include + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// Ok this test is super ugly. We want to check what happens with the file +// reporter in the presence of ReportAggregatesOnly(). +// We do not care about console output, the normal tests check that already. + +void BM_SummaryRepeat(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryRepeat)->Repetitions(3)->ReportAggregatesOnly(); + +int main(int argc, char* argv[]) { + const std::string output = GetFileReporterOutput(argc, argv); + + if (SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3") != 3 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_mean\"") != 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_median\"") != + 1 || + SubstrCnt(output, "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\"") != + 1) { + std::cout << "Precondition mismatch. Expected to only find three " + "occurrences of \"BM_SummaryRepeat/repeats:3\" substring:\n" + "\"name\": \"BM_SummaryRepeat/repeats:3_mean\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_median\", " + "\"name\": \"BM_SummaryRepeat/repeats:3_stddev\"\nThe entire " + "output:\n"; + std::cout << output; + return 1; + } + + return 0; +} diff --git a/thirdparty/benchmark-1.5.0/test/reporter_output_test.cc b/thirdparty/benchmark-1.5.0/test/reporter_output_test.cc new file mode 100644 index 0000000000..c8090d4aca --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/reporter_output_test.cc @@ -0,0 +1,742 @@ + +#undef NDEBUG +#include + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// ========================================================================= // +// ---------------------- Testing Prologue Output -------------------------- // +// ========================================================================= // + +ADD_CASES(TC_ConsoleOut, {{"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations$", MR_Next}, + {"^[-]+$", MR_Next}}); +static int AddContextCases() { + AddCases(TC_ConsoleErr, + { + {"%int[-/]%int[-/]%int %int:%int:%int$", MR_Default}, + {"Running .*/reporter_output_test(\\.exe)?$", MR_Next}, + {"Run on \\(%int X %float MHz CPU s?\\)", MR_Next}, + }); + AddCases(TC_JSONOut, + {{"^\\{", MR_Default}, + {"\"context\":", MR_Next}, + {"\"date\": \"", MR_Next}, + {"\"host_name\":", MR_Next}, + {"\"executable\": \".*(/|\\\\)reporter_output_test(\\.exe)?\",", + MR_Next}, + {"\"num_cpus\": %int,$", MR_Next}, + {"\"mhz_per_cpu\": %float,$", MR_Next}, + {"\"cpu_scaling_enabled\": ", MR_Next}, + {"\"caches\": \\[$", MR_Next}}); + auto const& Info = benchmark::CPUInfo::Get(); + auto const& Caches = Info.caches; + if (!Caches.empty()) { + AddCases(TC_ConsoleErr, {{"CPU Caches:$", MR_Next}}); + } + for (size_t I = 0; I < Caches.size(); ++I) { + std::string num_caches_str = + Caches[I].num_sharing != 0 ? " \\(x%int\\)$" : "$"; + AddCases( + TC_ConsoleErr, + {{"L%int (Data|Instruction|Unified) %intK" + num_caches_str, MR_Next}}); + AddCases(TC_JSONOut, {{"\\{$", MR_Next}, + {"\"type\": \"", MR_Next}, + {"\"level\": %int,$", MR_Next}, + {"\"size\": %int,$", MR_Next}, + {"\"num_sharing\": %int$", MR_Next}, + {"}[,]{0,1}$", MR_Next}}); + } + AddCases(TC_JSONOut, {{"],$"}}); + auto const& LoadAvg = Info.load_avg; + if (!LoadAvg.empty()) { + AddCases(TC_ConsoleErr, + {{"Load Average: (%float, ){0,2}%float$", MR_Next}}); + } + AddCases(TC_JSONOut, {{"\"load_avg\": \\[(%float,?){0,3}],$", MR_Next}}); + return 0; +} +int dummy_register = AddContextCases(); +ADD_CASES(TC_CSVOut, {{"%csv_header"}}); + +// ========================================================================= // +// ------------------------ Testing Basic Output --------------------------- // +// ========================================================================= // + +void BM_basic(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_basic); + +ADD_CASES(TC_ConsoleOut, {{"^BM_basic %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_basic\",$"}, + {"\"run_name\": \"BM_basic\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_basic\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Bytes per Second Output ---------------- // +// ========================================================================= // + +void BM_bytes_per_second(benchmark::State& state) { + for (auto _ : state) { + } + state.SetBytesProcessed(1); +} +BENCHMARK(BM_bytes_per_second); + +ADD_CASES(TC_ConsoleOut, {{"^BM_bytes_per_second %console_report " + "bytes_per_second=%float[kM]{0,1}/s$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_bytes_per_second\",$"}, + {"\"run_name\": \"BM_bytes_per_second\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bytes_per_second\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_bytes_per_second\",%csv_bytes_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Items per Second Output ---------------- // +// ========================================================================= // + +void BM_items_per_second(benchmark::State& state) { + for (auto _ : state) { + } + state.SetItemsProcessed(1); +} +BENCHMARK(BM_items_per_second); + +ADD_CASES(TC_ConsoleOut, {{"^BM_items_per_second %console_report " + "items_per_second=%float[kM]{0,1}/s$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_items_per_second\",$"}, + {"\"run_name\": \"BM_items_per_second\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"items_per_second\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_items_per_second\",%csv_items_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Label Output --------------------------- // +// ========================================================================= // + +void BM_label(benchmark::State& state) { + for (auto _ : state) { + } + state.SetLabel("some label"); +} +BENCHMARK(BM_label); + +ADD_CASES(TC_ConsoleOut, {{"^BM_label %console_report some label$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_label\",$"}, + {"\"run_name\": \"BM_label\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"label\": \"some label\"$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_label\",%csv_label_report_begin\"some " + "label\"%csv_label_report_end$"}}); + +// ========================================================================= // +// ------------------------ Testing Error Output --------------------------- // +// ========================================================================= // + +void BM_error(benchmark::State& state) { + state.SkipWithError("message"); + for (auto _ : state) { + } +} +BENCHMARK(BM_error); +ADD_CASES(TC_ConsoleOut, {{"^BM_error[ ]+ERROR OCCURRED: 'message'$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_error\",$"}, + {"\"run_name\": \"BM_error\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"error_occurred\": true,$", MR_Next}, + {"\"error_message\": \"message\",$", MR_Next}}); + +ADD_CASES(TC_CSVOut, {{"^\"BM_error\",,,,,,,,true,\"message\"$"}}); + +// ========================================================================= // +// ------------------------ Testing No Arg Name Output ----------------------- +// // +// ========================================================================= // + +void BM_no_arg_name(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_no_arg_name)->Arg(3); +ADD_CASES(TC_ConsoleOut, {{"^BM_no_arg_name/3 %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_no_arg_name/3\",$"}, + {"\"run_name\": \"BM_no_arg_name/3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_no_arg_name/3\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Arg Name Output ----------------------- // +// ========================================================================= // + +void BM_arg_name(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_arg_name)->ArgName("first")->Arg(3); +ADD_CASES(TC_ConsoleOut, {{"^BM_arg_name/first:3 %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_arg_name/first:3\",$"}, + {"\"run_name\": \"BM_arg_name/first:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_arg_name/first:3\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Arg Names Output ----------------------- // +// ========================================================================= // + +void BM_arg_names(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_arg_names)->Args({2, 5, 4})->ArgNames({"first", "", "third"}); +ADD_CASES(TC_ConsoleOut, + {{"^BM_arg_names/first:2/5/third:4 %console_report$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_arg_names/first:2/5/third:4\",$"}, + {"\"run_name\": \"BM_arg_names/first:2/5/third:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_arg_names/first:2/5/third:4\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------ Testing Big Args Output ------------------------ // +// ========================================================================= // + +void BM_BigArgs(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_BigArgs)->RangeMultiplier(2)->Range(1U << 30U, 1U << 31U); +ADD_CASES(TC_ConsoleOut, {{"^BM_BigArgs/1073741824 %console_report$"}, + {"^BM_BigArgs/2147483648 %console_report$"}}); + +// ========================================================================= // +// ----------------------- Testing Complexity Output ----------------------- // +// ========================================================================= // + +void BM_Complexity_O1(benchmark::State& state) { + for (auto _ : state) { + } + state.SetComplexityN(state.range(0)); +} +BENCHMARK(BM_Complexity_O1)->Range(1, 1 << 18)->Complexity(benchmark::o1); +SET_SUBSTITUTIONS({{"%bigOStr", "[ ]* %float \\([0-9]+\\)"}, + {"%RMS", "[ ]*[0-9]+ %"}}); +ADD_CASES(TC_ConsoleOut, {{"^BM_Complexity_O1_BigO %bigOStr %bigOStr[ ]*$"}, + {"^BM_Complexity_O1_RMS %RMS %RMS[ ]*$"}}); + +// ========================================================================= // +// ----------------------- Testing Aggregate Output ------------------------ // +// ========================================================================= // + +// Test that non-aggregate data is printed by default +void BM_Repeat(benchmark::State& state) { + for (auto _ : state) { + } +} +// need two repetitions min to be able to output any aggregate output +BENCHMARK(BM_Repeat)->Repetitions(2); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Repeat/repeats:2 %console_report$"}, + {"^BM_Repeat/repeats:2 %console_report$"}, + {"^BM_Repeat/repeats:2_mean %console_time_only_report [ ]*2$"}, + {"^BM_Repeat/repeats:2_median %console_time_only_report [ ]*2$"}, + {"^BM_Repeat/repeats:2_stddev %console_time_only_report [ ]*2$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Repeat/repeats:2\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:2\"", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2_mean\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2_median\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:2_stddev\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Repeat/repeats:2\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2_mean\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2_median\",%csv_report$"}, + {"^\"BM_Repeat/repeats:2_stddev\",%csv_report$"}}); +// but for two repetitions, mean and median is the same, so let's repeat.. +BENCHMARK(BM_Repeat)->Repetitions(3); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Repeat/repeats:3 %console_report$"}, + {"^BM_Repeat/repeats:3 %console_report$"}, + {"^BM_Repeat/repeats:3 %console_report$"}, + {"^BM_Repeat/repeats:3_mean %console_time_only_report [ ]*3$"}, + {"^BM_Repeat/repeats:3_median %console_time_only_report [ ]*3$"}, + {"^BM_Repeat/repeats:3_stddev %console_time_only_report [ ]*3$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Repeat/repeats:3\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3_mean\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3_median\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:3_stddev\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Repeat/repeats:3\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3_mean\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3_median\",%csv_report$"}, + {"^\"BM_Repeat/repeats:3_stddev\",%csv_report$"}}); +// median differs between even/odd number of repetitions, so just to be sure +BENCHMARK(BM_Repeat)->Repetitions(4); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4 %console_report$"}, + {"^BM_Repeat/repeats:4_mean %console_time_only_report [ ]*4$"}, + {"^BM_Repeat/repeats:4_median %console_time_only_report [ ]*4$"}, + {"^BM_Repeat/repeats:4_stddev %console_time_only_report [ ]*4$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"repetition_index\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4_mean\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 4,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4_median\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 4,$", MR_Next}, + {"\"name\": \"BM_Repeat/repeats:4_stddev\",$"}, + {"\"run_name\": \"BM_Repeat/repeats:4\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 4,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 4,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4_mean\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4_median\",%csv_report$"}, + {"^\"BM_Repeat/repeats:4_stddev\",%csv_report$"}}); + +// Test that a non-repeated test still prints non-aggregate results even when +// only-aggregate reports have been requested +void BM_RepeatOnce(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_RepeatOnce)->Repetitions(1)->ReportAggregatesOnly(); +ADD_CASES(TC_ConsoleOut, {{"^BM_RepeatOnce/repeats:1 %console_report$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_RepeatOnce/repeats:1\",$"}, + {"\"run_name\": \"BM_RepeatOnce/repeats:1\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 1,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_RepeatOnce/repeats:1\",%csv_report$"}}); + +// Test that non-aggregate data is not reported +void BM_SummaryRepeat(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryRepeat)->Repetitions(3)->ReportAggregatesOnly(); +ADD_CASES( + TC_ConsoleOut, + {{".*BM_SummaryRepeat/repeats:3 ", MR_Not}, + {"^BM_SummaryRepeat/repeats:3_mean %console_time_only_report [ ]*3$"}, + {"^BM_SummaryRepeat/repeats:3_median %console_time_only_report [ ]*3$"}, + {"^BM_SummaryRepeat/repeats:3_stddev %console_time_only_report [ ]*3$"}}); +ADD_CASES(TC_JSONOut, + {{".*BM_SummaryRepeat/repeats:3 ", MR_Not}, + {"\"name\": \"BM_SummaryRepeat/repeats:3_mean\",$"}, + {"\"run_name\": \"BM_SummaryRepeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_SummaryRepeat/repeats:3_median\",$"}, + {"\"run_name\": \"BM_SummaryRepeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"name\": \"BM_SummaryRepeat/repeats:3_stddev\",$"}, + {"\"run_name\": \"BM_SummaryRepeat/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{".*BM_SummaryRepeat/repeats:3 ", MR_Not}, + {"^\"BM_SummaryRepeat/repeats:3_mean\",%csv_report$"}, + {"^\"BM_SummaryRepeat/repeats:3_median\",%csv_report$"}, + {"^\"BM_SummaryRepeat/repeats:3_stddev\",%csv_report$"}}); + +// Test that non-aggregate data is not displayed. +// NOTE: this test is kinda bad. we are only testing the display output. +// But we don't check that the file output still contains everything... +void BM_SummaryDisplay(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_SummaryDisplay)->Repetitions(2)->DisplayAggregatesOnly(); +ADD_CASES( + TC_ConsoleOut, + {{".*BM_SummaryDisplay/repeats:2 ", MR_Not}, + {"^BM_SummaryDisplay/repeats:2_mean %console_time_only_report [ ]*2$"}, + {"^BM_SummaryDisplay/repeats:2_median %console_time_only_report [ ]*2$"}, + {"^BM_SummaryDisplay/repeats:2_stddev %console_time_only_report [ ]*2$"}}); +ADD_CASES(TC_JSONOut, + {{".*BM_SummaryDisplay/repeats:2 ", MR_Not}, + {"\"name\": \"BM_SummaryDisplay/repeats:2_mean\",$"}, + {"\"run_name\": \"BM_SummaryDisplay/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_SummaryDisplay/repeats:2_median\",$"}, + {"\"run_name\": \"BM_SummaryDisplay/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"name\": \"BM_SummaryDisplay/repeats:2_stddev\",$"}, + {"\"run_name\": \"BM_SummaryDisplay/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{".*BM_SummaryDisplay/repeats:2 ", MR_Not}, + {"^\"BM_SummaryDisplay/repeats:2_mean\",%csv_report$"}, + {"^\"BM_SummaryDisplay/repeats:2_median\",%csv_report$"}, + {"^\"BM_SummaryDisplay/repeats:2_stddev\",%csv_report$"}}); + +// Test repeats with custom time unit. +void BM_RepeatTimeUnit(benchmark::State& state) { + for (auto _ : state) { + } +} +BENCHMARK(BM_RepeatTimeUnit) + ->Repetitions(3) + ->ReportAggregatesOnly() + ->Unit(benchmark::kMicrosecond); +ADD_CASES( + TC_ConsoleOut, + {{".*BM_RepeatTimeUnit/repeats:3 ", MR_Not}, + {"^BM_RepeatTimeUnit/repeats:3_mean %console_us_time_only_report [ ]*3$"}, + {"^BM_RepeatTimeUnit/repeats:3_median %console_us_time_only_report [ " + "]*3$"}, + {"^BM_RepeatTimeUnit/repeats:3_stddev %console_us_time_only_report [ " + "]*3$"}}); +ADD_CASES(TC_JSONOut, + {{".*BM_RepeatTimeUnit/repeats:3 ", MR_Not}, + {"\"name\": \"BM_RepeatTimeUnit/repeats:3_mean\",$"}, + {"\"run_name\": \"BM_RepeatTimeUnit/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"time_unit\": \"us\",?$"}, + {"\"name\": \"BM_RepeatTimeUnit/repeats:3_median\",$"}, + {"\"run_name\": \"BM_RepeatTimeUnit/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"time_unit\": \"us\",?$"}, + {"\"name\": \"BM_RepeatTimeUnit/repeats:3_stddev\",$"}, + {"\"run_name\": \"BM_RepeatTimeUnit/repeats:3\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"time_unit\": \"us\",?$"}}); +ADD_CASES(TC_CSVOut, + {{".*BM_RepeatTimeUnit/repeats:3 ", MR_Not}, + {"^\"BM_RepeatTimeUnit/repeats:3_mean\",%csv_us_report$"}, + {"^\"BM_RepeatTimeUnit/repeats:3_median\",%csv_us_report$"}, + {"^\"BM_RepeatTimeUnit/repeats:3_stddev\",%csv_us_report$"}}); + +// ========================================================================= // +// -------------------- Testing user-provided statistics ------------------- // +// ========================================================================= // + +const auto UserStatistics = [](const std::vector& v) { + return v.back(); +}; +void BM_UserStats(benchmark::State& state) { + for (auto _ : state) { + state.SetIterationTime(150 / 10e8); + } +} +// clang-format off +BENCHMARK(BM_UserStats) + ->Repetitions(3) + ->Iterations(5) + ->UseManualTime() + ->ComputeStatistics("", UserStatistics); +// clang-format on + +// check that user-provided stats is calculated, and is after the default-ones +// empty string as name is intentional, it would sort before anything else +ADD_CASES(TC_ConsoleOut, {{"^BM_UserStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserStats/iterations:5/repeats:3/manual_time [ " + "]* 150 ns %time [ ]*5$"}, + {"^BM_UserStats/iterations:5/repeats:3/" + "manual_time_mean [ ]* 150 ns %time [ ]*3$"}, + {"^BM_UserStats/iterations:5/repeats:3/" + "manual_time_median [ ]* 150 ns %time [ ]*3$"}, + {"^BM_UserStats/iterations:5/repeats:3/" + "manual_time_stddev [ ]* 0.000 ns %time [ ]*3$"}, + {"^BM_UserStats/iterations:5/repeats:3/manual_time_ " + "[ ]* 150 ns %time [ ]*3$"}}); +ADD_CASES( + TC_JSONOut, + {{"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$"}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"repetition_index\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": 5,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_mean\",$"}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_median\",$"}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_stddev\",$"}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"name\": \"BM_UserStats/iterations:5/repeats:3/manual_time_\",$"}, + {"\"run_name\": \"BM_UserStats/iterations:5/repeats:3/manual_time\",$", + MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 3,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"\",$", MR_Next}, + {"\"iterations\": 3,$", MR_Next}, + {"\"real_time\": 1\\.5(0)*e\\+(0)*2,$", MR_Next}}); +ADD_CASES( + TC_CSVOut, + {{"^\"BM_UserStats/iterations:5/repeats:3/manual_time\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time_mean\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/" + "manual_time_median\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/" + "manual_time_stddev\",%csv_report$"}, + {"^\"BM_UserStats/iterations:5/repeats:3/manual_time_\",%csv_report$"}}); + +// ========================================================================= // +// ------------------------- Testing StrEscape JSON ------------------------ // +// ========================================================================= // +#if 0 // enable when csv testing code correctly handles multi-line fields +void BM_JSON_Format(benchmark::State& state) { + state.SkipWithError("val\b\f\n\r\t\\\"with\"es,capes"); + for (auto _ : state) { + } +} +BENCHMARK(BM_JSON_Format); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_JSON_Format\",$"}, + {"\"run_name\": \"BM_JSON_Format\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"error_occurred\": true,$", MR_Next}, + {R"("error_message": "val\\b\\f\\n\\r\\t\\\\\\"with\\"es,capes",$)", MR_Next}}); +#endif +// ========================================================================= // +// -------------------------- Testing CsvEscape ---------------------------- // +// ========================================================================= // + +void BM_CSV_Format(benchmark::State& state) { + state.SkipWithError("\"freedom\""); + for (auto _ : state) { + } +} +BENCHMARK(BM_CSV_Format); +ADD_CASES(TC_CSVOut, {{"^\"BM_CSV_Format\",,,,,,,,true,\"\"\"freedom\"\"\"$"}}); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/thirdparty/benchmark-1.5.0/test/skip_with_error_test.cc b/thirdparty/benchmark-1.5.0/test/skip_with_error_test.cc new file mode 100644 index 0000000000..06579772ff --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/skip_with_error_test.cc @@ -0,0 +1,189 @@ + +#undef NDEBUG +#include +#include + +#include "../src/check.h" // NOTE: check.h is for internal use only! +#include "benchmark/benchmark.h" + +namespace { + +class TestReporter : public benchmark::ConsoleReporter { + public: + virtual bool ReportContext(const Context& context) { + return ConsoleReporter::ReportContext(context); + }; + + virtual void ReportRuns(const std::vector& report) { + all_runs_.insert(all_runs_.end(), begin(report), end(report)); + ConsoleReporter::ReportRuns(report); + } + + TestReporter() {} + virtual ~TestReporter() {} + + mutable std::vector all_runs_; +}; + +struct TestCase { + std::string name; + bool error_occurred; + std::string error_message; + + typedef benchmark::BenchmarkReporter::Run Run; + + void CheckRun(Run const& run) const { + CHECK(name == run.benchmark_name()) + << "expected " << name << " got " << run.benchmark_name(); + CHECK(error_occurred == run.error_occurred); + CHECK(error_message == run.error_message); + if (error_occurred) { + // CHECK(run.iterations == 0); + } else { + CHECK(run.iterations != 0); + } + } +}; + +std::vector ExpectedResults; + +int AddCases(const char* base_name, std::initializer_list const& v) { + for (auto TC : v) { + TC.name = base_name + TC.name; + ExpectedResults.push_back(std::move(TC)); + } + return 0; +} + +#define CONCAT(x, y) CONCAT2(x, y) +#define CONCAT2(x, y) x##y +#define ADD_CASES(...) int CONCAT(dummy, __LINE__) = AddCases(__VA_ARGS__) + +} // end namespace + +void BM_error_before_running(benchmark::State& state) { + state.SkipWithError("error message"); + while (state.KeepRunning()) { + assert(false); + } +} +BENCHMARK(BM_error_before_running); +ADD_CASES("BM_error_before_running", {{"", true, "error message"}}); + +void BM_error_before_running_batch(benchmark::State& state) { + state.SkipWithError("error message"); + while (state.KeepRunningBatch(17)) { + assert(false); + } +} +BENCHMARK(BM_error_before_running_batch); +ADD_CASES("BM_error_before_running_batch", {{"", true, "error message"}}); + +void BM_error_before_running_range_for(benchmark::State& state) { + state.SkipWithError("error message"); + for (auto _ : state) { + assert(false); + } +} +BENCHMARK(BM_error_before_running_range_for); +ADD_CASES("BM_error_before_running_range_for", {{"", true, "error message"}}); + +void BM_error_during_running(benchmark::State& state) { + int first_iter = true; + while (state.KeepRunning()) { + if (state.range(0) == 1 && state.thread_index <= (state.threads / 2)) { + assert(first_iter); + first_iter = false; + state.SkipWithError("error message"); + } else { + state.PauseTiming(); + state.ResumeTiming(); + } + } +} +BENCHMARK(BM_error_during_running)->Arg(1)->Arg(2)->ThreadRange(1, 8); +ADD_CASES("BM_error_during_running", {{"/1/threads:1", true, "error message"}, + {"/1/threads:2", true, "error message"}, + {"/1/threads:4", true, "error message"}, + {"/1/threads:8", true, "error message"}, + {"/2/threads:1", false, ""}, + {"/2/threads:2", false, ""}, + {"/2/threads:4", false, ""}, + {"/2/threads:8", false, ""}}); + +void BM_error_during_running_ranged_for(benchmark::State& state) { + assert(state.max_iterations > 3 && "test requires at least a few iterations"); + int first_iter = true; + // NOTE: Users should not write the for loop explicitly. + for (auto It = state.begin(), End = state.end(); It != End; ++It) { + if (state.range(0) == 1) { + assert(first_iter); + first_iter = false; + state.SkipWithError("error message"); + // Test the unfortunate but documented behavior that the ranged-for loop + // doesn't automatically terminate when SkipWithError is set. + assert(++It != End); + break; // Required behavior + } + } +} +BENCHMARK(BM_error_during_running_ranged_for)->Arg(1)->Arg(2)->Iterations(5); +ADD_CASES("BM_error_during_running_ranged_for", + {{"/1/iterations:5", true, "error message"}, + {"/2/iterations:5", false, ""}}); + +void BM_error_after_running(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(state.iterations()); + } + if (state.thread_index <= (state.threads / 2)) + state.SkipWithError("error message"); +} +BENCHMARK(BM_error_after_running)->ThreadRange(1, 8); +ADD_CASES("BM_error_after_running", {{"/threads:1", true, "error message"}, + {"/threads:2", true, "error message"}, + {"/threads:4", true, "error message"}, + {"/threads:8", true, "error message"}}); + +void BM_error_while_paused(benchmark::State& state) { + bool first_iter = true; + while (state.KeepRunning()) { + if (state.range(0) == 1 && state.thread_index <= (state.threads / 2)) { + assert(first_iter); + first_iter = false; + state.PauseTiming(); + state.SkipWithError("error message"); + } else { + state.PauseTiming(); + state.ResumeTiming(); + } + } +} +BENCHMARK(BM_error_while_paused)->Arg(1)->Arg(2)->ThreadRange(1, 8); +ADD_CASES("BM_error_while_paused", {{"/1/threads:1", true, "error message"}, + {"/1/threads:2", true, "error message"}, + {"/1/threads:4", true, "error message"}, + {"/1/threads:8", true, "error message"}, + {"/2/threads:1", false, ""}, + {"/2/threads:2", false, ""}, + {"/2/threads:4", false, ""}, + {"/2/threads:8", false, ""}}); + +int main(int argc, char* argv[]) { + benchmark::Initialize(&argc, argv); + + TestReporter test_reporter; + benchmark::RunSpecifiedBenchmarks(&test_reporter); + + typedef benchmark::BenchmarkReporter::Run Run; + auto EB = ExpectedResults.begin(); + + for (Run const& run : test_reporter.all_runs_) { + assert(EB != ExpectedResults.end()); + EB->CheckRun(run); + ++EB; + } + assert(EB == ExpectedResults.end()); + + return 0; +} diff --git a/thirdparty/benchmark-1.5.0/test/state_assembly_test.cc b/thirdparty/benchmark-1.5.0/test/state_assembly_test.cc new file mode 100644 index 0000000000..7ddbb3b2a9 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/state_assembly_test.cc @@ -0,0 +1,68 @@ +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wreturn-type" +#endif + +// clang-format off +extern "C" { + extern int ExternInt; + benchmark::State& GetState(); + void Fn(); +} +// clang-format on + +using benchmark::State; + +// CHECK-LABEL: test_for_auto_loop: +extern "C" int test_for_auto_loop() { + State& S = GetState(); + int x = 42; + // CHECK: [[CALL:call(q)*]] _ZN9benchmark5State16StartKeepRunningEv + // CHECK-NEXT: testq %rbx, %rbx + // CHECK-NEXT: je [[LOOP_END:.*]] + + for (auto _ : S) { + // CHECK: .L[[LOOP_HEAD:[a-zA-Z0-9_]+]]: + // CHECK-GNU-NEXT: subq $1, %rbx + // CHECK-CLANG-NEXT: {{(addq \$1, %rax|incq %rax|addq \$-1, %rbx)}} + // CHECK-NEXT: jne .L[[LOOP_HEAD]] + benchmark::DoNotOptimize(x); + } + // CHECK: [[LOOP_END]]: + // CHECK: [[CALL]] _ZN9benchmark5State17FinishKeepRunningEv + + // CHECK: movl $101, %eax + // CHECK: ret + return 101; +} + +// CHECK-LABEL: test_while_loop: +extern "C" int test_while_loop() { + State& S = GetState(); + int x = 42; + + // CHECK: j{{(e|mp)}} .L[[LOOP_HEADER:[a-zA-Z0-9_]+]] + // CHECK-NEXT: .L[[LOOP_BODY:[a-zA-Z0-9_]+]]: + while (S.KeepRunning()) { + // CHECK-GNU-NEXT: subq $1, %[[IREG:[a-z]+]] + // CHECK-CLANG-NEXT: {{(addq \$-1,|decq)}} %[[IREG:[a-z]+]] + // CHECK: movq %[[IREG]], [[DEST:.*]] + benchmark::DoNotOptimize(x); + } + // CHECK-DAG: movq [[DEST]], %[[IREG]] + // CHECK-DAG: testq %[[IREG]], %[[IREG]] + // CHECK-DAG: jne .L[[LOOP_BODY]] + // CHECK-DAG: .L[[LOOP_HEADER]]: + + // CHECK: cmpb $0 + // CHECK-NEXT: jne .L[[LOOP_END:[a-zA-Z0-9_]+]] + // CHECK: [[CALL:call(q)*]] _ZN9benchmark5State16StartKeepRunningEv + + // CHECK: .L[[LOOP_END]]: + // CHECK: [[CALL]] _ZN9benchmark5State17FinishKeepRunningEv + + // CHECK: movl $101, %eax + // CHECK: ret + return 101; +} diff --git a/thirdparty/benchmark-1.5.0/test/statistics_gtest.cc b/thirdparty/benchmark-1.5.0/test/statistics_gtest.cc new file mode 100644 index 0000000000..99e314920c --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/statistics_gtest.cc @@ -0,0 +1,28 @@ +//===---------------------------------------------------------------------===// +// statistics_test - Unit tests for src/statistics.cc +//===---------------------------------------------------------------------===// + +#include "../src/statistics.h" +#include "gtest/gtest.h" + +namespace { +TEST(StatisticsTest, Mean) { + EXPECT_DOUBLE_EQ(benchmark::StatisticsMean({42, 42, 42, 42}), 42.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMean({1, 2, 3, 4}), 2.5); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMean({1, 2, 5, 10, 10, 14}), 7.0); +} + +TEST(StatisticsTest, Median) { + EXPECT_DOUBLE_EQ(benchmark::StatisticsMedian({42, 42, 42, 42}), 42.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMedian({1, 2, 3, 4}), 2.5); + EXPECT_DOUBLE_EQ(benchmark::StatisticsMedian({1, 2, 5, 10, 10}), 5.0); +} + +TEST(StatisticsTest, StdDev) { + EXPECT_DOUBLE_EQ(benchmark::StatisticsStdDev({101, 101, 101, 101}), 0.0); + EXPECT_DOUBLE_EQ(benchmark::StatisticsStdDev({1, 2, 3}), 1.0); + EXPECT_FLOAT_EQ(benchmark::StatisticsStdDev({1.5, 2.4, 3.3, 4.2, 5.1}), + 1.42302495); +} + +} // end namespace diff --git a/thirdparty/benchmark-1.5.0/test/string_util_gtest.cc b/thirdparty/benchmark-1.5.0/test/string_util_gtest.cc new file mode 100644 index 0000000000..2c5d073f61 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/string_util_gtest.cc @@ -0,0 +1,146 @@ +//===---------------------------------------------------------------------===// +// statistics_test - Unit tests for src/statistics.cc +//===---------------------------------------------------------------------===// + +#include "../src/string_util.h" +#include "gtest/gtest.h" + +namespace { +TEST(StringUtilTest, stoul) { + { + size_t pos = 0; + EXPECT_EQ(0ul, benchmark::stoul("0", &pos)); + EXPECT_EQ(1ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(7ul, benchmark::stoul("7", &pos)); + EXPECT_EQ(1ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(135ul, benchmark::stoul("135", &pos)); + EXPECT_EQ(3ul, pos); + } +#if ULONG_MAX == 0xFFFFFFFFul + { + size_t pos = 0; + EXPECT_EQ(0xFFFFFFFFul, benchmark::stoul("4294967295", &pos)); + EXPECT_EQ(10ul, pos); + } +#elif ULONG_MAX == 0xFFFFFFFFFFFFFFFFul + { + size_t pos = 0; + EXPECT_EQ(0xFFFFFFFFFFFFFFFFul, benchmark::stoul("18446744073709551615", &pos)); + EXPECT_EQ(20ul, pos); + } +#endif + { + size_t pos = 0; + EXPECT_EQ(10ul, benchmark::stoul("1010", &pos, 2)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(520ul, benchmark::stoul("1010", &pos, 8)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(1010ul, benchmark::stoul("1010", &pos, 10)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(4112ul, benchmark::stoul("1010", &pos, 16)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(0xBEEFul, benchmark::stoul("BEEF", &pos, 16)); + EXPECT_EQ(4ul, pos); + } + { + ASSERT_THROW(benchmark::stoul("this is a test"), std::invalid_argument); + } +} + +TEST(StringUtilTest, stoi) { + { + size_t pos = 0; + EXPECT_EQ(0, benchmark::stoi("0", &pos)); + EXPECT_EQ(1ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(-17, benchmark::stoi("-17", &pos)); + EXPECT_EQ(3ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(1357, benchmark::stoi("1357", &pos)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(10, benchmark::stoi("1010", &pos, 2)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(520, benchmark::stoi("1010", &pos, 8)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(1010, benchmark::stoi("1010", &pos, 10)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(4112, benchmark::stoi("1010", &pos, 16)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(0xBEEF, benchmark::stoi("BEEF", &pos, 16)); + EXPECT_EQ(4ul, pos); + } + { + ASSERT_THROW(benchmark::stoi("this is a test"), std::invalid_argument); + } +} + +TEST(StringUtilTest, stod) { + { + size_t pos = 0; + EXPECT_EQ(0.0, benchmark::stod("0", &pos)); + EXPECT_EQ(1ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(-84.0, benchmark::stod("-84", &pos)); + EXPECT_EQ(3ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(1234.0, benchmark::stod("1234", &pos)); + EXPECT_EQ(4ul, pos); + } + { + size_t pos = 0; + EXPECT_EQ(1.5, benchmark::stod("1.5", &pos)); + EXPECT_EQ(3ul, pos); + } + { + size_t pos = 0; + /* Note: exactly representable as double */ + EXPECT_EQ(-1.25e+9, benchmark::stod("-1.25e+9", &pos)); + EXPECT_EQ(8ul, pos); + } + { + ASSERT_THROW(benchmark::stod("this is a test"), std::invalid_argument); + } +} + +} // end namespace diff --git a/thirdparty/benchmark-1.5.0/test/templated_fixture_test.cc b/thirdparty/benchmark-1.5.0/test/templated_fixture_test.cc new file mode 100644 index 0000000000..fe9865cc77 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/templated_fixture_test.cc @@ -0,0 +1,28 @@ + +#include "benchmark/benchmark.h" + +#include +#include + +template +class MyFixture : public ::benchmark::Fixture { + public: + MyFixture() : data(0) {} + + T data; +}; + +BENCHMARK_TEMPLATE_F(MyFixture, Foo, int)(benchmark::State& st) { + for (auto _ : st) { + data += 1; + } +} + +BENCHMARK_TEMPLATE_DEFINE_F(MyFixture, Bar, double)(benchmark::State& st) { + for (auto _ : st) { + data += 1.0; + } +} +BENCHMARK_REGISTER_F(MyFixture, Bar); + +BENCHMARK_MAIN(); diff --git a/thirdparty/benchmark-1.5.0/test/user_counters_tabular_test.cc b/thirdparty/benchmark-1.5.0/test/user_counters_tabular_test.cc new file mode 100644 index 0000000000..099464ef99 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/user_counters_tabular_test.cc @@ -0,0 +1,283 @@ + +#undef NDEBUG + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// @todo: this checks the full output at once; the rule for +// CounterSet1 was failing because it was not matching "^[-]+$". +// @todo: check that the counters are vertically aligned. +ADD_CASES( + TC_ConsoleOut, + { + // keeping these lines long improves readability, so: + // clang-format off + {"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations %s Bar %s Bat %s Baz %s Foo %s Frob %s Lob$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^BM_Counters_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_Counters_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^BM_CounterRates_Tabular/threads:%int %console_report [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s [ ]*%hrfloat/s$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations %s Bar %s Baz %s Foo$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet0_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet1_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations %s Bat %s Baz %s Foo$", MR_Next}, + {"^[-]+$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$", MR_Next}, + {"^BM_CounterSet2_Tabular/threads:%int %console_report [ ]*%hrfloat [ ]*%hrfloat [ ]*%hrfloat$"}, + // clang-format on + }); +ADD_CASES(TC_CSVOut, {{"%csv_header," + "\"Bar\",\"Bat\",\"Baz\",\"Foo\",\"Frob\",\"Lob\""}}); + +// ========================================================================= // +// ------------------------- Tabular Counters Output ----------------------- // +// ========================================================================= // + +void BM_Counters_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {1, bm::Counter::kAvgThreads}}, + {"Bar", {2, bm::Counter::kAvgThreads}}, + {"Baz", {4, bm::Counter::kAvgThreads}}, + {"Bat", {8, bm::Counter::kAvgThreads}}, + {"Frob", {16, bm::Counter::kAvgThreads}}, + {"Lob", {32, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_Counters_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Tabular/threads:%int\",$"}, + {"\"run_name\": \"BM_Counters_Tabular/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_Tabular/threads:%int\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckTabular(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 1); + CHECK_COUNTER_VALUE(e, int, "Bar", EQ, 2); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 4); + CHECK_COUNTER_VALUE(e, int, "Bat", EQ, 8); + CHECK_COUNTER_VALUE(e, int, "Frob", EQ, 16); + CHECK_COUNTER_VALUE(e, int, "Lob", EQ, 32); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Tabular/threads:%int", &CheckTabular); + +// ========================================================================= // +// -------------------- Tabular+Rate Counters Output ----------------------- // +// ========================================================================= // + +void BM_CounterRates_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {1, bm::Counter::kAvgThreadsRate}}, + {"Bar", {2, bm::Counter::kAvgThreadsRate}}, + {"Baz", {4, bm::Counter::kAvgThreadsRate}}, + {"Bat", {8, bm::Counter::kAvgThreadsRate}}, + {"Frob", {16, bm::Counter::kAvgThreadsRate}}, + {"Lob", {32, bm::Counter::kAvgThreadsRate}}, + }); +} +BENCHMARK(BM_CounterRates_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterRates_Tabular/threads:%int\",$"}, + {"\"run_name\": \"BM_CounterRates_Tabular/threads:%int\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float,$", MR_Next}, + {"\"Frob\": %float,$", MR_Next}, + {"\"Lob\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterRates_Tabular/threads:%int\",%csv_report," + "%float,%float,%float,%float,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckTabularRate(Results const& e) { + double t = e.DurationCPUTime(); + CHECK_FLOAT_COUNTER_VALUE(e, "Foo", EQ, 1. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Bar", EQ, 2. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Baz", EQ, 4. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Bat", EQ, 8. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Frob", EQ, 16. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "Lob", EQ, 32. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_CounterRates_Tabular/threads:%int", + &CheckTabularRate); + +// ========================================================================= // +// ------------------------- Tabular Counters Output ----------------------- // +// ========================================================================= // + +// set only some of the counters +void BM_CounterSet0_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {10, bm::Counter::kAvgThreads}}, + {"Bar", {20, bm::Counter::kAvgThreads}}, + {"Baz", {40, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_CounterSet0_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterSet0_Tabular/threads:%int\",$"}, + {"\"run_name\": \"BM_CounterSet0_Tabular/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterSet0_Tabular/threads:%int\",%csv_report," + "%float,,%float,%float,,"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSet0(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 10); + CHECK_COUNTER_VALUE(e, int, "Bar", EQ, 20); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 40); +} +CHECK_BENCHMARK_RESULTS("BM_CounterSet0_Tabular", &CheckSet0); + +// again. +void BM_CounterSet1_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {15, bm::Counter::kAvgThreads}}, + {"Bar", {25, bm::Counter::kAvgThreads}}, + {"Baz", {45, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_CounterSet1_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterSet1_Tabular/threads:%int\",$"}, + {"\"run_name\": \"BM_CounterSet1_Tabular/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bar\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterSet1_Tabular/threads:%int\",%csv_report," + "%float,,%float,%float,,"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSet1(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 15); + CHECK_COUNTER_VALUE(e, int, "Bar", EQ, 25); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 45); +} +CHECK_BENCHMARK_RESULTS("BM_CounterSet1_Tabular/threads:%int", &CheckSet1); + +// ========================================================================= // +// ------------------------- Tabular Counters Output ----------------------- // +// ========================================================================= // + +// set only some of the counters, different set now. +void BM_CounterSet2_Tabular(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"Foo", {10, bm::Counter::kAvgThreads}}, + {"Bat", {30, bm::Counter::kAvgThreads}}, + {"Baz", {40, bm::Counter::kAvgThreads}}, + }); +} +BENCHMARK(BM_CounterSet2_Tabular)->ThreadRange(1, 16); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_CounterSet2_Tabular/threads:%int\",$"}, + {"\"run_name\": \"BM_CounterSet2_Tabular/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"Bat\": %float,$", MR_Next}, + {"\"Baz\": %float,$", MR_Next}, + {"\"Foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_CounterSet2_Tabular/threads:%int\",%csv_report," + ",%float,%float,%float,,"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSet2(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "Foo", EQ, 10); + CHECK_COUNTER_VALUE(e, int, "Bat", EQ, 30); + CHECK_COUNTER_VALUE(e, int, "Baz", EQ, 40); +} +CHECK_BENCHMARK_RESULTS("BM_CounterSet2_Tabular", &CheckSet2); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/thirdparty/benchmark-1.5.0/test/user_counters_test.cc b/thirdparty/benchmark-1.5.0/test/user_counters_test.cc new file mode 100644 index 0000000000..0775bc01f7 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/user_counters_test.cc @@ -0,0 +1,438 @@ + +#undef NDEBUG + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// ========================================================================= // +// ---------------------- Testing Prologue Output -------------------------- // +// ========================================================================= // + +// clang-format off + +ADD_CASES(TC_ConsoleOut, + {{"^[-]+$", MR_Next}, + {"^Benchmark %s Time %s CPU %s Iterations UserCounters...$", MR_Next}, + {"^[-]+$", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"%csv_header,\"bar\",\"foo\""}}); + +// clang-format on + +// ========================================================================= // +// ------------------------- Simple Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_Simple(benchmark::State& state) { + for (auto _ : state) { + } + state.counters["foo"] = 1; + state.counters["bar"] = 2 * (double)state.iterations(); +} +BENCHMARK(BM_Counters_Simple); +ADD_CASES(TC_ConsoleOut, + {{"^BM_Counters_Simple %console_report bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Counters_Simple\",$"}, + {"\"run_name\": \"BM_Counters_Simple\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_Simple\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckSimple(Results const& e) { + double its = e.NumIterations(); + CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1); + // check that the value of bar is within 0.1% of the expected value + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. * its, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Simple", &CheckSimple); + +// ========================================================================= // +// --------------------- Counters+Items+Bytes/s Output --------------------- // +// ========================================================================= // + +namespace { +int num_calls1 = 0; +} +void BM_Counters_WithBytesAndItemsPSec(benchmark::State& state) { + for (auto _ : state) { + } + state.counters["foo"] = 1; + state.counters["bar"] = ++num_calls1; + state.SetBytesProcessed(364); + state.SetItemsProcessed(150); +} +BENCHMARK(BM_Counters_WithBytesAndItemsPSec); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_WithBytesAndItemsPSec %console_report " + "bar=%hrfloat bytes_per_second=%hrfloat/s " + "foo=%hrfloat items_per_second=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_WithBytesAndItemsPSec\",$"}, + {"\"run_name\": \"BM_Counters_WithBytesAndItemsPSec\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"bytes_per_second\": %float,$", MR_Next}, + {"\"foo\": %float,$", MR_Next}, + {"\"items_per_second\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_WithBytesAndItemsPSec\"," + "%csv_bytes_items_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckBytesAndItemsPSec(Results const& e) { + double t = e.DurationCPUTime(); // this (and not real time) is the time used + CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1); + CHECK_COUNTER_VALUE(e, int, "bar", EQ, num_calls1); + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_RESULT_VALUE(e, "bytes_per_second", EQ, 364. / t, 0.001); + CHECK_FLOAT_RESULT_VALUE(e, "items_per_second", EQ, 150. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_WithBytesAndItemsPSec", + &CheckBytesAndItemsPSec); + +// ========================================================================= // +// ------------------------- Rate Counters Output -------------------------- // +// ========================================================================= // + +void BM_Counters_Rate(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kIsRate}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kIsRate}; +} +BENCHMARK(BM_Counters_Rate); +ADD_CASES( + TC_ConsoleOut, + {{"^BM_Counters_Rate %console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Counters_Rate\",$"}, + {"\"run_name\": \"BM_Counters_Rate\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_Rate\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckRate(Results const& e) { + double t = e.DurationCPUTime(); // this (and not real time) is the time used + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Rate", &CheckRate); + +// ========================================================================= // +// ------------------------- Thread Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_Threads(benchmark::State& state) { + for (auto _ : state) { + } + state.counters["foo"] = 1; + state.counters["bar"] = 2; +} +BENCHMARK(BM_Counters_Threads)->ThreadRange(1, 8); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_Threads/threads:%int %console_report " + "bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Threads/threads:%int\",$"}, + {"\"run_name\": \"BM_Counters_Threads/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES( + TC_CSVOut, + {{"^\"BM_Counters_Threads/threads:%int\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckThreads(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "foo", EQ, e.NumThreads()); + CHECK_COUNTER_VALUE(e, int, "bar", EQ, 2 * e.NumThreads()); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Threads/threads:%int", &CheckThreads); + +// ========================================================================= // +// ---------------------- ThreadAvg Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_AvgThreads(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgThreads}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kAvgThreads}; +} +BENCHMARK(BM_Counters_AvgThreads)->ThreadRange(1, 8); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_AvgThreads/threads:%int " + "%console_report bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_AvgThreads/threads:%int\",$"}, + {"\"run_name\": \"BM_Counters_AvgThreads/threads:%int\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES( + TC_CSVOut, + {{"^\"BM_Counters_AvgThreads/threads:%int\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgThreads(Results const& e) { + CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1); + CHECK_COUNTER_VALUE(e, int, "bar", EQ, 2); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_AvgThreads/threads:%int", + &CheckAvgThreads); + +// ========================================================================= // +// ---------------------- ThreadAvg Counters Output ------------------------ // +// ========================================================================= // + +void BM_Counters_AvgThreadsRate(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgThreadsRate}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kAvgThreadsRate}; +} +BENCHMARK(BM_Counters_AvgThreadsRate)->ThreadRange(1, 8); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_AvgThreadsRate/threads:%int " + "%console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_AvgThreadsRate/threads:%int\",$"}, + {"\"run_name\": \"BM_Counters_AvgThreadsRate/threads:%int\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_AvgThreadsRate/" + "threads:%int\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgThreadsRate(Results const& e) { + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / e.DurationCPUTime(), 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / e.DurationCPUTime(), 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_AvgThreadsRate/threads:%int", + &CheckAvgThreadsRate); + +// ========================================================================= // +// ------------------- IterationInvariant Counters Output ------------------ // +// ========================================================================= // + +void BM_Counters_IterationInvariant(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kIsIterationInvariant}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kIsIterationInvariant}; +} +BENCHMARK(BM_Counters_IterationInvariant); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_IterationInvariant %console_report " + "bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_IterationInvariant\",$"}, + {"\"run_name\": \"BM_Counters_IterationInvariant\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_IterationInvariant\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckIterationInvariant(Results const& e) { + double its = e.NumIterations(); + // check that the values are within 0.1% of the expected value + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, its, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. * its, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_IterationInvariant", + &CheckIterationInvariant); + +// ========================================================================= // +// ----------------- IterationInvariantRate Counters Output ---------------- // +// ========================================================================= // + +void BM_Counters_kIsIterationInvariantRate(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = + bm::Counter{1, bm::Counter::kIsIterationInvariantRate}; + state.counters["bar"] = + bm::Counter{2, bm::Counter::kIsRate | bm::Counter::kIsIterationInvariant}; +} +BENCHMARK(BM_Counters_kIsIterationInvariantRate); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_kIsIterationInvariantRate " + "%console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_kIsIterationInvariantRate\",$"}, + {"\"run_name\": \"BM_Counters_kIsIterationInvariantRate\",$", + MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_kIsIterationInvariantRate\",%csv_report," + "%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckIsIterationInvariantRate(Results const& e) { + double its = e.NumIterations(); + double t = e.DurationCPUTime(); // this (and not real time) is the time used + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, its * 1. / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, its * 2. / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_kIsIterationInvariantRate", + &CheckIsIterationInvariantRate); + +// ========================================================================= // +// ------------------- AvgIterations Counters Output ------------------ // +// ========================================================================= // + +void BM_Counters_AvgIterations(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgIterations}; + state.counters["bar"] = bm::Counter{2, bm::Counter::kAvgIterations}; +} +BENCHMARK(BM_Counters_AvgIterations); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_AvgIterations %console_report " + "bar=%hrfloat foo=%hrfloat$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_AvgIterations\",$"}, + {"\"run_name\": \"BM_Counters_AvgIterations\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, + {{"^\"BM_Counters_AvgIterations\",%csv_report,%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgIterations(Results const& e) { + double its = e.NumIterations(); + // check that the values are within 0.1% of the expected value + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / its, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / its, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_AvgIterations", &CheckAvgIterations); + +// ========================================================================= // +// ----------------- AvgIterationsRate Counters Output ---------------- // +// ========================================================================= // + +void BM_Counters_kAvgIterationsRate(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgIterationsRate}; + state.counters["bar"] = + bm::Counter{2, bm::Counter::kIsRate | bm::Counter::kAvgIterations}; +} +BENCHMARK(BM_Counters_kAvgIterationsRate); +ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_kAvgIterationsRate " + "%console_report bar=%hrfloat/s foo=%hrfloat/s$"}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_kAvgIterationsRate\",$"}, + {"\"run_name\": \"BM_Counters_kAvgIterationsRate\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 0,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"bar\": %float,$", MR_Next}, + {"\"foo\": %float$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_kAvgIterationsRate\",%csv_report," + "%float,%float$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckAvgIterationsRate(Results const& e) { + double its = e.NumIterations(); + double t = e.DurationCPUTime(); // this (and not real time) is the time used + // check that the values are within 0.1% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / its / t, 0.001); + CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / its / t, 0.001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_kAvgIterationsRate", + &CheckAvgIterationsRate); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/thirdparty/benchmark-1.5.0/test/user_counters_thousands_test.cc b/thirdparty/benchmark-1.5.0/test/user_counters_thousands_test.cc new file mode 100644 index 0000000000..21d8285ded --- /dev/null +++ b/thirdparty/benchmark-1.5.0/test/user_counters_thousands_test.cc @@ -0,0 +1,173 @@ + +#undef NDEBUG + +#include "benchmark/benchmark.h" +#include "output_test.h" + +// ========================================================================= // +// ------------------------ Thousands Customisation ------------------------ // +// ========================================================================= // + +void BM_Counters_Thousands(benchmark::State& state) { + for (auto _ : state) { + } + namespace bm = benchmark; + state.counters.insert({ + {"t0_1000000DefaultBase", + bm::Counter(1000 * 1000, bm::Counter::kDefaults)}, + {"t1_1000000Base1000", bm::Counter(1000 * 1000, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1000)}, + {"t2_1000000Base1024", bm::Counter(1000 * 1000, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1024)}, + {"t3_1048576Base1000", bm::Counter(1024 * 1024, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1000)}, + {"t4_1048576Base1024", bm::Counter(1024 * 1024, bm::Counter::kDefaults, + benchmark::Counter::OneK::kIs1024)}, + }); +} +BENCHMARK(BM_Counters_Thousands)->Repetitions(2); +ADD_CASES( + TC_ConsoleOut, + { + {"^BM_Counters_Thousands/repeats:2 %console_report " + "t0_1000000DefaultBase=1000k " + "t1_1000000Base1000=1000k t2_1000000Base1024=976.56[23]k " + "t3_1048576Base1000=1048.58k t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2 %console_report " + "t0_1000000DefaultBase=1000k " + "t1_1000000Base1000=1000k t2_1000000Base1024=976.56[23]k " + "t3_1048576Base1000=1048.58k t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2_mean %console_report " + "t0_1000000DefaultBase=1000k t1_1000000Base1000=1000k " + "t2_1000000Base1024=976.56[23]k t3_1048576Base1000=1048.58k " + "t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2_median %console_report " + "t0_1000000DefaultBase=1000k t1_1000000Base1000=1000k " + "t2_1000000Base1024=976.56[23]k t3_1048576Base1000=1048.58k " + "t4_1048576Base1024=1024k$"}, + {"^BM_Counters_Thousands/repeats:2_stddev %console_time_only_report [ " + "]*2 t0_1000000DefaultBase=0 t1_1000000Base1000=0 " + "t2_1000000Base1024=0 t3_1048576Base1000=0 t4_1048576Base1024=0$"}, + }); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2\",$"}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 0,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2\",$"}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"iteration\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"repetition_index\": 1,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"iterations\": %int,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2_mean\",$"}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"mean\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2_median\",$"}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"median\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t1_1000000Base1000\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t2_1000000Base1024\": 1\\.(0)*e\\+(0)*6,$", MR_Next}, + {"\"t3_1048576Base1000\": 1\\.048576(0)*e\\+(0)*6,$", MR_Next}, + {"\"t4_1048576Base1024\": 1\\.048576(0)*e\\+(0)*6$", MR_Next}, + {"}", MR_Next}}); +ADD_CASES(TC_JSONOut, + {{"\"name\": \"BM_Counters_Thousands/repeats:2_stddev\",$"}, + {"\"run_name\": \"BM_Counters_Thousands/repeats:2\",$", MR_Next}, + {"\"run_type\": \"aggregate\",$", MR_Next}, + {"\"repetitions\": 2,$", MR_Next}, + {"\"threads\": 1,$", MR_Next}, + {"\"aggregate_name\": \"stddev\",$", MR_Next}, + {"\"iterations\": 2,$", MR_Next}, + {"\"real_time\": %float,$", MR_Next}, + {"\"cpu_time\": %float,$", MR_Next}, + {"\"time_unit\": \"ns\",$", MR_Next}, + {"\"t0_1000000DefaultBase\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t1_1000000Base1000\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t2_1000000Base1024\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t3_1048576Base1000\": 0\\.(0)*e\\+(0)*,$", MR_Next}, + {"\"t4_1048576Base1024\": 0\\.(0)*e\\+(0)*$", MR_Next}, + {"}", MR_Next}}); + +ADD_CASES( + TC_CSVOut, + {{"^\"BM_Counters_Thousands/" + "repeats:2\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\.04858e\\+(" + "0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/" + "repeats:2\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\.04858e\\+(" + "0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/" + "repeats:2_mean\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\." + "04858e\\+(0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/" + "repeats:2_median\",%csv_report,1e\\+(0)*6,1e\\+(0)*6,1e\\+(0)*6,1\\." + "04858e\\+(0)*6,1\\.04858e\\+(0)*6$"}, + {"^\"BM_Counters_Thousands/repeats:2_stddev\",%csv_report,0,0,0,0,0$"}}); +// VS2013 does not allow this function to be passed as a lambda argument +// to CHECK_BENCHMARK_RESULTS() +void CheckThousands(Results const& e) { + if (e.name != "BM_Counters_Thousands/repeats:2") + return; // Do not check the aggregates! + + // check that the values are within 0.01% of the expected values + CHECK_FLOAT_COUNTER_VALUE(e, "t0_1000000DefaultBase", EQ, 1000 * 1000, + 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t1_1000000Base1000", EQ, 1000 * 1000, 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t2_1000000Base1024", EQ, 1000 * 1000, 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t3_1048576Base1000", EQ, 1024 * 1024, 0.0001); + CHECK_FLOAT_COUNTER_VALUE(e, "t4_1048576Base1024", EQ, 1024 * 1024, 0.0001); +} +CHECK_BENCHMARK_RESULTS("BM_Counters_Thousands", &CheckThousands); + +// ========================================================================= // +// --------------------------- TEST CASES END ------------------------------ // +// ========================================================================= // + +int main(int argc, char* argv[]) { RunOutputTests(argc, argv); } diff --git a/thirdparty/benchmark-1.5.0/tools/compare.py b/thirdparty/benchmark-1.5.0/tools/compare.py new file mode 100755 index 0000000000..539ace6fb1 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/compare.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python + +import unittest +""" +compare.py - versatile benchmark output compare tool +""" + +import argparse +from argparse import ArgumentParser +import sys +import gbench +from gbench import util, report +from gbench.util import * + + +def check_inputs(in1, in2, flags): + """ + Perform checking on the user provided inputs and diagnose any abnormalities + """ + in1_kind, in1_err = classify_input_file(in1) + in2_kind, in2_err = classify_input_file(in2) + output_file = find_benchmark_flag('--benchmark_out=', flags) + output_type = find_benchmark_flag('--benchmark_out_format=', flags) + if in1_kind == IT_Executable and in2_kind == IT_Executable and output_file: + print(("WARNING: '--benchmark_out=%s' will be passed to both " + "benchmarks causing it to be overwritten") % output_file) + if in1_kind == IT_JSON and in2_kind == IT_JSON and len(flags) > 0: + print("WARNING: passing optional flags has no effect since both " + "inputs are JSON") + if output_type is not None and output_type != 'json': + print(("ERROR: passing '--benchmark_out_format=%s' to 'compare.py`" + " is not supported.") % output_type) + sys.exit(1) + + +def create_parser(): + parser = ArgumentParser( + description='versatile benchmark output compare tool') + + parser.add_argument( + '-a', + '--display_aggregates_only', + dest='display_aggregates_only', + action="store_true", + help="If there are repetitions, by default, we display everything - the" + " actual runs, and the aggregates computed. Sometimes, it is " + "desirable to only view the aggregates. E.g. when there are a lot " + "of repetitions. Do note that only the display is affected. " + "Internally, all the actual runs are still used, e.g. for U test.") + + utest = parser.add_argument_group() + utest.add_argument( + '--no-utest', + dest='utest', + default=True, + action="store_false", + help="The tool can do a two-tailed Mann-Whitney U test with the null hypothesis that it is equally likely that a randomly selected value from one sample will be less than or greater than a randomly selected value from a second sample.\nWARNING: requires **LARGE** (no less than {}) number of repetitions to be meaningful!\nThe test is being done by default, if at least {} repetitions were done.\nThis option can disable the U Test.".format(report.UTEST_OPTIMAL_REPETITIONS, report.UTEST_MIN_REPETITIONS)) + alpha_default = 0.05 + utest.add_argument( + "--alpha", + dest='utest_alpha', + default=alpha_default, + type=float, + help=("significance level alpha. if the calculated p-value is below this value, then the result is said to be statistically significant and the null hypothesis is rejected.\n(default: %0.4f)") % + alpha_default) + + subparsers = parser.add_subparsers( + help='This tool has multiple modes of operation:', + dest='mode') + + parser_a = subparsers.add_parser( + 'benchmarks', + help='The most simple use-case, compare all the output of these two benchmarks') + baseline = parser_a.add_argument_group( + 'baseline', 'The benchmark baseline') + baseline.add_argument( + 'test_baseline', + metavar='test_baseline', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + contender = parser_a.add_argument_group( + 'contender', 'The benchmark that will be compared against the baseline') + contender.add_argument( + 'test_contender', + metavar='test_contender', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + parser_a.add_argument( + 'benchmark_options', + metavar='benchmark_options', + nargs=argparse.REMAINDER, + help='Arguments to pass when running benchmark executables') + + parser_b = subparsers.add_parser( + 'filters', help='Compare filter one with the filter two of benchmark') + baseline = parser_b.add_argument_group( + 'baseline', 'The benchmark baseline') + baseline.add_argument( + 'test', + metavar='test', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + baseline.add_argument( + 'filter_baseline', + metavar='filter_baseline', + type=str, + nargs=1, + help='The first filter, that will be used as baseline') + contender = parser_b.add_argument_group( + 'contender', 'The benchmark that will be compared against the baseline') + contender.add_argument( + 'filter_contender', + metavar='filter_contender', + type=str, + nargs=1, + help='The second filter, that will be compared against the baseline') + parser_b.add_argument( + 'benchmark_options', + metavar='benchmark_options', + nargs=argparse.REMAINDER, + help='Arguments to pass when running benchmark executables') + + parser_c = subparsers.add_parser( + 'benchmarksfiltered', + help='Compare filter one of first benchmark with filter two of the second benchmark') + baseline = parser_c.add_argument_group( + 'baseline', 'The benchmark baseline') + baseline.add_argument( + 'test_baseline', + metavar='test_baseline', + type=argparse.FileType('r'), + nargs=1, + help='A benchmark executable or JSON output file') + baseline.add_argument( + 'filter_baseline', + metavar='filter_baseline', + type=str, + nargs=1, + help='The first filter, that will be used as baseline') + contender = parser_c.add_argument_group( + 'contender', 'The benchmark that will be compared against the baseline') + contender.add_argument( + 'test_contender', + metavar='test_contender', + type=argparse.FileType('r'), + nargs=1, + help='The second benchmark executable or JSON output file, that will be compared against the baseline') + contender.add_argument( + 'filter_contender', + metavar='filter_contender', + type=str, + nargs=1, + help='The second filter, that will be compared against the baseline') + parser_c.add_argument( + 'benchmark_options', + metavar='benchmark_options', + nargs=argparse.REMAINDER, + help='Arguments to pass when running benchmark executables') + + return parser + + +def main(): + # Parse the command line flags + parser = create_parser() + args, unknown_args = parser.parse_known_args() + if args.mode is None: + parser.print_help() + exit(1) + assert not unknown_args + benchmark_options = args.benchmark_options + + if args.mode == 'benchmarks': + test_baseline = args.test_baseline[0].name + test_contender = args.test_contender[0].name + filter_baseline = '' + filter_contender = '' + + # NOTE: if test_baseline == test_contender, you are analyzing the stdev + + description = 'Comparing %s to %s' % (test_baseline, test_contender) + elif args.mode == 'filters': + test_baseline = args.test[0].name + test_contender = args.test[0].name + filter_baseline = args.filter_baseline[0] + filter_contender = args.filter_contender[0] + + # NOTE: if filter_baseline == filter_contender, you are analyzing the + # stdev + + description = 'Comparing %s to %s (from %s)' % ( + filter_baseline, filter_contender, args.test[0].name) + elif args.mode == 'benchmarksfiltered': + test_baseline = args.test_baseline[0].name + test_contender = args.test_contender[0].name + filter_baseline = args.filter_baseline[0] + filter_contender = args.filter_contender[0] + + # NOTE: if test_baseline == test_contender and + # filter_baseline == filter_contender, you are analyzing the stdev + + description = 'Comparing %s (from %s) to %s (from %s)' % ( + filter_baseline, test_baseline, filter_contender, test_contender) + else: + # should never happen + print("Unrecognized mode of operation: '%s'" % args.mode) + parser.print_help() + exit(1) + + check_inputs(test_baseline, test_contender, benchmark_options) + + if args.display_aggregates_only: + benchmark_options += ['--benchmark_display_aggregates_only=true'] + + options_baseline = [] + options_contender = [] + + if filter_baseline and filter_contender: + options_baseline = ['--benchmark_filter=%s' % filter_baseline] + options_contender = ['--benchmark_filter=%s' % filter_contender] + + # Run the benchmarks and report the results + json1 = json1_orig = gbench.util.run_or_load_benchmark( + test_baseline, benchmark_options + options_baseline) + json2 = json2_orig = gbench.util.run_or_load_benchmark( + test_contender, benchmark_options + options_contender) + + # Now, filter the benchmarks so that the difference report can work + if filter_baseline and filter_contender: + replacement = '[%s vs. %s]' % (filter_baseline, filter_contender) + json1 = gbench.report.filter_benchmark( + json1_orig, filter_baseline, replacement) + json2 = gbench.report.filter_benchmark( + json2_orig, filter_contender, replacement) + + # Diff and output + output_lines = gbench.report.generate_difference_report( + json1, json2, args.display_aggregates_only, + args.utest, args.utest_alpha) + print(description) + for ln in output_lines: + print(ln) + + +class TestParser(unittest.TestCase): + def setUp(self): + self.parser = create_parser() + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'gbench', + 'Inputs') + self.testInput0 = os.path.join(testInputs, 'test1_run1.json') + self.testInput1 = os.path.join(testInputs, 'test1_run2.json') + + def test_benchmarks_basic(self): + parsed = self.parser.parse_args( + ['benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_without_utest(self): + parsed = self.parser.parse_args( + ['--no-utest', 'benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertFalse(parsed.utest) + self.assertEqual(parsed.utest_alpha, 0.05) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_display_aggregates_only(self): + parsed = self.parser.parse_args( + ['-a', 'benchmarks', self.testInput0, self.testInput1]) + self.assertTrue(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_with_utest_alpha(self): + parsed = self.parser.parse_args( + ['--alpha=0.314', 'benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.utest_alpha, 0.314) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_basic_without_utest_with_utest_alpha(self): + parsed = self.parser.parse_args( + ['--no-utest', '--alpha=0.314', 'benchmarks', self.testInput0, self.testInput1]) + self.assertFalse(parsed.display_aggregates_only) + self.assertFalse(parsed.utest) + self.assertEqual(parsed.utest_alpha, 0.314) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertFalse(parsed.benchmark_options) + + def test_benchmarks_with_remainder(self): + parsed = self.parser.parse_args( + ['benchmarks', self.testInput0, self.testInput1, 'd']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.benchmark_options, ['d']) + + def test_benchmarks_with_remainder_after_doubleminus(self): + parsed = self.parser.parse_args( + ['benchmarks', self.testInput0, self.testInput1, '--', 'e']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarks') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.benchmark_options, ['e']) + + def test_filters_basic(self): + parsed = self.parser.parse_args( + ['filters', self.testInput0, 'c', 'd']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'filters') + self.assertEqual(parsed.test[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.filter_contender[0], 'd') + self.assertFalse(parsed.benchmark_options) + + def test_filters_with_remainder(self): + parsed = self.parser.parse_args( + ['filters', self.testInput0, 'c', 'd', 'e']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'filters') + self.assertEqual(parsed.test[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.filter_contender[0], 'd') + self.assertEqual(parsed.benchmark_options, ['e']) + + def test_filters_with_remainder_after_doubleminus(self): + parsed = self.parser.parse_args( + ['filters', self.testInput0, 'c', 'd', '--', 'f']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'filters') + self.assertEqual(parsed.test[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.filter_contender[0], 'd') + self.assertEqual(parsed.benchmark_options, ['f']) + + def test_benchmarksfiltered_basic(self): + parsed = self.parser.parse_args( + ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarksfiltered') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.filter_contender[0], 'e') + self.assertFalse(parsed.benchmark_options) + + def test_benchmarksfiltered_with_remainder(self): + parsed = self.parser.parse_args( + ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e', 'f']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarksfiltered') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.filter_contender[0], 'e') + self.assertEqual(parsed.benchmark_options[0], 'f') + + def test_benchmarksfiltered_with_remainder_after_doubleminus(self): + parsed = self.parser.parse_args( + ['benchmarksfiltered', self.testInput0, 'c', self.testInput1, 'e', '--', 'g']) + self.assertFalse(parsed.display_aggregates_only) + self.assertTrue(parsed.utest) + self.assertEqual(parsed.mode, 'benchmarksfiltered') + self.assertEqual(parsed.test_baseline[0].name, self.testInput0) + self.assertEqual(parsed.filter_baseline[0], 'c') + self.assertEqual(parsed.test_contender[0].name, self.testInput1) + self.assertEqual(parsed.filter_contender[0], 'e') + self.assertEqual(parsed.benchmark_options[0], 'g') + + +if __name__ == '__main__': + # unittest.main() + main() + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# kate: tab-width: 4; replace-tabs on; indent-width 4; tab-indents: off; +# kate: indent-mode python; remove-trailing-spaces modified; diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test1_run1.json b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test1_run1.json new file mode 100644 index 0000000000..601e327aef --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test1_run1.json @@ -0,0 +1,119 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_SameTimes", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "BM_2xFaster", + "iterations": 1000, + "real_time": 50, + "cpu_time": 50, + "time_unit": "ns" + }, + { + "name": "BM_2xSlower", + "iterations": 1000, + "real_time": 50, + "cpu_time": 50, + "time_unit": "ns" + }, + { + "name": "BM_1PercentFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_1PercentSlower", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_10PercentFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_10PercentSlower", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_100xSlower", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_100xFaster", + "iterations": 1000, + "real_time": 10000, + "cpu_time": 10000, + "time_unit": "ns" + }, + { + "name": "BM_10PercentCPUToTime", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_ThirdFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_BigO", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "BigO", + "cpu_coefficient": 4.2749856294592886e+00, + "real_coefficient": 6.4789275289789780e+00, + "big_o": "N", + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_RMS", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "RMS", + "rms": 4.5097802512472874e-03 + }, + { + "name": "BM_NotBadTimeUnit", + "iterations": 1000, + "real_time": 0.4, + "cpu_time": 0.5, + "time_unit": "s" + }, + { + "name": "BM_DifferentTimeUnit", + "iterations": 1, + "real_time": 1, + "cpu_time": 1, + "time_unit": "s" + } + ] +} diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test1_run2.json b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test1_run2.json new file mode 100644 index 0000000000..3cbcf39b0c --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test1_run2.json @@ -0,0 +1,119 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_SameTimes", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "BM_2xFaster", + "iterations": 1000, + "real_time": 25, + "cpu_time": 25, + "time_unit": "ns" + }, + { + "name": "BM_2xSlower", + "iterations": 20833333, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_1PercentFaster", + "iterations": 1000, + "real_time": 98.9999999, + "cpu_time": 98.9999999, + "time_unit": "ns" + }, + { + "name": "BM_1PercentSlower", + "iterations": 1000, + "real_time": 100.9999999, + "cpu_time": 100.9999999, + "time_unit": "ns" + }, + { + "name": "BM_10PercentFaster", + "iterations": 1000, + "real_time": 90, + "cpu_time": 90, + "time_unit": "ns" + }, + { + "name": "BM_10PercentSlower", + "iterations": 1000, + "real_time": 110, + "cpu_time": 110, + "time_unit": "ns" + }, + { + "name": "BM_100xSlower", + "iterations": 1000, + "real_time": 1.0000e+04, + "cpu_time": 1.0000e+04, + "time_unit": "ns" + }, + { + "name": "BM_100xFaster", + "iterations": 1000, + "real_time": 100, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_10PercentCPUToTime", + "iterations": 1000, + "real_time": 110, + "cpu_time": 90, + "time_unit": "ns" + }, + { + "name": "BM_ThirdFaster", + "iterations": 1000, + "real_time": 66.665, + "cpu_time": 66.664, + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_BigO", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "BigO", + "cpu_coefficient": 5.6215779594361486e+00, + "real_coefficient": 5.6288314793554610e+00, + "big_o": "N", + "time_unit": "ns" + }, + { + "name": "MyComplexityTest_RMS", + "run_name": "MyComplexityTest", + "run_type": "aggregate", + "aggregate_name": "RMS", + "rms": 3.3128901852342174e-03 + }, + { + "name": "BM_NotBadTimeUnit", + "iterations": 1000, + "real_time": 0.04, + "cpu_time": 0.6, + "time_unit": "s" + }, + { + "name": "BM_DifferentTimeUnit", + "iterations": 1, + "real_time": 1, + "cpu_time": 1, + "time_unit": "ns" + } + ] +} diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test2_run.json b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test2_run.json new file mode 100644 index 0000000000..15bc698030 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test2_run.json @@ -0,0 +1,81 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_Hi", + "iterations": 1234, + "real_time": 42, + "cpu_time": 24, + "time_unit": "ms" + }, + { + "name": "BM_Zero", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "BM_Zero/4", + "iterations": 4000, + "real_time": 40, + "cpu_time": 40, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_Zero", + "iterations": 2000, + "real_time": 20, + "cpu_time": 20, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_Zero/3", + "iterations": 3000, + "real_time": 30, + "cpu_time": 30, + "time_unit": "ns" + }, + { + "name": "BM_One", + "iterations": 5000, + "real_time": 5, + "cpu_time": 5, + "time_unit": "ns" + }, + { + "name": "BM_One/4", + "iterations": 2000, + "real_time": 20, + "cpu_time": 20, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_One", + "iterations": 1000, + "real_time": 10, + "cpu_time": 10, + "time_unit": "ns" + }, + { + "name": "Prefix/BM_One/3", + "iterations": 1500, + "real_time": 15, + "cpu_time": 15, + "time_unit": "ns" + }, + { + "name": "BM_Bye", + "iterations": 5321, + "real_time": 11, + "cpu_time": 63, + "time_unit": "ns" + } + ] +} diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test3_run0.json b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test3_run0.json new file mode 100644 index 0000000000..49f8b06143 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test3_run0.json @@ -0,0 +1,65 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_One", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 10, + "cpu_time": 100, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "iterations": 1000, + "real_time": 9, + "cpu_time": 90, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "iterations": 1000, + "real_time": 8, + "cpu_time": 86, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 8, + "cpu_time": 80, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 8, + "cpu_time": 77, + "time_unit": "ns" + }, + { + "name": "medium", + "run_type": "iteration", + "iterations": 1000, + "real_time": 8, + "cpu_time": 80, + "time_unit": "ns" + }, + { + "name": "medium", + "run_type": "iteration", + "iterations": 1000, + "real_time": 9, + "cpu_time": 82, + "time_unit": "ns" + } + ] +} diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test3_run1.json b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test3_run1.json new file mode 100644 index 0000000000..acc5ba17ae --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/Inputs/test3_run1.json @@ -0,0 +1,65 @@ +{ + "context": { + "date": "2016-08-02 17:44:46", + "num_cpus": 4, + "mhz_per_cpu": 4228, + "cpu_scaling_enabled": false, + "library_build_type": "release" + }, + "benchmarks": [ + { + "name": "BM_One", + "iterations": 1000, + "real_time": 9, + "cpu_time": 110, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 10, + "cpu_time": 89, + "time_unit": "ns" + }, + { + "name": "BM_Two", + "iterations": 1000, + "real_time": 7, + "cpu_time": 72, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 1000, + "real_time": 7, + "cpu_time": 75, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "aggregate", + "iterations": 762, + "real_time": 4.54, + "cpu_time": 66.6, + "time_unit": "ns" + }, + { + "name": "short", + "run_type": "iteration", + "iterations": 1000, + "real_time": 800, + "cpu_time": 1, + "time_unit": "ns" + }, + { + "name": "medium", + "run_type": "iteration", + "iterations": 1200, + "real_time": 5, + "cpu_time": 53, + "time_unit": "ns" + } + ] +} diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/__init__.py b/thirdparty/benchmark-1.5.0/tools/gbench/__init__.py new file mode 100644 index 0000000000..fce1a1acfb --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/__init__.py @@ -0,0 +1,8 @@ +"""Google Benchmark tooling""" + +__author__ = 'Eric Fiselier' +__email__ = 'eric@efcs.ca' +__versioninfo__ = (0, 5, 0) +__version__ = '.'.join(str(v) for v in __versioninfo__) + 'dev' + +__all__ = [] diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/report.py b/thirdparty/benchmark-1.5.0/tools/gbench/report.py new file mode 100644 index 0000000000..5bd3a8d85d --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/report.py @@ -0,0 +1,541 @@ +import unittest +"""report.py - Utilities for reporting statistics about benchmark results +""" +import os +import re +import copy + +from scipy.stats import mannwhitneyu + + +class BenchmarkColor(object): + def __init__(self, name, code): + self.name = name + self.code = code + + def __repr__(self): + return '%s%r' % (self.__class__.__name__, + (self.name, self.code)) + + def __format__(self, format): + return self.code + + +# Benchmark Colors Enumeration +BC_NONE = BenchmarkColor('NONE', '') +BC_MAGENTA = BenchmarkColor('MAGENTA', '\033[95m') +BC_CYAN = BenchmarkColor('CYAN', '\033[96m') +BC_OKBLUE = BenchmarkColor('OKBLUE', '\033[94m') +BC_OKGREEN = BenchmarkColor('OKGREEN', '\033[32m') +BC_HEADER = BenchmarkColor('HEADER', '\033[92m') +BC_WARNING = BenchmarkColor('WARNING', '\033[93m') +BC_WHITE = BenchmarkColor('WHITE', '\033[97m') +BC_FAIL = BenchmarkColor('FAIL', '\033[91m') +BC_ENDC = BenchmarkColor('ENDC', '\033[0m') +BC_BOLD = BenchmarkColor('BOLD', '\033[1m') +BC_UNDERLINE = BenchmarkColor('UNDERLINE', '\033[4m') + +UTEST_MIN_REPETITIONS = 2 +UTEST_OPTIMAL_REPETITIONS = 9 # Lowest reasonable number, More is better. +UTEST_COL_NAME = "_pvalue" + + +def color_format(use_color, fmt_str, *args, **kwargs): + """ + Return the result of 'fmt_str.format(*args, **kwargs)' after transforming + 'args' and 'kwargs' according to the value of 'use_color'. If 'use_color' + is False then all color codes in 'args' and 'kwargs' are replaced with + the empty string. + """ + assert use_color is True or use_color is False + if not use_color: + args = [arg if not isinstance(arg, BenchmarkColor) else BC_NONE + for arg in args] + kwargs = {key: arg if not isinstance(arg, BenchmarkColor) else BC_NONE + for key, arg in kwargs.items()} + return fmt_str.format(*args, **kwargs) + + +def find_longest_name(benchmark_list): + """ + Return the length of the longest benchmark name in a given list of + benchmark JSON objects + """ + longest_name = 1 + for bc in benchmark_list: + if len(bc['name']) > longest_name: + longest_name = len(bc['name']) + return longest_name + + +def calculate_change(old_val, new_val): + """ + Return a float representing the decimal change between old_val and new_val. + """ + if old_val == 0 and new_val == 0: + return 0.0 + if old_val == 0: + return float(new_val - old_val) / (float(old_val + new_val) / 2) + return float(new_val - old_val) / abs(old_val) + + +def filter_benchmark(json_orig, family, replacement=""): + """ + Apply a filter to the json, and only leave the 'family' of benchmarks. + """ + regex = re.compile(family) + filtered = {} + filtered['benchmarks'] = [] + for be in json_orig['benchmarks']: + if not regex.search(be['name']): + continue + filteredbench = copy.deepcopy(be) # Do NOT modify the old name! + filteredbench['name'] = regex.sub(replacement, filteredbench['name']) + filtered['benchmarks'].append(filteredbench) + return filtered + + +def get_unique_benchmark_names(json): + """ + While *keeping* the order, give all the unique 'names' used for benchmarks. + """ + seen = set() + uniqued = [x['name'] for x in json['benchmarks'] + if x['name'] not in seen and + (seen.add(x['name']) or True)] + return uniqued + + +def intersect(list1, list2): + """ + Given two lists, get a new list consisting of the elements only contained + in *both of the input lists*, while preserving the ordering. + """ + return [x for x in list1 if x in list2] + + +def is_potentially_comparable_benchmark(x): + return ('time_unit' in x and 'real_time' in x and 'cpu_time' in x) + + +def partition_benchmarks(json1, json2): + """ + While preserving the ordering, find benchmarks with the same names in + both of the inputs, and group them. + (i.e. partition/filter into groups with common name) + """ + json1_unique_names = get_unique_benchmark_names(json1) + json2_unique_names = get_unique_benchmark_names(json2) + names = intersect(json1_unique_names, json2_unique_names) + partitions = [] + for name in names: + time_unit = None + # Pick the time unit from the first entry of the lhs benchmark. + # We should be careful not to crash with unexpected input. + for x in json1['benchmarks']: + if (x['name'] == name and is_potentially_comparable_benchmark(x)): + time_unit = x['time_unit'] + break + if time_unit is None: + continue + # Filter by name and time unit. + # All the repetitions are assumed to be comparable. + lhs = [x for x in json1['benchmarks'] if x['name'] == name and + x['time_unit'] == time_unit] + rhs = [x for x in json2['benchmarks'] if x['name'] == name and + x['time_unit'] == time_unit] + partitions.append([lhs, rhs]) + return partitions + + +def extract_field(partition, field_name): + # The count of elements may be different. We want *all* of them. + lhs = [x[field_name] for x in partition[0]] + rhs = [x[field_name] for x in partition[1]] + return [lhs, rhs] + +def calc_utest(timings_cpu, timings_time): + min_rep_cnt = min(len(timings_time[0]), + len(timings_time[1]), + len(timings_cpu[0]), + len(timings_cpu[1])) + + # Does *everything* has at least UTEST_MIN_REPETITIONS repetitions? + if min_rep_cnt < UTEST_MIN_REPETITIONS: + return False, None, None + + time_pvalue = mannwhitneyu( + timings_time[0], timings_time[1], alternative='two-sided').pvalue + cpu_pvalue = mannwhitneyu( + timings_cpu[0], timings_cpu[1], alternative='two-sided').pvalue + + return (min_rep_cnt >= UTEST_OPTIMAL_REPETITIONS), cpu_pvalue, time_pvalue + +def print_utest(partition, utest_alpha, first_col_width, use_color=True): + def get_utest_color(pval): + return BC_FAIL if pval >= utest_alpha else BC_OKGREEN + + timings_time = extract_field(partition, 'real_time') + timings_cpu = extract_field(partition, 'cpu_time') + have_optimal_repetitions, cpu_pvalue, time_pvalue = calc_utest(timings_cpu, timings_time) + + # Check if we failed miserably with minimum required repetitions for utest + if not have_optimal_repetitions and cpu_pvalue is None and time_pvalue is None: + return [] + + dsc = "U Test, Repetitions: {} vs {}".format( + len(timings_cpu[0]), len(timings_cpu[1])) + dsc_color = BC_OKGREEN + + # We still got some results to show but issue a warning about it. + if not have_optimal_repetitions: + dsc_color = BC_WARNING + dsc += ". WARNING: Results unreliable! {}+ repetitions recommended.".format( + UTEST_OPTIMAL_REPETITIONS) + + special_str = "{}{:<{}s}{endc}{}{:16.4f}{endc}{}{:16.4f}{endc}{} {}" + + last_name = partition[0][0]['name'] + return [color_format(use_color, + special_str, + BC_HEADER, + "{}{}".format(last_name, UTEST_COL_NAME), + first_col_width, + get_utest_color(time_pvalue), time_pvalue, + get_utest_color(cpu_pvalue), cpu_pvalue, + dsc_color, dsc, + endc=BC_ENDC)] + + +def generate_difference_report( + json1, + json2, + display_aggregates_only=False, + utest=False, + utest_alpha=0.05, + use_color=True): + """ + Calculate and report the difference between each test of two benchmarks + runs specified as 'json1' and 'json2'. + """ + assert utest is True or utest is False + first_col_width = find_longest_name(json1['benchmarks']) + + def find_test(name): + for b in json2['benchmarks']: + if b['name'] == name: + return b + return None + + first_col_width = max( + first_col_width, + len('Benchmark')) + first_col_width += len(UTEST_COL_NAME) + first_line = "{:<{}s}Time CPU Time Old Time New CPU Old CPU New".format( + 'Benchmark', 12 + first_col_width) + output_strs = [first_line, '-' * len(first_line)] + + partitions = partition_benchmarks(json1, json2) + for partition in partitions: + # Careful, we may have different repetition count. + for i in range(min(len(partition[0]), len(partition[1]))): + bn = partition[0][i] + other_bench = partition[1][i] + + # *If* we were asked to only display aggregates, + # and if it is non-aggregate, then skip it. + if display_aggregates_only and 'run_type' in bn and 'run_type' in other_bench: + assert bn['run_type'] == other_bench['run_type'] + if bn['run_type'] != 'aggregate': + continue + + fmt_str = "{}{:<{}s}{endc}{}{:+16.4f}{endc}{}{:+16.4f}{endc}{:14.0f}{:14.0f}{endc}{:14.0f}{:14.0f}" + + def get_color(res): + if res > 0.05: + return BC_FAIL + elif res > -0.07: + return BC_WHITE + else: + return BC_CYAN + + tres = calculate_change(bn['real_time'], other_bench['real_time']) + cpures = calculate_change(bn['cpu_time'], other_bench['cpu_time']) + output_strs += [color_format(use_color, + fmt_str, + BC_HEADER, + bn['name'], + first_col_width, + get_color(tres), + tres, + get_color(cpures), + cpures, + bn['real_time'], + other_bench['real_time'], + bn['cpu_time'], + other_bench['cpu_time'], + endc=BC_ENDC)] + + # After processing the whole partition, if requested, do the U test. + if utest: + output_strs += print_utest(partition, + utest_alpha=utest_alpha, + first_col_width=first_col_width, + use_color=use_color) + + return output_strs + + +############################################################################### +# Unit tests + + +class TestGetUniqueBenchmarkNames(unittest.TestCase): + def load_results(self): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput = os.path.join(testInputs, 'test3_run0.json') + with open(testOutput, 'r') as f: + json = json.load(f) + return json + + def test_basic(self): + expect_lines = [ + 'BM_One', + 'BM_Two', + 'short', # These two are not sorted + 'medium', # These two are not sorted + ] + json = self.load_results() + output_lines = get_unique_benchmark_names(json) + print("\n") + print("\n".join(output_lines)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + self.assertEqual(expect_lines[i], output_lines[i]) + + +class TestReportDifference(unittest.TestCase): + def load_results(self): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput1 = os.path.join(testInputs, 'test1_run1.json') + testOutput2 = os.path.join(testInputs, 'test1_run2.json') + with open(testOutput1, 'r') as f: + json1 = json.load(f) + with open(testOutput2, 'r') as f: + json2 = json.load(f) + return json1, json2 + + def test_basic(self): + expect_lines = [ + ['BM_SameTimes', '+0.0000', '+0.0000', '10', '10', '10', '10'], + ['BM_2xFaster', '-0.5000', '-0.5000', '50', '25', '50', '25'], + ['BM_2xSlower', '+1.0000', '+1.0000', '50', '100', '50', '100'], + ['BM_1PercentFaster', '-0.0100', '-0.0100', '100', '99', '100', '99'], + ['BM_1PercentSlower', '+0.0100', '+0.0100', '100', '101', '100', '101'], + ['BM_10PercentFaster', '-0.1000', '-0.1000', '100', '90', '100', '90'], + ['BM_10PercentSlower', '+0.1000', '+0.1000', '100', '110', '100', '110'], + ['BM_100xSlower', '+99.0000', '+99.0000', + '100', '10000', '100', '10000'], + ['BM_100xFaster', '-0.9900', '-0.9900', + '10000', '100', '10000', '100'], + ['BM_10PercentCPUToTime', '+0.1000', + '-0.1000', '100', '110', '100', '90'], + ['BM_ThirdFaster', '-0.3333', '-0.3334', '100', '67', '100', '67'], + ['BM_NotBadTimeUnit', '-0.9000', '+0.2000', '0', '0', '0', '1'], + ] + json1, json2 = self.load_results() + output_lines_with_header = generate_difference_report( + json1, json2, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(len(parts), 7) + self.assertEqual(expect_lines[i], parts) + + +class TestReportDifferenceBetweenFamilies(unittest.TestCase): + def load_result(self): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput = os.path.join(testInputs, 'test2_run.json') + with open(testOutput, 'r') as f: + json = json.load(f) + return json + + def test_basic(self): + expect_lines = [ + ['.', '-0.5000', '-0.5000', '10', '5', '10', '5'], + ['./4', '-0.5000', '-0.5000', '40', '20', '40', '20'], + ['Prefix/.', '-0.5000', '-0.5000', '20', '10', '20', '10'], + ['Prefix/./3', '-0.5000', '-0.5000', '30', '15', '30', '15'], + ] + json = self.load_result() + json1 = filter_benchmark(json, "BM_Z.ro", ".") + json2 = filter_benchmark(json, "BM_O.e", ".") + output_lines_with_header = generate_difference_report( + json1, json2, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(len(parts), 7) + self.assertEqual(expect_lines[i], parts) + + +class TestReportDifferenceWithUTest(unittest.TestCase): + def load_results(self): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput1 = os.path.join(testInputs, 'test3_run0.json') + testOutput2 = os.path.join(testInputs, 'test3_run1.json') + with open(testOutput1, 'r') as f: + json1 = json.load(f) + with open(testOutput2, 'r') as f: + json2 = json.load(f) + return json1, json2 + + def test_utest(self): + expect_lines = [] + expect_lines = [ + ['BM_One', '-0.1000', '+0.1000', '10', '9', '100', '110'], + ['BM_Two', '+0.1111', '-0.0111', '9', '10', '90', '89'], + ['BM_Two', '-0.1250', '-0.1628', '8', '7', '86', '72'], + ['BM_Two_pvalue', + '0.6985', + '0.6985', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '2.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['short', '-0.1250', '-0.0625', '8', '7', '80', '75'], + ['short', '-0.4325', '-0.1351', '8', '5', '77', '67'], + ['short_pvalue', + '0.7671', + '0.1489', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '3.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['medium', '-0.3750', '-0.3375', '8', '5', '80', '53'], + ] + json1, json2 = self.load_results() + output_lines_with_header = generate_difference_report( + json1, json2, utest=True, utest_alpha=0.05, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(expect_lines[i], parts) + + +class TestReportDifferenceWithUTestWhileDisplayingAggregatesOnly( + unittest.TestCase): + def load_results(self): + import json + testInputs = os.path.join( + os.path.dirname( + os.path.realpath(__file__)), + 'Inputs') + testOutput1 = os.path.join(testInputs, 'test3_run0.json') + testOutput2 = os.path.join(testInputs, 'test3_run1.json') + with open(testOutput1, 'r') as f: + json1 = json.load(f) + with open(testOutput2, 'r') as f: + json2 = json.load(f) + return json1, json2 + + def test_utest(self): + expect_lines = [] + expect_lines = [ + ['BM_One', '-0.1000', '+0.1000', '10', '9', '100', '110'], + ['BM_Two', '+0.1111', '-0.0111', '9', '10', '90', '89'], + ['BM_Two', '-0.1250', '-0.1628', '8', '7', '86', '72'], + ['BM_Two_pvalue', + '0.6985', + '0.6985', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '2.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ['short', '-0.1250', '-0.0625', '8', '7', '80', '75'], + ['short', '-0.4325', '-0.1351', '8', '5', '77', '67'], + ['short_pvalue', + '0.7671', + '0.1489', + 'U', + 'Test,', + 'Repetitions:', + '2', + 'vs', + '3.', + 'WARNING:', + 'Results', + 'unreliable!', + '9+', + 'repetitions', + 'recommended.'], + ] + json1, json2 = self.load_results() + output_lines_with_header = generate_difference_report( + json1, json2, display_aggregates_only=True, + utest=True, utest_alpha=0.05, use_color=False) + output_lines = output_lines_with_header[2:] + print("\n") + print("\n".join(output_lines_with_header)) + self.assertEqual(len(output_lines), len(expect_lines)) + for i in range(0, len(output_lines)): + parts = [x for x in output_lines[i].split(' ') if x] + self.assertEqual(expect_lines[i], parts) + + +if __name__ == '__main__': + unittest.main() + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# kate: tab-width: 4; replace-tabs on; indent-width 4; tab-indents: off; +# kate: indent-mode python; remove-trailing-spaces modified; diff --git a/thirdparty/benchmark-1.5.0/tools/gbench/util.py b/thirdparty/benchmark-1.5.0/tools/gbench/util.py new file mode 100644 index 0000000000..1f8e8e2c47 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/gbench/util.py @@ -0,0 +1,164 @@ +"""util.py - General utilities for running, loading, and processing benchmarks +""" +import json +import os +import tempfile +import subprocess +import sys + +# Input file type enumeration +IT_Invalid = 0 +IT_JSON = 1 +IT_Executable = 2 + +_num_magic_bytes = 2 if sys.platform.startswith('win') else 4 + + +def is_executable_file(filename): + """ + Return 'True' if 'filename' names a valid file which is likely + an executable. A file is considered an executable if it starts with the + magic bytes for a EXE, Mach O, or ELF file. + """ + if not os.path.isfile(filename): + return False + with open(filename, mode='rb') as f: + magic_bytes = f.read(_num_magic_bytes) + if sys.platform == 'darwin': + return magic_bytes in [ + b'\xfe\xed\xfa\xce', # MH_MAGIC + b'\xce\xfa\xed\xfe', # MH_CIGAM + b'\xfe\xed\xfa\xcf', # MH_MAGIC_64 + b'\xcf\xfa\xed\xfe', # MH_CIGAM_64 + b'\xca\xfe\xba\xbe', # FAT_MAGIC + b'\xbe\xba\xfe\xca' # FAT_CIGAM + ] + elif sys.platform.startswith('win'): + return magic_bytes == b'MZ' + else: + return magic_bytes == b'\x7FELF' + + +def is_json_file(filename): + """ + Returns 'True' if 'filename' names a valid JSON output file. + 'False' otherwise. + """ + try: + with open(filename, 'r') as f: + json.load(f) + return True + except BaseException: + pass + return False + + +def classify_input_file(filename): + """ + Return a tuple (type, msg) where 'type' specifies the classified type + of 'filename'. If 'type' is 'IT_Invalid' then 'msg' is a human readable + string represeting the error. + """ + ftype = IT_Invalid + err_msg = None + if not os.path.exists(filename): + err_msg = "'%s' does not exist" % filename + elif not os.path.isfile(filename): + err_msg = "'%s' does not name a file" % filename + elif is_executable_file(filename): + ftype = IT_Executable + elif is_json_file(filename): + ftype = IT_JSON + else: + err_msg = "'%s' does not name a valid benchmark executable or JSON file" % filename + return ftype, err_msg + + +def check_input_file(filename): + """ + Classify the file named by 'filename' and return the classification. + If the file is classified as 'IT_Invalid' print an error message and exit + the program. + """ + ftype, msg = classify_input_file(filename) + if ftype == IT_Invalid: + print("Invalid input file: %s" % msg) + sys.exit(1) + return ftype + + +def find_benchmark_flag(prefix, benchmark_flags): + """ + Search the specified list of flags for a flag matching `` and + if it is found return the arg it specifies. If specified more than once the + last value is returned. If the flag is not found None is returned. + """ + assert prefix.startswith('--') and prefix.endswith('=') + result = None + for f in benchmark_flags: + if f.startswith(prefix): + result = f[len(prefix):] + return result + + +def remove_benchmark_flags(prefix, benchmark_flags): + """ + Return a new list containing the specified benchmark_flags except those + with the specified prefix. + """ + assert prefix.startswith('--') and prefix.endswith('=') + return [f for f in benchmark_flags if not f.startswith(prefix)] + + +def load_benchmark_results(fname): + """ + Read benchmark output from a file and return the JSON object. + REQUIRES: 'fname' names a file containing JSON benchmark output. + """ + with open(fname, 'r') as f: + return json.load(f) + + +def run_benchmark(exe_name, benchmark_flags): + """ + Run a benchmark specified by 'exe_name' with the specified + 'benchmark_flags'. The benchmark is run directly as a subprocess to preserve + real time console output. + RETURNS: A JSON object representing the benchmark output + """ + output_name = find_benchmark_flag('--benchmark_out=', + benchmark_flags) + is_temp_output = False + if output_name is None: + is_temp_output = True + thandle, output_name = tempfile.mkstemp() + os.close(thandle) + benchmark_flags = list(benchmark_flags) + \ + ['--benchmark_out=%s' % output_name] + + cmd = [exe_name] + benchmark_flags + print("RUNNING: %s" % ' '.join(cmd)) + exitCode = subprocess.call(cmd) + if exitCode != 0: + print('TEST FAILED...') + sys.exit(exitCode) + json_res = load_benchmark_results(output_name) + if is_temp_output: + os.unlink(output_name) + return json_res + + +def run_or_load_benchmark(filename, benchmark_flags): + """ + Get the results for a specified benchmark. If 'filename' specifies + an executable benchmark then the results are generated by running the + benchmark. Otherwise 'filename' must name a valid JSON output file, + which is loaded and the result returned. + """ + ftype = check_input_file(filename) + if ftype == IT_JSON: + return load_benchmark_results(filename) + elif ftype == IT_Executable: + return run_benchmark(filename, benchmark_flags) + else: + assert False # This branch is unreachable diff --git a/thirdparty/benchmark-1.5.0/tools/strip_asm.py b/thirdparty/benchmark-1.5.0/tools/strip_asm.py new file mode 100755 index 0000000000..9030550b43 --- /dev/null +++ b/thirdparty/benchmark-1.5.0/tools/strip_asm.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python + +""" +strip_asm.py - Cleanup ASM output for the specified file +""" + +from argparse import ArgumentParser +import sys +import os +import re + +def find_used_labels(asm): + found = set() + label_re = re.compile("\s*j[a-z]+\s+\.L([a-zA-Z0-9][a-zA-Z0-9_]*)") + for l in asm.splitlines(): + m = label_re.match(l) + if m: + found.add('.L%s' % m.group(1)) + return found + + +def normalize_labels(asm): + decls = set() + label_decl = re.compile("^[.]{0,1}L([a-zA-Z0-9][a-zA-Z0-9_]*)(?=:)") + for l in asm.splitlines(): + m = label_decl.match(l) + if m: + decls.add(m.group(0)) + if len(decls) == 0: + return asm + needs_dot = next(iter(decls))[0] != '.' + if not needs_dot: + return asm + for ld in decls: + asm = re.sub("(^|\s+)" + ld + "(?=:|\s)", '\\1.' + ld, asm) + return asm + + +def transform_labels(asm): + asm = normalize_labels(asm) + used_decls = find_used_labels(asm) + new_asm = '' + label_decl = re.compile("^\.L([a-zA-Z0-9][a-zA-Z0-9_]*)(?=:)") + for l in asm.splitlines(): + m = label_decl.match(l) + if not m or m.group(0) in used_decls: + new_asm += l + new_asm += '\n' + return new_asm + + +def is_identifier(tk): + if len(tk) == 0: + return False + first = tk[0] + if not first.isalpha() and first != '_': + return False + for i in range(1, len(tk)): + c = tk[i] + if not c.isalnum() and c != '_': + return False + return True + +def process_identifiers(l): + """ + process_identifiers - process all identifiers and modify them to have + consistent names across all platforms; specifically across ELF and MachO. + For example, MachO inserts an additional understore at the beginning of + names. This function removes that. + """ + parts = re.split(r'([a-zA-Z0-9_]+)', l) + new_line = '' + for tk in parts: + if is_identifier(tk): + if tk.startswith('__Z'): + tk = tk[1:] + elif tk.startswith('_') and len(tk) > 1 and \ + tk[1].isalpha() and tk[1] != 'Z': + tk = tk[1:] + new_line += tk + return new_line + + +def process_asm(asm): + """ + Strip the ASM of unwanted directives and lines + """ + new_contents = '' + asm = transform_labels(asm) + + # TODO: Add more things we want to remove + discard_regexes = [ + re.compile("\s+\..*$"), # directive + re.compile("\s*#(NO_APP|APP)$"), #inline ASM + re.compile("\s*#.*$"), # comment line + re.compile("\s*\.globa?l\s*([.a-zA-Z_][a-zA-Z0-9$_.]*)"), #global directive + re.compile("\s*\.(string|asciz|ascii|[1248]?byte|short|word|long|quad|value|zero)"), + ] + keep_regexes = [ + + ] + fn_label_def = re.compile("^[a-zA-Z_][a-zA-Z0-9_.]*:") + for l in asm.splitlines(): + # Remove Mach-O attribute + l = l.replace('@GOTPCREL', '') + add_line = True + for reg in discard_regexes: + if reg.match(l) is not None: + add_line = False + break + for reg in keep_regexes: + if reg.match(l) is not None: + add_line = True + break + if add_line: + if fn_label_def.match(l) and len(new_contents) != 0: + new_contents += '\n' + l = process_identifiers(l) + new_contents += l + new_contents += '\n' + return new_contents + +def main(): + parser = ArgumentParser( + description='generate a stripped assembly file') + parser.add_argument( + 'input', metavar='input', type=str, nargs=1, + help='An input assembly file') + parser.add_argument( + 'out', metavar='output', type=str, nargs=1, + help='The output file') + args, unknown_args = parser.parse_known_args() + input = args.input[0] + output = args.out[0] + if not os.path.isfile(input): + print(("ERROR: input file '%s' does not exist") % input) + sys.exit(1) + contents = None + with open(input, 'r') as f: + contents = f.read() + new_contents = process_asm(contents) + with open(output, 'w') as f: + f.write(new_contents) + + +if __name__ == '__main__': + main() + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# kate: tab-width: 4; replace-tabs on; indent-width 4; tab-indents: off; +# kate: indent-mode python; remove-trailing-spaces modified; diff --git a/thirdparty/mio/.gitignore b/thirdparty/mio/.gitignore new file mode 100644 index 0000000000..f37d11baea --- /dev/null +++ b/thirdparty/mio/.gitignore @@ -0,0 +1,5 @@ +test/** +!test/test.cpp +!test/example.cpp +!test/CMakeLists.txt +build/ diff --git a/thirdparty/mio/CMakeLists.txt b/thirdparty/mio/CMakeLists.txt new file mode 100644 index 0000000000..339430f916 --- /dev/null +++ b/thirdparty/mio/CMakeLists.txt @@ -0,0 +1,152 @@ +cmake_minimum_required(VERSION 3.8) + +# +# Here we check whether mio is being configured in isolation or as a component +# of a larger project. To do so, we query whether the `PROJECT_NAME` CMake +# variable has been defined. In the case it has, we can conclude mio is a +# subproject. +# +# This convention has been borrowed from the Catch C++ unit testing library. +# +if(DEFINED PROJECT_NAME) + set(subproject ON) +else() + set(subproject OFF) +endif() + +project(mio VERSION 1.0.0 LANGUAGES C CXX) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +include (CTest) +include (CMakeDependentOption) + +# Generate 'compile_commands.json' for clang_complete +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# +# The `mio.testing` options only appear as cmake-gui and ccmake options iff +# mio is the highest level project. In the case that mio is a subproject, these +# options are hidden from the user interface and set to `OFF` +# +# Iff mio is the highest level project, this option is defaulted to the value +# of the traditional course grain testing option `BUILD_TESTING` established by +# the CTest module +# +CMAKE_DEPENDENT_OPTION(mio.tests + "Build the mio tests and integrate with ctest" + ${BUILD_TESTING} "NOT subproject" OFF) + +# +# mio has no compiled components. As such, we declare it as an `INTERFACE` +# library, which denotes a collection of target properties to be applied +# transitively to linking targets. In our case, this amounts to an include +# directory and project header files. +# +add_library(mio_base INTERFACE) + +# +# mio requires C++ 11 support, at a minimum. Setting the `cxx_std_11` compile +# features ensures that the corresponding C++ standard flag is populated in +# targets linking to mio +# +target_compile_features(mio_base INTERFACE cxx_std_11) + +# +# On Windows, so as to be a "good citizen", mio offers two different +# targets that control the imported surface area of the Windows API. The +# default `mio` target sets the necessary flags for a minimal Win API +# (`WIN32_LEAN_AND_MEAN`, etc.), while the `mio_full_winapi` target sets +# none of these flags so will not disable any of the modules. +# +if(WIN32) + include(WinApiLevels) +else() + # On non-Windows systems, the `mio` and `mio_base` targets are + # effectively identical. + add_library(mio INTERFACE) + target_link_libraries(mio + INTERFACE mio_base + ) +endif() + +add_library(mio::mio ALIAS mio) + +# +# The include directory for mio can be expected to vary between build +# and installaion. Here we use a CMake generator expression to dispatch +# on how the configuration under which this library is being consumed. +# +target_include_directories(mio_base INTERFACE + $ + $) + +add_subdirectory(include/mio) + +if(mio.tests) + add_subdirectory(test) +endif() + +# +# Non-testing header files (preserving relative paths) are installed to the +# `include` subdirectory of the `$INSTALL_DIR/${CMAKE_INSTALL_PREFIX}` +# directory. Source file permissions preserved. +# +install(DIRECTORY include/ + DESTINATION include + USE_SOURCE_PERMISSIONS + FILES_MATCHING PATTERN "*.*pp") + +# +# As a header-only library, there are no target components to be installed +# directly (the PUBLIC_HEADER property is not white listed for INTERFACE +# targets for some reason). +# +# However, it is worthwhile export our target description in order to later +# generate a CMake configuration file for consumption by CMake's `find_package` +# intrinsic +# +install(TARGETS mio_base mio EXPORT mioConfig) +install(EXPORT mioConfig + FILE mioConfig.cmake + NAMESPACE mio:: + DESTINATION share/cmake/mio + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) + +include(CMakePackageConfigHelpers) # provides `write_basic_package_version_file` +write_basic_package_version_file("mioConfigVersion.cmake" + VERSION ${mio_VERSION} + COMPATIBILITY SameMajorVersion) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mioConfigVersion.cmake" + DESTINATION share/cmake/mio + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) + +# +# Rudimentary CPack support. +# +# CPack provides a mechanism to generate installation packaging for a project, +# e.g., self-extracting shell scripts, compressed tarballs, Debian Package files, +# RPM Package Manager files, Windows NSIS installation wizards, +# Apple Disk Images (.dmg), etc. +# +# Any system libraries required (runtimes, threading, etc) should be bundled +# with the project for this type of installation. The +# `InstallRequiredSystemLibraries` CMake module attempts to provide this +# functionality in an automated way. Additional libraries may be specified as +# +# ```cmake +# list(APPEND CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS ) +# ``` +# +# A packaged installation can be generated by calling +# +# ```sh +# cpack -G --config CPackConfig.cmake +# ``` +# +# See `cpack --help` or the CPack documentation for more information. +# +include( InstallRequiredSystemLibraries ) +set( CPACK_PACKAGE_VENDOR "mandreyel" ) +set( CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" ) +set( CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/mandreyel/mio" ) +include( CPack ) diff --git a/thirdparty/mio/LICENSE b/thirdparty/mio/LICENSE new file mode 100644 index 0000000000..361770744b --- /dev/null +++ b/thirdparty/mio/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 https://github.com/mandreyel/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/thirdparty/mio/README.md b/thirdparty/mio/README.md new file mode 100644 index 0000000000..e50b23ffc7 --- /dev/null +++ b/thirdparty/mio/README.md @@ -0,0 +1,337 @@ +# mio +An easy to use header-only cross-platform C++11 memory mapping library with an MIT license. + +mio has been created with the goal to be easily includable (i.e. no dependencies) in any C++ project that needs memory mapped file IO without the need to pull in Boost. + +Please feel free to open an issue, I'll try to address any concerns as best I can. + +### Why? +Because memory mapping is the best thing since sliced bread! + +More seriously, the primary motivation for writing this library instead of using Boost.Iostreams, was the lack of support for establishing a memory mapping with an already open file handle/descriptor. This is possible with mio. + +Furthermore, Boost.Iostreams' solution requires that the user pick offsets exactly at page boundaries, which is cumbersome and error prone. mio, on the other hand, manages this internally, accepting any offset and finding the nearest page boundary. + +Albeit a minor nitpick, Boost.Iostreams implements memory mapped file IO with a `std::shared_ptr` to provide shared semantics, even if not needed, and the overhead of the heap allocation may be unnecessary and/or unwanted. +In mio, there are two classes to cover the two use-cases: one that is move-only (basically a zero-cost abstraction over the system specific mmapping functions), and the other that acts just like its Boost.Iostreams counterpart, with shared semantics. + +### How to create a mapping +NOTE: the file must exist before creating a mapping. + +There are three ways to map a file into memory: + +- Using the constructor, which throws a `std::system_error` on failure: +```c++ +mio::mmap_source mmap(path, offset, size_to_map); +``` +or you can omit the `offset` and `size_to_map` arguments, in which case the +entire file is mapped: +```c++ +mio::mmap_source mmap(path); +``` + +- Using the factory function: +```c++ +std::error_code error; +mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error); +``` +or: +```c++ +mio::mmap_source mmap = mio::make_mmap_source(path, error); +``` + +- Using the `map` member function: +```c++ +std::error_code error; +mio::mmap_source mmap; +mmap.map(path, offset, size_to_map, error); +``` +or: +```c++ +mmap.map(path, error); +``` +**NOTE:** The constructors **require** exceptions to be enabled. If you prefer +to build your projects with `-fno-exceptions`, you can still use the other ways. + +Moreover, in each case, you can provide either some string type for the file's path, or you can use an existing, valid file handle. +```c++ +#include +#include +#include +#include +#include + +int main() +{ + // NOTE: error handling omitted for brevity. + const int fd = open("file.txt", O_RDONLY); + mio::mmap_source mmap(fd, 0, mio::map_entire_file); + // ... +} +``` +However, mio does not check whether the provided file descriptor has the same access permissions as the desired mapping, so the mapping may fail. Such errors are reported via the `std::error_code` out parameter that is passed to the mapping function. + +**WINDOWS USERS**: This library *does* support the use of wide character types +for functions where character strings are expected (e.g. path parameters). + +### Example + +```c++ +#include +#include // for std::error_code +#include // for std::printf +#include +#include +#include + +int handle_error(const std::error_code& error); +void allocate_file(const std::string& path, const int size); + +int main() +{ + const auto path = "file.txt"; + + // NOTE: mio does *not* create the file for you if it doesn't exist! You + // must ensure that the file exists before establishing a mapping. It + // must also be non-empty. So for illustrative purposes the file is + // created now. + allocate_file(path, 155); + + // Read-write memory map the whole file by using `map_entire_file` where the + // length of the mapping is otherwise expected, with the factory method. + std::error_code error; + mio::mmap_sink rw_mmap = mio::make_mmap_sink( + path, 0, mio::map_entire_file, error); + if (error) { return handle_error(error); } + + // You can use any iterator based function. + std::fill(rw_mmap.begin(), rw_mmap.end(), 'a'); + + // Or manually iterate through the mapped region just as if it were any other + // container, and change each byte's value (since this is a read-write mapping). + for (auto& b : rw_mmap) { + b += 10; + } + + // Or just change one value with the subscript operator. + const int answer_index = rw_mmap.size() / 2; + rw_mmap[answer_index] = 42; + + // Don't forget to flush changes to disk before unmapping. However, if + // `rw_mmap` were to go out of scope at this point, the destructor would also + // automatically invoke `sync` before `unmap`. + rw_mmap.sync(error); + if (error) { return handle_error(error); } + + // We can then remove the mapping, after which rw_mmap will be in a default + // constructed state, i.e. this and the above call to `sync` have the same + // effect as if the destructor had been invoked. + rw_mmap.unmap(); + + // Now create the same mapping, but in read-only mode. Note that calling the + // overload without the offset and file length parameters maps the entire + // file. + mio::mmap_source ro_mmap; + ro_mmap.map(path, error); + if (error) { return handle_error(error); } + + const int the_answer_to_everything = ro_mmap[answer_index]; + assert(the_answer_to_everything == 42); +} + +int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} + +void allocate_file(const std::string& path, const int size) +{ + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} +``` + +`mio::basic_mmap` is move-only, but if multiple copies to the same mapping are needed, use `mio::basic_shared_mmap` which has `std::shared_ptr` semantics and has the same interface as `mio::basic_mmap`. +```c++ +#include + +mio::shared_mmap_source shared_mmap1("path", offset, size_to_map); +mio::shared_mmap_source shared_mmap2(std::move(mmap1)); // or use operator= +mio::shared_mmap_source shared_mmap3(std::make_shared(mmap1)); // or use operator= +mio::shared_mmap_source shared_mmap4; +shared_mmap4.map("path", offset, size_to_map, error); +``` + +It's possible to define the type of a byte (which has to be the same width as `char`), though aliases for the most common ones are provided by default: +```c++ +using mmap_source = basic_mmap_source; +using ummap_source = basic_mmap_source; + +using mmap_sink = basic_mmap_sink; +using ummap_sink = basic_mmap_sink; +``` +But it may be useful to define your own types, say when using the new `std::byte` type in C++17: +```c++ +using mmap_source = mio::basic_mmap_source; +using mmap_sink = mio::basic_mmap_sink; +``` + +Though generally not needed, since mio maps users requested offsets to page boundaries, you can query the underlying system's page allocation granularity by invoking `mio::page_size()`, which is located in `mio/page.hpp`. + +### Single Header File +Mio can be added to your project as a single header file simply by including `\single_include\mio\mio.hpp`. Single header files can be regenerated at any time by running the `amalgamate.py` script within `\third_party`. +``` +python amalgamate.py -c config.json -s ../include +``` + +## CMake +As a header-only library, mio has no compiled components. Nevertheless, a [CMake](https://cmake.org/overview/) build system is provided to allow easy testing, installation, and subproject composition on many platforms and operating systems. + +### Testing +Mio is distributed with a small suite of tests and examples. +When mio is configured as the highest level CMake project, this suite of executables is built by default. +Mio's test executables are integrated with the CMake test driver program, [CTest](https://cmake.org/cmake/help/latest/manual/ctest.1.html). + +CMake supports a number of backends for compilation and linking. + +To use a static configuration build tool, such as GNU Make or Ninja: + +```sh +cd +mkdir build +cd build + +# Configure the build +cmake -D CMAKE_BUILD_TYPE= \ + -G <"Unix Makefiles" | "Ninja"> .. + +# build the tests +< make | ninja | cmake --build . > + +# run the tests +< make test | ninja test | cmake --build . --target test | ctest > +``` + +To use a dynamic configuration build tool, such as Visual Studio or Xcode: + +```sh +cd +mkdir build +cd build + +# Configure the build +cmake -G <"Visual Studio 14 2015 Win64" | "Xcode"> .. + +# build the tests +cmake --build . --config + +# run the tests via ctest... +ctest --build-config + +# ... or via CMake build tool mode... +cmake --build . --config --target test +``` + +Of course the **build** and **test** steps can also be executed via the **all** and **test** targets, respectively, from within the IDE after opening the project file generated during the configuration step. + +Mio's testing is also configured to operate as a client to the [CDash](https://www.cdash.org/) software quality dashboard application. Please see the [Kitware documentation](https://cmake.org/cmake/help/latest/manual/ctest.1.html#dashboard-client) for more information on this mode of operation. + +### Installation + +Mio's build system provides an installation target and support for downstream consumption via CMake's [`find_package`](https://cmake.org/cmake/help/v3.0/command/find_package.html) intrinsic function. +CMake allows installation to an arbitrary location, which may be specified by defining `CMAKE_INSTALL_PREFIX` at configure time. +In the absense of a user specification, CMake will install mio to conventional location based on the platform operating system. + +To use a static configuration build tool, such as GNU Make or Ninja: + +```sh +cd +mkdir build +cd build + +# Configure the build +cmake [-D CMAKE_INSTALL_PREFIX="path/to/installation"] \ + [-D BUILD_TESTING=False] \ + -D CMAKE_BUILD_TYPE=Release \ + -G <"Unix Makefiles" | "Ninja"> .. + +# install mio + +``` + +To use a dynamic configuration build tool, such as Visual Studio or Xcode: + +```sh +cd +mkdir build +cd build + +# Configure the project +cmake [-D CMAKE_INSTALL_PREFIX="path/to/installation"] \ + [-D BUILD_TESTING=False] \ + -G <"Visual Studio 14 2015 Win64" | "Xcode"> .. + +# install mio +cmake --build . --config Release --target install +``` + +Note that the last command of the installation sequence may require administrator privileges (e.g. `sudo`) if the installation root directory lies outside your home directory. + +This installation ++ copies the mio header files to the `include/mio` subdirectory of the installation root ++ generates and copies several CMake configuration files to the `share/cmake/mio` subdirectory of the installation root + +This latter step allows downstream CMake projects to consume mio via `find_package`, e.g. + +```cmake +find_package( mio REQUIRED ) +target_link_libraries( MyTarget PUBLIC mio::mio ) +``` + +**WINDOWS USERS**: The `mio::mio` target `#define`s `WIN32_LEAN_AND_MEAN` and `NOMINMAX`. The former ensures the imported surface area of the Win API is minimal, and the latter disables Windows' `min` and `max` macros so they don't intefere with `std::min` and `std::max`. Because *mio* is a header only library, these defintions will leak into downstream CMake builds. If their presence is causing problems with your build then you can use the alternative `mio::mio_full_winapi` target, which adds none of these defintions. + +If mio was installed to a non-conventional location, it may be necessary for downstream projects to specify the mio installation root directory via either + ++ the `CMAKE_PREFIX_PATH` configuration option, ++ the `CMAKE_PREFIX_PATH` environment variable, or ++ `mio_DIR` environment variable. + +Please see the [Kitware documentation](https://cmake.org/cmake/help/v3.0/command/find_package.html) for more information. + +In addition, mio supports packaged relocatable installations via [CPack](https://cmake.org/cmake/help/latest/manual/cpack.1.html). +Following configuration, from the build directory, invoke cpack as follows to generate a packaged installation: + +```sh +cpack -G -C Release +``` + +The list of supported generators varies from platform to platform. See the output of `cpack --help` for a complete list of supported generators on your platform. + +### Subproject Composition +To use mio as a subproject, copy the mio repository to your project's dependencies/externals folder. +If your project is version controlled using git, a git submodule or git subtree can be used to syncronize with the updstream repository. +The [use](https://services.github.com/on-demand/downloads/submodule-vs-subtree-cheat-sheet/) and [relative advantages](https://andrey.nering.com.br/2016/git-submodules-vs-subtrees/) of these git facilities is beyond the scope of this document, but in brief, each may be established as follows: + +```sh +# via git submodule +cd +git submodule add -b master https://github.com/mandreyel/mio.git + +# via git subtree +cd +git subtree add --prefix /mio \ + https://github.com/mandreyel/mio.git master --squash +``` + +Given a mio subdirectory in a project, simply add the following lines to your project's to add mio include directories to your target's include path. + +```cmake +add_subdirectory( path/to/mio/ ) +target_link_libraries( MyTarget PUBLIC ) +``` + +Note that, as a subproject, mio's tests and examples will not be built and CPack integration is deferred to the host project. + diff --git a/thirdparty/mio/cmake/WinApiLevels.cmake b/thirdparty/mio/cmake/WinApiLevels.cmake new file mode 100644 index 0000000000..5f3ebad84e --- /dev/null +++ b/thirdparty/mio/cmake/WinApiLevels.cmake @@ -0,0 +1,14 @@ +add_library(mio_full_winapi INTERFACE) +target_link_libraries(mio_full_winapi + INTERFACE mio_base +) +add_library(mio::mio_full_winapi ALIAS mio_full_winapi) + +add_library(mio INTERFACE) +target_link_libraries(mio + INTERFACE mio_full_winapi +) +target_compile_definitions(mio + INTERFACE WIN32_LEAN_AND_MEAN NOMINMAX +) +install(TARGETS mio_full_winapi EXPORT mioConfig) diff --git a/thirdparty/mio/include/mio/CMakeLists.txt b/thirdparty/mio/include/mio/CMakeLists.txt new file mode 100644 index 0000000000..c2d495c881 --- /dev/null +++ b/thirdparty/mio/include/mio/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# While not strictly necessary to specify header files as target sources, +# doing so populates these files in the source listing when CMake is used +# to generate XCode and Visual Studios projects +# +target_sources(mio_base INTERFACE + $ + $) + +add_subdirectory(detail) diff --git a/thirdparty/mio/include/mio/detail/CMakeLists.txt b/thirdparty/mio/include/mio/detail/CMakeLists.txt new file mode 100644 index 0000000000..d4f818c23a --- /dev/null +++ b/thirdparty/mio/include/mio/detail/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# iff mio is the highest level project, include the implementation +# detail files in the source listing for CMake-generated IDE projects +# +if(NOT subproject) + target_sources(mio_base INTERFACE + $) +endif() diff --git a/thirdparty/mio/include/mio/detail/mmap.ipp b/thirdparty/mio/include/mio/detail/mmap.ipp new file mode 100644 index 0000000000..361db300be --- /dev/null +++ b/thirdparty/mio/include/mio/detail/mmap.ipp @@ -0,0 +1,518 @@ +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_BASIC_MMAP_IMPL +#define MIO_BASIC_MMAP_IMPL + +#include "mio/mmap.hpp" +#include "mio/page.hpp" +#include "mio/detail/string_util.hpp" + +#include + +#ifndef _WIN32 +# include +# include +# include +# include +#endif + +namespace mio { +namespace detail { + +#ifdef _WIN32 +namespace win { + +/** Returns the 4 upper bytes of an 8-byte integer. */ +inline DWORD int64_high(int64_t n) noexcept +{ + return n >> 32; +} + +/** Returns the 4 lower bytes of an 8-byte integer. */ +inline DWORD int64_low(int64_t n) noexcept +{ + return n & 0xffffffff; +} + +template< + typename String, + typename = typename std::enable_if< + std::is_same::type, char>::value + >::type +> file_handle_type open_file_helper(const String& path, const access_mode mode) +{ + return ::CreateFileA(c_str(path), + mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); +} + +template +typename std::enable_if< + std::is_same::type, wchar_t>::value, + file_handle_type +>::type open_file_helper(const String& path, const access_mode mode) +{ + return ::CreateFileW(c_str(path), + mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); +} + +} // win +#endif // _WIN32 + +/** + * Returns the last platform specific system error (errno on POSIX and + * GetLastError on Win) as a `std::error_code`. + */ +inline std::error_code last_error() noexcept +{ + std::error_code error; +#ifdef _WIN32 + error.assign(GetLastError(), std::system_category()); +#else + error.assign(errno, std::system_category()); +#endif + return error; +} + +template +file_handle_type open_file(const String& path, const access_mode mode, + std::error_code& error) +{ + error.clear(); + if(detail::empty(path)) + { + error = std::make_error_code(std::errc::invalid_argument); + return invalid_handle; + } +#ifdef _WIN32 + const auto handle = win::open_file_helper(path, mode); +#else // POSIX + const auto handle = ::open(c_str(path), + mode == access_mode::read ? O_RDONLY : O_RDWR); +#endif + if(handle == invalid_handle) + { + error = detail::last_error(); + } + return handle; +} + +inline size_t query_file_size(file_handle_type handle, std::error_code& error) +{ + error.clear(); +#ifdef _WIN32 + LARGE_INTEGER file_size; + if(::GetFileSizeEx(handle, &file_size) == 0) + { + error = detail::last_error(); + return 0; + } + return static_cast(file_size.QuadPart); +#else // POSIX + struct stat sbuf; + if(::fstat(handle, &sbuf) == -1) + { + error = detail::last_error(); + return 0; + } + return sbuf.st_size; +#endif +} + +struct mmap_context +{ + char* data; + int64_t length; + int64_t mapped_length; +#ifdef _WIN32 + file_handle_type file_mapping_handle; +#endif +}; + +inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, + const int64_t length, const access_mode mode, std::error_code& error) +{ + const int64_t aligned_offset = make_offset_page_aligned(offset); + const int64_t length_to_map = offset - aligned_offset + length; +#ifdef _WIN32 + const int64_t max_file_size = offset + length; + const auto file_mapping_handle = ::CreateFileMapping( + file_handle, + 0, + mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + win::int64_high(max_file_size), + win::int64_low(max_file_size), + 0); + if(file_mapping_handle == invalid_handle) + { + error = detail::last_error(); + return {}; + } + char* mapping_start = static_cast(::MapViewOfFile( + file_mapping_handle, + mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + win::int64_high(aligned_offset), + win::int64_low(aligned_offset), + length_to_map)); + if(mapping_start == nullptr) + { + error = detail::last_error(); + return {}; + } +#else // POSIX + char* mapping_start = static_cast(::mmap( + 0, // Don't give hint as to where to map. + length_to_map, + mode == access_mode::read ? PROT_READ : PROT_WRITE, + MAP_SHARED, + file_handle, + aligned_offset)); + if(mapping_start == MAP_FAILED) + { + error = detail::last_error(); + return {}; + } +#endif + mmap_context ctx; + ctx.data = mapping_start + offset - aligned_offset; + ctx.length = length; + ctx.mapped_length = length_to_map; +#ifdef _WIN32 + ctx.file_mapping_handle = file_mapping_handle; +#endif + return ctx; +} + +} // namespace detail + +// -- basic_mmap -- + +template +basic_mmap::~basic_mmap() +{ + conditional_sync(); + unmap(); +} + +template +basic_mmap::basic_mmap(basic_mmap&& other) + : data_(std::move(other.data_)) + , length_(std::move(other.length_)) + , mapped_length_(std::move(other.mapped_length_)) + , file_handle_(std::move(other.file_handle_)) +#ifdef _WIN32 + , file_mapping_handle_(std::move(other.file_mapping_handle_)) +#endif + , is_handle_internal_(std::move(other.is_handle_internal_)) +{ + other.data_ = nullptr; + other.length_ = other.mapped_length_ = 0; + other.file_handle_ = invalid_handle; +#ifdef _WIN32 + other.file_mapping_handle_ = invalid_handle; +#endif +} + +template +basic_mmap& +basic_mmap::operator=(basic_mmap&& other) +{ + if(this != &other) + { + // First the existing mapping needs to be removed. + unmap(); + data_ = std::move(other.data_); + length_ = std::move(other.length_); + mapped_length_ = std::move(other.mapped_length_); + file_handle_ = std::move(other.file_handle_); +#ifdef _WIN32 + file_mapping_handle_ = std::move(other.file_mapping_handle_); +#endif + is_handle_internal_ = std::move(other.is_handle_internal_); + + // The moved from basic_mmap's fields need to be reset, because + // otherwise other's destructor will unmap the same mapping that was + // just moved into this. + other.data_ = nullptr; + other.length_ = other.mapped_length_ = 0; + other.file_handle_ = invalid_handle; +#ifdef _WIN32 + other.file_mapping_handle_ = invalid_handle; +#endif + other.is_handle_internal_ = false; + } + return *this; +} + +template +typename basic_mmap::handle_type +basic_mmap::mapping_handle() const noexcept +{ +#ifdef _WIN32 + return file_mapping_handle_; +#else + return file_handle_; +#endif +} + +template +template +void basic_mmap::map(const String& path, const size_type offset, + const size_type length, std::error_code& error) +{ + error.clear(); + if(detail::empty(path)) + { + error = std::make_error_code(std::errc::invalid_argument); + return; + } + const auto handle = detail::open_file(path, AccessMode, error); + if(error) + { + return; + } + + map(handle, offset, length, error); + // This MUST be after the call to map, as that sets this to true. + if(!error) + { + is_handle_internal_ = true; + } +} + +template +void basic_mmap::map(const handle_type handle, + const size_type offset, const size_type length, std::error_code& error) +{ + error.clear(); + if(handle == invalid_handle) + { + error = std::make_error_code(std::errc::bad_file_descriptor); + return; + } + + const auto file_size = detail::query_file_size(handle, error); + if(error) + { + return; + } + + if(offset + length > file_size) + { + error = std::make_error_code(std::errc::invalid_argument); + return; + } + + const auto ctx = detail::memory_map(handle, offset, + length == map_entire_file ? (file_size - offset) : length, + AccessMode, error); + if(!error) + { + // We must unmap the previous mapping that may have existed prior to this call. + // Note that this must only be invoked after a new mapping has been created in + // order to provide the strong guarantee that, should the new mapping fail, the + // `map` function leaves this instance in a state as though the function had + // never been invoked. + unmap(); + file_handle_ = handle; + is_handle_internal_ = false; + data_ = reinterpret_cast(ctx.data); + length_ = ctx.length; + mapped_length_ = ctx.mapped_length; +#ifdef _WIN32 + file_mapping_handle_ = ctx.file_mapping_handle; +#endif + } +} + +template +template +typename std::enable_if::type +basic_mmap::sync(std::error_code& error) +{ + error.clear(); + if(!is_open()) + { + error = std::make_error_code(std::errc::bad_file_descriptor); + return; + } + + if(data()) + { +#ifdef _WIN32 + if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 + || ::FlushFileBuffers(file_handle_) == 0) +#else // POSIX + if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) +#endif + { + error = detail::last_error(); + return; + } + } +#ifdef _WIN32 + if(::FlushFileBuffers(file_handle_) == 0) + { + error = detail::last_error(); + } +#endif +} + +template +void basic_mmap::unmap() +{ + if(!is_open()) { return; } + // TODO do we care about errors here? +#ifdef _WIN32 + if(is_mapped()) + { + ::UnmapViewOfFile(get_mapping_start()); + ::CloseHandle(file_mapping_handle_); + } +#else // POSIX + if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } +#endif + + // If `file_handle_` was obtained by our opening it (when map is called with + // a path, rather than an existing file handle), we need to close it, + // otherwise it must not be closed as it may still be used outside this + // instance. + if(is_handle_internal_) + { +#ifdef _WIN32 + ::CloseHandle(file_handle_); +#else // POSIX + ::close(file_handle_); +#endif + } + + // Reset fields to their default values. + data_ = nullptr; + length_ = mapped_length_ = 0; + file_handle_ = invalid_handle; +#ifdef _WIN32 + file_mapping_handle_ = invalid_handle; +#endif +} + +template +bool basic_mmap::is_mapped() const noexcept +{ +#ifdef _WIN32 + return file_mapping_handle_ != invalid_handle; +#else // POSIX + return is_open(); +#endif +} + +template +void basic_mmap::swap(basic_mmap& other) +{ + if(this != &other) + { + using std::swap; + swap(data_, other.data_); + swap(file_handle_, other.file_handle_); +#ifdef _WIN32 + swap(file_mapping_handle_, other.file_mapping_handle_); +#endif + swap(length_, other.length_); + swap(mapped_length_, other.mapped_length_); + swap(is_handle_internal_, other.is_handle_internal_); + } +} + +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // This is invoked from the destructor, so not much we can do about + // failures here. + std::error_code ec; + sync(ec); +} + +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // noop +} + +template +bool operator==(const basic_mmap& a, + const basic_mmap& b) +{ + return a.data() == b.data() + && a.size() == b.size(); +} + +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b) +{ + return !(a == b); +} + +template +bool operator<(const basic_mmap& a, + const basic_mmap& b) +{ + if(a.data() == b.data()) { return a.size() < b.size(); } + return a.data() < b.data(); +} + +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b) +{ + return !(a > b); +} + +template +bool operator>(const basic_mmap& a, + const basic_mmap& b) +{ + if(a.data() == b.data()) { return a.size() > b.size(); } + return a.data() > b.data(); +} + +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b) +{ + return !(a < b); +} + +} // namespace mio + +#endif // MIO_BASIC_MMAP_IMPL diff --git a/thirdparty/mio/include/mio/detail/string_util.hpp b/thirdparty/mio/include/mio/detail/string_util.hpp new file mode 100644 index 0000000000..2f375aa2c2 --- /dev/null +++ b/thirdparty/mio/include/mio/detail/string_util.hpp @@ -0,0 +1,170 @@ +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_STRING_UTIL_HEADER +#define MIO_STRING_UTIL_HEADER + +#include + +namespace mio { +namespace detail { + +template< + typename S, + typename C = typename std::decay::type, + typename = decltype(std::declval().data()), + typename = typename std::enable_if< + std::is_same::value +#ifdef _WIN32 + || std::is_same::value +#endif + >::type +> struct char_type_helper { + using type = typename C::value_type; +}; + +template +struct char_type { + using type = typename char_type_helper::type; +}; + +// TODO: can we avoid this brute force approach? +template<> +struct char_type { + using type = char; +}; + +template<> +struct char_type { + using type = char; +}; + +template +struct char_type { + using type = char; +}; + +template +struct char_type { + using type = char; +}; + +#ifdef _WIN32 +template<> +struct char_type { + using type = wchar_t; +}; + +template<> +struct char_type { + using type = wchar_t; +}; + +template +struct char_type { + using type = wchar_t; +}; + +template +struct char_type { + using type = wchar_t; +}; +#endif // _WIN32 + +template +struct is_c_str_helper +{ + static constexpr bool value = std::is_same< + CharT*, + // TODO: I'm so sorry for this... Can this be made cleaner? + typename std::add_pointer< + typename std::remove_cv< + typename std::remove_pointer< + typename std::decay< + S + >::type + >::type + >::type + >::type + >::value; +}; + +template +struct is_c_str +{ + static constexpr bool value = is_c_str_helper::value; +}; + +#ifdef _WIN32 +template +struct is_c_wstr +{ + static constexpr bool value = is_c_str_helper::value; +}; +#endif // _WIN32 + +template +struct is_c_str_or_c_wstr +{ + static constexpr bool value = is_c_str::value +#ifdef _WIN32 + || is_c_wstr::value +#endif + ; +}; + +template< + typename String, + typename = decltype(std::declval().data()), + typename = typename std::enable_if::value>::type +> const typename char_type::type* c_str(const String& path) +{ + return path.data(); +} + +template< + typename String, + typename = decltype(std::declval().empty()), + typename = typename std::enable_if::value>::type +> bool empty(const String& path) +{ + return path.empty(); +} + +template< + typename String, + typename = typename std::enable_if::value>::type +> const typename char_type::type* c_str(String path) +{ + return path; +} + +template< + typename String, + typename = typename std::enable_if::value>::type +> bool empty(String path) +{ + return !path || (*path == 0); +} + +} // namespace detail +} // namespace mio + +#endif // MIO_STRING_UTIL_HEADER diff --git a/thirdparty/mio/include/mio/mmap.hpp b/thirdparty/mio/include/mio/mmap.hpp new file mode 100644 index 0000000000..def559a9a2 --- /dev/null +++ b/thirdparty/mio/include/mio/mmap.hpp @@ -0,0 +1,492 @@ +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_MMAP_HEADER +#define MIO_MMAP_HEADER + +#include "mio/page.hpp" + +#include +#include +#include +#include + +#ifdef _WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif // WIN32_LEAN_AND_MEAN +# include +#else // ifdef _WIN32 +# define INVALID_HANDLE_VALUE -1 +#endif // ifdef _WIN32 + +namespace mio { + +// This value may be provided as the `length` parameter to the constructor or +// `map`, in which case a memory mapping of the entire file is created. +enum { map_entire_file = 0 }; + +#ifdef _WIN32 +using file_handle_type = HANDLE; +#else +using file_handle_type = int; +#endif + +// This value represents an invalid file handle type. This can be used to +// determine whether `basic_mmap::file_handle` is valid, for example. +const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; + +template +struct basic_mmap +{ + using value_type = ByteT; + using size_type = size_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using iterator_category = std::random_access_iterator_tag; + using handle_type = file_handle_type; + + static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); + +private: + // Points to the first requested byte, and not to the actual start of the mapping. + pointer data_ = nullptr; + + // Length--in bytes--requested by user (which may not be the length of the + // full mapping) and the length of the full mapping. + size_type length_ = 0; + size_type mapped_length_ = 0; + + // Letting user map a file using both an existing file handle and a path + // introcudes some complexity (see `is_handle_internal_`). + // On POSIX, we only need a file handle to create a mapping, while on + // Windows systems the file handle is necessary to retrieve a file mapping + // handle, but any subsequent operations on the mapped region must be done + // through the latter. + handle_type file_handle_ = INVALID_HANDLE_VALUE; +#ifdef _WIN32 + handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; +#endif + + // Letting user map a file using both an existing file handle and a path + // introcudes some complexity in that we must not close the file handle if + // user provided it, but we must close it if we obtained it using the + // provided path. For this reason, this flag is used to determine when to + // close `file_handle_`. + bool is_handle_internal_; + +public: + /** + * The default constructed mmap object is in a non-mapped state, that is, + * any operation that attempts to access nonexistent underlying data will + * result in undefined behaviour/segmentation faults. + */ + basic_mmap() = default; + +#ifdef __cpp_exceptions + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + template + basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(path, offset, length, error); + if(error) { throw std::system_error(error); } + } + + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(handle, offset, length, error); + if(error) { throw std::system_error(error); } + } +#endif // __cpp_exceptions + + /** + * `basic_mmap` has single-ownership semantics, so transferring ownership + * may only be accomplished by moving the object. + */ + basic_mmap(const basic_mmap&) = delete; + basic_mmap(basic_mmap&&); + basic_mmap& operator=(const basic_mmap&) = delete; + basic_mmap& operator=(basic_mmap&&); + + /** + * If this is a read-write mapping, the destructor invokes sync. Regardless + * of the access mode, unmap is invoked as a final step. + */ + ~basic_mmap(); + + /** + * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, + * however, a mapped region of a file gets its own handle, which is returned by + * 'mapping_handle'. + */ + handle_type file_handle() const noexcept { return file_handle_; } + handle_type mapping_handle() const noexcept; + + /** Returns whether a valid memory mapping has been created. */ + bool is_open() const noexcept { return file_handle_ != invalid_handle; } + + /** + * Returns true if no mapping was established, that is, conceptually the + * same as though the length that was mapped was 0. This function is + * provided so that this class has Container semantics. + */ + bool empty() const noexcept { return length() == 0; } + + /** Returns true if a mapping was established. */ + bool is_mapped() const noexcept; + + /** + * `size` and `length` both return the logical length, i.e. the number of bytes + * user requested to be mapped, while `mapped_length` returns the actual number of + * bytes that were mapped which is a multiple of the underlying operating system's + * page allocation granularity. + */ + size_type size() const noexcept { return length(); } + size_type length() const noexcept { return length_; } + size_type mapped_length() const noexcept { return mapped_length_; } + + /** Returns the offset relative to the start of the mapping. */ + size_type mapping_offset() const noexcept + { + return mapped_length_ - length_; + } + + /** + * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping + * exists. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer data() noexcept { return data_; } + const_pointer data() const noexcept { return data_; } + + /** + * Returns an iterator to the first requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > iterator begin() noexcept { return data(); } + const_iterator begin() const noexcept { return data(); } + const_iterator cbegin() const noexcept { return data(); } + + /** + * Returns an iterator one past the last requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > iterator end() noexcept { return data() + length(); } + const_iterator end() const noexcept { return data() + length(); } + const_iterator cend() const noexcept { return data() + length(); } + + /** + * Returns a reverse iterator to the last memory mapped byte, if a valid + * memory mapping exists, otherwise this function call is undefined + * behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const noexcept + { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const noexcept + { return const_reverse_iterator(end()); } + + /** + * Returns a reverse iterator past the first mapped byte, if a valid memory + * mapping exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rend() noexcept { return reverse_iterator(begin()); } + const_reverse_iterator rend() const noexcept + { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const noexcept + { return const_reverse_iterator(begin()); } + + /** + * Returns a reference to the `i`th byte from the first requested byte (as returned + * by `data`). If this is invoked when no valid memory mapping has been created + * prior to this call, undefined behaviour ensues. + */ + reference operator[](const size_type i) noexcept { return data_[i]; } + const_reference operator[](const size_type i) const noexcept { return data_[i]; } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + template + void map(const String& path, const size_type offset, + const size_type length, std::error_code& error); + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map(path, 0, map_entire_file, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is + * unsuccesful, the reason is reported via `error` and the object remains in + * a state as if this function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + void map(const handle_type handle, const size_type offset, + const size_type length, std::error_code& error); + + /** + * Establishes a memory mapping with AccessMode. If the mapping is + * unsuccesful, the reason is reported via `error` and the object remains in + * a state as if this function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * The entire file is mapped. + */ + void map(const handle_type handle, std::error_code& error) + { + map(handle, 0, map_entire_file, error); + } + + /** + * If a valid memory mapping has been created prior to this call, this call + * instructs the kernel to unmap the memory region and disassociate this object + * from the file. + * + * The file handle associated with the file that is mapped is only closed if the + * mapping was created using a file path. If, on the other hand, an existing + * file handle was used to create the mapping, the file handle is not closed. + */ + void unmap(); + + void swap(basic_mmap& other); + + /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ + template + typename std::enable_if::type + sync(std::error_code& error); + + /** + * All operators compare the address of the first byte and size of the two mapped + * regions. + */ + +private: + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer get_mapping_start() noexcept + { + return !data() ? nullptr : data() - mapping_offset(); + } + + const_pointer get_mapping_start() const noexcept + { + return !data() ? nullptr : data() - mapping_offset(); + } + + /** + * The destructor syncs changes to disk if `AccessMode` is `write`, but not + * if it's `read`, but since the destructor cannot be templated, we need to + * do SFINAE in a dedicated function, where one syncs and the other is a noop. + */ + template + typename std::enable_if::type + conditional_sync(); + template + typename std::enable_if::type conditional_sync(); +}; + +template +bool operator==(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b); + +/** + * This is the basis for all read-only mmap objects and should be preferred over + * directly using `basic_mmap`. + */ +template +using basic_mmap_source = basic_mmap; + +/** + * This is the basis for all read-write mmap objects and should be preferred over + * directly using `basic_mmap`. + */ +template +using basic_mmap_sink = basic_mmap; + +/** + * These aliases cover the most common use cases, both representing a raw byte stream + * (either with a char or an unsigned char/uint8_t). + */ +using mmap_source = basic_mmap_source; +using ummap_source = basic_mmap_source; + +using mmap_sink = basic_mmap_sink; +using ummap_sink = basic_mmap_sink; + +/** + * Convenience factory method that constructs a mapping for any `basic_mmap` or + * `basic_mmap` type. + */ +template< + typename MMap, + typename MappingToken +> MMap make_mmap(const MappingToken& token, + int64_t offset, int64_t length, std::error_code& error) +{ + MMap mmap; + mmap.map(token, offset, length, error); + return mmap; +} + +/** + * Convenience factory method. + * + * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, + * `std::filesystem::path`, `std::vector`, or similar), or a + * `mmap_source::handle_type`. + */ +template +mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, + mmap_source::size_type length, std::error_code& error) +{ + return make_mmap(token, offset, length, error); +} + +template +mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) +{ + return make_mmap_source(token, 0, map_entire_file, error); +} + +/** + * Convenience factory method. + * + * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, + * `std::filesystem::path`, `std::vector`, or similar), or a + * `mmap_sink::handle_type`. + */ +template +mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, + mmap_sink::size_type length, std::error_code& error) +{ + return make_mmap(token, offset, length, error); +} + +template +mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) +{ + return make_mmap_sink(token, 0, map_entire_file, error); +} + +} // namespace mio + +#include "detail/mmap.ipp" + +#endif // MIO_MMAP_HEADER diff --git a/thirdparty/mio/include/mio/page.hpp b/thirdparty/mio/include/mio/page.hpp new file mode 100644 index 0000000000..cae73775fd --- /dev/null +++ b/thirdparty/mio/include/mio/page.hpp @@ -0,0 +1,78 @@ +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_PAGE_HEADER +#define MIO_PAGE_HEADER + +#ifdef _WIN32 +# include +#else +# include +#endif + +namespace mio { + +/** + * This is used by `basic_mmap` to determine whether to create a read-only or + * a read-write memory mapping. + */ +enum class access_mode +{ + read, + write +}; + +/** + * Determines the operating system's page allocation granularity. + * + * On the first call to this function, it invokes the operating system specific syscall + * to determine the page size, caches the value, and returns it. Any subsequent call to + * this function serves the cached value, so no further syscalls are made. + */ +inline size_t page_size() +{ + static const size_t page_size = [] + { +#ifdef _WIN32 + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + return SystemInfo.dwAllocationGranularity; +#else + return sysconf(_SC_PAGE_SIZE); +#endif + }(); + return page_size; +} + +/** + * Alligns `offset` to the operating's system page size such that it subtracts the + * difference until the nearest page boundary before `offset`, or does nothing if + * `offset` is already page aligned. + */ +inline size_t make_offset_page_aligned(size_t offset) noexcept +{ + const size_t page_size_ = page_size(); + // Use integer division to round down to the nearest page alignment. + return offset / page_size_ * page_size_; +} + +} // namespace mio + +#endif // MIO_PAGE_HEADER diff --git a/thirdparty/mio/include/mio/shared_mmap.hpp b/thirdparty/mio/include/mio/shared_mmap.hpp new file mode 100644 index 0000000000..f125a59af8 --- /dev/null +++ b/thirdparty/mio/include/mio/shared_mmap.hpp @@ -0,0 +1,406 @@ +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_SHARED_MMAP_HEADER +#define MIO_SHARED_MMAP_HEADER + +#include "mio/mmap.hpp" + +#include // std::error_code +#include // std::shared_ptr + +namespace mio { + +/** + * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with + * `std::shared_ptr` semantics. + * + * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if + * shared semantics are not required. + */ +template< + access_mode AccessMode, + typename ByteT +> class basic_shared_mmap +{ + using impl_type = basic_mmap; + std::shared_ptr pimpl_; + +public: + using value_type = typename impl_type::value_type; + using size_type = typename impl_type::size_type; + using reference = typename impl_type::reference; + using const_reference = typename impl_type::const_reference; + using pointer = typename impl_type::pointer; + using const_pointer = typename impl_type::const_pointer; + using difference_type = typename impl_type::difference_type; + using iterator = typename impl_type::iterator; + using const_iterator = typename impl_type::const_iterator; + using reverse_iterator = typename impl_type::reverse_iterator; + using const_reverse_iterator = typename impl_type::const_reverse_iterator; + using iterator_category = typename impl_type::iterator_category; + using handle_type = typename impl_type::handle_type; + using mmap_type = impl_type; + + basic_shared_mmap() = default; + basic_shared_mmap(const basic_shared_mmap&) = default; + basic_shared_mmap& operator=(const basic_shared_mmap&) = default; + basic_shared_mmap(basic_shared_mmap&&) = default; + basic_shared_mmap& operator=(basic_shared_mmap&&) = default; + + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap(mmap_type&& mmap) + : pimpl_(std::make_shared(std::move(mmap))) + {} + + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap& operator=(mmap_type&& mmap) + { + pimpl_ = std::make_shared(std::move(mmap)); + return *this; + } + + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) {} + + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap& operator=(std::shared_ptr mmap) + { + pimpl_ = std::move(mmap); + return *this; + } + +#ifdef __cpp_exceptions + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + template + basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(path, offset, length, error); + if(error) { throw std::system_error(error); } + } + + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(handle, offset, length, error); + if(error) { throw std::system_error(error); } + } +#endif // __cpp_exceptions + + /** + * If this is a read-write mapping and the last reference to the mapping, + * the destructor invokes sync. Regardless of the access mode, unmap is + * invoked as a final step. + */ + ~basic_shared_mmap() = default; + + /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ + std::shared_ptr get_shared_ptr() { return pimpl_; } + + /** + * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, + * however, a mapped region of a file gets its own handle, which is returned by + * 'mapping_handle'. + */ + handle_type file_handle() const noexcept + { + return pimpl_ ? pimpl_->file_handle() : invalid_handle; + } + + handle_type mapping_handle() const noexcept + { + return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; + } + + /** Returns whether a valid memory mapping has been created. */ + bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } + + /** + * Returns true if no mapping was established, that is, conceptually the + * same as though the length that was mapped was 0. This function is + * provided so that this class has Container semantics. + */ + bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); } + + /** + * `size` and `length` both return the logical length, i.e. the number of bytes + * user requested to be mapped, while `mapped_length` returns the actual number of + * bytes that were mapped which is a multiple of the underlying operating system's + * page allocation granularity. + */ + size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } + size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } + size_type mapped_length() const noexcept + { + return pimpl_ ? pimpl_->mapped_length() : 0; + } + + /** + * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping + * exists. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer data() noexcept { return pimpl_->data(); } + const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } + + /** + * Returns an iterator to the first requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + iterator begin() noexcept { return pimpl_->begin(); } + const_iterator begin() const noexcept { return pimpl_->begin(); } + const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } + + /** + * Returns an iterator one past the last requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > iterator end() noexcept { return pimpl_->end(); } + const_iterator end() const noexcept { return pimpl_->end(); } + const_iterator cend() const noexcept { return pimpl_->cend(); } + + /** + * Returns a reverse iterator to the last memory mapped byte, if a valid + * memory mapping exists, otherwise this function call is undefined + * behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); } + const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } + const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } + + /** + * Returns a reverse iterator past the first mapped byte, if a valid memory + * mapping exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rend() noexcept { return pimpl_->rend(); } + const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } + const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } + + /** + * Returns a reference to the `i`th byte from the first requested byte (as returned + * by `data`). If this is invoked when no valid memory mapping has been created + * prior to this call, undefined behaviour ensues. + */ + reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } + const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + template + void map(const String& path, const size_type offset, + const size_type length, std::error_code& error) + { + map_impl(path, offset, length, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map_impl(path, 0, map_entire_file, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + void map(const handle_type handle, const size_type offset, + const size_type length, std::error_code& error) + { + map_impl(handle, offset, length, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * The entire file is mapped. + */ + void map(const handle_type handle, std::error_code& error) + { + map_impl(handle, 0, map_entire_file, error); + } + + /** + * If a valid memory mapping has been created prior to this call, this call + * instructs the kernel to unmap the memory region and disassociate this object + * from the file. + * + * The file handle associated with the file that is mapped is only closed if the + * mapping was created using a file path. If, on the other hand, an existing + * file handle was used to create the mapping, the file handle is not closed. + */ + void unmap() { if(pimpl_) pimpl_->unmap(); } + + void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); } + + /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } + + /** All operators compare the underlying `basic_mmap`'s addresses. */ + + friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ == b.pimpl_; + } + + friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return !(a == b); + } + + friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ < b.pimpl_; + } + + friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ <= b.pimpl_; + } + + friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ > b.pimpl_; + } + + friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ >= b.pimpl_; + } + +private: + template + void map_impl(const MappingToken& token, const size_type offset, + const size_type length, std::error_code& error) + { + if(!pimpl_) + { + mmap_type mmap = make_mmap(token, offset, length, error); + if(error) { return; } + pimpl_ = std::make_shared(std::move(mmap)); + } + else + { + pimpl_->map(token, offset, length, error); + } + } +}; + +/** + * This is the basis for all read-only mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ +template +using basic_shared_mmap_source = basic_shared_mmap; + +/** + * This is the basis for all read-write mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ +template +using basic_shared_mmap_sink = basic_shared_mmap; + +/** + * These aliases cover the most common use cases, both representing a raw byte stream + * (either with a char or an unsigned char/uint8_t). + */ +using shared_mmap_source = basic_shared_mmap_source; +using shared_ummap_source = basic_shared_mmap_source; + +using shared_mmap_sink = basic_shared_mmap_sink; +using shared_ummap_sink = basic_shared_mmap_sink; + +} // namespace mio + +#endif // MIO_SHARED_MMAP_HEADER diff --git a/thirdparty/mio/single_include/mio/mio.hpp b/thirdparty/mio/single_include/mio/mio.hpp new file mode 100644 index 0000000000..b4b8cd5e95 --- /dev/null +++ b/thirdparty/mio/single_include/mio/mio.hpp @@ -0,0 +1,1748 @@ +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_MMAP_HEADER +#define MIO_MMAP_HEADER + +// #include "mio/page.hpp" +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_PAGE_HEADER +#define MIO_PAGE_HEADER + +#ifdef _WIN32 +# include +#else +# include +#endif + +namespace mio { + +/** + * This is used by `basic_mmap` to determine whether to create a read-only or + * a read-write memory mapping. + */ +enum class access_mode +{ + read, + write +}; + +/** + * Determines the operating system's page allocation granularity. + * + * On the first call to this function, it invokes the operating system specific syscall + * to determine the page size, caches the value, and returns it. Any subsequent call to + * this function serves the cached value, so no further syscalls are made. + */ +inline size_t page_size() +{ + static const size_t page_size = [] + { +#ifdef _WIN32 + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + return SystemInfo.dwAllocationGranularity; +#else + return sysconf(_SC_PAGE_SIZE); +#endif + }(); + return page_size; +} + +/** + * Alligns `offset` to the operating's system page size such that it subtracts the + * difference until the nearest page boundary before `offset`, or does nothing if + * `offset` is already page aligned. + */ +inline size_t make_offset_page_aligned(size_t offset) noexcept +{ + const size_t page_size_ = page_size(); + // Use integer division to round down to the nearest page alignment. + return offset / page_size_ * page_size_; +} + +} // namespace mio + +#endif // MIO_PAGE_HEADER + + +#include +#include +#include +#include + +#ifdef _WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif // WIN32_LEAN_AND_MEAN +# include +#else // ifdef _WIN32 +# define INVALID_HANDLE_VALUE -1 +#endif // ifdef _WIN32 + +namespace mio { + +// This value may be provided as the `length` parameter to the constructor or +// `map`, in which case a memory mapping of the entire file is created. +enum { map_entire_file = 0 }; + +#ifdef _WIN32 +using file_handle_type = HANDLE; +#else +using file_handle_type = int; +#endif + +// This value represents an invalid file handle type. This can be used to +// determine whether `basic_mmap::file_handle` is valid, for example. +const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; + +template +struct basic_mmap +{ + using value_type = ByteT; + using size_type = size_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using iterator_category = std::random_access_iterator_tag; + using handle_type = file_handle_type; + + static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); + +private: + // Points to the first requested byte, and not to the actual start of the mapping. + pointer data_ = nullptr; + + // Length--in bytes--requested by user (which may not be the length of the + // full mapping) and the length of the full mapping. + size_type length_ = 0; + size_type mapped_length_ = 0; + + // Letting user map a file using both an existing file handle and a path + // introcudes some complexity (see `is_handle_internal_`). + // On POSIX, we only need a file handle to create a mapping, while on + // Windows systems the file handle is necessary to retrieve a file mapping + // handle, but any subsequent operations on the mapped region must be done + // through the latter. + handle_type file_handle_ = INVALID_HANDLE_VALUE; +#ifdef _WIN32 + handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; +#endif + + // Letting user map a file using both an existing file handle and a path + // introcudes some complexity in that we must not close the file handle if + // user provided it, but we must close it if we obtained it using the + // provided path. For this reason, this flag is used to determine when to + // close `file_handle_`. + bool is_handle_internal_; + +public: + /** + * The default constructed mmap object is in a non-mapped state, that is, + * any operation that attempts to access nonexistent underlying data will + * result in undefined behaviour/segmentation faults. + */ + basic_mmap() = default; + +#ifdef __cpp_exceptions + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + template + basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(path, offset, length, error); + if(error) { throw std::system_error(error); } + } + + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(handle, offset, length, error); + if(error) { throw std::system_error(error); } + } +#endif // __cpp_exceptions + + /** + * `basic_mmap` has single-ownership semantics, so transferring ownership + * may only be accomplished by moving the object. + */ + basic_mmap(const basic_mmap&) = delete; + basic_mmap(basic_mmap&&); + basic_mmap& operator=(const basic_mmap&) = delete; + basic_mmap& operator=(basic_mmap&&); + + /** + * If this is a read-write mapping, the destructor invokes sync. Regardless + * of the access mode, unmap is invoked as a final step. + */ + ~basic_mmap(); + + /** + * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, + * however, a mapped region of a file gets its own handle, which is returned by + * 'mapping_handle'. + */ + handle_type file_handle() const noexcept { return file_handle_; } + handle_type mapping_handle() const noexcept; + + /** Returns whether a valid memory mapping has been created. */ + bool is_open() const noexcept { return file_handle_ != invalid_handle; } + + /** + * Returns true if no mapping was established, that is, conceptually the + * same as though the length that was mapped was 0. This function is + * provided so that this class has Container semantics. + */ + bool empty() const noexcept { return length() == 0; } + + /** Returns true if a mapping was established. */ + bool is_mapped() const noexcept; + + /** + * `size` and `length` both return the logical length, i.e. the number of bytes + * user requested to be mapped, while `mapped_length` returns the actual number of + * bytes that were mapped which is a multiple of the underlying operating system's + * page allocation granularity. + */ + size_type size() const noexcept { return length(); } + size_type length() const noexcept { return length_; } + size_type mapped_length() const noexcept { return mapped_length_; } + + /** Returns the offset relative to the start of the mapping. */ + size_type mapping_offset() const noexcept + { + return mapped_length_ - length_; + } + + /** + * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping + * exists. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer data() noexcept { return data_; } + const_pointer data() const noexcept { return data_; } + + /** + * Returns an iterator to the first requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > iterator begin() noexcept { return data(); } + const_iterator begin() const noexcept { return data(); } + const_iterator cbegin() const noexcept { return data(); } + + /** + * Returns an iterator one past the last requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > iterator end() noexcept { return data() + length(); } + const_iterator end() const noexcept { return data() + length(); } + const_iterator cend() const noexcept { return data() + length(); } + + /** + * Returns a reverse iterator to the last memory mapped byte, if a valid + * memory mapping exists, otherwise this function call is undefined + * behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const noexcept + { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const noexcept + { return const_reverse_iterator(end()); } + + /** + * Returns a reverse iterator past the first mapped byte, if a valid memory + * mapping exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rend() noexcept { return reverse_iterator(begin()); } + const_reverse_iterator rend() const noexcept + { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const noexcept + { return const_reverse_iterator(begin()); } + + /** + * Returns a reference to the `i`th byte from the first requested byte (as returned + * by `data`). If this is invoked when no valid memory mapping has been created + * prior to this call, undefined behaviour ensues. + */ + reference operator[](const size_type i) noexcept { return data_[i]; } + const_reference operator[](const size_type i) const noexcept { return data_[i]; } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + template + void map(const String& path, const size_type offset, + const size_type length, std::error_code& error); + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map(path, 0, map_entire_file, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is + * unsuccesful, the reason is reported via `error` and the object remains in + * a state as if this function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + void map(const handle_type handle, const size_type offset, + const size_type length, std::error_code& error); + + /** + * Establishes a memory mapping with AccessMode. If the mapping is + * unsuccesful, the reason is reported via `error` and the object remains in + * a state as if this function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * The entire file is mapped. + */ + void map(const handle_type handle, std::error_code& error) + { + map(handle, 0, map_entire_file, error); + } + + /** + * If a valid memory mapping has been created prior to this call, this call + * instructs the kernel to unmap the memory region and disassociate this object + * from the file. + * + * The file handle associated with the file that is mapped is only closed if the + * mapping was created using a file path. If, on the other hand, an existing + * file handle was used to create the mapping, the file handle is not closed. + */ + void unmap(); + + void swap(basic_mmap& other); + + /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ + template + typename std::enable_if::type + sync(std::error_code& error); + + /** + * All operators compare the address of the first byte and size of the two mapped + * regions. + */ + +private: + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer get_mapping_start() noexcept + { + return !data() ? nullptr : data() - mapping_offset(); + } + + const_pointer get_mapping_start() const noexcept + { + return !data() ? nullptr : data() - mapping_offset(); + } + + /** + * The destructor syncs changes to disk if `AccessMode` is `write`, but not + * if it's `read`, but since the destructor cannot be templated, we need to + * do SFINAE in a dedicated function, where one syncs and the other is a noop. + */ + template + typename std::enable_if::type + conditional_sync(); + template + typename std::enable_if::type conditional_sync(); +}; + +template +bool operator==(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b); + +/** + * This is the basis for all read-only mmap objects and should be preferred over + * directly using `basic_mmap`. + */ +template +using basic_mmap_source = basic_mmap; + +/** + * This is the basis for all read-write mmap objects and should be preferred over + * directly using `basic_mmap`. + */ +template +using basic_mmap_sink = basic_mmap; + +/** + * These aliases cover the most common use cases, both representing a raw byte stream + * (either with a char or an unsigned char/uint8_t). + */ +using mmap_source = basic_mmap_source; +using ummap_source = basic_mmap_source; + +using mmap_sink = basic_mmap_sink; +using ummap_sink = basic_mmap_sink; + +/** + * Convenience factory method that constructs a mapping for any `basic_mmap` or + * `basic_mmap` type. + */ +template< + typename MMap, + typename MappingToken +> MMap make_mmap(const MappingToken& token, + int64_t offset, int64_t length, std::error_code& error) +{ + MMap mmap; + mmap.map(token, offset, length, error); + return mmap; +} + +/** + * Convenience factory method. + * + * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, + * `std::filesystem::path`, `std::vector`, or similar), or a + * `mmap_source::handle_type`. + */ +template +mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, + mmap_source::size_type length, std::error_code& error) +{ + return make_mmap(token, offset, length, error); +} + +template +mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) +{ + return make_mmap_source(token, 0, map_entire_file, error); +} + +/** + * Convenience factory method. + * + * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, + * `std::filesystem::path`, `std::vector`, or similar), or a + * `mmap_sink::handle_type`. + */ +template +mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, + mmap_sink::size_type length, std::error_code& error) +{ + return make_mmap(token, offset, length, error); +} + +template +mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) +{ + return make_mmap_sink(token, 0, map_entire_file, error); +} + +} // namespace mio + +// #include "detail/mmap.ipp" +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_BASIC_MMAP_IMPL +#define MIO_BASIC_MMAP_IMPL + +// #include "mio/mmap.hpp" + +// #include "mio/page.hpp" + +// #include "mio/detail/string_util.hpp" +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_STRING_UTIL_HEADER +#define MIO_STRING_UTIL_HEADER + +#include + +namespace mio { +namespace detail { + +template< + typename S, + typename C = typename std::decay::type, + typename = decltype(std::declval().data()), + typename = typename std::enable_if< + std::is_same::value +#ifdef _WIN32 + || std::is_same::value +#endif + >::type +> struct char_type_helper { + using type = typename C::value_type; +}; + +template +struct char_type { + using type = typename char_type_helper::type; +}; + +// TODO: can we avoid this brute force approach? +template<> +struct char_type { + using type = char; +}; + +template<> +struct char_type { + using type = char; +}; + +template +struct char_type { + using type = char; +}; + +template +struct char_type { + using type = char; +}; + +#ifdef _WIN32 +template<> +struct char_type { + using type = wchar_t; +}; + +template<> +struct char_type { + using type = wchar_t; +}; + +template +struct char_type { + using type = wchar_t; +}; + +template +struct char_type { + using type = wchar_t; +}; +#endif // _WIN32 + +template +struct is_c_str_helper +{ + static constexpr bool value = std::is_same< + CharT*, + // TODO: I'm so sorry for this... Can this be made cleaner? + typename std::add_pointer< + typename std::remove_cv< + typename std::remove_pointer< + typename std::decay< + S + >::type + >::type + >::type + >::type + >::value; +}; + +template +struct is_c_str +{ + static constexpr bool value = is_c_str_helper::value; +}; + +#ifdef _WIN32 +template +struct is_c_wstr +{ + static constexpr bool value = is_c_str_helper::value; +}; +#endif // _WIN32 + +template +struct is_c_str_or_c_wstr +{ + static constexpr bool value = is_c_str::value +#ifdef _WIN32 + || is_c_wstr::value +#endif + ; +}; + +template< + typename String, + typename = decltype(std::declval().data()), + typename = typename std::enable_if::value>::type +> const typename char_type::type* c_str(const String& path) +{ + return path.data(); +} + +template< + typename String, + typename = decltype(std::declval().empty()), + typename = typename std::enable_if::value>::type +> bool empty(const String& path) +{ + return path.empty(); +} + +template< + typename String, + typename = typename std::enable_if::value>::type +> const typename char_type::type* c_str(String path) +{ + return path; +} + +template< + typename String, + typename = typename std::enable_if::value>::type +> bool empty(String path) +{ + return !path || (*path == 0); +} + +} // namespace detail +} // namespace mio + +#endif // MIO_STRING_UTIL_HEADER + + +#include + +#ifndef _WIN32 +# include +# include +# include +# include +#endif + +namespace mio { +namespace detail { + +#ifdef _WIN32 +namespace win { + +/** Returns the 4 upper bytes of an 8-byte integer. */ +inline DWORD int64_high(int64_t n) noexcept +{ + return n >> 32; +} + +/** Returns the 4 lower bytes of an 8-byte integer. */ +inline DWORD int64_low(int64_t n) noexcept +{ + return n & 0xffffffff; +} + +template< + typename String, + typename = typename std::enable_if< + std::is_same::type, char>::value + >::type +> file_handle_type open_file_helper(const String& path, const access_mode mode) +{ + return ::CreateFileA(c_str(path), + mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); +} + +template +typename std::enable_if< + std::is_same::type, wchar_t>::value, + file_handle_type +>::type open_file_helper(const String& path, const access_mode mode) +{ + return ::CreateFileW(c_str(path), + mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); +} + +} // win +#endif // _WIN32 + +/** + * Returns the last platform specific system error (errno on POSIX and + * GetLastError on Win) as a `std::error_code`. + */ +inline std::error_code last_error() noexcept +{ + std::error_code error; +#ifdef _WIN32 + error.assign(GetLastError(), std::system_category()); +#else + error.assign(errno, std::system_category()); +#endif + return error; +} + +template +file_handle_type open_file(const String& path, const access_mode mode, + std::error_code& error) +{ + error.clear(); + if(detail::empty(path)) + { + error = std::make_error_code(std::errc::invalid_argument); + return invalid_handle; + } +#ifdef _WIN32 + const auto handle = win::open_file_helper(path, mode); +#else // POSIX + const auto handle = ::open(c_str(path), + mode == access_mode::read ? O_RDONLY : O_RDWR); +#endif + if(handle == invalid_handle) + { + error = detail::last_error(); + } + return handle; +} + +inline size_t query_file_size(file_handle_type handle, std::error_code& error) +{ + error.clear(); +#ifdef _WIN32 + LARGE_INTEGER file_size; + if(::GetFileSizeEx(handle, &file_size) == 0) + { + error = detail::last_error(); + return 0; + } + return static_cast(file_size.QuadPart); +#else // POSIX + struct stat sbuf; + if(::fstat(handle, &sbuf) == -1) + { + error = detail::last_error(); + return 0; + } + return sbuf.st_size; +#endif +} + +struct mmap_context +{ + char* data; + int64_t length; + int64_t mapped_length; +#ifdef _WIN32 + file_handle_type file_mapping_handle; +#endif +}; + +inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, + const int64_t length, const access_mode mode, std::error_code& error) +{ + const int64_t aligned_offset = make_offset_page_aligned(offset); + const int64_t length_to_map = offset - aligned_offset + length; +#ifdef _WIN32 + const int64_t max_file_size = offset + length; + const auto file_mapping_handle = ::CreateFileMapping( + file_handle, + 0, + mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + win::int64_high(max_file_size), + win::int64_low(max_file_size), + 0); + if(file_mapping_handle == invalid_handle) + { + error = detail::last_error(); + return {}; + } + char* mapping_start = static_cast(::MapViewOfFile( + file_mapping_handle, + mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + win::int64_high(aligned_offset), + win::int64_low(aligned_offset), + length_to_map)); + if(mapping_start == nullptr) + { + error = detail::last_error(); + return {}; + } +#else // POSIX + char* mapping_start = static_cast(::mmap( + 0, // Don't give hint as to where to map. + length_to_map, + mode == access_mode::read ? PROT_READ : PROT_WRITE, + MAP_SHARED, + file_handle, + aligned_offset)); + if(mapping_start == MAP_FAILED) + { + error = detail::last_error(); + return {}; + } +#endif + mmap_context ctx; + ctx.data = mapping_start + offset - aligned_offset; + ctx.length = length; + ctx.mapped_length = length_to_map; +#ifdef _WIN32 + ctx.file_mapping_handle = file_mapping_handle; +#endif + return ctx; +} + +} // namespace detail + +// -- basic_mmap -- + +template +basic_mmap::~basic_mmap() +{ + conditional_sync(); + unmap(); +} + +template +basic_mmap::basic_mmap(basic_mmap&& other) + : data_(std::move(other.data_)) + , length_(std::move(other.length_)) + , mapped_length_(std::move(other.mapped_length_)) + , file_handle_(std::move(other.file_handle_)) +#ifdef _WIN32 + , file_mapping_handle_(std::move(other.file_mapping_handle_)) +#endif + , is_handle_internal_(std::move(other.is_handle_internal_)) +{ + other.data_ = nullptr; + other.length_ = other.mapped_length_ = 0; + other.file_handle_ = invalid_handle; +#ifdef _WIN32 + other.file_mapping_handle_ = invalid_handle; +#endif +} + +template +basic_mmap& +basic_mmap::operator=(basic_mmap&& other) +{ + if(this != &other) + { + // First the existing mapping needs to be removed. + unmap(); + data_ = std::move(other.data_); + length_ = std::move(other.length_); + mapped_length_ = std::move(other.mapped_length_); + file_handle_ = std::move(other.file_handle_); +#ifdef _WIN32 + file_mapping_handle_ = std::move(other.file_mapping_handle_); +#endif + is_handle_internal_ = std::move(other.is_handle_internal_); + + // The moved from basic_mmap's fields need to be reset, because + // otherwise other's destructor will unmap the same mapping that was + // just moved into this. + other.data_ = nullptr; + other.length_ = other.mapped_length_ = 0; + other.file_handle_ = invalid_handle; +#ifdef _WIN32 + other.file_mapping_handle_ = invalid_handle; +#endif + other.is_handle_internal_ = false; + } + return *this; +} + +template +typename basic_mmap::handle_type +basic_mmap::mapping_handle() const noexcept +{ +#ifdef _WIN32 + return file_mapping_handle_; +#else + return file_handle_; +#endif +} + +template +template +void basic_mmap::map(const String& path, const size_type offset, + const size_type length, std::error_code& error) +{ + error.clear(); + if(detail::empty(path)) + { + error = std::make_error_code(std::errc::invalid_argument); + return; + } + const auto handle = detail::open_file(path, AccessMode, error); + if(error) + { + return; + } + + map(handle, offset, length, error); + // This MUST be after the call to map, as that sets this to true. + if(!error) + { + is_handle_internal_ = true; + } +} + +template +void basic_mmap::map(const handle_type handle, + const size_type offset, const size_type length, std::error_code& error) +{ + error.clear(); + if(handle == invalid_handle) + { + error = std::make_error_code(std::errc::bad_file_descriptor); + return; + } + + const auto file_size = detail::query_file_size(handle, error); + if(error) + { + return; + } + + if(offset + length > file_size) + { + error = std::make_error_code(std::errc::invalid_argument); + return; + } + + const auto ctx = detail::memory_map(handle, offset, + length == map_entire_file ? (file_size - offset) : length, + AccessMode, error); + if(!error) + { + // We must unmap the previous mapping that may have existed prior to this call. + // Note that this must only be invoked after a new mapping has been created in + // order to provide the strong guarantee that, should the new mapping fail, the + // `map` function leaves this instance in a state as though the function had + // never been invoked. + unmap(); + file_handle_ = handle; + is_handle_internal_ = false; + data_ = reinterpret_cast(ctx.data); + length_ = ctx.length; + mapped_length_ = ctx.mapped_length; +#ifdef _WIN32 + file_mapping_handle_ = ctx.file_mapping_handle; +#endif + } +} + +template +template +typename std::enable_if::type +basic_mmap::sync(std::error_code& error) +{ + error.clear(); + if(!is_open()) + { + error = std::make_error_code(std::errc::bad_file_descriptor); + return; + } + + if(data()) + { +#ifdef _WIN32 + if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 + || ::FlushFileBuffers(file_handle_) == 0) +#else // POSIX + if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) +#endif + { + error = detail::last_error(); + return; + } + } +#ifdef _WIN32 + if(::FlushFileBuffers(file_handle_) == 0) + { + error = detail::last_error(); + } +#endif +} + +template +void basic_mmap::unmap() +{ + if(!is_open()) { return; } + // TODO do we care about errors here? +#ifdef _WIN32 + if(is_mapped()) + { + ::UnmapViewOfFile(get_mapping_start()); + ::CloseHandle(file_mapping_handle_); + } +#else // POSIX + if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } +#endif + + // If `file_handle_` was obtained by our opening it (when map is called with + // a path, rather than an existing file handle), we need to close it, + // otherwise it must not be closed as it may still be used outside this + // instance. + if(is_handle_internal_) + { +#ifdef _WIN32 + ::CloseHandle(file_handle_); +#else // POSIX + ::close(file_handle_); +#endif + } + + // Reset fields to their default values. + data_ = nullptr; + length_ = mapped_length_ = 0; + file_handle_ = invalid_handle; +#ifdef _WIN32 + file_mapping_handle_ = invalid_handle; +#endif +} + +template +bool basic_mmap::is_mapped() const noexcept +{ +#ifdef _WIN32 + return file_mapping_handle_ != invalid_handle; +#else // POSIX + return is_open(); +#endif +} + +template +void basic_mmap::swap(basic_mmap& other) +{ + if(this != &other) + { + using std::swap; + swap(data_, other.data_); + swap(file_handle_, other.file_handle_); +#ifdef _WIN32 + swap(file_mapping_handle_, other.file_mapping_handle_); +#endif + swap(length_, other.length_); + swap(mapped_length_, other.mapped_length_); + swap(is_handle_internal_, other.is_handle_internal_); + } +} + +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // This is invoked from the destructor, so not much we can do about + // failures here. + std::error_code ec; + sync(ec); +} + +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // noop +} + +template +bool operator==(const basic_mmap& a, + const basic_mmap& b) +{ + return a.data() == b.data() + && a.size() == b.size(); +} + +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b) +{ + return !(a == b); +} + +template +bool operator<(const basic_mmap& a, + const basic_mmap& b) +{ + if(a.data() == b.data()) { return a.size() < b.size(); } + return a.data() < b.data(); +} + +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b) +{ + return !(a > b); +} + +template +bool operator>(const basic_mmap& a, + const basic_mmap& b) +{ + if(a.data() == b.data()) { return a.size() > b.size(); } + return a.data() > b.data(); +} + +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b) +{ + return !(a < b); +} + +} // namespace mio + +#endif // MIO_BASIC_MMAP_IMPL + + +#endif // MIO_MMAP_HEADER +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_PAGE_HEADER +#define MIO_PAGE_HEADER + +#ifdef _WIN32 +# include +#else +# include +#endif + +namespace mio { + +/** + * This is used by `basic_mmap` to determine whether to create a read-only or + * a read-write memory mapping. + */ +enum class access_mode +{ + read, + write +}; + +/** + * Determines the operating system's page allocation granularity. + * + * On the first call to this function, it invokes the operating system specific syscall + * to determine the page size, caches the value, and returns it. Any subsequent call to + * this function serves the cached value, so no further syscalls are made. + */ +inline size_t page_size() +{ + static const size_t page_size = [] + { +#ifdef _WIN32 + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + return SystemInfo.dwAllocationGranularity; +#else + return sysconf(_SC_PAGE_SIZE); +#endif + }(); + return page_size; +} + +/** + * Alligns `offset` to the operating's system page size such that it subtracts the + * difference until the nearest page boundary before `offset`, or does nothing if + * `offset` is already page aligned. + */ +inline size_t make_offset_page_aligned(size_t offset) noexcept +{ + const size_t page_size_ = page_size(); + // Use integer division to round down to the nearest page alignment. + return offset / page_size_ * page_size_; +} + +} // namespace mio + +#endif // MIO_PAGE_HEADER +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_SHARED_MMAP_HEADER +#define MIO_SHARED_MMAP_HEADER + +// #include "mio/mmap.hpp" + + +#include // std::error_code +#include // std::shared_ptr + +namespace mio { + +/** + * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with + * `std::shared_ptr` semantics. + * + * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if + * shared semantics are not required. + */ +template< + access_mode AccessMode, + typename ByteT +> class basic_shared_mmap +{ + using impl_type = basic_mmap; + std::shared_ptr pimpl_; + +public: + using value_type = typename impl_type::value_type; + using size_type = typename impl_type::size_type; + using reference = typename impl_type::reference; + using const_reference = typename impl_type::const_reference; + using pointer = typename impl_type::pointer; + using const_pointer = typename impl_type::const_pointer; + using difference_type = typename impl_type::difference_type; + using iterator = typename impl_type::iterator; + using const_iterator = typename impl_type::const_iterator; + using reverse_iterator = typename impl_type::reverse_iterator; + using const_reverse_iterator = typename impl_type::const_reverse_iterator; + using iterator_category = typename impl_type::iterator_category; + using handle_type = typename impl_type::handle_type; + using mmap_type = impl_type; + + basic_shared_mmap() = default; + basic_shared_mmap(const basic_shared_mmap&) = default; + basic_shared_mmap& operator=(const basic_shared_mmap&) = default; + basic_shared_mmap(basic_shared_mmap&&) = default; + basic_shared_mmap& operator=(basic_shared_mmap&&) = default; + + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap(mmap_type&& mmap) + : pimpl_(std::make_shared(std::move(mmap))) + {} + + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap& operator=(mmap_type&& mmap) + { + pimpl_ = std::make_shared(std::move(mmap)); + return *this; + } + + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) {} + + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap& operator=(std::shared_ptr mmap) + { + pimpl_ = std::move(mmap); + return *this; + } + +#ifdef __cpp_exceptions + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + template + basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(path, offset, length, error); + if(error) { throw std::system_error(error); } + } + + /** + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. + */ + basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) + { + std::error_code error; + map(handle, offset, length, error); + if(error) { throw std::system_error(error); } + } +#endif // __cpp_exceptions + + /** + * If this is a read-write mapping and the last reference to the mapping, + * the destructor invokes sync. Regardless of the access mode, unmap is + * invoked as a final step. + */ + ~basic_shared_mmap() = default; + + /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ + std::shared_ptr get_shared_ptr() { return pimpl_; } + + /** + * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, + * however, a mapped region of a file gets its own handle, which is returned by + * 'mapping_handle'. + */ + handle_type file_handle() const noexcept + { + return pimpl_ ? pimpl_->file_handle() : invalid_handle; + } + + handle_type mapping_handle() const noexcept + { + return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; + } + + /** Returns whether a valid memory mapping has been created. */ + bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } + + /** + * Returns true if no mapping was established, that is, conceptually the + * same as though the length that was mapped was 0. This function is + * provided so that this class has Container semantics. + */ + bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); } + + /** + * `size` and `length` both return the logical length, i.e. the number of bytes + * user requested to be mapped, while `mapped_length` returns the actual number of + * bytes that were mapped which is a multiple of the underlying operating system's + * page allocation granularity. + */ + size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } + size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } + size_type mapped_length() const noexcept + { + return pimpl_ ? pimpl_->mapped_length() : 0; + } + + /** + * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping + * exists. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer data() noexcept { return pimpl_->data(); } + const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } + + /** + * Returns an iterator to the first requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + iterator begin() noexcept { return pimpl_->begin(); } + const_iterator begin() const noexcept { return pimpl_->begin(); } + const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } + + /** + * Returns an iterator one past the last requested byte, if a valid memory mapping + * exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > iterator end() noexcept { return pimpl_->end(); } + const_iterator end() const noexcept { return pimpl_->end(); } + const_iterator cend() const noexcept { return pimpl_->cend(); } + + /** + * Returns a reverse iterator to the last memory mapped byte, if a valid + * memory mapping exists, otherwise this function call is undefined + * behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); } + const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } + const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } + + /** + * Returns a reverse iterator past the first mapped byte, if a valid memory + * mapping exists, otherwise this function call is undefined behaviour. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rend() noexcept { return pimpl_->rend(); } + const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } + const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } + + /** + * Returns a reference to the `i`th byte from the first requested byte (as returned + * by `data`). If this is invoked when no valid memory mapping has been created + * prior to this call, undefined behaviour ensues. + */ + reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } + const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + template + void map(const String& path, const size_type offset, + const size_type length, std::error_code& error) + { + map_impl(path, offset, length, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map_impl(path, 0, map_entire_file, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * `offset` is the number of bytes, relative to the start of the file, where the + * mapping should begin. When specifying it, there is no need to worry about + * providing a value that is aligned with the operating system's page allocation + * granularity. This is adjusted by the implementation such that the first requested + * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at + * `offset` from the start of the file. + * + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. + */ + void map(const handle_type handle, const size_type offset, + const size_type length, std::error_code& error) + { + map_impl(handle, offset, length, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * The entire file is mapped. + */ + void map(const handle_type handle, std::error_code& error) + { + map_impl(handle, 0, map_entire_file, error); + } + + /** + * If a valid memory mapping has been created prior to this call, this call + * instructs the kernel to unmap the memory region and disassociate this object + * from the file. + * + * The file handle associated with the file that is mapped is only closed if the + * mapping was created using a file path. If, on the other hand, an existing + * file handle was used to create the mapping, the file handle is not closed. + */ + void unmap() { if(pimpl_) pimpl_->unmap(); } + + void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); } + + /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } + + /** All operators compare the underlying `basic_mmap`'s addresses. */ + + friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ == b.pimpl_; + } + + friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return !(a == b); + } + + friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ < b.pimpl_; + } + + friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ <= b.pimpl_; + } + + friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ > b.pimpl_; + } + + friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ >= b.pimpl_; + } + +private: + template + void map_impl(const MappingToken& token, const size_type offset, + const size_type length, std::error_code& error) + { + if(!pimpl_) + { + mmap_type mmap = make_mmap(token, offset, length, error); + if(error) { return; } + pimpl_ = std::make_shared(std::move(mmap)); + } + else + { + pimpl_->map(token, offset, length, error); + } + } +}; + +/** + * This is the basis for all read-only mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ +template +using basic_shared_mmap_source = basic_shared_mmap; + +/** + * This is the basis for all read-write mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ +template +using basic_shared_mmap_sink = basic_shared_mmap; + +/** + * These aliases cover the most common use cases, both representing a raw byte stream + * (either with a char or an unsigned char/uint8_t). + */ +using shared_mmap_source = basic_shared_mmap_source; +using shared_ummap_source = basic_shared_mmap_source; + +using shared_mmap_sink = basic_shared_mmap_sink; +using shared_ummap_sink = basic_shared_mmap_sink; + +} // namespace mio + +#endif // MIO_SHARED_MMAP_HEADER diff --git a/thirdparty/mio/test/CMakeLists.txt b/thirdparty/mio/test/CMakeLists.txt new file mode 100644 index 0000000000..6bfc47e5a1 --- /dev/null +++ b/thirdparty/mio/test/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable(mio.test test.cpp) +target_link_libraries(mio.test PRIVATE mio::mio) +add_test(NAME mio.test COMMAND mio.test) + +if(WIN32) + add_executable(mio.unicode.test test.cpp) + target_link_libraries(mio.unicode.test PRIVATE mio::mio) + target_compile_definitions(mio.unicode.test PRIVATE UNICODE) + add_test(NAME mio.unicode.test COMMAND mio.test) + + add_executable(mio.fullwinapi.test test.cpp) + target_link_libraries(mio.fullwinapi.test + PRIVATE mio::mio_full_winapi) + add_test(NAME mio.fullwinapi.test COMMAND mio.test) +endif() diff --git a/thirdparty/mio/test/example.cpp b/thirdparty/mio/test/example.cpp new file mode 100644 index 0000000000..841a57fd81 --- /dev/null +++ b/thirdparty/mio/test/example.cpp @@ -0,0 +1,75 @@ +#include +#include // for std::error_code +#include // for std::printf +#include +#include +#include + +int handle_error(const std::error_code& error); +void allocate_file(const std::string& path, const int size); + +int main() +{ + const auto path = "file.txt"; + + // NOTE: mio does *not* create the file for you if it doesn't exist! You + // must ensure that the file exists before establishing a mapping. It + // must also be non-empty. So for illustrative purposes the file is + // created now. + allocate_file(path, 155); + + // Read-write memory map the whole file by using `map_entire_file` where the + // length of the mapping is otherwise expected, with the factory method. + std::error_code error; + mio::mmap_sink rw_mmap = mio::make_mmap_sink( + path, 0, mio::map_entire_file, error); + if (error) { return handle_error(error); } + + // You can use any iterator based function. + std::fill(rw_mmap.begin(), rw_mmap.end(), 'a'); + + // Or manually iterate through the mapped region just as if it were any other + // container, and change each byte's value (since this is a read-write mapping). + for (auto& b : rw_mmap) { + b += 10; + } + + // Or just change one value with the subscript operator. + const int answer_index = rw_mmap.size() / 2; + rw_mmap[answer_index] = 42; + + // Don't forget to flush changes to disk before unmapping. However, if + // `rw_mmap` were to go out of scope at this point, the destructor would also + // automatically invoke `sync` before `unmap`. + rw_mmap.sync(error); + if (error) { return handle_error(error); } + + // We can then remove the mapping, after which rw_mmap will be in a default + // constructed state, i.e. this and the above call to `sync` have the same + // effect as if the destructor had been invoked. + rw_mmap.unmap(); + + // Now create the same mapping, but in read-only mode. Note that calling the + // overload without the offset and file length parameters maps the entire + // file. + mio::mmap_source ro_mmap; + ro_mmap.map(path, error); + if (error) { return handle_error(error); } + + const int the_answer_to_everything = ro_mmap[answer_index]; + assert(the_answer_to_everything == 42); +} + +int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} + +void allocate_file(const std::string& path, const int size) +{ + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} diff --git a/thirdparty/mio/test/test.cpp b/thirdparty/mio/test/test.cpp new file mode 100644 index 0000000000..82827f924c --- /dev/null +++ b/thirdparty/mio/test/test.cpp @@ -0,0 +1,182 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +// Just make sure this compiles. +#ifdef CXX17 +# include +using mmap_source = mio::basic_mmap_source; +#endif + +template +void test_at_offset(const MMap& file_view, const std::string& buffer, + const size_t offset); +void test_at_offset(const std::string& buffer, const char* path, + const size_t offset, std::error_code& error); +int handle_error(const std::error_code& error); + +int main() +{ + std::error_code error; + + // Make sure mio compiles with non-const char* strings too. + const char _path[] = "test-file"; + const int path_len = sizeof(_path); + char* path = new char[path_len]; + std::copy(_path, _path + path_len, path); + + const auto page_size = mio::page_size(); + // Fill buffer, then write it to file. + const int file_size = 4 * page_size - 250; // 16134, if page size is 4KiB + std::string buffer(file_size, 0); + // Start at first printable ASCII character. + char v = 33; + for (auto& b : buffer) { + b = v; + ++v; + // Limit to last printable ASCII character. + v %= 126; + if(v == 0) { + v = 33; + } + } + + std::ofstream file(path); + file << buffer; + file.close(); + + // Test whole file mapping. + test_at_offset(buffer, path, 0, error); + if (error) { return handle_error(error); } + + // Test starting from below the page size. + test_at_offset(buffer, path, page_size - 3, error); + if (error) { return handle_error(error); } + + // Test starting from above the page size. + test_at_offset(buffer, path, page_size + 3, error); + if (error) { return handle_error(error); } + + // Test starting from above the page size. + test_at_offset(buffer, path, 2 * page_size + 3, error); + if (error) { return handle_error(error); } + + { +#define CHECK_INVALID_MMAP(m) do { \ + assert(error); \ + assert(m.empty()); \ + assert(!m.is_open()); \ + error.clear(); } while(0) + + mio::mmap_source m; + + // See if mapping an invalid file results in an error. + m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error); + CHECK_INVALID_MMAP(m); + + // Empty path? + m = mio::make_mmap_source(static_cast(0), 0, 0, error); + CHECK_INVALID_MMAP(m); + m = mio::make_mmap_source(std::string(), 0, 0, error); + CHECK_INVALID_MMAP(m); + + // Invalid handle? + m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error); + CHECK_INVALID_MMAP(m); + + // Invalid offset? + m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error); + CHECK_INVALID_MMAP(m); + } + + // Make sure these compile. + { + mio::ummap_source _1; + mio::shared_ummap_source _2; + // Make sure shared_mmap mapping compiles as all testing was done on + // normal mmaps. + mio::shared_mmap_source _3(path, 0, mio::map_entire_file); + auto _4 = mio::make_mmap_source(path, error); + auto _5 = mio::make_mmap(path, 0, mio::map_entire_file, error); +#ifdef _WIN32 + const wchar_t* wpath1 = L"dasfsf"; + auto _6 = mio::make_mmap_source(wpath1, error); + mio::mmap_source _7; + _7.map(wpath1, error); + const std::wstring wpath2 = wpath1; + auto _8 = mio::make_mmap_source(wpath2, error); + mio::mmap_source _9; + _9.map(wpath1, error); +#else + const int fd = open(path, O_RDONLY); + mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file); + _fdmmap.unmap(); + _fdmmap.map(fd, error); +#endif + } + + std::printf("all tests passed!\n"); +} + +void test_at_offset(const std::string& buffer, const char* path, + const size_t offset, std::error_code& error) +{ + // Sanity check. + assert(offset < buffer.size()); + + // Map the region of the file to which buffer was written. + mio::mmap_source file_view = mio::make_mmap_source( + path, offset, mio::map_entire_file, error); + if(error) { return; } + + assert(file_view.is_open()); + const size_t mapped_size = buffer.size() - offset; + assert(file_view.size() == mapped_size); + + test_at_offset(file_view, buffer, offset); + + // Turn file_view into a shared mmap. + mio::shared_mmap_source shared_file_view(std::move(file_view)); + assert(!file_view.is_open()); + assert(shared_file_view.is_open()); + assert(shared_file_view.size() == mapped_size); + + //test_at_offset(shared_file_view, buffer, offset); +} + +template +void test_at_offset(const MMap& file_view, const std::string& buffer, + const size_t offset) +{ + // Then verify that mmap's bytes correspond to that of buffer. + for(size_t buf_idx = offset, view_idx = 0; + buf_idx < buffer.size() && view_idx < file_view.size(); + ++buf_idx, ++view_idx) { + if(file_view[view_idx] != buffer[buf_idx]) { + std::printf("%luth byte mismatch: expected(%d) <> actual(%d)", + buf_idx, buffer[buf_idx], file_view[view_idx]); + std::cout << std::flush; + assert(0); + } + } +} + +int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("Error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} diff --git a/thirdparty/mio/third_party/LICENSE.md b/thirdparty/mio/third_party/LICENSE.md new file mode 100644 index 0000000000..23edca5d15 --- /dev/null +++ b/thirdparty/mio/third_party/LICENSE.md @@ -0,0 +1,28 @@ +amalgamate.py - Amalgamate C source and header files +Copyright (c) 2012, Erik Edlund + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Erik Edlund, nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/thirdparty/mio/third_party/amalgamate.py b/thirdparty/mio/third_party/amalgamate.py new file mode 100644 index 0000000000..174e1c5379 --- /dev/null +++ b/thirdparty/mio/third_party/amalgamate.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python +# coding=utf-8 + +# amalgamate.py - Amalgamate C source and header files. +# Copyright (c) 2012, Erik Edlund +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of Erik Edlund, nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import datetime +import json +import os +import re + + +class Amalgamation(object): + + # Prepends self.source_path to file_path if needed. + def actual_path(self, file_path): + if not os.path.isabs(file_path): + file_path = os.path.join(self.source_path, file_path) + return file_path + + # Search included file_path in self.include_paths and + # in source_dir if specified. + def find_included_file(self, file_path, source_dir): + search_dirs = self.include_paths[:] + if source_dir: + search_dirs.insert(0, source_dir) + + for search_dir in search_dirs: + search_path = os.path.join(search_dir, file_path) + if os.path.isfile(self.actual_path(search_path)): + return search_path + return None + + def __init__(self, args): + with open(args.config, 'r') as f: + config = json.loads(f.read()) + for key in config: + setattr(self, key, config[key]) + + self.verbose = args.verbose == "yes" + self.prologue = args.prologue + self.source_path = args.source_path + self.included_files = [] + + # Generate the amalgamation and write it to the target file. + def generate(self): + amalgamation = "" + + if self.prologue: + with open(self.prologue, 'r') as f: + amalgamation += datetime.datetime.now().strftime(f.read()) + + if self.verbose: + print("Config:") + print(" target = {0}".format(self.target)) + print(" working_dir = {0}".format(os.getcwd())) + print(" include_paths = {0}".format(self.include_paths)) + print("Creating amalgamation:") + for file_path in self.sources: + # Do not check the include paths while processing the source + # list, all given source paths must be correct. + # actual_path = self.actual_path(file_path) + print(" - processing \"{0}\"".format(file_path)) + t = TranslationUnit(file_path, self, True) + amalgamation += t.content + + with open(self.target, 'w') as f: + f.write(amalgamation) + + print("...done!\n") + if self.verbose: + print("Files processed: {0}".format(self.sources)) + print("Files included: {0}".format(self.included_files)) + print("") + + +def _is_within(match, matches): + for m in matches: + if match.start() > m.start() and \ + match.end() < m.end(): + return True + return False + + +class TranslationUnit(object): + # // C++ comment. + cpp_comment_pattern = re.compile(r"//.*?\n") + + # /* C comment. */ + c_comment_pattern = re.compile(r"/\*.*?\*/", re.S) + + # "complex \"stri\\\ng\" value". + string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S) + + # Handle simple include directives. Support for advanced + # directives where macros and defines needs to expanded is + # not a concern right now. + include_pattern = re.compile( + r'#\s*include\s+(<|")(?P.*?)("|>)', re.S) + + # #pragma once + pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S) + + # Search for pattern in self.content, add the match to + # contexts if found and update the index accordingly. + def _search_content(self, index, pattern, contexts): + match = pattern.search(self.content, index) + if match: + contexts.append(match) + return match.end() + return index + 2 + + # Return all the skippable contexts, i.e., comments and strings + def _find_skippable_contexts(self): + # Find contexts in the content in which a found include + # directive should not be processed. + skippable_contexts = [] + + # Walk through the content char by char, and try to grab + # skippable contexts using regular expressions when found. + i = 1 + content_len = len(self.content) + while i < content_len: + j = i - 1 + current = self.content[i] + previous = self.content[j] + + if current == '"': + # String value. + i = self._search_content(j, self.string_pattern, + skippable_contexts) + elif current == '*' and previous == '/': + # C style comment. + i = self._search_content(j, self.c_comment_pattern, + skippable_contexts) + elif current == '/' and previous == '/': + # C++ style comment. + i = self._search_content(j, self.cpp_comment_pattern, + skippable_contexts) + else: + # Skip to the next char. + i += 1 + + return skippable_contexts + + # Returns True if the match is within list of other matches + + # Removes pragma once from content + def _process_pragma_once(self): + content_len = len(self.content) + if content_len < len("#include "): + return 0 + + # Find contexts in the content in which a found include + # directive should not be processed. + skippable_contexts = self._find_skippable_contexts() + + pragmas = [] + pragma_once_match = self.pragma_once_pattern.search(self.content) + while pragma_once_match: + if not _is_within(pragma_once_match, skippable_contexts): + pragmas.append(pragma_once_match) + + pragma_once_match = self.pragma_once_pattern.search(self.content, + pragma_once_match.end()) + + # Handle all collected pragma once directives. + prev_end = 0 + tmp_content = '' + for pragma_match in pragmas: + tmp_content += self.content[prev_end:pragma_match.start()] + prev_end = pragma_match.end() + tmp_content += self.content[prev_end:] + self.content = tmp_content + + # Include all trivial #include directives into self.content. + def _process_includes(self): + content_len = len(self.content) + if content_len < len("#include "): + return 0 + + # Find contexts in the content in which a found include + # directive should not be processed. + skippable_contexts = self._find_skippable_contexts() + + # Search for include directives in the content, collect those + # which should be included into the content. + includes = [] + include_match = self.include_pattern.search(self.content) + while include_match: + if not _is_within(include_match, skippable_contexts): + include_path = include_match.group("path") + search_same_dir = include_match.group(1) == '"' + found_included_path = self.amalgamation.find_included_file( + include_path, self.file_dir if search_same_dir else None) + if found_included_path: + includes.append((include_match, found_included_path)) + + include_match = self.include_pattern.search(self.content, + include_match.end()) + + # Handle all collected include directives. + prev_end = 0 + tmp_content = '' + for include in includes: + include_match, found_included_path = include + tmp_content += self.content[prev_end:include_match.start()] + tmp_content += "// {0}\n".format(include_match.group(0)) + if found_included_path not in self.amalgamation.included_files: + t = TranslationUnit(found_included_path, self.amalgamation, False) + tmp_content += t.content + prev_end = include_match.end() + tmp_content += self.content[prev_end:] + self.content = tmp_content + + return len(includes) + + # Make all content processing + def _process(self): + if not self.is_root: + self._process_pragma_once() + self._process_includes() + + def __init__(self, file_path, amalgamation, is_root): + self.file_path = file_path + self.file_dir = os.path.dirname(file_path) + self.amalgamation = amalgamation + self.is_root = is_root + + self.amalgamation.included_files.append(self.file_path) + + actual_path = self.amalgamation.actual_path(file_path) + if not os.path.isfile(actual_path): + raise IOError("File not found: \"{0}\"".format(file_path)) + with open(actual_path, 'r') as f: + self.content = f.read() + self._process() + + +def main(): + description = "Amalgamate C source and header files." + usage = " ".join([ + "amalgamate.py", + "[-v]", + "-c path/to/config.json", + "-s path/to/source/dir", + "[-p path/to/prologue.(c|h)]" + ]) + argsparser = argparse.ArgumentParser( + description=description, usage=usage) + + argsparser.add_argument("-v", "--verbose", dest="verbose", + choices=["yes", "no"], metavar="", help="be verbose") + + argsparser.add_argument("-c", "--config", dest="config", + required=True, metavar="", help="path to a JSON config file") + + argsparser.add_argument("-s", "--source", dest="source_path", + required=True, metavar="", help="source code path") + + argsparser.add_argument("-p", "--prologue", dest="prologue", + required=False, metavar="", help="path to a C prologue file") + + amalgamation = Amalgamation(argsparser.parse_args()) + amalgamation.generate() + + +if __name__ == "__main__": + main() + diff --git a/thirdparty/mio/third_party/config.json b/thirdparty/mio/third_party/config.json new file mode 100644 index 0000000000..fa2e71ad93 --- /dev/null +++ b/thirdparty/mio/third_party/config.json @@ -0,0 +1,11 @@ +{ + "project": "Cross-platform C++11 header-only library for memory mapped file IO", + "target": "../single_include/mio/mio.hpp", + "sources": [ + "../include/mio/mmap.hpp", + "../include/mio/page.hpp", + "../include/mio/shared_mmap.hpp" + ], + "include_paths": ["../include"] +} + diff --git a/thirdparty/rocksdb/.gitignore b/thirdparty/rocksdb/.gitignore index 03b805983a..e88ccfc008 100644 --- a/thirdparty/rocksdb/.gitignore +++ b/thirdparty/rocksdb/.gitignore @@ -45,6 +45,8 @@ etags rocksdb_dump rocksdb_undump db_test2 +trace_analyzer +trace_analyzer_test java/out java/target diff --git a/thirdparty/rocksdb/.lgtm.yml b/thirdparty/rocksdb/.lgtm.yml new file mode 100644 index 0000000000..12d6f1d4e5 --- /dev/null +++ b/thirdparty/rocksdb/.lgtm.yml @@ -0,0 +1,4 @@ +extraction: + cpp: + index: + build_command: make static_lib diff --git a/thirdparty/rocksdb/.travis.yml b/thirdparty/rocksdb/.travis.yml index b76973d4e8..e759a642a0 100644 --- a/thirdparty/rocksdb/.travis.yml +++ b/thirdparty/rocksdb/.travis.yml @@ -15,12 +15,23 @@ cache: - apt addons: - apt: - packages: ['zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl', 'libgflags-dev', 'mingw-w64'] + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - curl + - g++-8 + - libbz2-dev + - libgflags-dev + - libsnappy-dev + - mingw-w64 + - zlib1g-dev env: - TEST_GROUP=platform_dependent # 16-18 minutes - TEST_GROUP=1 # 33-35 minutes - - TEST_GROUP=2 # 30-32 minutes + - TEST_GROUP=2 # 18-20 minutes + - TEST_GROUP=3 # 20-22 minutes + - TEST_GROUP=4 # 12-14 minutes # Run java tests - JOB_NAME=java_test # 4-11 minutes # Build ROCKSDB_LITE @@ -28,6 +39,7 @@ env: # Build examples - JOB_NAME=examples # 5-7 minutes - JOB_NAME=cmake # 3-5 minutes + - JOB_NAME=cmake-gcc8 # 3-5 minutes - JOB_NAME=cmake-mingw # 3 minutes matrix: @@ -36,6 +48,12 @@ matrix: env: TEST_GROUP=1 - os: osx env: TEST_GROUP=2 + - os: osx + env: TEST_GROUP=3 + - os: osx + env: TEST_GROUP=4 + - os: osx + env: JOB_NAME=cmake-gcc8 - os : osx env: JOB_NAME=cmake-mingw - os : linux @@ -46,9 +64,15 @@ matrix: # https://docs.travis-ci.com/user/caching/#ccache-cache install: - if [ "${TRAVIS_OS_NAME}" == osx ]; then - brew install ccache; + brew install ccache zstd lz4 snappy xz; PATH=$PATH:/usr/local/opt/ccache/libexec; fi + - if [ "${JOB_NAME}" == cmake-gcc8 ]; then + CC=gcc-8 && CXX=g++-8; + fi + - if [[ "${JOB_NAME}" == cmake* ]] && [ "${TRAVIS_OS_NAME}" == linux ]; then + mkdir cmake-dist && curl -sfSL https://cmake.org/files/v3.8/cmake-3.8.1-Linux-x86_64.tar.gz | tar --strip-components=1 -C cmake-dist -xz && export PATH=$PWD/cmake-dist/bin:$PATH; + fi before_script: # Increase the maximum number of open file descriptors, since some tests use @@ -57,14 +81,41 @@ before_script: script: - ${CXX} --version - - if [ "${TEST_GROUP}" == 'platform_dependent' ]; then ccache -C && OPT=-DTRAVIS V=1 ROCKSDBTESTS_END=db_block_cache_test make -j4 all_but_some_tests check_some; fi - - if [ "${TEST_GROUP}" == '1' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=db_block_cache_test ROCKSDBTESTS_END=comparator_db_test make -j4 check_some; fi - - if [ "${TEST_GROUP}" == '2' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=comparator_db_test make -j4 check_some; fi - - if [ "${JOB_NAME}" == 'java_test' ]; then OPT=-DTRAVIS V=1 make clean jclean && make rocksdbjava jtest; fi - - if [ "${JOB_NAME}" == 'lite_build' ]; then OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib tools; fi - - if [ "${JOB_NAME}" == 'examples' ]; then OPT=-DTRAVIS V=1 make -j4 static_lib; cd examples; make -j4; fi - - if [ "${JOB_NAME}" == 'cmake' ]; then mkdir build && cd build && cmake .. && make -j4 rocksdb; fi - - if [ "${JOB_NAME}" == 'cmake-mingw' ]; then mkdir build && cd build && cmake .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb; fi + - if [ `command -v ccache` ]; then ccache -C; fi + - case $TEST_GROUP in + platform_dependent) + OPT=-DTRAVIS V=1 ROCKSDBTESTS_END=db_block_cache_test make -j4 all_but_some_tests check_some + ;; + 1) + OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=db_block_cache_test ROCKSDBTESTS_END=full_filter_block_test make -j4 check_some + ;; + 2) + OPT=-DTRAVIS V=1 make -j4 tools && OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=full_filter_block_test ROCKSDBTESTS_END=write_batch_with_index_test make -j4 check_some + ;; + 3) + OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_batch_with_index_test ROCKSDBTESTS_END=write_prepared_transaction_test make -j4 check_some + ;; + 4) + OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_prepared_transaction_test make -j4 check_some + ;; + esac + - case $JOB_NAME in + java_test) + OPT=-DTRAVIS V=1 make clean jclean && make rocksdbjava jtest + ;; + lite_build) + OPT='-DTRAVIS -DROCKSDB_LITE' V=1 make -j4 static_lib tools + ;; + examples) + OPT=-DTRAVIS V=1 make -j4 static_lib && cd examples && make -j4 + ;; + cmake-mingw) + mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni + ;; + cmake*) + mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_BUILD_TYPE=Release && make -j4 rocksdb rocksdbjni + ;; + esac notifications: email: - leveldb@fb.com diff --git a/thirdparty/rocksdb/AUTHORS b/thirdparty/rocksdb/AUTHORS index e644f5530f..a451875f1a 100644 --- a/thirdparty/rocksdb/AUTHORS +++ b/thirdparty/rocksdb/AUTHORS @@ -9,3 +9,4 @@ Sanjay Ghemawat # Partial list of contributors: Kevin Regan Johan Bilien +Matthew Von-Maszewski (Basho Technologies) diff --git a/thirdparty/rocksdb/CMakeLists.txt b/thirdparty/rocksdb/CMakeLists.txt index f20d09abf2..132d3b04e9 100644 --- a/thirdparty/rocksdb/CMakeLists.txt +++ b/thirdparty/rocksdb/CMakeLists.txt @@ -14,9 +14,9 @@ # cd build # 3. Run cmake to generate project files for Windows, add more options to enable required third-party libraries. # See thirdparty.inc for more information. -# sample command: cmake -G "Visual Studio 14 Win64" -DGFLAGS=1 -DSNAPPY=1 -DJEMALLOC=1 -DJNI=1 .. +# sample command: cmake -G "Visual Studio 15 Win64" -DWITH_GFLAGS=1 -DWITH_SNAPPY=1 -DWITH_JEMALLOC=1 -DWITH_JNI=1 .. # 4. Then build the project in debug mode (you may want to add /m[:] flag to run msbuild in parallel threads -# or simply /m ot use all avail cores) +# or simply /m to use all avail cores) # msbuild rocksdb.sln # # rocksdb.sln build features exclusions of test only code in Release. If you build ALL_BUILD then everything @@ -32,21 +32,36 @@ # 3. cmake .. # 4. make -j -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 2.8.12) project(rocksdb) +enable_language(CXX) +enable_language(C) +enable_language(ASM) if(POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules/") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/") option(WITH_JEMALLOC "build with JeMalloc" OFF) +option(WITH_SNAPPY "build with SNAPPY" OFF) +option(WITH_LZ4 "build with lz4" OFF) +option(WITH_ZLIB "build with zlib" OFF) +option(WITH_ZSTD "build with zstd" OFF) +option(WITH_WINDOWS_UTF8_FILENAMES "use UTF8 as characterset for opening files, regardles of the system code page" OFF) +if (WITH_WINDOWS_UTF8_FILENAMES) + add_definitions(-DROCKSDB_WINDOWS_UTF8_FILENAMES) +endif() if(MSVC) + # Defaults currently different for GFLAGS. + # We will address find_package work a little later + option(WITH_GFLAGS "build with GFlags" OFF) + option(WITH_XPRESS "build with windows built in compression" OFF) include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc) else() if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - # FreeBSD has jemaloc as default malloc + # FreeBSD has jemalloc as default malloc # but it does not have all the jemalloc files in include/... set(WITH_JEMALLOC ON) else() @@ -54,10 +69,21 @@ else() find_package(JeMalloc REQUIRED) add_definitions(-DROCKSDB_JEMALLOC -DJEMALLOC_NO_DEMANGLE) include_directories(${JEMALLOC_INCLUDE_DIR}) + list(APPEND THIRDPARTY_LIBS ${JEMALLOC_LIBRARIES}) + endif() + endif() + + # No config file for this + option(WITH_GFLAGS "build with GFlags" ON) + if(WITH_GFLAGS) + find_package(gflags) + if(gflags_FOUND) + add_definitions(-DGFLAGS=1) + include_directories(${gflags_INCLUDE_DIR}) + list(APPEND THIRDPARTY_LIBS ${gflags_LIBRARIES}) endif() endif() - option(WITH_SNAPPY "build with SNAPPY" OFF) if(WITH_SNAPPY) find_package(snappy REQUIRED) add_definitions(-DSNAPPY) @@ -65,11 +91,16 @@ else() list(APPEND THIRDPARTY_LIBS ${SNAPPY_LIBRARIES}) endif() - option(WITH_ZLIB "build with zlib" OFF) if(WITH_ZLIB) find_package(zlib REQUIRED) add_definitions(-DZLIB) - include_directories(${ZLIB_INCLUDE_DIR}) + if(ZLIB_INCLUDE_DIRS) + # CMake 3 + include_directories(${ZLIB_INCLUDE_DIRS}) + else() + # CMake 2 + include_directories(${ZLIB_INCLUDE_DIR}) + endif() list(APPEND THIRDPARTY_LIBS ${ZLIB_LIBRARIES}) endif() @@ -81,7 +112,6 @@ else() list(APPEND THIRDPARTY_LIBS ${BZIP2_LIBRARIES}) endif() - option(WITH_LZ4 "build with lz4" OFF) if(WITH_LZ4) find_package(lz4 REQUIRED) add_definitions(-DLZ4) @@ -89,7 +119,6 @@ else() list(APPEND THIRDPARTY_LIBS ${LZ4_LIBRARIES}) endif() - option(WITH_ZSTD "build with zstd" OFF) if(WITH_ZSTD) find_package(zstd REQUIRED) add_definitions(-DZSTD) @@ -98,17 +127,7 @@ else() endif() endif() -if(WIN32) - execute_process(COMMAND powershell -noprofile -Command "Get-Date -format MM_dd_yyyy" OUTPUT_VARIABLE DATE) - execute_process(COMMAND powershell -noprofile -Command "Get-Date -format HH:mm:ss" OUTPUT_VARIABLE TIME) - string(REGEX REPLACE "(..)_(..)_..(..).*" "\\1/\\2/\\3" DATE "${DATE}") - string(REGEX REPLACE "(..):(.....).*" " \\1:\\2" TIME "${TIME}") - set(GIT_DATE_TIME "${DATE} ${TIME}") -else() - execute_process(COMMAND date "+%Y/%m/%d %H:%M:%S" OUTPUT_VARIABLE DATETIME) - string(REGEX REPLACE "\n" "" DATETIME ${DATETIME}) - set(GIT_DATE_TIME "${DATETIME}") -endif() +string(TIMESTAMP GIT_DATE_TIME "%Y/%m/%d %H:%M:%S" UTC) find_package(Git) @@ -124,20 +143,17 @@ endif() string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA "${GIT_SHA}") -if(NOT WIN32) - execute_process(COMMAND - "./build_tools/version.sh" "full" - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE ROCKSDB_VERSION - ) - string(STRIP "${ROCKSDB_VERSION}" ROCKSDB_VERSION) - execute_process(COMMAND - "./build_tools/version.sh" "major" - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE ROCKSDB_VERSION_MAJOR - ) - string(STRIP "${ROCKSDB_VERSION_MAJOR}" ROCKSDB_VERSION_MAJOR) -endif() + +# Read rocksdb version from version.h header file. +file(READ include/rocksdb/version.h version_header_file) +string(REGEX MATCH "#define ROCKSDB_MAJOR ([0-9]+)" _ ${version_header_file}) +set(ROCKSDB_VERSION_MAJOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "#define ROCKSDB_MINOR ([0-9]+)" _ ${version_header_file}) +set(ROCKSDB_VERSION_MINOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "#define ROCKSDB_PATCH ([0-9]+)" _ ${version_header_file}) +set(ROCKSDB_VERSION_PATCH ${CMAKE_MATCH_1}) +set(ROCKSDB_VERSION ${ROCKSDB_VERSION_MAJOR}.${ROCKSDB_VERSION_MINOR}.${ROCKSDB_VERSION_PATCH}) + option(WITH_MD_LIBRARY "build with MD" ON) if(WIN32 AND MSVC) @@ -155,16 +171,16 @@ target_include_directories(build_version PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/util) if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W3 /wd4127 /wd4800 /wd4996 /wd4351") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4800 /wd4996 /wd4351 /wd4100 /wd4204 /wd4324") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing") if(MINGW) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format") endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer") include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-momit-leaf-frame-pointer" HAVE_OMIT_LEAF_FRAME_POINTER) if(HAVE_OMIT_LEAF_FRAME_POINTER) @@ -173,33 +189,56 @@ else() endif() endif() +include(CheckCCompilerFlag) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le") + CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC) + if(HAS_ALTIVEC) + message(STATUS " HAS_ALTIVEC yes") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maltivec") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maltivec") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8") + endif(HAS_ALTIVEC) +endif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le") + option(PORTABLE "build a portable binary" OFF) option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF) if(PORTABLE) # MSVC does not need a separate compiler flag to enable SSE4.2; if nmmintrin.h # is available, it is available by default. if(FORCE_SSE42 AND NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -mpclmul") endif() else() if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") + if(NOT HAVE_POWER8) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") + endif() endif() endif() -set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX_FLAGS}) include(CheckCXXSourceCompiles) +if(NOT MSVC) + set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul") +endif() CHECK_CXX_SOURCE_COMPILES(" #include #include +#include int main() { volatile uint32_t x = _mm_crc32_u32(0, 0); + const auto a = _mm_set_epi64x(0, 0); + const auto b = _mm_set_epi64x(0, 0); + const auto c = _mm_clmulepi64_si128(a, b, 0x00); + auto d = _mm_cvtsi128_si64(c); } " HAVE_SSE42) +unset(CMAKE_REQUIRED_FLAGS) if(HAVE_SSE42) add_definitions(-DHAVE_SSE42) + add_definitions(-DHAVE_PCLMUL) elseif(FORCE_SSE42) message(FATAL_ERROR "FORCE_SSE42=ON but unable to compile with SSE4.2 enabled") endif() @@ -256,21 +295,69 @@ if(WITH_UBSAN) endif() endif() -# Used to run CI build and tests so we can run faster -set(OPTIMIZE_DEBUG_DEFAULT 0) # Debug build is unoptimized by default use -DOPTDBG=1 to optimize +option(WITH_NUMA "build with NUMA policy support" OFF) +if(WITH_NUMA) + find_package(NUMA REQUIRED) + add_definitions(-DNUMA) + include_directories(${NUMA_INCLUDE_DIR}) + list(APPEND THIRDPARTY_LIBS ${NUMA_LIBRARIES}) +endif() + +option(WITH_TBB "build with Threading Building Blocks (TBB)" OFF) +if(WITH_TBB) + find_package(TBB REQUIRED) + add_definitions(-DTBB) + include_directories(${TBB_INCLUDE_DIR}) + list(APPEND THIRDPARTY_LIBS ${TBB_LIBRARIES}) +endif() -if(DEFINED OPTDBG) - set(OPTIMIZE_DEBUG ${OPTDBG}) +# Stall notifications eat some performance from inserts +option(DISABLE_STALL_NOTIF "Build with stall notifications" OFF) +if(DISABLE_STALL_NOTIF) + add_definitions(-DROCKSDB_DISABLE_STALL_NOTIFICATION) +endif() + + +if(DEFINED USE_RTTI) + if(USE_RTTI) + message(STATUS "Enabling RTTI") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DROCKSDB_USE_RTTI") + else() + if(MSVC) + message(STATUS "Disabling RTTI in Release builds. Always on in Debug.") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-") + else() + message(STATUS "Disabling RTTI in Release builds") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti") + endif() + endif() else() - set(OPTIMIZE_DEBUG ${OPTIMIZE_DEBUG_DEFAULT}) + message(STATUS "Enabling RTTI in Debug builds only (default)") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI") + if(MSVC) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-") + else() + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti") + endif() endif() +# Used to run CI build and tests so we can run faster +option(OPTDBG "Build optimized debug build with MSVC" OFF) +option(WITH_RUNTIME_DEBUG "build with debug version of runtime library" ON) if(MSVC) - if((${OPTIMIZE_DEBUG} EQUAL 1)) + if(OPTDBG) message(STATUS "Debug optimization is enabled") - set(CMAKE_CXX_FLAGS_DEBUG "/Oxt /${RUNTIME_LIBRARY}d") + set(CMAKE_CXX_FLAGS_DEBUG "/Oxt") else() - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1 /Gm /${RUNTIME_LIBRARY}d") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1 /Gm") + endif() + if(WITH_RUNTIME_DEBUG) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d") + else() + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}") endif() set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /${RUNTIME_LIBRARY}") @@ -285,7 +372,7 @@ endif() option(ROCKSDB_LITE "Build RocksDBLite version" OFF) if(ROCKSDB_LITE) add_definitions(-DROCKSDB_LITE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -Os") endif() if(CMAKE_SYSTEM_NAME MATCHES "Cygwin") @@ -323,16 +410,13 @@ if(NOT WIN32) endif() option(WITH_FALLOCATE "build with fallocate" ON) - if(WITH_FALLOCATE) - set(CMAKE_REQUIRED_FLAGS ${CMAKE_C_FLAGS}) - include(CheckCSourceCompiles) - CHECK_C_SOURCE_COMPILES(" + CHECK_CXX_SOURCE_COMPILES(" #include #include int main() { int fd = open(\"/dev/null\", 0); - fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, 1024); + fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024); } " HAVE_FALLOCATE) if(HAVE_FALLOCATE) @@ -340,16 +424,44 @@ int main() { endif() endif() -include(CheckFunctionExists) -CHECK_FUNCTION_EXISTS(malloc_usable_size HAVE_MALLOC_USABLE_SIZE) +CHECK_CXX_SOURCE_COMPILES(" +#include +int main() { + int fd = open(\"/dev/null\", 0); + sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE); +} +" HAVE_SYNC_FILE_RANGE_WRITE) +if(HAVE_SYNC_FILE_RANGE_WRITE) + add_definitions(-DROCKSDB_RANGESYNC_PRESENT) +endif() + +CHECK_CXX_SOURCE_COMPILES(" +#include +int main() { + (void) PTHREAD_MUTEX_ADAPTIVE_NP; +} +" HAVE_PTHREAD_MUTEX_ADAPTIVE_NP) +if(HAVE_PTHREAD_MUTEX_ADAPTIVE_NP) + add_definitions(-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX) +endif() + +include(CheckCXXSymbolExists) +check_cxx_symbol_exists(malloc_usable_size malloc.h HAVE_MALLOC_USABLE_SIZE) if(HAVE_MALLOC_USABLE_SIZE) add_definitions(-DROCKSDB_MALLOC_USABLE_SIZE) endif() +check_cxx_symbol_exists(sched_getcpu sched.h HAVE_SCHED_GETCPU) +if(HAVE_SCHED_GETCPU) + add_definitions(-DROCKSDB_SCHED_GETCPU_PRESENT) +endif() + include_directories(${PROJECT_SOURCE_DIR}) include_directories(${PROJECT_SOURCE_DIR}/include) +include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src) find_package(Threads REQUIRED) +add_subdirectory(third-party/gtest-1.7.0/fused-src/gtest) # Main library source code @@ -365,6 +477,7 @@ set(SOURCES db/compaction_iterator.cc db/compaction_job.cc db/compaction_picker.cc + db/compaction_picker_fifo.cc db/compaction_picker_universal.cc db/convenience.cc db/db_filesnapshot.cc @@ -376,9 +489,11 @@ set(SOURCES db/db_impl_debug.cc db/db_impl_experimental.cc db/db_impl_readonly.cc + db/db_impl_secondary.cc db/db_info_dumper.cc db/db_iter.cc db/dbformat.cc + db/error_handler.cc db/event_helpers.cc db/experimental.cc db/external_sst_file_ingestion_job.cc @@ -387,15 +502,17 @@ set(SOURCES db/flush_scheduler.cc db/forward_iterator.cc db/internal_stats.cc + db/in_memory_stats_history.cc + db/logs_with_prep_tracker.cc db/log_reader.cc db/log_writer.cc db/malloc_stats.cc - db/managed_iterator.cc db/memtable.cc db/memtable_list.cc db/merge_helper.cc db/merge_operator.cc db/range_del_aggregator.cc + db/range_tombstone_fragmenter.cc db/repair.cc db/snapshot_impl.cc db/table_cache.cc @@ -415,7 +532,6 @@ set(SOURCES env/env_hdfs.cc env/mock_env.cc memtable/alloc_tracker.cc - memtable/hash_cuckoo_rep.cc memtable/hash_linklist_rep.cc memtable/hash_skiplist_rep.cc memtable/skiplistrep.cc @@ -446,11 +562,14 @@ set(SOURCES table/block_based_table_factory.cc table/block_based_table_reader.cc table/block_builder.cc + table/block_fetcher.cc table/block_prefix_index.cc table/bloom_block.cc table/cuckoo_table_builder.cc table/cuckoo_table_factory.cc table/cuckoo_table_reader.cc + table/data_block_hash_index.cc + table/data_block_footer.cc table/flush_block_policy.cc table/format.cc table/full_filter_block.cc @@ -466,6 +585,7 @@ set(SOURCES table/plain_table_index.cc table/plain_table_key_coding.cc table/plain_table_reader.cc + table/sst_file_reader.cc table/sst_file_writer.cc table/table_properties.cc table/two_level_iterator.cc @@ -474,13 +594,16 @@ set(SOURCES tools/ldb_cmd.cc tools/ldb_tool.cc tools/sst_dump_tool.cc + tools/trace_analyzer_tool.cc util/arena.cc util/auto_roll_logger.cc util/bloom.cc util/coding.cc util/compaction_job_stats_impl.cc util/comparator.cc + util/compression_context_cache.cc util/concurrent_arena.cc + util/concurrent_task_limiter_impl.cc util/crc32c.cc util/delete_scheduler.cc util/dynamic_bloom.cc @@ -490,6 +613,7 @@ set(SOURCES util/filename.cc util/filter_policy.cc util/hash.cc + util/jemalloc_nodump_allocator.cc util/log_buffer.cc util/murmurhash.cc util/random.cc @@ -497,40 +621,36 @@ set(SOURCES util/slice.cc util/sst_file_manager_impl.cc util/status.cc - util/status_message.cc util/string_util.cc util/sync_point.cc + util/sync_point_impl.cc + util/testutil.cc util/thread_local.cc util/threadpool_imp.cc + util/trace_replay.cc + util/transaction_test_util.cc util/xxhash.cc utilities/backupable/backupable_db.cc + utilities/blob_db/blob_compaction_filter.cc utilities/blob_db/blob_db.cc utilities/blob_db/blob_db_impl.cc + utilities/blob_db/blob_db_impl_filesnapshot.cc utilities/blob_db/blob_dump_tool.cc utilities/blob_db/blob_file.cc utilities/blob_db/blob_log_reader.cc utilities/blob_db/blob_log_writer.cc utilities/blob_db/blob_log_format.cc - utilities/blob_db/ttl_extractor.cc utilities/cassandra/cassandra_compaction_filter.cc utilities/cassandra/format.cc utilities/cassandra/merge_operator.cc utilities/checkpoint/checkpoint_impl.cc - utilities/col_buf_decoder.cc - utilities/col_buf_encoder.cc - utilities/column_aware_encoding_util.cc utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc - utilities/date_tiered/date_tiered_db_impl.cc utilities/debug.cc - utilities/document/document_db.cc - utilities/document/json_document.cc - utilities/document/json_document_builder.cc utilities/env_mirror.cc utilities/env_timed.cc - utilities/geodb/geodb_impl.cc utilities/leveldb_options/leveldb_options.cc - utilities/lua/rocks_lua_compaction_filter.cc utilities/memory/memory_util.cc + utilities/merge_operators/bytesxor.cc utilities/merge_operators/max.cc utilities/merge_operators/put.cc utilities/merge_operators/string_append/stringappend.cc @@ -543,24 +663,39 @@ set(SOURCES utilities/persistent_cache/block_cache_tier_metadata.cc utilities/persistent_cache/persistent_cache_tier.cc utilities/persistent_cache/volatile_tier_impl.cc - utilities/redis/redis_lists.cc utilities/simulator_cache/sim_cache.cc - utilities/spatialdb/spatial_db.cc utilities/table_properties_collectors/compact_on_deletion_collector.cc + utilities/trace/file_trace_reader_writer.cc utilities/transactions/optimistic_transaction_db_impl.cc utilities/transactions/optimistic_transaction.cc - utilities/transactions/transaction_base.cc + utilities/transactions/pessimistic_transaction.cc utilities/transactions/pessimistic_transaction_db.cc + utilities/transactions/snapshot_checker.cc + utilities/transactions/transaction_base.cc utilities/transactions/transaction_db_mutex_impl.cc - utilities/transactions/pessimistic_transaction.cc utilities/transactions/transaction_lock_mgr.cc utilities/transactions/transaction_util.cc utilities/transactions/write_prepared_txn.cc + utilities/transactions/write_prepared_txn_db.cc + utilities/transactions/write_unprepared_txn.cc + utilities/transactions/write_unprepared_txn_db.cc utilities/ttl/db_ttl_impl.cc utilities/write_batch_with_index/write_batch_with_index.cc utilities/write_batch_with_index/write_batch_with_index_internal.cc $) +if(HAVE_SSE42 AND NOT MSVC) + set_source_files_properties( + util/crc32c.cc + PROPERTIES COMPILE_FLAGS "-msse4.2 -mpclmul") +endif() + +if(HAVE_POWER8) + list(APPEND SOURCES + util/crc32c_ppc.c + util/crc32c_ppc_asm.S) +endif(HAVE_POWER8) + if(WIN32) list(APPEND SOURCES port/win/io_win.cc @@ -568,14 +703,18 @@ if(WIN32) port/win/env_default.cc port/win/port_win.cc port/win/win_logger.cc - port/win/win_thread.cc + port/win/win_thread.cc) + +if(WITH_XPRESS) + list(APPEND SOURCES port/win/xpress_win.cc) - +endif() + if(WITH_JEMALLOC) list(APPEND SOURCES port/win/win_jemalloc.cc) endif() - + else() list(APPEND SOURCES port/port_posix.cc @@ -584,29 +723,8 @@ else() endif() set(ROCKSDB_STATIC_LIB rocksdb${ARTIFACT_SUFFIX}) -# commented out to avoid building the shared lib -#set(ROCKSDB_SHARED_LIB rocksdb-shared${ARTIFACT_SUFFIX}) +set(ROCKSDB_SHARED_LIB rocksdb-shared${ARTIFACT_SUFFIX}) set(ROCKSDB_IMPORT_LIB ${ROCKSDB_SHARED_LIB}) -if(WIN32) - #set(SYSTEM_LIBS ${SYSTEM_LIBS} shlwapi.lib Rpcrt4.lib) - set(SYSTEM_LIBS ${SYSTEM_LIBS} Rpcrt4.lib) - set(LIBS ${ROCKSDB_STATIC_LIB} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) -else() - set(SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) - set(LIBS ${ROCKSDB_STATIC_LIB} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) -# commented out to avoid building the shared lib -# as there is no reason -#add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES}) - -# target_link_libraries(${ROCKSDB_SHARED_LIB} -# ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) -# set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES -# LINKER_LANGUAGE CXX -# VERSION ${ROCKSDB_VERSION} -# SOVERSION ${ROCKSDB_VERSION_MAJOR} -# CXX_STANDARD 11 -# OUTPUT_NAME "rocksdb") -endif() option(WITH_LIBRADOS "Build with librados" OFF) if(WITH_LIBRADOS) @@ -615,13 +733,32 @@ if(WITH_LIBRADOS) list(APPEND THIRDPARTY_LIBS rados) endif() +if(WIN32) + set(SYSTEM_LIBS ${SYSTEM_LIBS} Shlwapi.lib Rpcrt4.lib) + set(LIBS ${ROCKSDB_STATIC_LIB} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) +else() + set(SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) + set(LIBS ${ROCKSDB_SHARED_LIB} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) + + add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES}) + target_link_libraries(${ROCKSDB_SHARED_LIB} + ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) + set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES + LINKER_LANGUAGE CXX + VERSION ${ROCKSDB_VERSION} + SOVERSION ${ROCKSDB_VERSION_MAJOR} + CXX_STANDARD 11 + OUTPUT_NAME "rocksdb") +endif() + add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES}) target_link_libraries(${ROCKSDB_STATIC_LIB} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) if(WIN32) add_library(${ROCKSDB_IMPORT_LIB} SHARED ${SOURCES}) - #target_link_libraries(${ROCKSDB_IMPORT_LIB} ${SYSTEM_LIBS}) + target_link_libraries(${ROCKSDB_IMPORT_LIB} + ${THIRDPARTY_LIBS} ${SYSTEM_LIBS}) set_target_properties(${ROCKSDB_IMPORT_LIB} PROPERTIES COMPILE_DEFINITIONS "ROCKSDB_DLL;ROCKSDB_LIBRARY_EXPORTS") if(MSVC) @@ -658,7 +795,7 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS) set(package_config_destination ${CMAKE_INSTALL_LIBDIR}/cmake/rocksdb) configure_package_config_file( - ${CMAKE_SOURCE_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake + ${CMAKE_CURRENT_LIST_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake INSTALL_DESTINATION ${package_config_destination} ) @@ -682,6 +819,7 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS) TARGETS ${ROCKSDB_SHARED_LIB} EXPORT RocksDBTargets COMPONENT runtime + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" @@ -728,6 +866,7 @@ if(WITH_TESTS) db/db_inplace_update_test.cc db/db_io_failure_test.cc db/db_iter_test.cc + db/db_iter_stress_test.cc db/db_iterator_test.cc db/db_log_iter_test.cc db/db_memtable_test.cc @@ -735,6 +874,7 @@ if(WITH_TESTS) db/db_options_test.cc db/db_properties_test.cc db/db_range_del_test.cc + db/db_secondary_test.cc db/db_sst_test.cc db/db_statistics_test.cc db/db_table_properties_test.cc @@ -746,6 +886,8 @@ if(WITH_TESTS) db/db_write_test.cc db/dbformat_test.cc db/deletefile_test.cc + db/error_handler_test.cc + db/obsolete_files_test.cc db/external_sst_file_basic_test.cc db/external_sst_file_test.cc db/fault_injection_test.cc @@ -762,6 +904,8 @@ if(WITH_TESTS) db/perf_context_test.cc db/plain_table_db_test.cc db/prefix_test.cc + db/range_del_aggregator_test.cc + db/range_tombstone_fragmenter_test.cc db/repair_test.cc db/table_properties_collector_test.cc db/version_builder_test.cc @@ -787,12 +931,15 @@ if(WITH_TESTS) table/cleanable_test.cc table/cuckoo_table_builder_test.cc table/cuckoo_table_reader_test.cc + table/data_block_hash_index_test.cc table/full_filter_block_test.cc table/merger_test.cc + table/sst_file_reader_test.cc table/table_test.cc tools/ldb_cmd_test.cc tools/reduce_levels_test.cc tools/sst_dump_test.cc + tools/trace_analyzer_test.cc util/arena_test.cc util/auto_roll_logger_test.cc util/autovector_test.cc @@ -805,7 +952,9 @@ if(WITH_TESTS) util/file_reader_writer_test.cc util/filelock_test.cc util/hash_test.cc + util/heap_test.cc util/rate_limiter_test.cc + util/repeatable_thread_test.cc util/slice_transform_test.cc util/timer_queue_test.cc util/thread_list_test.cc @@ -817,12 +966,6 @@ if(WITH_TESTS) utilities/cassandra/cassandra_row_merge_test.cc utilities/cassandra/cassandra_serialize_test.cc utilities/checkpoint/checkpoint_test.cc - utilities/column_aware_encoding_test.cc - utilities/date_tiered/date_tiered_test.cc - utilities/document/document_db_test.cc - utilities/document/json_document_test.cc - utilities/geodb/geodb_test.cc - utilities/lua/rocks_lua_test.cc utilities/memory/memory_test.cc utilities/merge_operators/string_append/stringappend_test.cc utilities/object_registry_test.cc @@ -830,12 +973,12 @@ if(WITH_TESTS) utilities/options/options_util_test.cc utilities/persistent_cache/hash_table_test.cc utilities/persistent_cache/persistent_cache_test.cc - utilities/redis/redis_lists_test.cc - utilities/spatialdb/spatial_db_test.cc utilities/simulator_cache/sim_cache_test.cc utilities/table_properties_collectors/compact_on_deletion_collector_test.cc utilities/transactions/optimistic_transaction_test.cc utilities/transactions/transaction_test.cc + utilities/transactions/write_prepared_transaction_test.cc + utilities/transactions/write_unprepared_transaction_test.cc utilities/ttl/ttl_test.cc utilities/write_batch_with_index/write_batch_with_index_test.cc ) @@ -843,7 +986,21 @@ if(WITH_TESTS) list(APPEND TESTS utilities/env_librados_test.cc) endif() - + set(BENCHMARKS + cache/cache_bench.cc + memtable/memtablerep_bench.cc + db/range_del_aggregator_bench.cc + tools/db_bench.cc + table/table_reader_bench.cc + utilities/persistent_cache/hash_table_bench.cc) + add_library(testharness OBJECT util/testharness.cc) + foreach(sourcefile ${BENCHMARKS}) + get_filename_component(exename ${sourcefile} NAME_WE) + add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile} + $) + target_link_libraries(${exename}${ARTIFACT_SUFFIX} gtest ${LIBS}) + endforeach(sourcefile ${BENCHMARKS}) + # For test util library that is build only in DEBUG mode # and linked to tests. Add test only code that is not #ifdefed for Release here. set(TESTUTIL_SOURCE @@ -868,25 +1025,24 @@ if(WITH_TESTS) ) # Tests are excluded from Release builds - #set(TEST_EXES ${TESTS}) - - # while tests are not built, we want to ensure that any reference to gtest is removed in case the user - # builds rocksdb manually from our third party directory - #foreach(sourcefile ${TEST_EXES}) - # get_filename_component(exename ${sourcefile} NAME_WE) - # add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile} - # $) - # set_target_properties(${exename}${ARTIFACT_SUFFIX} - # PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1 - # EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1 - # EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1 - # ) - # target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} gtest ${LIBS}) - # if(NOT "${exename}" MATCHES "db_sanity_test") - # add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX}) - # add_dependencies(check ${exename}${ARTIFACT_SUFFIX}) - # endif() - #endforeach(sourcefile ${TEST_EXES}) + set(TEST_EXES ${TESTS}) + + foreach(sourcefile ${TEST_EXES}) + get_filename_component(exename ${sourcefile} NAME_WE) + add_executable(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} ${sourcefile} + $) + set_target_properties(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} + PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1 + EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1 + EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1 + OUTPUT_NAME ${exename}${ARTIFACT_SUFFIX} + ) + target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} gtest ${LIBS}) + if(NOT "${exename}" MATCHES "db_sanity_test") + add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX}) + add_dependencies(check ${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX}) + endif() + endforeach(sourcefile ${TEST_EXES}) # C executables must link to a shared object set(C_TESTS db/c_test.c) @@ -906,3 +1062,8 @@ if(WITH_TESTS) add_dependencies(check ${exename}${ARTIFACT_SUFFIX}) endforeach(sourcefile ${C_TEST_EXES}) endif() + +option(WITH_TOOLS "build with tools" ON) +if(WITH_TOOLS) + add_subdirectory(tools) +endif() diff --git a/thirdparty/rocksdb/CODE_OF_CONDUCT.md b/thirdparty/rocksdb/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..0a45f9bd5f --- /dev/null +++ b/thirdparty/rocksdb/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. diff --git a/thirdparty/rocksdb/CONTRIBUTING.md b/thirdparty/rocksdb/CONTRIBUTING.md index b8b1a412e3..190100b429 100644 --- a/thirdparty/rocksdb/CONTRIBUTING.md +++ b/thirdparty/rocksdb/CONTRIBUTING.md @@ -1,5 +1,8 @@ # Contributing to RocksDB +## Code of Conduct +The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) + ## Contributor License Agreement ("CLA") In order to accept your pull request, we need you to submit a CLA. You diff --git a/thirdparty/rocksdb/HISTORY.md b/thirdparty/rocksdb/HISTORY.md index 9156290e0c..6c281cc241 100644 --- a/thirdparty/rocksdb/HISTORY.md +++ b/thirdparty/rocksdb/HISTORY.md @@ -1,11 +1,301 @@ # Rocksdb Change Log -## 5.8.6 (11/20/2017) +## 6.1.2 (6/4/2019) ### Bug Fixes -* Fixed aligned_alloc issues with Windows. +* Fix flush's/compaction's merge processing logic which allowed `Put`s covered by range tombstones to reappear. Note `Put`s may exist even if the user only ever called `Merge()` due to an internal conversion during compaction to the bottommost level. -## 5.8.1 (10/23/2017) +## 6.1.1 (4/9/2019) ### New Features +* When reading from option file/string/map, customized comparators and/or merge operators can be filled according to object registry. +### Public API Change +### Bug Fixes +* Fix a bug in 2PC where a sequence of txn prepare, memtable flush, and crash could result in losing the prepared transaction. +* Fix a bug in Encryption Env which could cause encrypted files to be read beyond file boundaries. + +## 6.1.0 (3/27/2019) +### New Features +* Introduce two more stats levels, kExceptHistogramOrTimers and kExceptTimers. +* Added a feature to perform data-block sampling for compressibility, and report stats to user. +* Add support for trace filtering. +* Add DBOptions.avoid_unnecessary_blocking_io. If true, we avoid file deletion when destorying ColumnFamilyHandle and Iterator. Instead, a job is scheduled to delete the files in background. + +### Public API Change +* Remove bundled fbson library. +* statistics.stats_level_ becomes atomic. It is preferred to use statistics.set_stats_level() and statistics.get_stats_level() to access it. +* Introduce a new IOError subcode, PathNotFound, to indicate trying to open a nonexistent file or directory for read. +* Add initial support for multiple db instances sharing the same data in single-writer, multi-reader mode. +* Removed some "using std::xxx" from public headers. + +### Bug Fixes +* Fix JEMALLOC_CXX_THROW macro missing from older Jemalloc versions, causing build failures on some platforms. +* Fix SstFileReader not able to open file ingested with write_glbal_seqno=true. + +## 6.0.0 (2/19/2019) +### New Features +* Enabled checkpoint on readonly db (DBImplReadOnly). +* Make DB ignore dropped column families while committing results of atomic flush. +* RocksDB may choose to preopen some files even if options.max_open_files != -1. This may make DB open slightly longer. +* For users of dictionary compression with ZSTD v0.7.0+, we now reuse the same digested dictionary when compressing each of an SST file's data blocks for faster compression speeds. +* For all users of dictionary compression who set `cache_index_and_filter_blocks == true`, we now store dictionary data used for decompression in the block cache for better control over memory usage. For users of ZSTD v1.1.4+ who compile with -DZSTD_STATIC_LINKING_ONLY, this includes a digested dictionary, which is used to increase decompression speed. +* Add support for block checksums verification for external SST files before ingestion. +* Introduce stats history which periodically saves Statistics snapshots and added `GetStatsHistory` API to retrieve these snapshots. +* Add a place holder in manifest which indicate a record from future that can be safely ignored. +* Add support for trace sampling. +* Enable properties block checksum verification for block-based tables. +* For all users of dictionary compression, we now generate a separate dictionary for compressing each bottom-level SST file. Previously we reused a single dictionary for a whole compaction to bottom level. The new approach achieves better compression ratios; however, it uses more memory and CPU for buffering/sampling data blocks and training dictionaries. +* Add whole key bloom filter support in memtable. +* Files written by `SstFileWriter` will now use dictionary compression if it is configured in the file writer's `CompressionOptions`. + +### Public API Change +* Disallow CompactionFilter::IgnoreSnapshots() = false, because it is not very useful and the behavior is confusing. The filter will filter everything if there is no snapshot declared by the time the compaction starts. However, users can define a snapshot after the compaction starts and before it finishes and this new snapshot won't be repeatable, because after the compaction finishes, some keys may be dropped. +* CompactionPri = kMinOverlappingRatio also uses compensated file size, which boosts file with lots of tombstones to be compacted first. +* Transaction::GetForUpdate is extended with a do_validate parameter with default value of true. If false it skips validating the snapshot before doing the read. Similarly ::Merge, ::Put, ::Delete, and ::SingleDelete are extended with assume_tracked with default value of false. If true it indicates that call is assumed to be after a ::GetForUpdate. +* `TableProperties::num_entries` and `TableProperties::num_deletions` now also account for number of range tombstones. +* Remove geodb, spatial_db, document_db, json_document, date_tiered_db, and redis_lists. +* With "ldb ----try_load_options", when wal_dir specified by the option file doesn't exist, ignore it. +* Change time resolution in FileOperationInfo. +* Deleting Blob files also go through SStFileManager. +* Remove CuckooHash memtable. +* The counter stat `number.block.not_compressed` now also counts blocks not compressed due to poor compression ratio. +* Remove ttl option from `CompactionOptionsFIFO`. The option has been deprecated and ttl in `ColumnFamilyOptions` is used instead. +* Support SST file ingestion across multiple column families via DB::IngestExternalFiles. See the function's comment about atomicity. +* Remove Lua compaction filter. + +### Bug Fixes +* Fix a deadlock caused by compaction and file ingestion waiting for each other in the event of write stalls. +* Fix a memory leak when files with range tombstones are read in mmap mode and block cache is enabled +* Fix handling of corrupt range tombstone blocks such that corruptions cannot cause deleted keys to reappear +* Lock free MultiGet +* Fix incorrect `NotFound` point lookup result when querying the endpoint of a file that has been extended by a range tombstone. +* Fix with pipelined write, write leaders's callback failure lead to the whole write group fail. + +### Change Default Options +* Change options.compaction_pri's default to kMinOverlappingRatio + +## 5.18.0 (11/30/2018) +### New Features +* Introduced `JemallocNodumpAllocator` memory allocator. When being use, block cache will be excluded from core dump. +* Introduced `PerfContextByLevel` as part of `PerfContext` which allows storing perf context at each level. Also replaced `__thread` with `thread_local` keyword for perf_context. Added per-level perf context for bloom filter and `Get` query. +* With level_compaction_dynamic_level_bytes = true, level multiplier may be adjusted automatically when Level 0 to 1 compaction is lagged behind. +* Introduced DB option `atomic_flush`. If true, RocksDB supports flushing multiple column families and atomically committing the result to MANIFEST. Useful when WAL is disabled. +* Added `num_deletions` and `num_merge_operands` members to `TableProperties`. +* Added "rocksdb.min-obsolete-sst-number-to-keep" DB property that reports the lower bound on SST file numbers that are being kept from deletion, even if the SSTs are obsolete. +* Add xxhash64 checksum support +* Introduced `MemoryAllocator`, which lets the user specify custom memory allocator for block based table. +* Improved `DeleteRange` to prevent read performance degradation. The feature is no longer marked as experimental. + +### Public API Change +* `DBOptions::use_direct_reads` now affects reads issued by `BackupEngine` on the database's SSTs. +* `NO_ITERATORS` is divided into two counters `NO_ITERATOR_CREATED` and `NO_ITERATOR_DELETE`. Both of them are only increasing now, just as other counters. + +### Bug Fixes +* Fix corner case where a write group leader blocked due to write stall blocks other writers in queue with WriteOptions::no_slowdown set. +* Fix in-memory range tombstone truncation to avoid erroneously covering newer keys at a lower level, and include range tombstones in compacted files whose largest key is the range tombstone's start key. +* Properly set the stop key for a truncated manual CompactRange +* Fix slow flush/compaction when DB contains many snapshots. The problem became noticeable to us in DBs with 100,000+ snapshots, though it will affect others at different thresholds. +* Fix the bug that WriteBatchWithIndex's SeekForPrev() doesn't see the entries with the same key. +* Fix the bug where user comparator was sometimes fed with InternalKey instead of the user key. The bug manifests when during GenerateBottommostFiles. +* Fix a bug in WritePrepared txns where if the number of old snapshots goes beyond the snapshot cache size (128 default) the rest will not be checked when evicting a commit entry from the commit cache. +* Fixed Get correctness bug in the presence of range tombstones where merge operands covered by a range tombstone always result in NotFound. +* Start populating `NO_FILE_CLOSES` ticker statistic, which was always zero previously. +* The default value of NewBloomFilterPolicy()'s argument use_block_based_builder is changed to false. Note that this new default may cause large temp memory usage when building very large SST files. + +## 5.17.0 (10/05/2018) +### Public API Change +* `OnTableFileCreated` will now be called for empty files generated during compaction. In that case, `TableFileCreationInfo::file_path` will be "(nil)" and `TableFileCreationInfo::file_size` will be zero. +* Add `FlushOptions::allow_write_stall`, which controls whether Flush calls start working immediately, even if it causes user writes to stall, or will wait until flush can be performed without causing write stall (similar to `CompactRangeOptions::allow_write_stall`). Note that the default value is false, meaning we add delay to Flush calls until stalling can be avoided when possible. This is behavior change compared to previous RocksDB versions, where Flush calls didn't check if they might cause stall or not. +* Application using PessimisticTransactionDB is expected to rollback/commit recovered transactions before starting new ones. This assumption is used to skip concurrency control during recovery. +* Expose column family id to `OnCompactionCompleted`. + +### New Features +* TransactionOptions::skip_concurrency_control allows pessimistic transactions to skip the overhead of concurrency control. Could be used for optimizing certain transactions or during recovery. + +### Bug Fixes +* Avoid creating empty SSTs and subsequently deleting them in certain cases during compaction. +* Sync CURRENT file contents during checkpoint. + +## 5.16.3 (10/1/2018) +### Bug Fixes +* Fix crash caused when `CompactFiles` run with `CompactionOptions::compression == CompressionType::kDisableCompressionOption`. Now that setting causes the compression type to be chosen according to the column family-wide compression options. + +## 5.16.2 (9/21/2018) +### Bug Fixes +* Fix bug in partition filters with format_version=4. + +## 5.16.1 (9/17/2018) +### Bug Fixes +* Remove trace_analyzer_tool from rocksdb_lib target in TARGETS file. +* Fix RocksDB Java build and tests. +* Remove sync point in Block destructor. + +## 5.16.0 (8/21/2018) +### Public API Change +* The merge operands are passed to `MergeOperator::ShouldMerge` in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2) for performance reasons +* GetAllKeyVersions() to take an extra argument of `max_num_ikeys`. +* Using ZSTD dictionary trainer (i.e., setting `CompressionOptions::zstd_max_train_bytes` to a nonzero value) now requires ZSTD version 1.1.3 or later. + +### New Features +* Changes the format of index blocks by delta encoding the index values, which are the block handles. This saves the encoding of BlockHandle::offset of the non-head index entries in each restart interval. The feature is backward compatible but not forward compatible. It is disabled by default unless format_version 4 or above is used. +* Add a new tool: trace_analyzer. Trace_analyzer analyzes the trace file generated by using trace_replay API. It can convert the binary format trace file to a human readable txt file, output the statistics of the analyzed query types such as access statistics and size statistics, combining the dumped whole key space file to analyze, support query correlation analyzing, and etc. Current supported query types are: Get, Put, Delete, SingleDelete, DeleteRange, Merge, Iterator (Seek, SeekForPrev only). +* Add hash index support to data blocks, which helps reducing the cpu utilization of point-lookup operations. This feature is backward compatible with the data block created without the hash index. It is disabled by default unless BlockBasedTableOptions::data_block_index_type is set to data_block_index_type = kDataBlockBinaryAndHash. + +### Bug Fixes +* Fix a bug in misreporting the estimated partition index size in properties block. + +## 5.15.0 (7/17/2018) +### Public API Change +* Remove managed iterator. ReadOptions.managed is not effective anymore. +* For bottommost_compression, a compatible CompressionOptions is added via `bottommost_compression_opts`. To keep backward compatible, a new boolean `enabled` is added to CompressionOptions. For compression_opts, it will be always used no matter what value of `enabled` is. For bottommost_compression_opts, it will only be used when user set `enabled=true`, otherwise, compression_opts will be used for bottommost_compression as default. +* With LRUCache, when high_pri_pool_ratio > 0, midpoint insertion strategy will be enabled to put low-pri items to the tail of low-pri list (the midpoint) when they first inserted into the cache. This is to make cache entries never get hit age out faster, improving cache efficiency when large background scan presents. +* For users of `Statistics` objects created via `CreateDBStatistics()`, the format of the string returned by its `ToString()` method has changed. +* The "rocksdb.num.entries" table property no longer counts range deletion tombstones as entries. + +### New Features +* Changes the format of index blocks by storing the key in their raw form rather than converting them to InternalKey. This saves 8 bytes per index key. The feature is backward compatible but not forward compatible. It is disabled by default unless format_version 3 or above is used. +* Avoid memcpy when reading mmap files with OpenReadOnly and max_open_files==-1. +* Support dynamically changing `ColumnFamilyOptions::ttl` via `SetOptions()`. +* Add a new table property, "rocksdb.num.range-deletions", which counts the number of range deletion tombstones in the table. +* Improve the performance of iterators doing long range scans by using readahead, when using direct IO. +* pin_top_level_index_and_filter (default true) in BlockBasedTableOptions can be used in combination with cache_index_and_filter_blocks to prefetch and pin the top-level index of partitioned index and filter blocks in cache. It has no impact when cache_index_and_filter_blocks is false. +* Write properties meta-block at the end of block-based table to save read-ahead IO. + +### Bug Fixes +* Fix deadlock with enable_pipelined_write=true and max_successive_merges > 0 +* Check conflict at output level in CompactFiles. +* Fix corruption in non-iterator reads when mmap is used for file reads +* Fix bug with prefix search in partition filters where a shared prefix would be ignored from the later partitions. The bug could report an eixstent key as missing. The bug could be triggered if prefix_extractor is set and partition filters is enabled. +* Change default value of `bytes_max_delete_chunk` to 0 in NewSstFileManager() as it doesn't work well with checkpoints. +* Fix a bug caused by not copying the block trailer with compressed SST file, direct IO, prefetcher and no compressed block cache. +* Fix write can stuck indefinitely if enable_pipelined_write=true. The issue exists since pipelined write was introduced in 5.5.0. + +## 5.14.0 (5/16/2018) +### Public API Change +* Add a BlockBasedTableOption to align uncompressed data blocks on the smaller of block size or page size boundary, to reduce flash reads by avoiding reads spanning 4K pages. +* The background thread naming convention changed (on supporting platforms) to "rocksdb:", e.g., "rocksdb:low0". +* Add a new ticker stat rocksdb.number.multiget.keys.found to count number of keys successfully read in MultiGet calls +* Touch-up to write-related counters in PerfContext. New counters added: write_scheduling_flushes_compactions_time, write_thread_wait_nanos. Counters whose behavior was fixed or modified: write_memtable_time, write_pre_and_post_process_time, write_delay_time. +* Posix Env's NewRandomRWFile() will fail if the file doesn't exist. +* Now, `DBOptions::use_direct_io_for_flush_and_compaction` only applies to background writes, and `DBOptions::use_direct_reads` applies to both user reads and background reads. This conforms with Linux's `open(2)` manpage, which advises against simultaneously reading a file in buffered and direct modes, due to possibly undefined behavior and degraded performance. +* Iterator::Valid() always returns false if !status().ok(). So, now when doing a Seek() followed by some Next()s, there's no need to check status() after every operation. +* Iterator::Seek()/SeekForPrev()/SeekToFirst()/SeekToLast() always resets status(). +* Introduced `CompressionOptions::kDefaultCompressionLevel`, which is a generic way to tell RocksDB to use the compression library's default level. It is now the default value for `CompressionOptions::level`. Previously the level defaulted to -1, which gave poor compression ratios in ZSTD. + +### New Features +* Introduce TTL for level compaction so that all files older than ttl go through the compaction process to get rid of old data. +* TransactionDBOptions::write_policy can be configured to enable WritePrepared 2PC transactions. Read more about them in the wiki. +* Add DB properties "rocksdb.block-cache-capacity", "rocksdb.block-cache-usage", "rocksdb.block-cache-pinned-usage" to show block cache usage. +* Add `Env::LowerThreadPoolCPUPriority(Priority)` method, which lowers the CPU priority of background (esp. compaction) threads to minimize interference with foreground tasks. +* Fsync parent directory after deleting a file in delete scheduler. +* In level-based compaction, if bottom-pri thread pool was setup via `Env::SetBackgroundThreads()`, compactions to the bottom level will be delegated to that thread pool. +* `prefix_extractor` has been moved from ImmutableCFOptions to MutableCFOptions, meaning it can be dynamically changed without a DB restart. + +### Bug Fixes +* Fsync after writing global seq number to the ingestion file in ExternalSstFileIngestionJob. +* Fix WAL corruption caused by race condition between user write thread and FlushWAL when two_write_queue is not set. +* Fix `BackupableDBOptions::max_valid_backups_to_open` to not delete backup files when refcount cannot be accurately determined. +* Fix memory leak when pin_l0_filter_and_index_blocks_in_cache is used with partitioned filters +* Disable rollback of merge operands in WritePrepared transactions to work around an issue in MyRocks. It can be enabled back by setting TransactionDBOptions::rollback_merge_operands to true. +* Fix wrong results by ReverseBytewiseComparator::FindShortSuccessor() + +### Java API Changes +* Add `BlockBasedTableConfig.setBlockCache` to allow sharing a block cache across DB instances. +* Added SstFileManager to the Java API to allow managing SST files across DB instances. + +## 5.13.0 (3/20/2018) +### Public API Change +* RocksDBOptionsParser::Parse()'s `ignore_unknown_options` argument will only be effective if the option file shows it is generated using a higher version of RocksDB than the current version. +* Remove CompactionEventListener. + +### New Features +* SstFileManager now can cancel compactions if they will result in max space errors. SstFileManager users can also use SetCompactionBufferSize to specify how much space must be leftover during a compaction for auxiliary file functions such as logging and flushing. +* Avoid unnecessarily flushing in `CompactRange()` when the range specified by the user does not overlap unflushed memtables. +* If `ColumnFamilyOptions::max_subcompactions` is set greater than one, we now parallelize large manual level-based compactions. +* Add "rocksdb.live-sst-files-size" DB property to return total bytes of all SST files belong to the latest LSM tree. +* NewSstFileManager to add an argument bytes_max_delete_chunk with default 64MB. With this argument, a file larger than 64MB will be ftruncated multiple times based on this size. + +### Bug Fixes +* Fix a leak in prepared_section_completed_ where the zeroed entries would not removed from the map. +* Fix WAL corruption caused by race condition between user write thread and backup/checkpoint thread. + +## 5.12.0 (2/14/2018) +### Public API Change +* Iterator::SeekForPrev is now a pure virtual method. This is to prevent user who implement the Iterator interface fail to implement SeekForPrev by mistake. +* Add `include_end` option to make the range end exclusive when `include_end == false` in `DeleteFilesInRange()`. +* Add `CompactRangeOptions::allow_write_stall`, which makes `CompactRange` start working immediately, even if it causes user writes to stall. The default value is false, meaning we add delay to `CompactRange` calls until stalling can be avoided when possible. Note this delay is not present in previous RocksDB versions. +* Creating checkpoint with empty directory now returns `Status::InvalidArgument`; previously, it returned `Status::IOError`. +* Adds a BlockBasedTableOption to turn off index block compression. +* Close() method now returns a status when closing a db. + +### New Features +* Improve the performance of iterators doing long range scans by using readahead. +* Add new function `DeleteFilesInRanges()` to delete files in multiple ranges at once for better performance. +* FreeBSD build support for RocksDB and RocksJava. +* Improved performance of long range scans with readahead. +* Updated to and now continuously tested in Visual Studio 2017. + +### Bug Fixes +* Fix `DisableFileDeletions()` followed by `GetSortedWalFiles()` to not return obsolete WAL files that `PurgeObsoleteFiles()` is going to delete. +* Fix Handle error return from WriteBuffer() during WAL file close and DB close. +* Fix advance reservation of arena block addresses. +* Fix handling of empty string as checkpoint directory. + +## 5.11.0 (01/08/2018) +### Public API Change +* Add `autoTune` and `getBytesPerSecond()` to RocksJava RateLimiter + +### New Features +* Add a new histogram stat called rocksdb.db.flush.micros for memtable flush. +* Add "--use_txn" option to use transactional API in db_stress. +* Disable onboard cache for compaction output in Windows platform. +* Improve the performance of iterators doing long range scans by using readahead. + +### Bug Fixes +* Fix a stack-use-after-scope bug in ForwardIterator. +* Fix builds on platforms including Linux, Windows, and PowerPC. +* Fix buffer overrun in backup engine for DBs with huge number of files. +* Fix a mislabel bug for bottom-pri compaction threads. +* Fix DB::Flush() keep waiting after flush finish under certain condition. + +## 5.10.0 (12/11/2017) +### Public API Change +* When running `make` with environment variable `USE_SSE` set and `PORTABLE` unset, will use all machine features available locally. Previously this combination only compiled SSE-related features. + +### New Features +* Provide lifetime hints when writing files on Linux. This reduces hardware write-amp on storage devices supporting multiple streams. +* Add a DB stat, `NUMBER_ITER_SKIP`, which returns how many internal keys were skipped during iterations (e.g., due to being tombstones or duplicate versions of a key). +* Add PerfContext counters, `key_lock_wait_count` and `key_lock_wait_time`, which measure the number of times transactions wait on key locks and total amount of time waiting. + +### Bug Fixes +* Fix IOError on WAL write doesn't propagate to write group follower +* Make iterator invalid on merge error. +* Fix performance issue in `IngestExternalFile()` affecting databases with large number of SST files. +* Fix possible corruption to LSM structure when `DeleteFilesInRange()` deletes a subset of files spanned by a `DeleteRange()` marker. + +## 5.9.0 (11/1/2017) +### Public API Change +* `BackupableDBOptions::max_valid_backups_to_open == 0` now means no backups will be opened during BackupEngine initialization. Previously this condition disabled limiting backups opened. +* `DBOptions::preserve_deletes` is a new option that allows one to specify that DB should not drop tombstones for regular deletes if they have sequence number larger than what was set by the new API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)`. Disabled by default. +* API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)` was added, users who wish to preserve deletes are expected to periodically call this function to advance the cutoff seqnum (all deletes made before this seqnum can be dropped by DB). It's user responsibility to figure out how to advance the seqnum in the way so the tombstones are kept for the desired period of time, yet are eventually processed in time and don't eat up too much space. +* `ReadOptions::iter_start_seqnum` was added; +if set to something > 0 user will see 2 changes in iterators behavior 1) only keys written with sequence larger than this parameter would be returned and 2) the `Slice` returned by iter->key() now points to the memory that keep User-oriented representation of the internal key, rather than user key. New struct `FullKey` was added to represent internal keys, along with a new helper function `ParseFullKey(const Slice& internal_key, FullKey* result);`. +* Deprecate trash_dir param in NewSstFileManager, right now we will rename deleted files to .trash instead of moving them to trash directory +* Allow setting a custom trash/DB size ratio limit in the SstFileManager, after which files that are to be scheduled for deletion are deleted immediately, regardless of any delete ratelimit. +* Return an error on write if write_options.sync = true and write_options.disableWAL = true to warn user of inconsistent options. Previously we will not write to WAL and not respecting the sync options in this case. + +### New Features +* CRC32C is now using the 3-way pipelined SSE algorithm `crc32c_3way` on supported platforms to improve performance. The system will choose to use this algorithm on supported platforms automatically whenever possible. If PCLMULQDQ is not supported it will fall back to the old Fast_CRC32 algorithm. +* `DBOptions::writable_file_max_buffer_size` can now be changed dynamically. +* `DBOptions::bytes_per_sync`, `DBOptions::compaction_readahead_size`, and `DBOptions::wal_bytes_per_sync` can now be changed dynamically, `DBOptions::wal_bytes_per_sync` will flush all memtables and switch to a new WAL file. +* Support dynamic adjustment of rate limit according to demand for background I/O. It can be enabled by passing `true` to the `auto_tuned` parameter in `NewGenericRateLimiter()`. The value passed as `rate_bytes_per_sec` will still be respected as an upper-bound. +* Support dynamically changing `ColumnFamilyOptions::compaction_options_fifo`. +* Introduce `EventListener::OnStallConditionsChanged()` callback. Users can implement it to be notified when user writes are stalled, stopped, or resumed. * Add a new db property "rocksdb.estimate-oldest-key-time" to return oldest data timestamp. The property is available only for FIFO compaction with compaction_options_fifo.allow_compaction = false. +* Upon snapshot release, recompact bottommost files containing deleted/overwritten keys that previously could not be dropped due to the snapshot. This alleviates space-amp caused by long-held snapshots. +* Support lower bound on iterators specified via `ReadOptions::iterate_lower_bound`. +* Support for differential snapshots (via iterator emitting the sequence of key-values representing the difference between DB state at two different sequence numbers). Supports preserving and emitting puts and regular deletes, doesn't support SingleDeletes, MergeOperator, Blobs and Range Deletes. + +### Bug Fixes +* Fix a potential data inconsistency issue during point-in-time recovery. `DB:Open()` will abort if column family inconsistency is found during PIT recovery. +* Fix possible metadata corruption in databases using `DeleteRange()`. ## 5.8.0 (08/30/2017) ### Public API Change diff --git a/thirdparty/rocksdb/INSTALL.md b/thirdparty/rocksdb/INSTALL.md index 04f0eb2797..91a0935b27 100644 --- a/thirdparty/rocksdb/INSTALL.md +++ b/thirdparty/rocksdb/INSTALL.md @@ -43,6 +43,8 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi command line flags processing. You can compile rocksdb library even if you don't have gflags installed. +* If you wish to build the RocksJava static target, then cmake is required for building Snappy. + ## Supported platforms * **Linux - Ubuntu** @@ -107,6 +109,62 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi * run `brew tap homebrew/versions; brew install gcc48 --use-llvm` to install gcc 4.8 (or higher). * run `brew install rocksdb` +* **FreeBSD** (11.01): + + * You can either install RocksDB from the Ports system using `cd /usr/ports/databases/rocksdb && make install`, or you can follow the details below to install dependencies and compile from source code: + + * Install the dependencies for RocksDB: + + export BATCH=YES + cd /usr/ports/devel/gmake && make install + cd /usr/ports/devel/gflags && make install + + cd /usr/ports/archivers/snappy && make install + cd /usr/ports/archivers/bzip2 && make install + cd /usr/ports/archivers/liblz4 && make install + cd /usr/ports/archivesrs/zstd && make install + + cd /usr/ports/devel/git && make install + + + * Install the dependencies for RocksJava (optional): + + export BATCH=yes + cd /usr/ports/java/openjdk7 && make install + + * Build RocksDB from source: + cd ~ + git clone https://github.com/facebook/rocksdb.git + cd rocksdb + gmake static_lib + + * Build RocksJava from source (optional): + cd rocksdb + export JAVA_HOME=/usr/local/openjdk7 + gmake rocksdbjava + +* **OpenBSD** (6.3/-current): + + * As RocksDB is not available in the ports yet you have to build it on your own: + + * Install the dependencies for RocksDB: + + pkg_add gmake gflags snappy bzip2 lz4 zstd git jdk bash findutils gnuwatch + + * Build RocksDB from source: + + cd ~ + git clone https://github.com/facebook/rocksdb.git + cd rocksdb + gmake static_lib + + * Build RocksJava from source (optional): + + cd rocksdb + export JAVA_HOME=/usr/local/jdk-1.8.0 + export PATH=$PATH:/usr/local/jdk-1.8.0/bin + gmake rocksdbjava + * **iOS**: * Run: `TARGET_OS=IOS make static_lib`. When building the project which uses rocksdb iOS library, make sure to define two important pre-processing macros: `ROCKSDB_LITE` and `IOS_CROSS_COMPILE`. @@ -114,7 +172,7 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi * For building with MS Visual Studio 13 you will need Update 4 installed. * Read and follow the instructions at CMakeLists.txt * Or install via [vcpkg](https://github.com/microsoft/vcpkg) - * run `vcpkg install rocksdb` + * run `vcpkg install rocksdb:x64-windows` * **AIX 6.1** * Install AIX Toolbox rpms with gcc diff --git a/thirdparty/rocksdb/LANGUAGE-BINDINGS.md b/thirdparty/rocksdb/LANGUAGE-BINDINGS.md index ffeed98f28..73c2355a5d 100644 --- a/thirdparty/rocksdb/LANGUAGE-BINDINGS.md +++ b/thirdparty/rocksdb/LANGUAGE-BINDINGS.md @@ -1,7 +1,9 @@ This is the list of all known third-party language bindings for RocksDB. If something is missing, please open a pull request to add it. * Java - https://github.com/facebook/rocksdb/tree/master/java -* Python - http://pyrocksdb.readthedocs.org/en/latest/ +* Python + * http://python-rocksdb.readthedocs.io/en/latest/ + * http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained) * Perl - https://metacpan.org/pod/RocksDB * Node.js - https://npmjs.org/package/rocksdb * Go - https://github.com/tecbot/gorocksdb @@ -10,7 +12,11 @@ This is the list of all known third-party language bindings for RocksDB. If some * PHP - https://github.com/Photonios/rocksdb-php * C# - https://github.com/warrenfalk/rocksdb-sharp * Rust + * https://github.com/pingcap/rust-rocksdb (used in production fork of https://github.com/spacejam/rust-rocksdb) * https://github.com/spacejam/rust-rocksdb * https://github.com/bh1xuw/rust-rocks * D programming language - https://github.com/b1naryth1ef/rocksdb * Erlang - https://gitlab.com/barrel-db/erlang-rocksdb +* Elixir - https://github.com/urbint/rox +* Nim - https://github.com/status-im/nim-rocksdb +* Swift and Objective-C (iOS/OSX) - https://github.com/iabudiab/ObjectiveRocks diff --git a/thirdparty/rocksdb/Makefile b/thirdparty/rocksdb/Makefile index 5a89f6bf79..eee0f9fba0 100644 --- a/thirdparty/rocksdb/Makefile +++ b/thirdparty/rocksdb/Makefile @@ -76,31 +76,70 @@ ifeq ($(MAKECMDGOALS),install) endif ifeq ($(MAKECMDGOALS),rocksdbjavastatic) - DEBUG_LEVEL=0 + ifneq ($(DEBUG_LEVEL),2) + DEBUG_LEVEL=0 + endif endif ifeq ($(MAKECMDGOALS),rocksdbjavastaticrelease) DEBUG_LEVEL=0 endif +ifeq ($(MAKECMDGOALS),rocksdbjavastaticreleasedocker) + DEBUG_LEVEL=0 +endif + ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish) DEBUG_LEVEL=0 endif +# Lite build flag. +LITE ?= 0 +ifeq ($(LITE), 0) +ifneq ($(filter -DROCKSDB_LITE,$(OPT)),) + # Be backward compatible and support older format where OPT=-DROCKSDB_LITE is + # specified instead of LITE=1 on the command line. + LITE=1 +endif +else ifeq ($(LITE), 1) +ifeq ($(filter -DROCKSDB_LITE,$(OPT)),) + OPT += -DROCKSDB_LITE +endif +endif + +# Figure out optimize level. +ifneq ($(DEBUG_LEVEL), 2) +ifeq ($(LITE), 0) + OPT += -O2 +else + OPT += -Os +endif +endif + # compile with -O2 if debug level is not 2 ifneq ($(DEBUG_LEVEL), 2) -OPT += -O2 -fno-omit-frame-pointer +OPT += -fno-omit-frame-pointer # Skip for archs that don't support -momit-leaf-frame-pointer ifeq (,$(shell $(CXX) -fsyntax-only -momit-leaf-frame-pointer -xc /dev/null 2>&1)) OPT += -momit-leaf-frame-pointer endif endif -# if we're compiling for release, compile without debug code (-DNDEBUG) and -# don't treat warnings as errors +ifeq (,$(shell $(CXX) -fsyntax-only -maltivec -xc /dev/null 2>&1)) +CXXFLAGS += -DHAS_ALTIVEC +CFLAGS += -DHAS_ALTIVEC +HAS_ALTIVEC=1 +endif + +ifeq (,$(shell $(CXX) -fsyntax-only -mcpu=power8 -xc /dev/null 2>&1)) +CXXFLAGS += -DHAVE_POWER8 +CFLAGS += -DHAVE_POWER8 +HAVE_POWER8=1 +endif + +# if we're compiling for release, compile without debug code (-DNDEBUG) ifeq ($(DEBUG_LEVEL),0) OPT += -DNDEBUG -DISABLE_WARNING_AS_ERROR=1 ifneq ($(USE_RTTI), 1) CXXFLAGS += -fno-rtti @@ -159,7 +198,7 @@ include make_config.mk CLEAN_FILES += make_config.mk missing_make_config_paths := $(shell \ - grep "\/\S*" -o $(CURDIR)/make_config.mk | \ + grep "\./\S*\|/\S*" -o $(CURDIR)/make_config.mk | \ while read path; \ do [ -e $$path ] || echo $$path; \ done | sort | uniq) @@ -212,6 +251,9 @@ ifdef COMPILE_WITH_TSAN PROFILING_FLAGS = # LUA is not supported under TSAN LUA_PATH = + # Limit keys for crash test under TSAN to avoid error: + # "ThreadSanitizer: DenseSlabAllocator overflow. Dying." + CRASH_TEST_EXT_ARGS += --max_key=1000000 endif # AIX doesn't work with -pg @@ -222,9 +264,18 @@ endif # USAN doesn't work well with jemalloc. If we're compiling with USAN, we should use regular malloc. ifdef COMPILE_WITH_UBSAN DISABLE_JEMALLOC=1 - EXEC_LDFLAGS += -fsanitize=undefined - PLATFORM_CCFLAGS += -fsanitize=undefined -DROCKSDB_UBSAN_RUN - PLATFORM_CXXFLAGS += -fsanitize=undefined -DROCKSDB_UBSAN_RUN + # Suppress alignment warning because murmurhash relies on casting unaligned + # memory to integer. Fixing it may cause performance regression. 3-way crc32 + # relies on it too, although it can be rewritten to eliminate with minimal + # performance regression. + EXEC_LDFLAGS += -fsanitize=undefined -fno-sanitize-recover=all + PLATFORM_CCFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN + PLATFORM_CXXFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN +endif + +ifdef ROCKSDB_VALGRIND_RUN + PLATFORM_CCFLAGS += -DROCKSDB_VALGRIND_RUN + PLATFORM_CXXFLAGS += -DROCKSDB_VALGRIND_RUN endif ifndef DISABLE_JEMALLOC @@ -257,7 +308,11 @@ endif default: all WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \ - -Wno-unused-parameter + -Wunused-parameter + +ifeq ($(PLATFORM), OS_OPENBSD) + WARNING_FLAGS += -Wno-unused-lambda-capture +endif ifndef DISABLE_WARNING_AS_ERROR WARNING_FLAGS += -Werror @@ -286,10 +341,13 @@ endif ifeq ("$(wildcard $(LUA_LIB))", "") # LUA_LIB does not exist $(error $(LUA_LIB) does not exist. Try to specify both LUA_PATH and LUA_LIB manually) endif -LDFLAGS += $(LUA_LIB) +EXEC_LDFLAGS += $(LUA_LIB) endif +ifeq ($(NO_THREEWAY_CRC32C), 1) + CXXFLAGS += -DNO_THREEWAY_CRC32C +endif CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT) CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers @@ -322,6 +380,14 @@ util/build_version.cc: FORCE endif LIBOBJECTS = $(LIB_SOURCES:.cc=.o) +ifeq ($(HAVE_POWER8),1) +LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o) +LIBOBJECTS += $(LIB_SOURCES_C:.c=.o) +LIBOBJECTS += $(LIB_SOURCES_ASM:.S=.o) +else +LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o) +endif + LIBOBJECTS += $(TOOL_LIB_SOURCES:.cc=.o) MOCKOBJECTS = $(MOCK_LIB_SOURCES:.cc=.o) @@ -335,7 +401,9 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full BENCHTOOLOBJECTS = $(BENCH_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL) -EXPOBJECTS = $(EXP_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL) +ANALYZETOOLOBJECTS = $(ANALYZER_LIB_SOURCES:.cc=.o) + +EXPOBJECTS = $(LIBOBJECTS) $(TESTUTIL) TESTS = \ db_basic_test \ @@ -363,6 +431,7 @@ TESTS = \ db_blob_index_test \ db_bloom_filter_test \ db_iter_test \ + db_iter_stress_test \ db_log_iter_test \ db_compaction_filter_test \ db_compaction_test \ @@ -374,14 +443,15 @@ TESTS = \ db_merge_operator_test \ db_options_test \ db_range_del_test \ + db_secondary_test \ db_sst_test \ db_tailing_iter_test \ - db_universal_compaction_test \ db_io_failure_test \ db_properties_test \ db_table_properties_test \ db_statistics_test \ db_write_test \ + error_handler_test \ autovector_test \ blob_db_test \ cleanable_test \ @@ -389,6 +459,7 @@ TESTS = \ table_properties_collector_test \ arena_test \ block_test \ + data_block_hash_index_test \ cache_test \ corruption_test \ slice_transform_test \ @@ -412,7 +483,6 @@ TESTS = \ merger_test \ util_merge_operators_test \ options_file_test \ - redis_test \ reduce_levels_test \ plain_table_db_test \ comparator_db_test \ @@ -426,12 +496,8 @@ TESTS = \ cassandra_row_merge_test \ cassandra_serialize_test \ ttl_test \ - date_tiered_test \ backupable_db_test \ - document_db_test \ - json_document_test \ sim_cache_test \ - spatial_db_test \ version_edit_test \ version_set_test \ compaction_picker_test \ @@ -441,8 +507,8 @@ TESTS = \ write_batch_with_index_test \ write_controller_test\ deletefile_test \ + obsolete_files_test \ table_test \ - geodb_test \ delete_scheduler_test \ options_test \ options_settable_test \ @@ -459,7 +525,6 @@ TESTS = \ compaction_job_test \ thread_list_test \ sst_dump_test \ - column_aware_encoding_test \ compact_files_test \ optimistic_transaction_test \ write_callback_test \ @@ -471,17 +536,25 @@ TESTS = \ ldb_cmd_test \ persistent_cache_test \ statistics_test \ - lua_test \ - range_del_aggregator_test \ lru_cache_test \ object_registry_test \ repair_test \ env_timed_test \ + write_prepared_transaction_test \ + write_unprepared_transaction_test \ + db_universal_compaction_test \ + trace_analyzer_test \ + repeatable_thread_test \ + range_tombstone_fragmenter_test \ + range_del_aggregator_test \ + sst_file_reader_test \ + db_secondary_test \ PARALLEL_TEST = \ backupable_db_test \ db_compaction_filter_test \ db_compaction_test \ + db_merge_operator_test \ db_sst_test \ db_test \ db_universal_compaction_test \ @@ -492,8 +565,14 @@ PARALLEL_TEST = \ manual_compaction_test \ persistent_cache_test \ table_test \ - transaction_test + transaction_test \ + write_prepared_transaction_test \ + write_unprepared_transaction_test \ +# options_settable_test doesn't pass with UBSAN as we use hack in the test +ifdef COMPILE_WITH_UBSAN + TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g') +endif SUBSET := $(TESTS) ifdef ROCKSDBTESTS_START SUBSET := $(shell echo $(SUBSET) | sed 's/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/') @@ -513,12 +592,13 @@ TOOLS = \ rocksdb_dump \ rocksdb_undump \ blob_dump \ + trace_analyzer \ TEST_LIBS = \ librocksdb_env_basic_test.a # TODO: add back forward_iterator_bench, after making it build in all environemnts. -BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench column_aware_encoding_exp persistent_cache_bench +BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench persistent_cache_bench range_del_aggregator_bench # if user didn't config LIBNAME, set the default ifeq ($(LIBNAME),) @@ -572,22 +652,44 @@ $(SHARED2): $(SHARED4) $(SHARED3): $(SHARED4) ln -fs $(SHARED4) $(SHARED3) endif - +ifeq ($(HAVE_POWER8),1) +SHARED_C_OBJECTS = $(LIB_SOURCES_C:.c=.o) +SHARED_ASM_OBJECTS = $(LIB_SOURCES_ASM:.S=.o) +SHARED_C_LIBOBJECTS = $(patsubst %.o,shared-objects/%.o,$(SHARED_C_OBJECTS)) +SHARED_ASM_LIBOBJECTS = $(patsubst %.o,shared-objects/%.o,$(SHARED_ASM_OBJECTS)) +shared_libobjects = $(patsubst %,shared-objects/%,$(LIB_CC_OBJECTS)) +else shared_libobjects = $(patsubst %,shared-objects/%,$(LIBOBJECTS)) +endif + CLEAN_FILES += shared-objects +shared_all_libobjects = $(shared_libobjects) + +ifeq ($(HAVE_POWER8),1) +shared-ppc-objects = $(SHARED_C_LIBOBJECTS) $(SHARED_ASM_LIBOBJECTS) +shared-objects/util/crc32c_ppc.o: util/crc32c_ppc.c + $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@ + +shared-objects/util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S + $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@ +endif $(shared_libobjects): shared-objects/%.o: %.cc $(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) -c $< -o $@ -$(SHARED4): $(shared_libobjects) - $(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(shared_libobjects) $(LDFLAGS) -o $@ +ifeq ($(HAVE_POWER8),1) +shared_all_libobjects = $(shared_libobjects) $(shared-ppc-objects) +endif +$(SHARED4): $(shared_all_libobjects) + $(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(shared_all_libobjects) $(LDFLAGS) -o $@ endif # PLATFORM_SHARED_EXT .PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests package \ - release tags valgrind_check whitebox_crash_test format static_lib shared_lib all \ + release tags tags0 valgrind_check whitebox_crash_test format static_lib shared_lib all \ dbg rocksdbjavastatic rocksdbjava install install-static install-shared uninstall \ - analyze tools tools_lib + analyze tools tools_lib \ + blackbox_crash_test_with_atomic_flush whitebox_crash_test_with_atomic_flush all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS) @@ -616,7 +718,7 @@ coverage: COVERAGEFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS+="-lgcov" $(MAKE) J=1 all check cd coverage && ./coverage_test.sh # Delete intermediate files - find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \; + $(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \; ifneq (,$(filter check parallel_check,$(MAKECMDGOALS)),) # Use /dev/shm if it has the sticky bit set (otherwise, /tmp), @@ -701,7 +803,7 @@ gen_parallel_tests: # 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest # slow_test_regexp = \ - ^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$ + ^.*SnapshotConcurrentAccessTest.*$$|^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$ prioritize_long_running_tests = \ perl -pe 's,($(slow_test_regexp)),100 $$1,' \ | sort -k1,1gr \ @@ -717,7 +819,6 @@ J ?= 100% # Use this regexp to select the subset of tests whose names match. tests-regexp = . -t_run = $(wildcard t/run-*) .PHONY: check_0 check_0: $(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \ @@ -727,13 +828,13 @@ check_0: test -t 1 && eta=--eta || eta=; \ { \ printf './%s\n' $(filter-out $(PARALLEL_TEST),$(TESTS)); \ - printf '%s\n' $(t_run); \ + find t -name 'run-*' -print; \ } \ | $(prioritize_long_running_tests) \ | grep -E '$(tests-regexp)' \ | build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu '{} >& t/log-{/}' -valgrind-blacklist-regexp = InlineSkipTest.ConcurrentInsert|TransactionTest.DeadlockStress|DBCompactionTest.SuggestCompactRangeNoTwoLevel0Compactions|BackupableDBTest.RateLimiting|DBTest.CloseSpeedup|DBTest.ThreadStatusFlush|DBTest.RateLimitingTest|DBTest.EncodeDecompressedBlockSizeTest|FaultInjectionTest.UninstalledCompaction|HarnessTest.Randomized|ExternalSSTFileTest.CompactDuringAddFileRandom|ExternalSSTFileTest.IngestFileWithGlobalSeqnoRandomized +valgrind-blacklist-regexp = InlineSkipTest.ConcurrentInsert|TransactionStressTest.DeadlockStress|DBCompactionTest.SuggestCompactRangeNoTwoLevel0Compactions|BackupableDBTest.RateLimiting|DBTest.CloseSpeedup|DBTest.ThreadStatusFlush|DBTest.RateLimitingTest|DBTest.EncodeDecompressedBlockSizeTest|FaultInjectionTest.UninstalledCompaction|HarnessTest.Randomized|ExternalSSTFileTest.CompactDuringAddFileRandom|ExternalSSTFileTest.IngestFileWithGlobalSeqnoRandomized|MySQLStyleTransactionTest.TransactionStressTest .PHONY: valgrind_check_0 valgrind_check_0: @@ -744,7 +845,7 @@ valgrind_check_0: test -t 1 && eta=--eta || eta=; \ { \ printf './%s\n' $(filter-out $(PARALLEL_TEST) %skiplist_test options_settable_test, $(TESTS)); \ - printf '%s\n' $(t_run); \ + find t -name 'run-*' -print; \ } \ | $(prioritize_long_running_tests) \ | grep -E '$(tests-regexp)' \ @@ -763,7 +864,7 @@ CLEAN_FILES += t LOG $(TMPD) # regardless of their duration. As with any use of "watch", hit ^C to # interrupt. watch-log: - watch --interval=0 'sort -k7,7nr -k4,4gr LOG|$(quoted_perl_command)' + $(WATCH) --interval=0 'sort -k7,7nr -k4,4gr LOG|$(quoted_perl_command)' # If J != 1 and GNU parallel is installed, run the tests in parallel, # via the check_0 rule above. Otherwise, run them sequentially. @@ -796,10 +897,15 @@ ldb_tests: ldb crash_test: whitebox_crash_test blackbox_crash_test +crash_test_with_atomic_flush: whitebox_crash_test_with_atomic_flush blackbox_crash_test_with_atomic_flush + blackbox_crash_test: db_stress python -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS) python -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS) +blackbox_crash_test_with_atomic_flush: db_stress + python -u tools/db_crashtest.py --enable_atomic_flush blackbox $(CRASH_TEST_EXT_ARGS) + ifeq ($(CRASH_TEST_KILL_ODD),) CRASH_TEST_KILL_ODD=888887 endif @@ -810,6 +916,10 @@ whitebox_crash_test: db_stress python -u tools/db_crashtest.py whitebox --random_kill_odd \ $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS) +whitebox_crash_test_with_atomic_flush: db_stress + python -u tools/db_crashtest.py --enable_atomic_flush whitebox --random_kill_odd \ + $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS) + asan_check: $(MAKE) clean COMPILE_WITH_ASAN=1 $(MAKE) check -j32 @@ -820,6 +930,11 @@ asan_crash_test: COMPILE_WITH_ASAN=1 $(MAKE) crash_test $(MAKE) clean +asan_crash_test_with_atomic_flush: + $(MAKE) clean + COMPILE_WITH_ASAN=1 $(MAKE) crash_test_with_atomic_flush + $(MAKE) clean + ubsan_check: $(MAKE) clean COMPILE_WITH_UBSAN=1 $(MAKE) check -j32 @@ -830,8 +945,13 @@ ubsan_crash_test: COMPILE_WITH_UBSAN=1 $(MAKE) crash_test $(MAKE) clean +ubsan_crash_test_with_atomic_flush: + $(MAKE) clean + COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_atomic_flush + $(MAKE) clean + valgrind_test: - DISABLE_JEMALLOC=1 $(MAKE) valgrind_check + ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check valgrind_check: $(TESTS) $(MAKE) DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" gen_parallel_tests @@ -917,8 +1037,10 @@ unity.a: unity.o $(AM_V_AR)rm -f $@ $(AM_V_at)$(AR) $(ARFLAGS) $@ unity.o + +TOOLLIBOBJECTS = $(TOOL_LIB_SOURCES:.cc=.o) # try compiling db_test with unity -unity_test: db/db_test.o db/db_test_util.o $(TESTHARNESS) unity.a +unity_test: db/db_test.o db/db_test_util.o $(TESTHARNESS) $(TOOLLIBOBJECTS) unity.a $(AM_LINK) ./unity_test @@ -928,14 +1050,21 @@ rocksdb.h rocksdb.cc: build_tools/amalgamate.py Makefile $(LIB_SOURCES) unity.cc clean: rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(LIBRARY) $(SHARED) rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report - find . -name "*.[oda]" -exec rm -f {} \; - find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \; + $(FIND) . -name "*.[oda]" -exec rm -f {} \; + $(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \; rm -rf bzip2* snappy* zlib* lz4* zstd* cd java; $(MAKE) clean tags: - ctags * -R - cscope -b `find . -name '*.cc'` `find . -name '*.h'` `find . -name '*.c'` + ctags -R . + cscope -b `$(FIND) . -name '*.cc'` `$(FIND) . -name '*.h'` `$(FIND) . -name '*.c'` + ctags -e -R -o etags * + +tags0: + ctags -R . + cscope -b `$(FIND) . -name '*.cc' -and ! -name '*_test.cc'` \ + `$(FIND) . -name '*.c' -and ! -name '*_test.c'` \ + `$(FIND) . -name '*.h' -and ! -name '*_test.h'` ctags -e -R -o etags * format: @@ -951,7 +1080,7 @@ $(LIBRARY): $(LIBOBJECTS) $(AM_V_AR)rm -f $@ $(AM_V_at)$(AR) $(ARFLAGS) $@ $(LIBOBJECTS) -$(TOOLS_LIBRARY): $(BENCH_LIB_SOURCES:.cc=.o) $(TOOL_LIB_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL) +$(TOOLS_LIBRARY): $(BENCH_LIB_SOURCES:.cc=.o) $(TOOL_LIB_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL) $(ANALYZER_LIB_SOURCES:.cc=.o) $(AM_V_AR)rm -f $@ $(AM_V_at)$(AR) $(ARFLAGS) $@ $^ @@ -962,6 +1091,9 @@ librocksdb_env_basic_test.a: env/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS) db_bench: tools/db_bench.o $(BENCHTOOLOBJECTS) $(AM_LINK) +trace_analyzer: tools/trace_analyzer.o $(ANALYZETOOLOBJECTS) $(LIBOBJECTS) + $(AM_LINK) + cache_bench: cache/cache_bench.o $(LIBOBJECTS) $(TESTUTIL) $(AM_LINK) @@ -1031,9 +1163,6 @@ cassandra_row_merge_test: utilities/cassandra/cassandra_row_merge_test.o utiliti cassandra_serialize_test: utilities/cassandra/cassandra_serialize_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -redis_test: utilities/redis/redis_lists_test.o $(LIBOBJECTS) $(TESTHARNESS) - $(AM_LINK) - hash_table_test: utilities/persistent_cache/hash_table_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1115,6 +1244,9 @@ db_statistics_test: db/db_statistics_test.o db/db_test_util.o $(LIBOBJECTS) $(TE db_write_test: db/db_write_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) +error_handler_test: db/error_handler_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + external_sst_file_basic_test: db/external_sst_file_basic_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1127,6 +1259,9 @@ db_tailing_iter_test: db/db_tailing_iter_test.o db/db_test_util.o $(LIBOBJECTS) db_iter_test: db/db_iter_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) +db_iter_stress_test: db/db_iter_stress_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + db_universal_compaction_test: db/db_universal_compaction_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1166,18 +1301,9 @@ backupable_db_test: utilities/backupable/backupable_db_test.o $(LIBOBJECTS) $(TE checkpoint_test: utilities/checkpoint/checkpoint_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -document_db_test: utilities/document/document_db_test.o $(LIBOBJECTS) $(TESTHARNESS) - $(AM_LINK) - -json_document_test: utilities/document/json_document_test.o $(LIBOBJECTS) $(TESTHARNESS) - $(AM_LINK) - sim_cache_test: utilities/simulator_cache/sim_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -spatial_db_test: utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNESS) - $(AM_LINK) - env_mirror_test: utilities/env_mirror_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1195,9 +1321,6 @@ object_registry_test: utilities/object_registry_test.o $(LIBOBJECTS) $(TESTHARNE ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -date_tiered_test: utilities/date_tiered/date_tiered_test.o $(LIBOBJECTS) $(TESTHARNESS) - $(AM_LINK) - write_batch_with_index_test: utilities/write_batch_with_index/write_batch_with_index_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1231,7 +1354,7 @@ env_test: env/env_test.o $(LIBOBJECTS) $(TESTHARNESS) fault_injection_test: db/fault_injection_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -rate_limiter_test: util/rate_limiter_test.o $(LIBOBJECTS) $(TESTHARNESS) +rate_limiter_test: util/rate_limiter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) delete_scheduler_test: util/delete_scheduler_test.o $(LIBOBJECTS) $(TESTHARNESS) @@ -1264,6 +1387,9 @@ table_test: table/table_test.o $(LIBOBJECTS) $(TESTHARNESS) block_test: table/block_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) +data_block_hash_index_test: table/data_block_hash_index_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + inlineskiplist_test: memtable/inlineskiplist_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1318,7 +1444,7 @@ options_file_test: db/options_file_test.o $(LIBOBJECTS) $(TESTHARNESS) deletefile_test: db/deletefile_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -geodb_test: utilities/geodb/geodb_test.o $(LIBOBJECTS) $(TESTHARNESS) +obsolete_files_test: db/obsolete_files_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) rocksdb_dump: tools/dump/rocksdb_dump.o $(LIBOBJECTS) @@ -1357,6 +1483,9 @@ options_util_test: utilities/options/options_util_test.o $(LIBOBJECTS) $(TESTHAR db_bench_tool_test: tools/db_bench_tool_test.o $(BENCHTOOLOBJECTS) $(TESTHARNESS) $(AM_LINK) +trace_analyzer_test: tools/trace_analyzer_test.o $(LIBOBJECTS) $(ANALYZETOOLOBJECTS) $(TESTHARNESS) + $(AM_LINK) + event_logger_test: util/event_logger_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1366,9 +1495,6 @@ timer_queue_test: util/timer_queue_test.o $(LIBOBJECTS) $(TESTHARNESS) sst_dump_test: tools/sst_dump_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -column_aware_encoding_test: utilities/column_aware_encoding_test.o $(TESTHARNESS) $(EXPOBJECTS) - $(AM_LINK) - optimistic_transaction_test: utilities/transactions/optimistic_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1396,13 +1522,16 @@ heap_test: util/heap_test.o $(GTEST) transaction_test: utilities/transactions/transaction_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -sst_dump: tools/sst_dump.o $(LIBOBJECTS) +write_prepared_transaction_test: utilities/transactions/write_prepared_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -blob_dump: tools/blob_dump.o $(LIBOBJECTS) +write_unprepared_transaction_test: utilities/transactions/write_unprepared_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -column_aware_encoding_exp: utilities/column_aware_encoding_exp.o $(EXPOBJECTS) +sst_dump: tools/sst_dump.o $(LIBOBJECTS) + $(AM_LINK) + +blob_dump: tools/blob_dump.o $(LIBOBJECTS) $(AM_LINK) repair_test: db/repair_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) @@ -1426,15 +1555,27 @@ statistics_test: monitoring/statistics_test.o $(LIBOBJECTS) $(TESTHARNESS) lru_cache_test: cache/lru_cache_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -lua_test: utilities/lua/rocks_lua_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) +range_del_aggregator_test: db/range_del_aggregator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) -range_del_aggregator_test: db/range_del_aggregator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) +range_del_aggregator_bench: db/range_del_aggregator_bench.o $(LIBOBJECTS) $(TESTUTIL) $(AM_LINK) blob_db_test: utilities/blob_db/blob_db_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) +repeatable_thread_test: util/repeatable_thread_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + +range_tombstone_fragmenter_test: db/range_tombstone_fragmenter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + +sst_file_reader_test: table/sst_file_reader_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + +db_secondary_test: db/db_secondary_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + #------------------------------------------------- # make install related stuff INSTALL_PATH ?= /usr/local @@ -1449,10 +1590,10 @@ uninstall: install-headers: install -d $(INSTALL_PATH)/lib - for header_dir in `find "include/rocksdb" -type d`; do \ + for header_dir in `$(FIND) "include/rocksdb" -type d`; do \ install -d $(INSTALL_PATH)/$$header_dir; \ done - for header in `find "include/rocksdb" -type f -name *.h`; do \ + for header in `$(FIND) "include/rocksdb" -type f -name *.h`; do \ install -C -m 644 $$header $(INSTALL_PATH)/$$header; \ done @@ -1479,6 +1620,12 @@ install: install-static JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux ifeq ($(PLATFORM), OS_SOLARIS) ARCH := $(shell isainfo -b) +else ifeq ($(PLATFORM), OS_OPENBSD) + ifneq (,$(filter $(MACHINE), amd64 arm64 sparc64)) + ARCH := 64 + else + ARCH := 32 + endif else ARCH := $(shell getconf LONG_BIT) endif @@ -1499,16 +1646,17 @@ ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 ZLIB_DOWNLOAD_BASE ?= http://zlib.net BZIP2_VER ?= 1.0.6 BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd -BZIP2_DOWNLOAD_BASE ?= http://www.bzip.org -SNAPPY_VER ?= 1.1.4 -SNAPPY_SHA256 ?= 134bfe122fd25599bb807bb8130e7ba6d9bdb851e0b16efcb83ac4f5d0b70057 -SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/releases/download -LZ4_VER ?= 1.7.5 -LZ4_SHA256 ?= 0190cacd63022ccb86f44fa5041dc6c3804407ad61550ca21c382827319e7e7e +BZIP2_DOWNLOAD_BASE ?= https://web.archive.org/web/20180624184835/http://www.bzip.org +SNAPPY_VER ?= 1.1.7 +SNAPPY_SHA256 ?= 3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4 +SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive +LZ4_VER ?= 1.8.3 +LZ4_SHA256 ?= 33af5936ac06536805f9745e0b6d61da606a1f8b4cc5c04dd3cbaca3b9b4fc43 LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive -ZSTD_VER ?= 1.2.0 -ZSTD_SHA256 ?= 4a7e4593a3638276ca7f2a09dc4f38e674d8317bbea51626393ca73fc047cbfb +ZSTD_VER ?= 1.3.7 +ZSTD_SHA256 ?= 5dd1e90eb16c25425880c8a91327f63de22891ffed082fcc17e5ae84fce0d5fb ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive +CURL_SSL_OPTS ?= --tlsv1 ifeq ($(PLATFORM), OS_MACOSX) ROCKSDBJNILIB = librocksdbjni-osx.jnilib @@ -1521,7 +1669,7 @@ else endif endif ifeq ($(PLATFORM), OS_FREEBSD) - JAVA_INCLUDE += -I$(JAVA_HOME)/include/freebsd + JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/freebsd ROCKSDBJNILIB = librocksdbjni-freebsd$(ARCH).so ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-freebsd$(ARCH).jar endif @@ -1537,92 +1685,123 @@ ifeq ($(PLATFORM), OS_AIX) EXTRACT_SOURCES = gunzip < TAR_GZ | tar xvf - SNAPPY_MAKE_TARGET = libsnappy.la endif +ifeq ($(PLATFORM), OS_OPENBSD) + JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/openbsd + ROCKSDBJNILIB = librocksdbjni-openbsd$(ARCH).so + ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-openbsd$(ARCH).jar +endif libz.a: -rm -rf zlib-$(ZLIB_VER) - curl -O -L ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz +ifeq (,$(wildcard ./zlib-$(ZLIB_VER).tar.gz)) + curl --output zlib-$(ZLIB_VER).tar.gz -L ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz +endif ZLIB_SHA256_ACTUAL=`$(SHA256_CMD) zlib-$(ZLIB_VER).tar.gz | cut -d ' ' -f 1`; \ if [ "$(ZLIB_SHA256)" != "$$ZLIB_SHA256_ACTUAL" ]; then \ echo zlib-$(ZLIB_VER).tar.gz checksum mismatch, expected=\"$(ZLIB_SHA256)\" actual=\"$$ZLIB_SHA256_ACTUAL\"; \ exit 1; \ fi tar xvzf zlib-$(ZLIB_VER).tar.gz - cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && make + cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && $(MAKE) cp zlib-$(ZLIB_VER)/libz.a . libbz2.a: -rm -rf bzip2-$(BZIP2_VER) - curl -O -L ${BZIP2_DOWNLOAD_BASE}/$(BZIP2_VER)/bzip2-$(BZIP2_VER).tar.gz +ifeq (,$(wildcard ./bzip2-$(BZIP2_VER).tar.gz)) + curl --output bzip2-$(BZIP2_VER).tar.gz -L ${BZIP2_DOWNLOAD_BASE}/$(BZIP2_VER)/bzip2-$(BZIP2_VER).tar.gz +endif BZIP2_SHA256_ACTUAL=`$(SHA256_CMD) bzip2-$(BZIP2_VER).tar.gz | cut -d ' ' -f 1`; \ if [ "$(BZIP2_SHA256)" != "$$BZIP2_SHA256_ACTUAL" ]; then \ echo bzip2-$(BZIP2_VER).tar.gz checksum mismatch, expected=\"$(BZIP2_SHA256)\" actual=\"$$BZIP2_SHA256_ACTUAL\"; \ exit 1; \ fi tar xvzf bzip2-$(BZIP2_VER).tar.gz - cd bzip2-$(BZIP2_VER) && make CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}' + cd bzip2-$(BZIP2_VER) && $(MAKE) CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}' cp bzip2-$(BZIP2_VER)/libbz2.a . libsnappy.a: -rm -rf snappy-$(SNAPPY_VER) - curl -O -L ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER)/snappy-$(SNAPPY_VER).tar.gz +ifeq (,$(wildcard ./snappy-$(SNAPPY_VER).tar.gz)) + curl --output snappy-$(SNAPPY_VER).tar.gz -L ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz +endif SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \ if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \ echo snappy-$(SNAPPY_VER).tar.gz checksum mismatch, expected=\"$(SNAPPY_SHA256)\" actual=\"$$SNAPPY_SHA256_ACTUAL\"; \ exit 1; \ fi tar xvzf snappy-$(SNAPPY_VER).tar.gz - cd snappy-$(SNAPPY_VER) && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --with-pic --enable-static --disable-shared - cd snappy-$(SNAPPY_VER) && make ${SNAPPY_MAKE_TARGET} - cp snappy-$(SNAPPY_VER)/.libs/libsnappy.a . + mkdir snappy-$(SNAPPY_VER)/build + cd snappy-$(SNAPPY_VER)/build && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && $(MAKE) ${SNAPPY_MAKE_TARGET} + cp snappy-$(SNAPPY_VER)/build/libsnappy.a . liblz4.a: -rm -rf lz4-$(LZ4_VER) - curl -O -L ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz - mv v$(LZ4_VER).tar.gz lz4-$(LZ4_VER).tar.gz +ifeq (,$(wildcard ./lz4-$(LZ4_VER).tar.gz)) + curl --output lz4-$(LZ4_VER).tar.gz -L ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz +endif LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \ if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \ echo lz4-$(LZ4_VER).tar.gz checksum mismatch, expected=\"$(LZ4_SHA256)\" actual=\"$$LZ4_SHA256_ACTUAL\"; \ exit 1; \ fi tar xvzf lz4-$(LZ4_VER).tar.gz - cd lz4-$(LZ4_VER)/lib && make CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all + cd lz4-$(LZ4_VER)/lib && $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all cp lz4-$(LZ4_VER)/lib/liblz4.a . libzstd.a: -rm -rf zstd-$(ZSTD_VER) - curl -O -L ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz - mv v$(ZSTD_VER).tar.gz zstd-$(ZSTD_VER).tar.gz +ifeq (,$(wildcard ./zstd-$(ZSTD_VER).tar.gz)) + curl --output zstd-$(ZSTD_VER).tar.gz -L ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz +endif ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \ if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \ echo zstd-$(ZSTD_VER).tar.gz checksum mismatch, expected=\"$(ZSTD_SHA256)\" actual=\"$$ZSTD_SHA256_ACTUAL\"; \ exit 1; \ fi tar xvzf zstd-$(ZSTD_VER).tar.gz - cd zstd-$(ZSTD_VER)/lib && make CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all + cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' install cp zstd-$(ZSTD_VER)/lib/libzstd.a . # A version of each $(LIBOBJECTS) compiled with -fPIC and a fixed set of static compression libraries -java_static_libobjects = $(patsubst %,jls/%,$(LIBOBJECTS)) +java_static_libobjects = $(patsubst %,jls/%,$(LIB_CC_OBJECTS)) CLEAN_FILES += jls +java_static_all_libobjects = $(java_static_libobjects) ifneq ($(ROCKSDB_JAVA_NO_COMPRESSION), 1) JAVA_COMPRESSIONS = libz.a libbz2.a libsnappy.a liblz4.a libzstd.a endif JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4 -DZSTD -JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib +JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib/include + +ifeq ($(HAVE_POWER8),1) +JAVA_STATIC_C_LIBOBJECTS = $(patsubst %.c.o,jls/%.c.o,$(LIB_SOURCES_C:.c=.o)) +JAVA_STATIC_ASM_LIBOBJECTS = $(patsubst %.S.o,jls/%.S.o,$(LIB_SOURCES_ASM:.S=.o)) + +java_static_ppc_libobjects = $(JAVA_STATIC_C_LIBOBJECTS) $(JAVA_STATIC_ASM_LIBOBJECTS) + +jls/util/crc32c_ppc.o: util/crc32c_ppc.c + $(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@ + +jls/util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S + $(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@ + +java_static_all_libobjects += $(java_static_ppc_libobjects) +endif $(java_static_libobjects): jls/%.o: %.cc $(JAVA_COMPRESSIONS) $(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -fPIC -c $< -o $@ $(COVERAGEFLAGS) -rocksdbjavastatic: $(java_static_libobjects) +rocksdbjavastatic: $(java_static_all_libobjects) cd java;$(MAKE) javalib; rm -f ./java/target/$(ROCKSDBJNILIB) $(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC \ -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) \ - $(java_static_libobjects) $(COVERAGEFLAGS) \ + $(java_static_all_libobjects) $(COVERAGEFLAGS) \ $(JAVA_COMPRESSIONS) $(JAVA_STATIC_LDFLAGS) - cd java/target;strip $(STRIPFLAGS) $(ROCKSDBJNILIB) + cd java/target;if [ "$(DEBUG_LEVEL)" == "0" ]; then \ + strip $(STRIPFLAGS) $(ROCKSDBJNILIB); \ + fi cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md cd java/target;jar -uf $(ROCKSDB_JAR) $(ROCKSDBJNILIB) cd java/target/classes;jar -uf ../$(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class @@ -1635,20 +1814,34 @@ rocksdbjavastaticrelease: rocksdbjavastatic cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class -rocksdbjavastaticreleasedocker: rocksdbjavastatic +rocksdbjavastaticreleasedocker: rocksdbjavastatic rocksdbjavastaticdockerx86 rocksdbjavastaticdockerx86_64 + cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md + cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib + cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class + +rocksdbjavastaticdockerx86: + mkdir -p java/target + DOCKER_LINUX_X86_CONTAINER=`docker ps -aqf name=rocksdb_linux_x86-be`; \ + if [ -z "$$DOCKER_LINUX_X86_CONTAINER" ]; then \ + docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_x86-be evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \ + fi + docker start -a rocksdb_linux_x86-be + +rocksdbjavastaticdockerx86_64: + mkdir -p java/target DOCKER_LINUX_X64_CONTAINER=`docker ps -aqf name=rocksdb_linux_x64-be`; \ if [ -z "$$DOCKER_LINUX_X64_CONTAINER" ]; then \ docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_x64-be evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \ fi docker start -a rocksdb_linux_x64-be - DOCKER_LINUX_X86_CONTAINER=`docker ps -aqf name=rocksdb_linux_x86-be`; \ - if [ -z "$$DOCKER_LINUX_X86_CONTAINER" ]; then \ - docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_x86-be evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \ + +rocksdbjavastaticdockerppc64le: + mkdir -p java/target + DOCKER_LINUX_PPC64LE_CONTAINER=`docker ps -aqf name=rocksdb_linux_ppc64le-be`; \ + if [ -z "$$DOCKER_LINUX_PPC64LE_CONTAINER" ]; then \ + docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_ppc64le-be evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \ fi - docker start -a rocksdb_linux_x86-be - cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md - cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib - cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class + docker start -a rocksdb_linux_ppc64le-be rocksdbjavastaticpublish: rocksdbjavastaticrelease rocksdbjavastaticpublishcentral @@ -1664,16 +1857,39 @@ rocksdbjavastaticpublishcentral: mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH).jar # A version of each $(LIBOBJECTS) compiled with -fPIC -java_libobjects = $(patsubst %,jl/%,$(LIBOBJECTS)) +ifeq ($(HAVE_POWER8),1) +JAVA_CC_OBJECTS = $(SHARED_CC_OBJECTS) +JAVA_C_OBJECTS = $(SHARED_C_OBJECTS) +JAVA_ASM_OBJECTS = $(SHARED_ASM_OBJECTS) + +JAVA_C_LIBOBJECTS = $(patsubst %.c.o,jl/%.c.o,$(JAVA_C_OBJECTS)) +JAVA_ASM_LIBOBJECTS = $(patsubst %.S.o,jl/%.S.o,$(JAVA_ASM_OBJECTS)) +endif + +java_libobjects = $(patsubst %,jl/%,$(LIB_CC_OBJECTS)) CLEAN_FILES += jl +java_all_libobjects = $(java_libobjects) + +ifeq ($(HAVE_POWER8),1) +java_ppc_libobjects = $(JAVA_C_LIBOBJECTS) $(JAVA_ASM_LIBOBJECTS) + +jl/crc32c_ppc.o: util/crc32c_ppc.c + $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@ + +jl/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S + $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@ +java_all_libobjects += $(java_ppc_libobjects) +endif $(java_libobjects): jl/%.o: %.cc $(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) -fPIC -c $< -o $@ $(COVERAGEFLAGS) -rocksdbjava: $(java_libobjects) + + +rocksdbjava: $(java_all_libobjects) $(AM_V_GEN)cd java;$(MAKE) javalib; $(AM_V_at)rm -f ./java/target/$(ROCKSDBJNILIB) - $(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS) + $(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_all_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS) $(AM_V_at)cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md $(AM_V_at)cd java/target;jar -uf $(ROCKSDB_JAR) $(ROCKSDBJNILIB) $(AM_V_at)cd java/target/classes;jar -uf ../$(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class @@ -1705,7 +1921,8 @@ commit_prereq: build_tools/rocksdb-lego-determinator \ ifeq ($(PLATFORM), IOS) # For iOS, create universal object files to be used on both the simulator and # a device. -PLATFORMSROOT=/Applications/Xcode.app/Contents/Developer/Platforms +XCODEROOT=$(shell xcode-select -print-path) +PLATFORMSROOT=$(XCODEROOT)/Platforms SIMULATORROOT=$(PLATFORMSROOT)/iPhoneSimulator.platform/Developer DEVICEROOT=$(PLATFORMSROOT)/iPhoneOS.platform/Developer IOSVERSION=$(shell defaults read $(PLATFORMSROOT)/iPhoneOS.platform/version CFBundleShortVersionString) @@ -1725,30 +1942,54 @@ IOSVERSION=$(shell defaults read $(PLATFORMSROOT)/iPhoneOS.platform/version CFBu lipo ios-x86/$@ ios-arm/$@ -create -output $@ else +ifeq ($(HAVE_POWER8),1) +util/crc32c_ppc.o: util/crc32c_ppc.c + $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@ + +util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S + $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@ +endif .cc.o: $(AM_V_CC)$(CXX) $(CXXFLAGS) -c $< -o $@ $(COVERAGEFLAGS) .c.o: $(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@ endif - # --------------------------------------------------------------------------- # Source files dependencies detection # --------------------------------------------------------------------------- -all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_LIB_SOURCES) $(EXP_LIB_SOURCES) -DEPFILES = $(all_sources:.cc=.d) +all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) +DEPFILES = $(all_sources:.cc=.cc.d) # Add proper dependency support so changing a .h file forces a .cc file to # rebuild. # The .d file indicates .cc file's dependencies on .h files. We generate such # dependency by g++'s -MM option, whose output is a make dependency rule. -$(DEPFILES): %.d: %.cc +%.cc.d: %.cc @$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \ -MM -MT'$@' -MT'$(<:.cc=.o)' "$<" -o '$@' +ifeq ($(HAVE_POWER8),1) +DEPFILES_C = $(LIB_SOURCES_C:.c=.c.d) +DEPFILES_ASM = $(LIB_SOURCES_ASM:.S=.S.d) + +%.c.d: %.c + @$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \ + -MM -MT'$@' -MT'$(<:.c=.o)' "$<" -o '$@' + +%.S.d: %.S + @$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \ + -MM -MT'$@' -MT'$(<:.S=.o)' "$<" -o '$@' + +$(DEPFILES_C): %.c.d + +$(DEPFILES_ASM): %.S.d +depend: $(DEPFILES) $(DEPFILES_C) $(DEPFILES_ASM) +else depend: $(DEPFILES) +endif # if the make goal is either "clean" or "format", we shouldn't # try to import the *.d files. diff --git a/thirdparty/rocksdb/README.md b/thirdparty/rocksdb/README.md index 550c352b88..f1bc0c05f3 100644 --- a/thirdparty/rocksdb/README.md +++ b/thirdparty/rocksdb/README.md @@ -1,11 +1,11 @@ ## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage -[![Build Status](https://travis-ci.org/facebook/rocksdb.svg?branch=master)](https://travis-ci.org/facebook/rocksdb) -[![Build status](https://ci.appveyor.com/api/projects/status/fbgfu0so3afcno78/branch/master?svg=true)](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master) - +[![Linux/Mac Build Status](https://travis-ci.org/facebook/rocksdb.svg?branch=master)](https://travis-ci.org/facebook/rocksdb) +[![Windows Build status](https://ci.appveyor.com/api/projects/status/fbgfu0so3afcno78/branch/master?svg=true)](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master) +[![PPC64le Build Status](http://140.211.168.68:8080/buildStatus/icon?job=Rocksdb)](http://140.211.168.68:8080/job/Rocksdb) RocksDB is developed and maintained by Facebook Database Engineering Team. -It is built on earlier work on LevelDB by Sanjay Ghemawat (sanjay@google.com) +It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com) and Jeff Dean (jeff@google.com) This code is a library that forms the core building block for a fast @@ -25,3 +25,7 @@ rely on the details of any other header files in this package. Those internal APIs may be changed without warning. Design discussions are conducted in https://www.facebook.com/groups/rocksdb.dev/ + +## License + +RocksDB is dual-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). You may select, at your option, one of the above-listed licenses. diff --git a/thirdparty/rocksdb/ROCKSDB_LITE.md b/thirdparty/rocksdb/ROCKSDB_LITE.md index 41cfbecc2c..8991b95063 100644 --- a/thirdparty/rocksdb/ROCKSDB_LITE.md +++ b/thirdparty/rocksdb/ROCKSDB_LITE.md @@ -5,7 +5,7 @@ RocksDBLite is a project focused on mobile use cases, which don't need a lot of Some examples of the features disabled by ROCKSDB_LITE: * compiled-in support for LDB tool * No backupable DB -* No support for replication (which we provide in form of TrasactionalIterator) +* No support for replication (which we provide in form of TransactionalIterator) * No advanced monitoring tools * No special-purpose memtables that are highly optimized for specific use cases * No Transactions diff --git a/thirdparty/rocksdb/TARGETS b/thirdparty/rocksdb/TARGETS index ac85eab93c..073c977e5a 100644 --- a/thirdparty/rocksdb/TARGETS +++ b/thirdparty/rocksdb/TARGETS @@ -1,534 +1,1081 @@ +load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders") +load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library") +load(":defs.bzl", "test_binary") -import os +REPO_PATH = package_name() + "/" -TARGETS_PATH = os.path.dirname(__file__) -REPO_PATH = TARGETS_PATH[(TARGETS_PATH.find('fbcode/') + len('fbcode/')):] + "/" -BUCK_BINS = "buck-out/gen/" + REPO_PATH -TEST_RUNNER = REPO_PATH + "buckifier/rocks_test_runner.sh" -rocksdb_compiler_flags = [ - "-fno-builtin-memcmp", - "-DROCKSDB_PLATFORM_POSIX", - "-DROCKSDB_LIB_IO_POSIX", - "-DROCKSDB_FALLOCATE_PRESENT", - "-DROCKSDB_MALLOC_USABLE_SIZE", - "-DROCKSDB_RANGESYNC_PRESENT", - "-DROCKSDB_SCHED_GETCPU_PRESENT", - "-DROCKSDB_SUPPORT_THREAD_LOCAL", - "-DOS_LINUX", - "-DROCKSDB_UBSAN_RUN", - # Flags to enable libs we include - "-DSNAPPY", - "-DZLIB", - "-DBZIP2", - "-DLZ4", - "-DZSTD", - "-DGFLAGS=gflags", - "-DNUMA", - "-DTBB", - # Needed to compile in fbcode - "-Wno-expansion-to-defined", +ROCKSDB_COMPILER_FLAGS = [ + "-fno-builtin-memcmp", + "-DROCKSDB_PLATFORM_POSIX", + "-DROCKSDB_LIB_IO_POSIX", + "-DROCKSDB_FALLOCATE_PRESENT", + "-DROCKSDB_MALLOC_USABLE_SIZE", + "-DROCKSDB_RANGESYNC_PRESENT", + "-DROCKSDB_SCHED_GETCPU_PRESENT", + "-DROCKSDB_SUPPORT_THREAD_LOCAL", + "-DOS_LINUX", + # Flags to enable libs we include + "-DSNAPPY", + "-DZLIB", + "-DBZIP2", + "-DLZ4", + "-DZSTD", + "-DZSTD_STATIC_LINKING_ONLY", + "-DGFLAGS=gflags", + "-DNUMA", + "-DTBB", + # Needed to compile in fbcode + "-Wno-expansion-to-defined", + # Added missing flags from output of build_detect_platform + "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX", + "-DROCKSDB_BACKTRACE", + "-Wnarrowing", ] -rocksdb_external_deps = [ - ('bzip2', None, 'bz2'), - ('snappy', None, "snappy"), - ('zlib', None, 'z'), - ('gflags', None, 'gflags'), - ('lz4', None, 'lz4'), - ('zstd', None), - ('tbb', None), - ("numa", None, "numa"), - ("googletest", None, "gtest"), +ROCKSDB_EXTERNAL_DEPS = [ + ("bzip2", None, "bz2"), + ("snappy", None, "snappy"), + ("zlib", None, "z"), + ("gflags", None, "gflags"), + ("lz4", None, "lz4"), + ("zstd", None), + ("tbb", None), + ("numa", None, "numa"), + ("googletest", None, "gtest"), ] -rocksdb_preprocessor_flags = [ - # Directories with files for #include - "-I" + REPO_PATH + "include/", - "-I" + REPO_PATH, +ROCKSDB_PREPROCESSOR_FLAGS = [ + # Directories with files for #include + "-I" + REPO_PATH + "include/", + "-I" + REPO_PATH, ] -rocksdb_arch_preprocessor_flags = { - "x86_64": ["-DHAVE_SSE42"], +ROCKSDB_ARCH_PREPROCESSOR_FLAGS = { + "x86_64": [ + "-DHAVE_SSE42", + "-DHAVE_PCLMUL", + ], } +build_mode = read_config("fbcode", "build_mode") + +is_opt_mode = build_mode.startswith("opt") + +# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice +# doesn't harm and avoid forgetting to add it. +ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else []) + +sanitizer = read_config("fbcode", "sanitizer") + +# Do not enable jemalloc if sanitizer presents. RocksDB will further detect +# whether the binary is linked with jemalloc at runtime. +ROCKSDB_COMPILER_FLAGS += (["-DROCKSDB_JEMALLOC"] if sanitizer == "" else []) + +ROCKSDB_EXTERNAL_DEPS += ([("jemalloc", None, "headers")] if sanitizer == "" else []) + cpp_library( name = "rocksdb_lib", - headers = AutoHeaders.RECURSIVE_GLOB, srcs = [ - "cache/clock_cache.cc", - "cache/lru_cache.cc", - "cache/sharded_cache.cc", - "db/builder.cc", - "db/c.cc", - "db/column_family.cc", - "db/compacted_db_impl.cc", - "db/compaction.cc", - "db/compaction_iterator.cc", - "db/compaction_job.cc", - "db/compaction_picker.cc", - "db/compaction_picker_universal.cc", - "db/convenience.cc", - "db/db_filesnapshot.cc", - "db/db_impl.cc", - "db/db_impl_write.cc", - "db/db_impl_compaction_flush.cc", - "db/db_impl_files.cc", - "db/db_impl_open.cc", - "db/db_impl_debug.cc", - "db/db_impl_experimental.cc", - "db/db_impl_readonly.cc", - "db/db_info_dumper.cc", - "db/db_iter.cc", - "db/dbformat.cc", - "db/event_helpers.cc", - "db/experimental.cc", - "db/external_sst_file_ingestion_job.cc", - "db/file_indexer.cc", - "db/flush_job.cc", - "db/flush_scheduler.cc", - "db/forward_iterator.cc", - "db/internal_stats.cc", - "db/log_reader.cc", - "db/log_writer.cc", - "db/malloc_stats.cc", - "db/managed_iterator.cc", - "db/memtable.cc", - "db/memtable_list.cc", - "db/merge_helper.cc", - "db/merge_operator.cc", - "db/range_del_aggregator.cc", - "db/repair.cc", - "db/snapshot_impl.cc", - "db/table_cache.cc", - "db/table_properties_collector.cc", - "db/transaction_log_impl.cc", - "db/version_builder.cc", - "db/version_edit.cc", - "db/version_set.cc", - "db/wal_manager.cc", - "db/write_batch.cc", - "db/write_batch_base.cc", - "db/write_controller.cc", - "db/write_thread.cc", - "env/env.cc", - "env/env_chroot.cc", - "env/env_encryption.cc", - "env/env_hdfs.cc", - "env/env_posix.cc", - "env/io_posix.cc", - "env/mock_env.cc", - "memtable/alloc_tracker.cc", - "memtable/hash_cuckoo_rep.cc", - "memtable/hash_linklist_rep.cc", - "memtable/hash_skiplist_rep.cc", - "memtable/skiplistrep.cc", - "memtable/vectorrep.cc", - "memtable/write_buffer_manager.cc", - "monitoring/histogram.cc", - "monitoring/histogram_windowing.cc", - "monitoring/instrumented_mutex.cc", - "monitoring/iostats_context.cc", - "monitoring/perf_context.cc", - "monitoring/perf_level.cc", - "monitoring/statistics.cc", - "monitoring/thread_status_impl.cc", - "monitoring/thread_status_updater.cc", - "monitoring/thread_status_updater_debug.cc", - "monitoring/thread_status_util.cc", - "monitoring/thread_status_util_debug.cc", - "options/cf_options.cc", - "options/db_options.cc", - "options/options.cc", - "options/options_helper.cc", - "options/options_parser.cc", - "options/options_sanity_check.cc", - "port/port_posix.cc", - "port/stack_trace.cc", - "table/adaptive_table_factory.cc", - "table/block.cc", - "table/block_based_filter_block.cc", - "table/block_based_table_builder.cc", - "table/block_based_table_factory.cc", - "table/block_based_table_reader.cc", - "table/block_builder.cc", - "table/block_prefix_index.cc", - "table/bloom_block.cc", - "table/cuckoo_table_builder.cc", - "table/cuckoo_table_factory.cc", - "table/cuckoo_table_reader.cc", - "table/flush_block_policy.cc", - "table/format.cc", - "table/full_filter_block.cc", - "table/get_context.cc", - "table/index_builder.cc", - "table/iterator.cc", - "table/merging_iterator.cc", - "table/meta_blocks.cc", - "table/partitioned_filter_block.cc", - "table/persistent_cache_helper.cc", - "table/plain_table_builder.cc", - "table/plain_table_factory.cc", - "table/plain_table_index.cc", - "table/plain_table_key_coding.cc", - "table/plain_table_reader.cc", - "table/sst_file_writer.cc", - "table/table_properties.cc", - "table/two_level_iterator.cc", - "tools/dump/db_dump_tool.cc", - "util/arena.cc", - "util/auto_roll_logger.cc", - "util/bloom.cc", - "util/build_version.cc", - "util/coding.cc", - "util/compaction_job_stats_impl.cc", - "util/comparator.cc", - "util/concurrent_arena.cc", - "util/crc32c.cc", - "util/delete_scheduler.cc", - "util/dynamic_bloom.cc", - "util/event_logger.cc", - "util/file_reader_writer.cc", - "util/file_util.cc", - "util/filename.cc", - "util/filter_policy.cc", - "util/hash.cc", - "util/log_buffer.cc", - "util/murmurhash.cc", - "util/random.cc", - "util/rate_limiter.cc", - "util/slice.cc", - "util/sst_file_manager_impl.cc", - "util/status.cc", - "util/status_message.cc", - "util/string_util.cc", - "util/sync_point.cc", - "util/thread_local.cc", - "util/threadpool_imp.cc", - "util/transaction_test_util.cc", - "util/xxhash.cc", - "utilities/backupable/backupable_db.cc", - "utilities/blob_db/blob_db.cc", - "utilities/blob_db/blob_db_impl.cc", - "utilities/blob_db/blob_file.cc", - "utilities/blob_db/blob_log_reader.cc", - "utilities/blob_db/blob_log_writer.cc", - "utilities/blob_db/blob_log_format.cc", - "utilities/blob_db/ttl_extractor.cc", - "utilities/cassandra/cassandra_compaction_filter.cc", - "utilities/cassandra/format.cc", - "utilities/cassandra/merge_operator.cc", - "utilities/checkpoint/checkpoint_impl.cc", - "utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc", - "utilities/convenience/info_log_finder.cc", - "utilities/date_tiered/date_tiered_db_impl.cc", - "utilities/debug.cc", - "utilities/document/document_db.cc", - "utilities/document/json_document.cc", - "utilities/document/json_document_builder.cc", - "utilities/env_mirror.cc", - "utilities/env_timed.cc", - "utilities/geodb/geodb_impl.cc", - "utilities/leveldb_options/leveldb_options.cc", - "utilities/lua/rocks_lua_compaction_filter.cc", - "utilities/memory/memory_util.cc", - "utilities/merge_operators/max.cc", - "utilities/merge_operators/put.cc", - "utilities/merge_operators/string_append/stringappend.cc", - "utilities/merge_operators/string_append/stringappend2.cc", - "utilities/merge_operators/uint64add.cc", - "utilities/option_change_migration/option_change_migration.cc", - "utilities/options/options_util.cc", - "utilities/persistent_cache/block_cache_tier.cc", - "utilities/persistent_cache/block_cache_tier_file.cc", - "utilities/persistent_cache/block_cache_tier_metadata.cc", - "utilities/persistent_cache/persistent_cache_tier.cc", - "utilities/persistent_cache/volatile_tier_impl.cc", - "utilities/redis/redis_lists.cc", - "utilities/simulator_cache/sim_cache.cc", - "utilities/spatialdb/spatial_db.cc", - "utilities/table_properties_collectors/compact_on_deletion_collector.cc", - "utilities/transactions/optimistic_transaction_db_impl.cc", - "utilities/transactions/optimistic_transaction.cc", - "utilities/transactions/transaction_base.cc", - "utilities/transactions/pessimistic_transaction_db.cc", - "utilities/transactions/transaction_db_mutex_impl.cc", - "utilities/transactions/pessimistic_transaction.cc", - "utilities/transactions/transaction_lock_mgr.cc", - "utilities/transactions/transaction_util.cc", - "utilities/transactions/write_prepared_txn.cc", - "utilities/ttl/db_ttl_impl.cc", - "utilities/write_batch_with_index/write_batch_with_index.cc", - "utilities/write_batch_with_index/write_batch_with_index_internal.cc", - "tools/ldb_cmd.cc", - "tools/ldb_tool.cc", - "tools/sst_dump_tool.cc", - "utilities/blob_db/blob_dump_tool.cc", + "cache/clock_cache.cc", + "cache/lru_cache.cc", + "cache/sharded_cache.cc", + "db/builder.cc", + "db/c.cc", + "db/column_family.cc", + "db/compacted_db_impl.cc", + "db/compaction.cc", + "db/compaction_iterator.cc", + "db/compaction_job.cc", + "db/compaction_picker.cc", + "db/compaction_picker_fifo.cc", + "db/compaction_picker_universal.cc", + "db/convenience.cc", + "db/db_filesnapshot.cc", + "db/db_impl.cc", + "db/db_impl_compaction_flush.cc", + "db/db_impl_debug.cc", + "db/db_impl_experimental.cc", + "db/db_impl_files.cc", + "db/db_impl_open.cc", + "db/db_impl_readonly.cc", + "db/db_impl_secondary.cc", + "db/db_impl_write.cc", + "db/db_info_dumper.cc", + "db/db_iter.cc", + "db/dbformat.cc", + "db/error_handler.cc", + "db/event_helpers.cc", + "db/experimental.cc", + "db/external_sst_file_ingestion_job.cc", + "db/file_indexer.cc", + "db/flush_job.cc", + "db/flush_scheduler.cc", + "db/forward_iterator.cc", + "db/in_memory_stats_history.cc", + "db/internal_stats.cc", + "db/log_reader.cc", + "db/log_writer.cc", + "db/logs_with_prep_tracker.cc", + "db/malloc_stats.cc", + "db/memtable.cc", + "db/memtable_list.cc", + "db/merge_helper.cc", + "db/merge_operator.cc", + "db/range_del_aggregator.cc", + "db/range_tombstone_fragmenter.cc", + "db/repair.cc", + "db/snapshot_impl.cc", + "db/table_cache.cc", + "db/table_properties_collector.cc", + "db/transaction_log_impl.cc", + "db/version_builder.cc", + "db/version_edit.cc", + "db/version_set.cc", + "db/wal_manager.cc", + "db/write_batch.cc", + "db/write_batch_base.cc", + "db/write_controller.cc", + "db/write_thread.cc", + "env/env.cc", + "env/env_chroot.cc", + "env/env_encryption.cc", + "env/env_hdfs.cc", + "env/env_posix.cc", + "env/io_posix.cc", + "env/mock_env.cc", + "memtable/alloc_tracker.cc", + "memtable/hash_linklist_rep.cc", + "memtable/hash_skiplist_rep.cc", + "memtable/skiplistrep.cc", + "memtable/vectorrep.cc", + "memtable/write_buffer_manager.cc", + "monitoring/histogram.cc", + "monitoring/histogram_windowing.cc", + "monitoring/instrumented_mutex.cc", + "monitoring/iostats_context.cc", + "monitoring/perf_context.cc", + "monitoring/perf_level.cc", + "monitoring/statistics.cc", + "monitoring/thread_status_impl.cc", + "monitoring/thread_status_updater.cc", + "monitoring/thread_status_updater_debug.cc", + "monitoring/thread_status_util.cc", + "monitoring/thread_status_util_debug.cc", + "options/cf_options.cc", + "options/db_options.cc", + "options/options.cc", + "options/options_helper.cc", + "options/options_parser.cc", + "options/options_sanity_check.cc", + "port/port_posix.cc", + "port/stack_trace.cc", + "table/adaptive_table_factory.cc", + "table/block.cc", + "table/block_based_filter_block.cc", + "table/block_based_table_builder.cc", + "table/block_based_table_factory.cc", + "table/block_based_table_reader.cc", + "table/block_builder.cc", + "table/block_fetcher.cc", + "table/block_prefix_index.cc", + "table/bloom_block.cc", + "table/cuckoo_table_builder.cc", + "table/cuckoo_table_factory.cc", + "table/cuckoo_table_reader.cc", + "table/data_block_footer.cc", + "table/data_block_hash_index.cc", + "table/flush_block_policy.cc", + "table/format.cc", + "table/full_filter_block.cc", + "table/get_context.cc", + "table/index_builder.cc", + "table/iterator.cc", + "table/merging_iterator.cc", + "table/meta_blocks.cc", + "table/partitioned_filter_block.cc", + "table/persistent_cache_helper.cc", + "table/plain_table_builder.cc", + "table/plain_table_factory.cc", + "table/plain_table_index.cc", + "table/plain_table_key_coding.cc", + "table/plain_table_reader.cc", + "table/sst_file_reader.cc", + "table/sst_file_writer.cc", + "table/table_properties.cc", + "table/two_level_iterator.cc", + "tools/dump/db_dump_tool.cc", + "tools/ldb_cmd.cc", + "tools/ldb_tool.cc", + "tools/sst_dump_tool.cc", + "util/arena.cc", + "util/auto_roll_logger.cc", + "util/bloom.cc", + "util/build_version.cc", + "util/coding.cc", + "util/compaction_job_stats_impl.cc", + "util/comparator.cc", + "util/compression_context_cache.cc", + "util/concurrent_arena.cc", + "util/concurrent_task_limiter_impl.cc", + "util/crc32c.cc", + "util/delete_scheduler.cc", + "util/dynamic_bloom.cc", + "util/event_logger.cc", + "util/file_reader_writer.cc", + "util/file_util.cc", + "util/filename.cc", + "util/filter_policy.cc", + "util/hash.cc", + "util/jemalloc_nodump_allocator.cc", + "util/log_buffer.cc", + "util/murmurhash.cc", + "util/random.cc", + "util/rate_limiter.cc", + "util/slice.cc", + "util/sst_file_manager_impl.cc", + "util/status.cc", + "util/string_util.cc", + "util/sync_point.cc", + "util/sync_point_impl.cc", + "util/thread_local.cc", + "util/threadpool_imp.cc", + "util/trace_replay.cc", + "util/transaction_test_util.cc", + "util/xxhash.cc", + "utilities/backupable/backupable_db.cc", + "utilities/blob_db/blob_compaction_filter.cc", + "utilities/blob_db/blob_db.cc", + "utilities/blob_db/blob_db_impl.cc", + "utilities/blob_db/blob_db_impl_filesnapshot.cc", + "utilities/blob_db/blob_dump_tool.cc", + "utilities/blob_db/blob_file.cc", + "utilities/blob_db/blob_log_format.cc", + "utilities/blob_db/blob_log_reader.cc", + "utilities/blob_db/blob_log_writer.cc", + "utilities/cassandra/cassandra_compaction_filter.cc", + "utilities/cassandra/format.cc", + "utilities/cassandra/merge_operator.cc", + "utilities/checkpoint/checkpoint_impl.cc", + "utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc", + "utilities/convenience/info_log_finder.cc", + "utilities/debug.cc", + "utilities/env_mirror.cc", + "utilities/env_timed.cc", + "utilities/leveldb_options/leveldb_options.cc", + "utilities/memory/memory_util.cc", + "utilities/merge_operators/bytesxor.cc", + "utilities/merge_operators/max.cc", + "utilities/merge_operators/put.cc", + "utilities/merge_operators/string_append/stringappend.cc", + "utilities/merge_operators/string_append/stringappend2.cc", + "utilities/merge_operators/uint64add.cc", + "utilities/option_change_migration/option_change_migration.cc", + "utilities/options/options_util.cc", + "utilities/persistent_cache/block_cache_tier.cc", + "utilities/persistent_cache/block_cache_tier_file.cc", + "utilities/persistent_cache/block_cache_tier_metadata.cc", + "utilities/persistent_cache/persistent_cache_tier.cc", + "utilities/persistent_cache/volatile_tier_impl.cc", + "utilities/simulator_cache/sim_cache.cc", + "utilities/table_properties_collectors/compact_on_deletion_collector.cc", + "utilities/trace/file_trace_reader_writer.cc", + "utilities/transactions/optimistic_transaction.cc", + "utilities/transactions/optimistic_transaction_db_impl.cc", + "utilities/transactions/pessimistic_transaction.cc", + "utilities/transactions/pessimistic_transaction_db.cc", + "utilities/transactions/snapshot_checker.cc", + "utilities/transactions/transaction_base.cc", + "utilities/transactions/transaction_db_mutex_impl.cc", + "utilities/transactions/transaction_lock_mgr.cc", + "utilities/transactions/transaction_util.cc", + "utilities/transactions/write_prepared_txn.cc", + "utilities/transactions/write_prepared_txn_db.cc", + "utilities/transactions/write_unprepared_txn.cc", + "utilities/transactions/write_unprepared_txn_db.cc", + "utilities/ttl/db_ttl_impl.cc", + "utilities/write_batch_with_index/write_batch_with_index.cc", + "utilities/write_batch_with_index/write_batch_with_index_internal.cc", ], + auto_headers = AutoHeaders.RECURSIVE_GLOB, + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, deps = [], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, + external_deps = ROCKSDB_EXTERNAL_DEPS, ) cpp_library( name = "rocksdb_test_lib", - headers = AutoHeaders.RECURSIVE_GLOB, srcs = [ - "table/mock_table.cc", - "util/fault_injection_test_env.cc", - "util/testharness.cc", - "util/testutil.cc", - "db/db_test_util.cc", - "utilities/cassandra/test_utils.cc", - "utilities/col_buf_encoder.cc", - "utilities/col_buf_decoder.cc", - "utilities/column_aware_encoding_util.cc", + "db/db_test_util.cc", + "table/mock_table.cc", + "tools/trace_analyzer_tool.cc", + "util/fault_injection_test_env.cc", + "util/testharness.cc", + "util/testutil.cc", + "utilities/cassandra/test_utils.cc", ], + auto_headers = AutoHeaders.RECURSIVE_GLOB, + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, deps = [":rocksdb_lib"], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, + external_deps = ROCKSDB_EXTERNAL_DEPS, ) cpp_library( name = "rocksdb_tools_lib", - headers = AutoHeaders.RECURSIVE_GLOB, srcs = [ - "tools/db_bench_tool.cc", - "util/testutil.cc", + "tools/db_bench_tool.cc", + "tools/trace_analyzer_tool.cc", + "util/testutil.cc", ], + auto_headers = AutoHeaders.RECURSIVE_GLOB, + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, deps = [":rocksdb_lib"], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, + external_deps = ROCKSDB_EXTERNAL_DEPS, ) cpp_library( name = "env_basic_test_lib", - headers = AutoHeaders.RECURSIVE_GLOB, srcs = ["env/env_basic_test.cc"], + auto_headers = AutoHeaders.RECURSIVE_GLOB, + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, deps = [":rocksdb_test_lib"], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, + external_deps = ROCKSDB_EXTERNAL_DEPS, ) # [test_name, test_src, test_type] -ROCKS_TESTS = [['arena_test', 'util/arena_test.cc', 'serial'], - ['auto_roll_logger_test', 'util/auto_roll_logger_test.cc', 'serial'], - ['autovector_test', 'util/autovector_test.cc', 'serial'], - ['backupable_db_test', - 'utilities/backupable/backupable_db_test.cc', - 'parallel'], - ['blob_db_test', 'utilities/blob_db/blob_db_test.cc', 'serial'], - ['block_based_filter_block_test', - 'table/block_based_filter_block_test.cc', - 'serial'], - ['block_test', 'table/block_test.cc', 'serial'], - ['bloom_test', 'util/bloom_test.cc', 'serial'], - ['c_test', 'db/c_test.c', 'serial'], - ['cache_test', 'cache/cache_test.cc', 'serial'], - ['cassandra_format_test', - 'utilities/cassandra/cassandra_format_test.cc', - 'serial'], - ['cassandra_functional_test', - 'utilities/cassandra/cassandra_functional_test.cc', - 'serial'], - ['cassandra_row_merge_test', - 'utilities/cassandra/cassandra_row_merge_test.cc', - 'serial'], - ['cassandra_serialize_test', - 'utilities/cassandra/cassandra_serialize_test.cc', - 'serial'], - ['checkpoint_test', 'utilities/checkpoint/checkpoint_test.cc', 'serial'], - ['cleanable_test', 'table/cleanable_test.cc', 'serial'], - ['coding_test', 'util/coding_test.cc', 'serial'], - ['column_aware_encoding_test', - 'utilities/column_aware_encoding_test.cc', - 'serial'], - ['column_family_test', 'db/column_family_test.cc', 'serial'], - ['compact_files_test', 'db/compact_files_test.cc', 'serial'], - ['compact_on_deletion_collector_test', - 'utilities/table_properties_collectors/compact_on_deletion_collector_test.cc', - 'serial'], - ['compaction_iterator_test', 'db/compaction_iterator_test.cc', 'serial'], - ['compaction_job_stats_test', 'db/compaction_job_stats_test.cc', 'serial'], - ['compaction_job_test', 'db/compaction_job_test.cc', 'serial'], - ['compaction_picker_test', 'db/compaction_picker_test.cc', 'serial'], - ['comparator_db_test', 'db/comparator_db_test.cc', 'serial'], - ['corruption_test', 'db/corruption_test.cc', 'serial'], - ['crc32c_test', 'util/crc32c_test.cc', 'serial'], - ['cuckoo_table_builder_test', 'table/cuckoo_table_builder_test.cc', 'serial'], - ['cuckoo_table_db_test', 'db/cuckoo_table_db_test.cc', 'serial'], - ['cuckoo_table_reader_test', 'table/cuckoo_table_reader_test.cc', 'serial'], - ['date_tiered_test', 'utilities/date_tiered/date_tiered_test.cc', 'serial'], - ['db_basic_test', 'db/db_basic_test.cc', 'serial'], - ['db_blob_index_test', 'db/db_blob_index_test.cc', 'serial'], - ['db_block_cache_test', 'db/db_block_cache_test.cc', 'serial'], - ['db_bloom_filter_test', 'db/db_bloom_filter_test.cc', 'serial'], - ['db_compaction_filter_test', 'db/db_compaction_filter_test.cc', 'parallel'], - ['db_compaction_test', 'db/db_compaction_test.cc', 'parallel'], - ['db_dynamic_level_test', 'db/db_dynamic_level_test.cc', 'serial'], - ['db_encryption_test', 'db/db_encryption_test.cc', 'serial'], - ['db_flush_test', 'db/db_flush_test.cc', 'serial'], - ['db_inplace_update_test', 'db/db_inplace_update_test.cc', 'serial'], - ['db_io_failure_test', 'db/db_io_failure_test.cc', 'serial'], - ['db_iter_test', 'db/db_iter_test.cc', 'serial'], - ['db_iterator_test', 'db/db_iterator_test.cc', 'serial'], - ['db_log_iter_test', 'db/db_log_iter_test.cc', 'serial'], - ['db_memtable_test', 'db/db_memtable_test.cc', 'serial'], - ['db_merge_operator_test', 'db/db_merge_operator_test.cc', 'serial'], - ['db_options_test', 'db/db_options_test.cc', 'serial'], - ['db_properties_test', 'db/db_properties_test.cc', 'serial'], - ['db_range_del_test', 'db/db_range_del_test.cc', 'serial'], - ['db_sst_test', 'db/db_sst_test.cc', 'parallel'], - ['db_statistics_test', 'db/db_statistics_test.cc', 'serial'], - ['db_table_properties_test', 'db/db_table_properties_test.cc', 'serial'], - ['db_tailing_iter_test', 'db/db_tailing_iter_test.cc', 'serial'], - ['db_test', 'db/db_test.cc', 'parallel'], - ['db_test2', 'db/db_test2.cc', 'serial'], - ['db_universal_compaction_test', - 'db/db_universal_compaction_test.cc', - 'parallel'], - ['db_wal_test', 'db/db_wal_test.cc', 'parallel'], - ['db_write_test', 'db/db_write_test.cc', 'serial'], - ['dbformat_test', 'db/dbformat_test.cc', 'serial'], - ['delete_scheduler_test', 'util/delete_scheduler_test.cc', 'serial'], - ['deletefile_test', 'db/deletefile_test.cc', 'serial'], - ['document_db_test', 'utilities/document/document_db_test.cc', 'serial'], - ['dynamic_bloom_test', 'util/dynamic_bloom_test.cc', 'serial'], - ['env_basic_test', 'env/env_basic_test.cc', 'serial'], - ['env_test', 'env/env_test.cc', 'serial'], - ['env_timed_test', 'utilities/env_timed_test.cc', 'serial'], - ['event_logger_test', 'util/event_logger_test.cc', 'serial'], - ['external_sst_file_basic_test', - 'db/external_sst_file_basic_test.cc', - 'serial'], - ['external_sst_file_test', 'db/external_sst_file_test.cc', 'parallel'], - ['fault_injection_test', 'db/fault_injection_test.cc', 'parallel'], - ['file_indexer_test', 'db/file_indexer_test.cc', 'serial'], - ['file_reader_writer_test', 'util/file_reader_writer_test.cc', 'serial'], - ['filelock_test', 'util/filelock_test.cc', 'serial'], - ['filename_test', 'db/filename_test.cc', 'serial'], - ['flush_job_test', 'db/flush_job_test.cc', 'serial'], - ['full_filter_block_test', 'table/full_filter_block_test.cc', 'serial'], - ['geodb_test', 'utilities/geodb/geodb_test.cc', 'serial'], - ['hash_table_test', - 'utilities/persistent_cache/hash_table_test.cc', - 'serial'], - ['hash_test', 'util/hash_test.cc', 'serial'], - ['heap_test', 'util/heap_test.cc', 'serial'], - ['histogram_test', 'monitoring/histogram_test.cc', 'serial'], - ['inlineskiplist_test', 'memtable/inlineskiplist_test.cc', 'parallel'], - ['iostats_context_test', 'monitoring/iostats_context_test.cc', 'serial'], - ['json_document_test', 'utilities/document/json_document_test.cc', 'serial'], - ['ldb_cmd_test', 'tools/ldb_cmd_test.cc', 'serial'], - ['listener_test', 'db/listener_test.cc', 'serial'], - ['log_test', 'db/log_test.cc', 'serial'], - ['lru_cache_test', 'cache/lru_cache_test.cc', 'serial'], - ['manual_compaction_test', 'db/manual_compaction_test.cc', 'parallel'], - ['memory_test', 'utilities/memory/memory_test.cc', 'serial'], - ['memtable_list_test', 'db/memtable_list_test.cc', 'serial'], - ['merge_helper_test', 'db/merge_helper_test.cc', 'serial'], - ['merge_test', 'db/merge_test.cc', 'serial'], - ['merger_test', 'table/merger_test.cc', 'serial'], - ['mock_env_test', 'env/mock_env_test.cc', 'serial'], - ['object_registry_test', 'utilities/object_registry_test.cc', 'serial'], - ['optimistic_transaction_test', - 'utilities/transactions/optimistic_transaction_test.cc', - 'serial'], - ['option_change_migration_test', - 'utilities/option_change_migration/option_change_migration_test.cc', - 'serial'], - ['options_file_test', 'db/options_file_test.cc', 'serial'], - ['options_settable_test', 'options/options_settable_test.cc', 'serial'], - ['options_test', 'options/options_test.cc', 'serial'], - ['options_util_test', 'utilities/options/options_util_test.cc', 'serial'], - ['partitioned_filter_block_test', - 'table/partitioned_filter_block_test.cc', - 'serial'], - ['perf_context_test', 'db/perf_context_test.cc', 'serial'], - ['persistent_cache_test', - 'utilities/persistent_cache/persistent_cache_test.cc', - 'parallel'], - ['plain_table_db_test', 'db/plain_table_db_test.cc', 'serial'], - ['prefix_test', 'db/prefix_test.cc', 'serial'], - ['range_del_aggregator_test', 'db/range_del_aggregator_test.cc', 'serial'], - ['rate_limiter_test', 'util/rate_limiter_test.cc', 'serial'], - ['reduce_levels_test', 'tools/reduce_levels_test.cc', 'serial'], - ['repair_test', 'db/repair_test.cc', 'serial'], - ['sim_cache_test', 'utilities/simulator_cache/sim_cache_test.cc', 'serial'], - ['skiplist_test', 'memtable/skiplist_test.cc', 'serial'], - ['slice_transform_test', 'util/slice_transform_test.cc', 'serial'], - ['spatial_db_test', 'utilities/spatialdb/spatial_db_test.cc', 'serial'], - ['sst_dump_test', 'tools/sst_dump_test.cc', 'serial'], - ['statistics_test', 'monitoring/statistics_test.cc', 'serial'], - ['stringappend_test', - 'utilities/merge_operators/string_append/stringappend_test.cc', - 'serial'], - ['table_properties_collector_test', - 'db/table_properties_collector_test.cc', - 'serial'], - ['table_test', 'table/table_test.cc', 'parallel'], - ['thread_list_test', 'util/thread_list_test.cc', 'serial'], - ['thread_local_test', 'util/thread_local_test.cc', 'serial'], - ['timer_queue_test', 'util/timer_queue_test.cc', 'serial'], - ['transaction_test', 'utilities/transactions/transaction_test.cc', 'serial'], - ['ttl_test', 'utilities/ttl/ttl_test.cc', 'serial'], - ['util_merge_operators_test', - 'utilities/util_merge_operators_test.cc', - 'serial'], - ['version_builder_test', 'db/version_builder_test.cc', 'serial'], - ['version_edit_test', 'db/version_edit_test.cc', 'serial'], - ['version_set_test', 'db/version_set_test.cc', 'serial'], - ['wal_manager_test', 'db/wal_manager_test.cc', 'serial'], - ['write_batch_test', 'db/write_batch_test.cc', 'serial'], - ['write_batch_with_index_test', - 'utilities/write_batch_with_index/write_batch_with_index_test.cc', - 'serial'], - ['write_buffer_manager_test', - 'memtable/write_buffer_manager_test.cc', - 'serial'], - ['write_callback_test', 'db/write_callback_test.cc', 'serial'], - ['write_controller_test', 'db/write_controller_test.cc', 'serial']] - +ROCKS_TESTS = [ + [ + "arena_test", + "util/arena_test.cc", + "serial", + ], + [ + "auto_roll_logger_test", + "util/auto_roll_logger_test.cc", + "serial", + ], + [ + "autovector_test", + "util/autovector_test.cc", + "serial", + ], + [ + "backupable_db_test", + "utilities/backupable/backupable_db_test.cc", + "parallel", + ], + [ + "blob_db_test", + "utilities/blob_db/blob_db_test.cc", + "serial", + ], + [ + "block_based_filter_block_test", + "table/block_based_filter_block_test.cc", + "serial", + ], + [ + "block_test", + "table/block_test.cc", + "serial", + ], + [ + "bloom_test", + "util/bloom_test.cc", + "serial", + ], + [ + "c_test", + "db/c_test.c", + "serial", + ], + [ + "cache_test", + "cache/cache_test.cc", + "serial", + ], + [ + "cassandra_format_test", + "utilities/cassandra/cassandra_format_test.cc", + "serial", + ], + [ + "cassandra_functional_test", + "utilities/cassandra/cassandra_functional_test.cc", + "serial", + ], + [ + "cassandra_row_merge_test", + "utilities/cassandra/cassandra_row_merge_test.cc", + "serial", + ], + [ + "cassandra_serialize_test", + "utilities/cassandra/cassandra_serialize_test.cc", + "serial", + ], + [ + "checkpoint_test", + "utilities/checkpoint/checkpoint_test.cc", + "serial", + ], + [ + "cleanable_test", + "table/cleanable_test.cc", + "serial", + ], + [ + "coding_test", + "util/coding_test.cc", + "serial", + ], + [ + "column_family_test", + "db/column_family_test.cc", + "serial", + ], + [ + "compact_files_test", + "db/compact_files_test.cc", + "serial", + ], + [ + "compact_on_deletion_collector_test", + "utilities/table_properties_collectors/compact_on_deletion_collector_test.cc", + "serial", + ], + [ + "compaction_iterator_test", + "db/compaction_iterator_test.cc", + "serial", + ], + [ + "compaction_job_stats_test", + "db/compaction_job_stats_test.cc", + "serial", + ], + [ + "compaction_job_test", + "db/compaction_job_test.cc", + "serial", + ], + [ + "compaction_picker_test", + "db/compaction_picker_test.cc", + "serial", + ], + [ + "comparator_db_test", + "db/comparator_db_test.cc", + "serial", + ], + [ + "corruption_test", + "db/corruption_test.cc", + "serial", + ], + [ + "crc32c_test", + "util/crc32c_test.cc", + "serial", + ], + [ + "cuckoo_table_builder_test", + "table/cuckoo_table_builder_test.cc", + "serial", + ], + [ + "cuckoo_table_db_test", + "db/cuckoo_table_db_test.cc", + "serial", + ], + [ + "cuckoo_table_reader_test", + "table/cuckoo_table_reader_test.cc", + "serial", + ], + [ + "data_block_hash_index_test", + "table/data_block_hash_index_test.cc", + "serial", + ], + [ + "db_basic_test", + "db/db_basic_test.cc", + "serial", + ], + [ + "db_blob_index_test", + "db/db_blob_index_test.cc", + "serial", + ], + [ + "db_block_cache_test", + "db/db_block_cache_test.cc", + "serial", + ], + [ + "db_bloom_filter_test", + "db/db_bloom_filter_test.cc", + "serial", + ], + [ + "db_compaction_filter_test", + "db/db_compaction_filter_test.cc", + "parallel", + ], + [ + "db_compaction_test", + "db/db_compaction_test.cc", + "parallel", + ], + [ + "db_dynamic_level_test", + "db/db_dynamic_level_test.cc", + "serial", + ], + [ + "db_encryption_test", + "db/db_encryption_test.cc", + "serial", + ], + [ + "db_flush_test", + "db/db_flush_test.cc", + "serial", + ], + [ + "db_inplace_update_test", + "db/db_inplace_update_test.cc", + "serial", + ], + [ + "db_io_failure_test", + "db/db_io_failure_test.cc", + "serial", + ], + [ + "db_iter_stress_test", + "db/db_iter_stress_test.cc", + "serial", + ], + [ + "db_iter_test", + "db/db_iter_test.cc", + "serial", + ], + [ + "db_iterator_test", + "db/db_iterator_test.cc", + "serial", + ], + [ + "db_log_iter_test", + "db/db_log_iter_test.cc", + "serial", + ], + [ + "db_memtable_test", + "db/db_memtable_test.cc", + "serial", + ], + [ + "db_merge_operator_test", + "db/db_merge_operator_test.cc", + "parallel", + ], + [ + "db_options_test", + "db/db_options_test.cc", + "serial", + ], + [ + "db_properties_test", + "db/db_properties_test.cc", + "serial", + ], + [ + "db_range_del_test", + "db/db_range_del_test.cc", + "serial", + ], + [ + "db_secondary_test", + "db/db_secondary_test.cc", + "serial", + ], + [ + "db_sst_test", + "db/db_sst_test.cc", + "parallel", + ], + [ + "db_statistics_test", + "db/db_statistics_test.cc", + "serial", + ], + [ + "db_table_properties_test", + "db/db_table_properties_test.cc", + "serial", + ], + [ + "db_tailing_iter_test", + "db/db_tailing_iter_test.cc", + "serial", + ], + [ + "db_test", + "db/db_test.cc", + "parallel", + ], + [ + "db_test2", + "db/db_test2.cc", + "serial", + ], + [ + "db_universal_compaction_test", + "db/db_universal_compaction_test.cc", + "parallel", + ], + [ + "db_wal_test", + "db/db_wal_test.cc", + "parallel", + ], + [ + "db_write_test", + "db/db_write_test.cc", + "serial", + ], + [ + "dbformat_test", + "db/dbformat_test.cc", + "serial", + ], + [ + "delete_scheduler_test", + "util/delete_scheduler_test.cc", + "serial", + ], + [ + "deletefile_test", + "db/deletefile_test.cc", + "serial", + ], + [ + "dynamic_bloom_test", + "util/dynamic_bloom_test.cc", + "serial", + ], + [ + "env_basic_test", + "env/env_basic_test.cc", + "serial", + ], + [ + "env_test", + "env/env_test.cc", + "serial", + ], + [ + "env_timed_test", + "utilities/env_timed_test.cc", + "serial", + ], + [ + "error_handler_test", + "db/error_handler_test.cc", + "serial", + ], + [ + "event_logger_test", + "util/event_logger_test.cc", + "serial", + ], + [ + "external_sst_file_basic_test", + "db/external_sst_file_basic_test.cc", + "serial", + ], + [ + "external_sst_file_test", + "db/external_sst_file_test.cc", + "parallel", + ], + [ + "fault_injection_test", + "db/fault_injection_test.cc", + "parallel", + ], + [ + "file_indexer_test", + "db/file_indexer_test.cc", + "serial", + ], + [ + "file_reader_writer_test", + "util/file_reader_writer_test.cc", + "serial", + ], + [ + "filelock_test", + "util/filelock_test.cc", + "serial", + ], + [ + "filename_test", + "db/filename_test.cc", + "serial", + ], + [ + "flush_job_test", + "db/flush_job_test.cc", + "serial", + ], + [ + "full_filter_block_test", + "table/full_filter_block_test.cc", + "serial", + ], + [ + "hash_table_test", + "utilities/persistent_cache/hash_table_test.cc", + "serial", + ], + [ + "hash_test", + "util/hash_test.cc", + "serial", + ], + [ + "heap_test", + "util/heap_test.cc", + "serial", + ], + [ + "histogram_test", + "monitoring/histogram_test.cc", + "serial", + ], + [ + "inlineskiplist_test", + "memtable/inlineskiplist_test.cc", + "parallel", + ], + [ + "iostats_context_test", + "monitoring/iostats_context_test.cc", + "serial", + ], + [ + "ldb_cmd_test", + "tools/ldb_cmd_test.cc", + "serial", + ], + [ + "listener_test", + "db/listener_test.cc", + "serial", + ], + [ + "log_test", + "db/log_test.cc", + "serial", + ], + [ + "lru_cache_test", + "cache/lru_cache_test.cc", + "serial", + ], + [ + "manual_compaction_test", + "db/manual_compaction_test.cc", + "parallel", + ], + [ + "memory_test", + "utilities/memory/memory_test.cc", + "serial", + ], + [ + "memtable_list_test", + "db/memtable_list_test.cc", + "serial", + ], + [ + "merge_helper_test", + "db/merge_helper_test.cc", + "serial", + ], + [ + "merge_test", + "db/merge_test.cc", + "serial", + ], + [ + "merger_test", + "table/merger_test.cc", + "serial", + ], + [ + "mock_env_test", + "env/mock_env_test.cc", + "serial", + ], + [ + "object_registry_test", + "utilities/object_registry_test.cc", + "serial", + ], + [ + "obsolete_files_test", + "db/obsolete_files_test.cc", + "serial", + ], + [ + "optimistic_transaction_test", + "utilities/transactions/optimistic_transaction_test.cc", + "serial", + ], + [ + "option_change_migration_test", + "utilities/option_change_migration/option_change_migration_test.cc", + "serial", + ], + [ + "options_file_test", + "db/options_file_test.cc", + "serial", + ], + [ + "options_settable_test", + "options/options_settable_test.cc", + "serial", + ], + [ + "options_test", + "options/options_test.cc", + "serial", + ], + [ + "options_util_test", + "utilities/options/options_util_test.cc", + "serial", + ], + [ + "partitioned_filter_block_test", + "table/partitioned_filter_block_test.cc", + "serial", + ], + [ + "perf_context_test", + "db/perf_context_test.cc", + "serial", + ], + [ + "persistent_cache_test", + "utilities/persistent_cache/persistent_cache_test.cc", + "parallel", + ], + [ + "plain_table_db_test", + "db/plain_table_db_test.cc", + "serial", + ], + [ + "prefix_test", + "db/prefix_test.cc", + "serial", + ], + [ + "range_del_aggregator_test", + "db/range_del_aggregator_test.cc", + "serial", + ], + [ + "range_tombstone_fragmenter_test", + "db/range_tombstone_fragmenter_test.cc", + "serial", + ], + [ + "rate_limiter_test", + "util/rate_limiter_test.cc", + "serial", + ], + [ + "reduce_levels_test", + "tools/reduce_levels_test.cc", + "serial", + ], + [ + "repair_test", + "db/repair_test.cc", + "serial", + ], + [ + "repeatable_thread_test", + "util/repeatable_thread_test.cc", + "serial", + ], + [ + "sim_cache_test", + "utilities/simulator_cache/sim_cache_test.cc", + "serial", + ], + [ + "skiplist_test", + "memtable/skiplist_test.cc", + "serial", + ], + [ + "slice_transform_test", + "util/slice_transform_test.cc", + "serial", + ], + [ + "sst_dump_test", + "tools/sst_dump_test.cc", + "serial", + ], + [ + "sst_file_reader_test", + "table/sst_file_reader_test.cc", + "serial", + ], + [ + "statistics_test", + "monitoring/statistics_test.cc", + "serial", + ], + [ + "stringappend_test", + "utilities/merge_operators/string_append/stringappend_test.cc", + "serial", + ], + [ + "table_properties_collector_test", + "db/table_properties_collector_test.cc", + "serial", + ], + [ + "table_test", + "table/table_test.cc", + "parallel", + ], + [ + "thread_list_test", + "util/thread_list_test.cc", + "serial", + ], + [ + "thread_local_test", + "util/thread_local_test.cc", + "serial", + ], + [ + "timer_queue_test", + "util/timer_queue_test.cc", + "serial", + ], + [ + "trace_analyzer_test", + "tools/trace_analyzer_test.cc", + "serial", + ], + [ + "transaction_test", + "utilities/transactions/transaction_test.cc", + "parallel", + ], + [ + "ttl_test", + "utilities/ttl/ttl_test.cc", + "serial", + ], + [ + "util_merge_operators_test", + "utilities/util_merge_operators_test.cc", + "serial", + ], + [ + "version_builder_test", + "db/version_builder_test.cc", + "serial", + ], + [ + "version_edit_test", + "db/version_edit_test.cc", + "serial", + ], + [ + "version_set_test", + "db/version_set_test.cc", + "serial", + ], + [ + "wal_manager_test", + "db/wal_manager_test.cc", + "serial", + ], + [ + "write_batch_test", + "db/write_batch_test.cc", + "serial", + ], + [ + "write_batch_with_index_test", + "utilities/write_batch_with_index/write_batch_with_index_test.cc", + "serial", + ], + [ + "write_buffer_manager_test", + "memtable/write_buffer_manager_test.cc", + "serial", + ], + [ + "write_callback_test", + "db/write_callback_test.cc", + "serial", + ], + [ + "write_controller_test", + "db/write_controller_test.cc", + "serial", + ], + [ + "write_prepared_transaction_test", + "utilities/transactions/write_prepared_transaction_test.cc", + "parallel", + ], + [ + "write_unprepared_transaction_test", + "utilities/transactions/write_unprepared_transaction_test.cc", + "parallel", + ], +] # Generate a test rule for each entry in ROCKS_TESTS -for test_cfg in ROCKS_TESTS: - test_name = test_cfg[0] - test_cc = test_cfg[1] - ttype = "gtest" if test_cfg[2] == "parallel" else "simple" - test_bin = test_name + "_bin" - - cpp_binary ( - name = test_bin, - srcs = [test_cc], - deps = [":rocksdb_test_lib"], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, +# Do not build the tests in opt mode, since SyncPoint and other test code +# will not be included. +[ + test_binary( + parallelism = parallelism, + rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS, + rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS, + rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + test_cc = test_cc, + test_name = test_name, ) - - custom_unittest( - name = test_name, - type = ttype, - deps = [":" + test_bin], - command = [TEST_RUNNER, BUCK_BINS + test_bin] - ) - -custom_unittest( - name = "make_rocksdbjavastatic", - type = "simple", - command = ["internal_repo_rocksdb/make_rocksdbjavastatic.sh"], -) - -custom_unittest( - name = "make_rocksdb_lite_release", - type = "simple", - command = ["internal_repo_rocksdb/make_rocksdb_lite_release.sh"], -) + for test_name, test_cc, parallelism in ROCKS_TESTS + if not is_opt_mode +] diff --git a/thirdparty/rocksdb/USERS.md b/thirdparty/rocksdb/USERS.md index 7be093f958..a95903f066 100644 --- a/thirdparty/rocksdb/USERS.md +++ b/thirdparty/rocksdb/USERS.md @@ -5,12 +5,15 @@ At Facebook, we use RocksDB as storage engines in multiple data management servi 1. MyRocks -- https://github.com/MySQLOnRocksDB/mysql-5.6 2. MongoRocks -- https://github.com/mongodb-partners/mongo-rocks -3. ZippyDB -- Facebook's distributed key-value store with Paxos-style replication, built on top of RocksDB.[*] https://www.youtube.com/watch?v=DfiN7pG0D0khtt -4. Laser -- Laser is a high query throughput, low (millisecond) latency, key-value storage service built on top of RocksDB.[*] +3. ZippyDB -- Facebook's distributed key-value store with Paxos-style replication, built on top of RocksDB.[1] https://www.youtube.com/watch?v=DfiN7pG0D0khtt +4. Laser -- Laser is a high query throughput, low (millisecond) latency, key-value storage service built on top of RocksDB.[1] 4. Dragon -- a distributed graph query engine. https://code.facebook.com/posts/1737605303120405/dragon-a-distributed-graph-query-engine/ -5. Stylus -- a low-level stream processing framework writtenin C++.[*] +5. Stylus -- a low-level stream processing framework writtenin C++.[1] +6. LogDevice -- a distributed data store for logs [2] -[*] https://research.facebook.com/publications/realtime-data-processing-at-facebook/ +[1] https://research.facebook.com/publications/realtime-data-processing-at-facebook/ + +[2] https://code.facebook.com/posts/357056558062811/logdevice-a-distributed-data-store-for-logs/ ## LinkedIn Two different use cases at Linkedin are using RocksDB as a storage engine: @@ -24,7 +27,7 @@ Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasu Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights ## CockroachDB -CockroachDB is an open-source geo-replicated transactional database (still in development). They are using RocksDB as their storage engine. Check out their github: https://github.com/cockroachdb/cockroach +CockroachDB is an open-source geo-replicated transactional database. They are using RocksDB as their storage engine. Check out their github: https://github.com/cockroachdb/cockroach ## DNANexus DNANexus is using RocksDB to speed up processing of genomics data. @@ -83,3 +86,9 @@ quasardb uses a heavily tuned RocksDB as its persistence layer. ## LzLabs LzLabs is using RocksDB as a storage engine in their multi-database distributed framework to store application configuration and user data. + +## ProfaneDB +[ProfaneDB](https://profanedb.gitlab.io/) is a database for Protocol Buffers, and uses RocksDB for storage. It is accessible via gRPC, and the schema is defined using directly `.proto` files. + +## IOTA Foundation + [IOTA Foundation](https://www.iota.org/) is using RocksDB in the [IOTA Reference Implementation (IRI)](https://github.com/iotaledger/iri) to store the local state of the Tangle. The Tangle is the first open-source distributed ledger powering the future of the Internet of Things. \ No newline at end of file diff --git a/thirdparty/rocksdb/Vagrantfile b/thirdparty/rocksdb/Vagrantfile index d7c2991d79..07f2e99fdd 100644 --- a/thirdparty/rocksdb/Vagrantfile +++ b/thirdparty/rocksdb/Vagrantfile @@ -14,6 +14,11 @@ Vagrant.configure("2") do |config| box.vm.box = "chef/centos-6.5" end + config.vm.define "centos7" do |box| + box.vm.box = "centos/7" + box.vm.provision "shell", path: "build_tools/setup_centos7.sh" + end + config.vm.define "FreeBSD10" do |box| box.vm.guest = :freebsd box.vm.box = "robin/freebsd-10" diff --git a/thirdparty/rocksdb/WINDOWS_PORT.md b/thirdparty/rocksdb/WINDOWS_PORT.md index a0fe1fe11f..57293c97c9 100644 --- a/thirdparty/rocksdb/WINDOWS_PORT.md +++ b/thirdparty/rocksdb/WINDOWS_PORT.md @@ -43,9 +43,9 @@ We plan to use this port for our business purposes here at Bing and this provide * Certain headers that are not present and not necessary on Windows were simply `#ifndef OS_WIN` in a few places (`unistd.h`) * All posix specific headers were replaced to port/port.h which worked well -* Replaced `dirent.h` for `port/dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace +* Replaced `dirent.h` for `port/port_dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace * Replaced `sys/time.h` to `port/sys_time.h` (few places) implemented equivalents within `rocksdb::port` -* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `%z` on posix systems and to Iu on windows. +* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `zu` on posix systems and to `Iu` on windows. * in class member initialization were moved to a __ctors in some cases * `constexpr` is not supported. We had to replace `std::numeric_limits<>::max/min()` to its C macros for constants. Sometimes we had to make class members `static const` and place a definition within a .cc file. * `constexpr` for functions was replaced to a template specialization (1 place) diff --git a/thirdparty/rocksdb/appveyor.yml b/thirdparty/rocksdb/appveyor.yml index be9b66b45c..9dae40af8f 100644 --- a/thirdparty/rocksdb/appveyor.yml +++ b/thirdparty/rocksdb/appveyor.yml @@ -1,14 +1,14 @@ version: 1.0.{build} -image: Visual Studio 2015 +image: Visual Studio 2017 before_build: - md %APPVEYOR_BUILD_FOLDER%\build - cd %APPVEYOR_BUILD_FOLDER%\build -- cmake -G "Visual Studio 14 2015 Win64" -DOPTDBG=1 -DXPRESS=1 -DPORTABLE=1 .. +- cmake -G "Visual Studio 15 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 -DJNI=1 .. - cd .. build: project: build\rocksdb.sln parallel: true - verbosity: minimal + verbosity: normal test: test_script: - ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test2,db_test,env_basic_test,env_test -Concurrency 8 diff --git a/thirdparty/rocksdb/buckifier/buckify_rocksdb.py b/thirdparty/rocksdb/buckifier/buckify_rocksdb.py index a3c8be3b17..96903af682 100644 --- a/thirdparty/rocksdb/buckifier/buckify_rocksdb.py +++ b/thirdparty/rocksdb/buckifier/buckify_rocksdb.py @@ -3,14 +3,11 @@ from __future__ import print_function from __future__ import unicode_literals from targets_builder import TARGETSBuilder -from optparse import OptionParser import os import fnmatch import sys -import tempfile from util import ColorString -import util # tests to export as libraries for inclusion in other projects _EXPORTED_TEST_LIBS = ["env_basic_test"] @@ -36,7 +33,7 @@ def parse_src_mk(repo_path): # get all .cc / .c files def get_cc_files(repo_path): cc_files = [] - for root, dirnames, filenames in os.walk(repo_path): + for root, dirnames, filenames in os.walk(repo_path): # noqa: B007 T25377293 Grandfathered in root = root[(len(repo_path) + 1):] if "java" in root: # Skip java @@ -112,12 +109,14 @@ def generate_targets(repo_path): "rocksdb_test_lib", src_mk.get("MOCK_LIB_SOURCES", []) + src_mk.get("TEST_LIB_SOURCES", []) + - src_mk.get("EXP_LIB_SOURCES", []), + src_mk.get("EXP_LIB_SOURCES", []) + + src_mk.get("ANALYZER_LIB_SOURCES", []), [":rocksdb_lib"]) # rocksdb_tools_lib TARGETS.add_library( "rocksdb_tools_lib", src_mk.get("BENCH_LIB_SOURCES", []) + + src_mk.get("ANALYZER_LIB_SOURCES", []) + ["util/testutil.cc"], [":rocksdb_lib"]) diff --git a/thirdparty/rocksdb/buckifier/rocks_test_runner.sh b/thirdparty/rocksdb/buckifier/rocks_test_runner.sh index e1f48a760d..baca6c2e40 100755 --- a/thirdparty/rocksdb/buckifier/rocks_test_runner.sh +++ b/thirdparty/rocksdb/buckifier/rocks_test_runner.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash # Create a tmp directory for the test to use TEST_DIR=$(mktemp -d /dev/shm/fbcode_rocksdb_XXXXXXX) +# shellcheck disable=SC2068 TEST_TMPDIR="$TEST_DIR" $@ && rm -rf "$TEST_DIR" diff --git a/thirdparty/rocksdb/buckifier/targets_builder.py b/thirdparty/rocksdb/buckifier/targets_builder.py index 7d47d2d1f9..3d5822d3cb 100644 --- a/thirdparty/rocksdb/buckifier/targets_builder.py +++ b/thirdparty/rocksdb/buckifier/targets_builder.py @@ -3,10 +3,8 @@ from __future__ import print_function from __future__ import unicode_literals import targets_cfg -import pprint -# TODO(tec): replace this with PrettyPrinter -def pretty_list(lst, indent=6): +def pretty_list(lst, indent=8): if lst is None or len(lst) == 0: return "" @@ -14,8 +12,8 @@ def pretty_list(lst, indent=6): return "\"%s\"" % lst[0] separator = "\",\n%s\"" % (" " * indent) - res = separator.join(lst) - res = "\n" + (" " * indent) + "\"" + res + "\",\n" + (" " * (indent - 2)) + res = separator.join(sorted(lst)) + res = "\n" + (" " * indent) + "\"" + res + "\",\n" + (" " * (indent - 4)) return res @@ -27,19 +25,22 @@ def __init__(self, path): self.total_lib = 0 self.total_bin = 0 self.total_test = 0 - self.tests_cfg = [] + self.tests_cfg = "" def __del__(self): self.targets_file.close() def add_library(self, name, srcs, deps=None, headers=None): + headers_attr_prefix = "" if headers is None: + headers_attr_prefix = "auto_" headers = "AutoHeaders.RECURSIVE_GLOB" - self.targets_file.write(targets_cfg.library_template % ( - name, - headers, - pretty_list(srcs), - pretty_list(deps))) + self.targets_file.write(targets_cfg.library_template.format( + name=name, + srcs=pretty_list(srcs), + headers_attr_prefix=headers_attr_prefix, + headers=headers, + deps=pretty_list(deps))) self.total_lib = self.total_lib + 1 def add_binary(self, name, srcs, deps=None): @@ -53,13 +54,13 @@ def register_test(self, test_name, src, is_parallel): exec_mode = "serial" if is_parallel: exec_mode = "parallel" - self.tests_cfg.append([test_name, str(src), str(exec_mode)]) + self.tests_cfg += targets_cfg.test_cfg_template % ( + test_name, + str(src), + str(exec_mode)) self.total_test = self.total_test + 1 def flush_tests(self): - self.targets_file.write(targets_cfg.unittests_template % ( - pprint.PrettyPrinter().pformat(self.tests_cfg) - )) - - self.tests_cfg = [] + self.targets_file.write(targets_cfg.unittests_template % self.tests_cfg) + self.tests_cfg = "" diff --git a/thirdparty/rocksdb/buckifier/targets_cfg.py b/thirdparty/rocksdb/buckifier/targets_cfg.py index 33023a589f..f881588c59 100644 --- a/thirdparty/rocksdb/buckifier/targets_cfg.py +++ b/thirdparty/rocksdb/buckifier/targets_cfg.py @@ -2,123 +2,134 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals -rocksdb_target_header = """ -import os - -TARGETS_PATH = os.path.dirname(__file__) -REPO_PATH = "rocksdb/src/" -BUCK_BINS = "buck-out/gen/" + REPO_PATH -TEST_RUNNER = REPO_PATH + "buckifier/rocks_test_runner.sh" -rocksdb_compiler_flags = [ - "-fno-builtin-memcmp", - "-DROCKSDB_PLATFORM_POSIX", - "-DROCKSDB_LIB_IO_POSIX", - "-DROCKSDB_FALLOCATE_PRESENT", - "-DROCKSDB_MALLOC_USABLE_SIZE", - "-DROCKSDB_RANGESYNC_PRESENT", - "-DROCKSDB_SCHED_GETCPU_PRESENT", - "-DROCKSDB_SUPPORT_THREAD_LOCAL", - "-DOS_LINUX", - # Flags to enable libs we include - "-DSNAPPY", - "-DZLIB", - "-DBZIP2", - "-DLZ4", - "-DZSTD", - "-DGFLAGS=gflags", - "-DNUMA", - "-DTBB", - # Needed to compile in fbcode - "-Wno-expansion-to-defined", +rocksdb_target_header = """load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders") +load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library") +load(":defs.bzl", "test_binary") + +REPO_PATH = package_name() + "/" + +ROCKSDB_COMPILER_FLAGS = [ + "-fno-builtin-memcmp", + "-DROCKSDB_PLATFORM_POSIX", + "-DROCKSDB_LIB_IO_POSIX", + "-DROCKSDB_FALLOCATE_PRESENT", + "-DROCKSDB_MALLOC_USABLE_SIZE", + "-DROCKSDB_RANGESYNC_PRESENT", + "-DROCKSDB_SCHED_GETCPU_PRESENT", + "-DROCKSDB_SUPPORT_THREAD_LOCAL", + "-DOS_LINUX", + # Flags to enable libs we include + "-DSNAPPY", + "-DZLIB", + "-DBZIP2", + "-DLZ4", + "-DZSTD", + "-DZSTD_STATIC_LINKING_ONLY", + "-DGFLAGS=gflags", + "-DNUMA", + "-DTBB", + # Needed to compile in fbcode + "-Wno-expansion-to-defined", + # Added missing flags from output of build_detect_platform + "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX", + "-DROCKSDB_BACKTRACE", + "-Wnarrowing", ] -rocksdb_external_deps = [ - ('bzip2', None, 'bz2'), - ('snappy', None, "snappy"), - ('zlib', None, 'z'), - ('gflags', None, 'gflags'), - ('lz4', None, 'lz4'), - ('zstd', None), - ('tbb', None), - ("numa", None, "numa"), - ("googletest", None, "gtest"), +ROCKSDB_EXTERNAL_DEPS = [ + ("bzip2", None, "bz2"), + ("snappy", None, "snappy"), + ("zlib", None, "z"), + ("gflags", None, "gflags"), + ("lz4", None, "lz4"), + ("zstd", None), + ("tbb", None), + ("numa", None, "numa"), + ("googletest", None, "gtest"), ] -rocksdb_preprocessor_flags = [ - # Directories with files for #include - "-I" + REPO_PATH + "include/", - "-I" + REPO_PATH, +ROCKSDB_PREPROCESSOR_FLAGS = [ + # Directories with files for #include + "-I" + REPO_PATH + "include/", + "-I" + REPO_PATH, ] -rocksdb_arch_preprocessor_flags = { - "x86_64": ["-DHAVE_SSE42"], +ROCKSDB_ARCH_PREPROCESSOR_FLAGS = { + "x86_64": [ + "-DHAVE_SSE42", + "-DHAVE_PCLMUL", + ], } + +build_mode = read_config("fbcode", "build_mode") + +is_opt_mode = build_mode.startswith("opt") + +# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice +# doesn't harm and avoid forgetting to add it. +ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else []) + +sanitizer = read_config("fbcode", "sanitizer") + +# Do not enable jemalloc if sanitizer presents. RocksDB will further detect +# whether the binary is linked with jemalloc at runtime. +ROCKSDB_COMPILER_FLAGS += (["-DROCKSDB_JEMALLOC"] if sanitizer == "" else []) + +ROCKSDB_EXTERNAL_DEPS += ([("jemalloc", None, "headers")] if sanitizer == "" else []) """ library_template = """ cpp_library( - name = "%s", - headers = %s, - srcs = [%s], - deps = [%s], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, + name = "{name}", + srcs = [{srcs}], + {headers_attr_prefix}headers = {headers}, + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + deps = [{deps}], + external_deps = ROCKSDB_EXTERNAL_DEPS, ) """ binary_template = """ cpp_binary( - name = "%s", - srcs = [%s], - deps = [%s], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, + name = "%s", + srcs = [%s], + arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + compiler_flags = ROCKSDB_COMPILER_FLAGS, + preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + deps = [%s], + external_deps = ROCKSDB_EXTERNAL_DEPS, ) """ +test_cfg_template = """ [ + "%s", + "%s", + "%s", + ], +""" + unittests_template = """ # [test_name, test_src, test_type] -ROCKS_TESTS = %s - +ROCKS_TESTS = [ +%s] # Generate a test rule for each entry in ROCKS_TESTS -for test_cfg in ROCKS_TESTS: - test_name = test_cfg[0] - test_cc = test_cfg[1] - ttype = "gtest" if test_cfg[2] == "parallel" else "simple" - test_bin = test_name + "_bin" - - cpp_binary ( - name = test_bin, - srcs = [test_cc], - deps = [":rocksdb_test_lib"], - preprocessor_flags = rocksdb_preprocessor_flags, - arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, - compiler_flags = rocksdb_compiler_flags, - external_deps = rocksdb_external_deps, - ) - - custom_unittest( - name = test_name, - type = ttype, - deps = [":" + test_bin], - command = [TEST_RUNNER, BUCK_BINS + test_bin] +# Do not build the tests in opt mode, since SyncPoint and other test code +# will not be included. +[ + test_binary( + parallelism = parallelism, + rocksdb_arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS, + rocksdb_compiler_flags = ROCKSDB_COMPILER_FLAGS, + rocksdb_external_deps = ROCKSDB_EXTERNAL_DEPS, + rocksdb_preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS, + test_cc = test_cc, + test_name = test_name, ) - -custom_unittest( - name = "make_rocksdbjavastatic", - type = "simple", - command = ["internal_repo_rocksdb/make_rocksdbjavastatic.sh"], -) - -custom_unittest( - name = "make_rocksdb_lite_release", - type = "simple", - command = ["internal_repo_rocksdb/make_rocksdb_lite_release.sh"], -) + for test_name, test_cc, parallelism in ROCKS_TESTS + if not is_opt_mode +] """ diff --git a/thirdparty/rocksdb/build_tools/RocksDBCommonHelper.php b/thirdparty/rocksdb/build_tools/RocksDBCommonHelper.php index 9fe770fe95..e7bfb52034 100644 --- a/thirdparty/rocksdb/build_tools/RocksDBCommonHelper.php +++ b/thirdparty/rocksdb/build_tools/RocksDBCommonHelper.php @@ -7,12 +7,12 @@ // Name of the environment variables which need to be set by the entity which // triggers continuous runs so that code at the end of the file gets executed // and Sandcastle run starts. -define("ENV_POST_RECEIVE_HOOK", "POST_RECEIVE_HOOK"); -define("ENV_HTTPS_APP_VALUE", "HTTPS_APP_VALUE"); -define("ENV_HTTPS_TOKEN_VALUE", "HTTPS_TOKEN_VALUE"); +const ENV_POST_RECEIVE_HOOK = "POST_RECEIVE_HOOK"; +const ENV_HTTPS_APP_VALUE = "HTTPS_APP_VALUE"; +const ENV_HTTPS_TOKEN_VALUE = "HTTPS_TOKEN_VALUE"; -define("PRIMARY_TOKEN_FILE", '/home/krad/.sandcastle'); -define("CONT_RUN_ALIAS", "leveldb"); +const PRIMARY_TOKEN_FILE = '/home/krad/.sandcastle'; +const CONT_RUN_ALIAS = "leveldb"; ////////////////////////////////////////////////////////////////////// /* Run tests in sandcastle */ @@ -97,7 +97,7 @@ function getSteps($applyDiff, $diffID, $username, $test) { } // fbcode is a sub-repo. We cannot patch until we add it to ignore otherwise - // Git thinks it is an uncommited change. + // Git thinks it is an uncommitted change. $fix_git_ignore = array( "name" => "Fix git ignore", "shell" => "echo fbcode >> .git/info/exclude", diff --git a/thirdparty/rocksdb/build_tools/build_detect_platform b/thirdparty/rocksdb/build_tools/build_detect_platform index c7ddb7ccee..057f77ec53 100755 --- a/thirdparty/rocksdb/build_tools/build_detect_platform +++ b/thirdparty/rocksdb/build_tools/build_detect_platform @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # # Detects OS we're compiling on and outputs a file specified by the first # argument, which in turn gets read while processing Makefile. @@ -16,6 +16,8 @@ # PLATFORM_CXXFLAGS C++ compiler flags. Will contain: # PLATFORM_SHARED_VERSIONED Set to 'true' if platform supports versioned # shared libraries, empty otherwise. +# FIND Command for the find utility +# WATCH Command for the watch utility # # The PLATFORM_CCFLAGS and PLATFORM_CXXFLAGS might include the following: # @@ -51,11 +53,13 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then FBCODE_BUILD="true" # If we're compiling with TSAN we need pic build PIC_BUILD=$COMPILE_WITH_TSAN - if [ -z "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then - source "$PWD/build_tools/fbcode_config.sh" - else + if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then # we need this to build with MySQL. Don't use for other purposes. source "$PWD/build_tools/fbcode_config4.8.1.sh" + elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007" ]; then + source "$PWD/build_tools/fbcode_config_platform007.sh" + else + source "$PWD/build_tools/fbcode_config.sh" fi fi @@ -64,11 +68,23 @@ rm -f "$OUTPUT" touch "$OUTPUT" if test -z "$CC"; then - CC=cc + if [ -x "$(command -v cc)" ]; then + CC=cc + elif [ -x "$(command -v clang)" ]; then + CC=clang + else + CC=cc + fi fi if test -z "$CXX"; then - CXX=g++ + if [ -x "$(command -v g++)" ]; then + CXX=g++ + elif [ -x "$(command -v clang++)" ]; then + CXX=clang++ + else + CXX=g++ + fi fi # Detect OS @@ -85,7 +101,15 @@ if test -z "$CLANG_SCAN_BUILD"; then fi if test -z "$CLANG_ANALYZER"; then - CLANG_ANALYZER=$(which clang++ 2> /dev/null) + CLANG_ANALYZER=$(command -v clang++ 2> /dev/null) +fi + +if test -z "$FIND"; then + FIND=find +fi + +if test -z "$WATCH"; then + WATCH=watch fi COMMON_FLAGS="$COMMON_FLAGS ${CFLAGS}" @@ -122,6 +146,8 @@ case "$TARGET_OS" in COMMON_FLAGS="$COMMON_FLAGS -DOS_LINUX" if [ -z "$USE_CLANG" ]; then COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp" + else + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic" fi PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt" # PORT_FILES=port/linux/linux_specific.cc @@ -141,6 +167,7 @@ case "$TARGET_OS" in ;; FreeBSD) PLATFORM=OS_FREEBSD + CXX=clang++ COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_FREEBSD" PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread" # PORT_FILES=port/freebsd/freebsd_specific.cc @@ -153,9 +180,12 @@ case "$TARGET_OS" in ;; OpenBSD) PLATFORM=OS_OPENBSD + CXX=clang++ COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_OPENBSD" PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -pthread" # PORT_FILES=port/openbsd/openbsd_specific.cc + FIND=gfind + WATCH=gnuwatch ;; DragonFly) PLATFORM=OS_DRAGONFLYBSD @@ -170,6 +200,8 @@ case "$TARGET_OS" in COMMON_FLAGS="$COMMON_FLAGS -DCYGWIN" if [ -z "$USE_CLANG" ]; then COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp" + else + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic" fi PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt" # PORT_FILES=port/linux/linux_specific.cc @@ -202,7 +234,7 @@ else #include int main() { int fd = open("/dev/null", 0); - fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, 1024); + fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024); } EOF if [ "$?" = 0 ]; then @@ -210,119 +242,147 @@ EOF fi fi - # Test whether Snappy library is installed - # http://code.google.com/p/snappy/ - $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() {} + if ! test $ROCKSDB_DISABLE_SNAPPY; then + # Test whether Snappy library is installed + # http://code.google.com/p/snappy/ + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DSNAPPY" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lsnappy" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lsnappy" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DSNAPPY" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lsnappy" + JAVA_LDFLAGS="$JAVA_LDFLAGS -lsnappy" + fi fi - # Test whether gflags library is installed - # http://gflags.github.io/gflags/ - # check if the namespace is gflags - $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF - #include - using namespace gflags; - int main() {} + if ! test $ROCKSDB_DISABLE_GFLAGS; then + # Test whether gflags library is installed + # http://gflags.github.io/gflags/ + # check if the namespace is gflags + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF + #include + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=gflags" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags" - else - # check if namespace is google - $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF - #include - using namespace google; - int main() {} + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags" + else + # check if namespace is google + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF + #include + using namespace google; + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=google" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags" - fi + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=google" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags" + fi + fi fi - # Test whether zlib library is installed - $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() {} + if ! test $ROCKSDB_DISABLE_ZLIB; then + # Test whether zlib library is installed + $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DZLIB" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lz" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lz" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DZLIB" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lz" + JAVA_LDFLAGS="$JAVA_LDFLAGS -lz" + fi fi - # Test whether bzip library is installed - $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() {} + if ! test $ROCKSDB_DISABLE_BZIP; then + # Test whether bzip library is installed + $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DBZIP2" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbz2" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lbz2" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DBZIP2" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbz2" + JAVA_LDFLAGS="$JAVA_LDFLAGS -lbz2" + fi fi - # Test whether lz4 library is installed - $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < - #include - int main() {} + if ! test $ROCKSDB_DISABLE_LZ4; then + # Test whether lz4 library is installed + $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < + #include + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DLZ4" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -llz4" - JAVA_LDFLAGS="$JAVA_LDFLAGS -llz4" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DLZ4" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -llz4" + JAVA_LDFLAGS="$JAVA_LDFLAGS -llz4" + fi fi - # Test whether zstd library is installed - $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() {} + if ! test $ROCKSDB_DISABLE_ZSTD; then + # Test whether zstd library is installed + $CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DZSTD" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lzstd" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lzstd" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DZSTD" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lzstd" + JAVA_LDFLAGS="$JAVA_LDFLAGS -lzstd" + fi fi - # Test whether numa is available - $CXX $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null < - #include - int main() {} + if ! test $ROCKSDB_DISABLE_NUMA; then + # Test whether numa is available + $CXX $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null < + #include + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DNUMA" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lnuma" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lnuma" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DNUMA" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lnuma" + JAVA_LDFLAGS="$JAVA_LDFLAGS -lnuma" + fi fi - # Test whether tbb is available - $CXX $CFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null < - int main() {} + if ! test $ROCKSDB_DISABLE_TBB; then + # Test whether tbb is available + $CXX $CFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null < + int main() {} EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DTBB" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltbb" - JAVA_LDFLAGS="$JAVA_LDFLAGS -ltbb" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DTBB" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltbb" + JAVA_LDFLAGS="$JAVA_LDFLAGS -ltbb" + fi fi - # Test whether jemalloc is available - if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \ - 2>/dev/null; then - # This will enable some preprocessor identifiers in the Makefile - JEMALLOC=1 - # JEMALLOC can be enabled either using the flag (like here) or by - # providing direct link to the jemalloc library - WITH_JEMALLOC_FLAG=1 - else + if ! test $ROCKSDB_DISABLE_JEMALLOC; then + # Test whether jemalloc is available + if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \ + 2>/dev/null; then + # This will enable some preprocessor identifiers in the Makefile + JEMALLOC=1 + # JEMALLOC can be enabled either using the flag (like here) or by + # providing direct link to the jemalloc library + WITH_JEMALLOC_FLAG=1 + # check for JEMALLOC installed with HomeBrew + if [ "$PLATFORM" == "OS_MACOSX" ]; then + if hash brew 2>/dev/null && brew ls --versions jemalloc > /dev/null; then + JEMALLOC_VER=$(brew ls --versions jemalloc | tail -n 1 | cut -f 2 -d ' ') + JEMALLOC_INCLUDE="-I/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/include" + JEMALLOC_LIB="/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/lib/libjemalloc_pic.a" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $JEMALLOC_LIB" + JAVA_STATIC_LDFLAGS="$JAVA_STATIC_LDFLAGS $JEMALLOC_LIB" + fi + fi + fi + fi + if ! test $JEMALLOC && ! test $ROCKSDB_DISABLE_TCMALLOC; then # jemalloc is not available. Let's try tcmalloc if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null \ -ltcmalloc 2>/dev/null; then @@ -331,88 +391,111 @@ EOF fi fi - # Test whether malloc_usable_size is available - $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() { - size_t res = malloc_usable_size(0); - return 0; - } + if ! test $ROCKSDB_DISABLE_MALLOC_USABLE_SIZE; then + # Test whether malloc_usable_size is available + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() { + size_t res = malloc_usable_size(0); + return 0; + } EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE" + fi fi - # Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available - $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() { - int x = PTHREAD_MUTEX_ADAPTIVE_NP; - return 0; - } + if ! test $ROCKSDB_DISABLE_PTHREAD_MUTEX_ADAPTIVE_NP; then + # Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() { + int x = PTHREAD_MUTEX_ADAPTIVE_NP; + return 0; + } EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_PTHREAD_ADAPTIVE_MUTEX" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_PTHREAD_ADAPTIVE_MUTEX" + fi fi - # Test whether backtrace is available - $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <> - int main() { - void* frames[1]; - backtrace_symbols(frames, backtrace(frames, 1)); - return 0; - } -EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE" - else - # Test whether execinfo library is installed - $CXX $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null < + if ! test $ROCKSDB_DISABLE_BACKTRACE; then + # Test whether backtrace is available + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <> int main() { void* frames[1]; backtrace_symbols(frames, backtrace(frames, 1)); + return 0; } EOF if [ "$?" = 0 ]; then COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE" - PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lexecinfo" - JAVA_LDFLAGS="$JAVA_LDFLAGS -lexecinfo" + else + # Test whether execinfo library is installed + $CXX $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null < + int main() { + void* frames[1]; + backtrace_symbols(frames, backtrace(frames, 1)); + } +EOF + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE" + PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lexecinfo" + JAVA_LDFLAGS="$JAVA_LDFLAGS -lexecinfo" + fi fi fi - # Test if -pg is supported - $CXX $CFLAGS -pg -x c++ - -o /dev/null 2>/dev/null </dev/null </dev/null < - int main() { - int fd = open("/dev/null", 0); - sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE); - } + if ! test $ROCKSDB_DISABLE_SYNC_FILE_RANGE; then + # Test whether sync_file_range is supported for compatibility with an old glibc + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() { + int fd = open("/dev/null", 0); + sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE); + } EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_RANGESYNC_PRESENT" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_RANGESYNC_PRESENT" + fi fi - # Test whether sched_getcpu is supported - $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < - int main() { - int cpuid = sched_getcpu(); - } + if ! test $ROCKSDB_DISABLE_SCHED_GETCPU; then + # Test whether sched_getcpu is supported + $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null < + int main() { + int cpuid = sched_getcpu(); + } EOF - if [ "$?" = 0 ]; then - COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_SCHED_GETCPU_PRESENT" + if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_SCHED_GETCPU_PRESENT" + fi + fi + + if ! test $ROCKSDB_DISABLE_ALIGNED_NEW; then + # Test whether c++17 aligned-new is supported + $CXX $PLATFORM_CXXFLAGS -faligned-new -x c++ - -o /dev/null 2>/dev/null </dev/null </dev/null < + #include + int main() { + const auto a = _mm_set_epi64x(0, 0); + const auto b = _mm_set_epi64x(0, 0); + const auto c = _mm_clmulepi64_si128(a, b, 0x00); + auto d = _mm_cvtsi128_si64(c); + } +EOF +if [ "$?" = 0 ]; then + COMMON_FLAGS="$COMMON_FLAGS -DHAVE_PCLMUL" +elif test "$USE_SSE"; then + echo "warning: USE_SSE specified but compiler could not use PCLMUL intrinsics, disabling" + exit 1 fi # iOS doesn't support thread-local storage, but this check would erroneously @@ -519,6 +627,8 @@ echo "ROCKSDB_PATCH=$ROCKSDB_PATCH" >> "$OUTPUT" echo "CLANG_SCAN_BUILD=$CLANG_SCAN_BUILD" >> "$OUTPUT" echo "CLANG_ANALYZER=$CLANG_ANALYZER" >> "$OUTPUT" echo "PROFILING_FLAGS=$PROFILING_FLAGS" >> "$OUTPUT" +echo "FIND=$FIND" >> "$OUTPUT" +echo "WATCH=$WATCH" >> "$OUTPUT" # This will enable some related identifiers for the preprocessor if test -n "$JEMALLOC"; then echo "JEMALLOC=1" >> "$OUTPUT" diff --git a/thirdparty/rocksdb/build_tools/cont_integration.sh b/thirdparty/rocksdb/build_tools/cont_integration.sh index 06f25c596e..66d2552278 100755 --- a/thirdparty/rocksdb/build_tools/cont_integration.sh +++ b/thirdparty/rocksdb/build_tools/cont_integration.sh @@ -13,10 +13,12 @@ error=0 function log { DATE=`date +%Y-%m-%d:%H:%M:%S` + # shellcheck disable=SC2068 echo $DATE $@ } function log_err { + # shellcheck disable=SC2145 log "ERROR: $@ Error code: $error." } diff --git a/thirdparty/rocksdb/build_tools/dependencies.sh b/thirdparty/rocksdb/build_tools/dependencies.sh index 868753b8a7..868e0bbddf 100644 --- a/thirdparty/rocksdb/build_tools/dependencies.sh +++ b/thirdparty/rocksdb/build_tools/dependencies.sh @@ -1,18 +1,18 @@ -GCC_BASE=/mnt/gvfs/third-party2/gcc/2928bb3ed95bf64f5b388ee88c30dc74710c3b35/5.x/centos6-native/f4950a1 -CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/a5fea028cb7ba43498976e1f8054b0b2e790c295/stable/centos6-native/6aaf4de -LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/7a9099f6587ee4378c0b1fa32bb8934019d30ca4/5.x/gcc-5-glibc-2.23/339d858 -GLIBC_BASE=/mnt/gvfs/third-party2/glibc/3b7c6469854dfc7832a1c3cc5b86919a84e5f865/2.23/gcc-5-glibc-2.23/ca1d1c0 -SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-5-glibc-2.23/9bc6787 -ZLIB_BASE=/mnt/gvfs/third-party2/zlib/d7861abe6f0e27ab98c9303b95a662f0e4cdedb5/1.2.8/gcc-5-glibc-2.23/9bc6787 -BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/740325875f6729f42d28deaa2147b0854f3a347e/1.0.6/gcc-5-glibc-2.23/9bc6787 -LZ4_BASE=/mnt/gvfs/third-party2/lz4/0815d59804160c96caac5f27ca004f51af893dc6/r131/gcc-5-glibc-2.23/9bc6787 -ZSTD_BASE=/mnt/gvfs/third-party2/zstd/c15a4f5f619a2930478d01e2e34dc1e0652b0873/1.1.4/gcc-5-glibc-2.23/03859b5 -GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/f905a5e1032fb30c05db3d3752319857388c0c49/2.2.0/gcc-5-glibc-2.23/9bc6787 -JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/8d60633d822a2a55849c73db24e74a25e52b71db/master/gcc-5-glibc-2.23/1c32b4b -NUMA_BASE=/mnt/gvfs/third-party2/numa/17c514c4d102a25ca15f4558be564eeed76f4b6a/2.0.8/gcc-5-glibc-2.23/9bc6787 -LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/8db74270cd6d0212ac92d69e7fc7beefe617d772/trunk/gcc-5-glibc-2.23/b1847cb -TBB_BASE=/mnt/gvfs/third-party2/tbb/9d9a554877d0c5bef330fe818ab7178806dd316a/4.0_update2/gcc-5-glibc-2.23/9bc6787 -KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/90c9734afc5579c9d1db529fa788d09f97763b85/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e -BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/9e829389ef61b92c62de8748c80169aaf25ce1f0/2.26.1/centos6-native/da39a3e -VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d7f4d4d86674a57668e3a96f76f0e17dd0eb8765/3.11.0/gcc-5-glibc-2.23/9bc6787 -LUA_BASE=/mnt/gvfs/third-party2/lua/61e4abf5813bbc39bc4f548757ccfcadde175a48/5.2.3/gcc-5-glibc-2.23/65372bd +GCC_BASE=/mnt/gvfs/third-party2/gcc/112ec378fec7002ad3e09afde022e656049f7191/5.x/centos7-native/c447969 +CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/04999bdb3ce81a11073535dcb00b5e13dc1cbaf5/stable/centos7-native/c9f9104 +LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/92b0c8e5c8eecc71eb042594ce1ab3413799b385/5.x/gcc-5-glibc-2.23/339d858 +GLIBC_BASE=/mnt/gvfs/third-party2/glibc/3d8698d5973ba94f41620a80a67e4457fdf01e90/2.23/gcc-5-glibc-2.23/ca1d1c0 +SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/gcc-5-glibc-2.23/9bc6787 +ZLIB_BASE=/mnt/gvfs/third-party2/zlib/22c2d65676fb7c23cfa797c4f6937f38b026f3cf/1.2.8/gcc-5-glibc-2.23/9bc6787 +BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/gcc-5-glibc-2.23/9bc6787 +LZ4_BASE=/mnt/gvfs/third-party2/lz4/907b498203d297947f3bb70b9466f47e100f1873/r131/gcc-5-glibc-2.23/9bc6787 +ZSTD_BASE=/mnt/gvfs/third-party2/zstd/af6628a46758f1a15484a1760cd7294164bc5ba1/1.3.5/gcc-5-glibc-2.23/03859b5 +GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/gcc-5-glibc-2.23/9bc6787 +JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/b1a0e56c1e3e6929813a4331ade3a58ff083afbb/master/gcc-5-glibc-2.23/aa64d6b +NUMA_BASE=/mnt/gvfs/third-party2/numa/9cbf2460284c669ed19c3ccb200a71f7dd7e53c7/2.0.11/gcc-5-glibc-2.23/9bc6787 +LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/bf3d7497fe4e6d007354f0adffa16ce3003f8338/1.3/gcc-5-glibc-2.23/b443de1 +TBB_BASE=/mnt/gvfs/third-party2/tbb/ff4e0b093534704d8abab678a4fd7f5ea7b094c7/2018_U5/gcc-5-glibc-2.23/9bc6787 +KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/b5c4a61a5c483ba24722005ae07895971a2ac707/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e +BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/55031de95a2b46c82948743419a603b3d6aefe28/2.29.1/centos7-native/da39a3e +VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/f3f697a28122e6bcd513273dd9c1ff23852fc59f/3.13.0/gcc-5-glibc-2.23/9bc6787 +LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.2.3/gcc-5-glibc-2.23/65372bd diff --git a/thirdparty/rocksdb/build_tools/dependencies_4.8.1.sh b/thirdparty/rocksdb/build_tools/dependencies_4.8.1.sh index ef0cda2398..bd02165d8a 100644 --- a/thirdparty/rocksdb/build_tools/dependencies_4.8.1.sh +++ b/thirdparty/rocksdb/build_tools/dependencies_4.8.1.sh @@ -1,3 +1,4 @@ +# shellcheck disable=SC2148 GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.8.1/centos6-native/cc6c9dc CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc diff --git a/thirdparty/rocksdb/build_tools/dependencies_platform007.sh b/thirdparty/rocksdb/build_tools/dependencies_platform007.sh new file mode 100644 index 0000000000..44e9e58f8e --- /dev/null +++ b/thirdparty/rocksdb/build_tools/dependencies_platform007.sh @@ -0,0 +1,18 @@ +GCC_BASE=/mnt/gvfs/third-party2/gcc/6e8e715624fd15256a7970073387793dfcf79b46/7.x/centos7-native/b2ef2b6 +CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/ef37e1faa1c29782abfac1ae65a291b9b7966f6d/stable/centos7-native/c9f9104 +LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/c67031f0f739ac61575a061518d6ef5038f99f90/7.x/platform007/5620abc +GLIBC_BASE=/mnt/gvfs/third-party2/glibc/60d6f124a78798b73944f5ba87c2306ae3460153/2.26/platform007/f259413 +SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d +ZLIB_BASE=/mnt/gvfs/third-party2/zlib/22c2d65676fb7c23cfa797c4f6937f38b026f3cf/1.2.8/platform007/ca4da3d +BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d +LZ4_BASE=/mnt/gvfs/third-party2/lz4/907b498203d297947f3bb70b9466f47e100f1873/r131/platform007/ca4da3d +ZSTD_BASE=/mnt/gvfs/third-party2/zstd/3ee276cbacfad3074e3f07bf826ac47f06970f4e/1.3.5/platform007/15a3614 +GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d +JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/9c910d36d6235cc40e8ff559358f1833452300ca/master/platform007/5b0f53e +NUMA_BASE=/mnt/gvfs/third-party2/numa/9cbf2460284c669ed19c3ccb200a71f7dd7e53c7/2.0.11/platform007/ca4da3d +LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/bf3d7497fe4e6d007354f0adffa16ce3003f8338/1.3/platform007/6f3e0a9 +TBB_BASE=/mnt/gvfs/third-party2/tbb/ff4e0b093534704d8abab678a4fd7f5ea7b094c7/2018_U5/platform007/ca4da3d +KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/b5c4a61a5c483ba24722005ae07895971a2ac707/fb/platform007/da39a3e +BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/92ff90349e2f43ea0a8246d8b1cf17b6869013e3/2.29.1/centos7-native/da39a3e +VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/f3f697a28122e6bcd513273dd9c1ff23852fc59f/3.13.0/platform007/ca4da3d +LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832 diff --git a/thirdparty/rocksdb/build_tools/error_filter.py b/thirdparty/rocksdb/build_tools/error_filter.py index 9f619cf4ba..5ef1e9c269 100644 --- a/thirdparty/rocksdb/build_tools/error_filter.py +++ b/thirdparty/rocksdb/build_tools/error_filter.py @@ -64,8 +64,12 @@ def parse_error(self, line): class CompilerErrorParser(MatchErrorParser): def __init__(self): - # format: '::: error: ' - super(CompilerErrorParser, self).__init__(r'\S+:\d+:\d+: error:') + # format (compile error): + # '::: error: ' + # format (link error): + # ':: error: ' + # The below regex catches both + super(CompilerErrorParser, self).__init__(r'\S+:\d+: error:') class ScanBuildErrorParser(MatchErrorParser): @@ -128,11 +132,14 @@ def __init__(self): 'lite': [CompilerErrorParser], 'lite_test': [CompilerErrorParser, GTestErrorParser], 'stress_crash': [CompilerErrorParser, DbCrashErrorParser], + 'stress_crash_with_atomic_flush': [CompilerErrorParser, DbCrashErrorParser], 'write_stress': [CompilerErrorParser, WriteStressErrorParser], 'asan': [CompilerErrorParser, GTestErrorParser, AsanErrorParser], 'asan_crash': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser], + 'asan_crash_with_atomic_flush': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser], 'ubsan': [CompilerErrorParser, GTestErrorParser, UbsanErrorParser], 'ubsan_crash': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser], + 'ubsan_crash_with_atomic_flush': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser], 'valgrind': [CompilerErrorParser, GTestErrorParser, ValgrindErrorParser], 'tsan': [CompilerErrorParser, GTestErrorParser, TsanErrorParser], 'format_compatible': [CompilerErrorParser, CompatErrorParser], diff --git a/thirdparty/rocksdb/build_tools/fbcode_config.sh b/thirdparty/rocksdb/build_tools/fbcode_config.sh index b8609a11c6..f46a580bde 100644 --- a/thirdparty/rocksdb/build_tools/fbcode_config.sh +++ b/thirdparty/rocksdb/build_tools/fbcode_config.sh @@ -43,11 +43,15 @@ if test -z $PIC_BUILD; then LZ4_INCLUDE=" -I $LZ4_BASE/include/" LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a" CFLAGS+=" -DLZ4" +fi - ZSTD_INCLUDE=" -I $ZSTD_BASE/include/" +ZSTD_INCLUDE=" -I $ZSTD_BASE/include/" +if test -z $PIC_BUILD; then ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a" - CFLAGS+=" -DZSTD" +else + ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a" fi +CFLAGS+=" -DZSTD -DZSTD_STATIC_LINKING_ONLY" # location of gflags headers and libraries GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/" @@ -83,6 +87,7 @@ CFLAGS+=" -DTBB" # use Intel SSE support for checksum calculations export USE_SSE=1 +export PORTABLE=1 BINUTILS="$BINUTILS_BASE/bin" AR="$BINUTILS/ar" diff --git a/thirdparty/rocksdb/build_tools/fbcode_config4.8.1.sh b/thirdparty/rocksdb/build_tools/fbcode_config4.8.1.sh index f5b8334db2..c40c10131a 100644 --- a/thirdparty/rocksdb/build_tools/fbcode_config4.8.1.sh +++ b/thirdparty/rocksdb/build_tools/fbcode_config4.8.1.sh @@ -54,6 +54,7 @@ TBB_LIBS="$TBB_BASE/lib/libtbb.a" # use Intel SSE support for checksum calculations export USE_SSE=1 +export PORTABLE=1 BINUTILS="$BINUTILS_BASE/bin" AR="$BINUTILS/ar" diff --git a/thirdparty/rocksdb/build_tools/fbcode_config_platform007.sh b/thirdparty/rocksdb/build_tools/fbcode_config_platform007.sh new file mode 100644 index 0000000000..67d156e4c9 --- /dev/null +++ b/thirdparty/rocksdb/build_tools/fbcode_config_platform007.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Set environment variables so that we can compile rocksdb using +# fbcode settings. It uses the latest g++ and clang compilers and also +# uses jemalloc +# Environment variables that change the behavior of this script: +# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included + + +BASEDIR=`dirname $BASH_SOURCE` +source "$BASEDIR/dependencies_platform007.sh" + +CFLAGS="" + +# libgcc +LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0" +LIBGCC_LIBS=" -L $LIBGCC_BASE/lib" + +# glibc +GLIBC_INCLUDE="$GLIBC_BASE/include" +GLIBC_LIBS=" -L $GLIBC_BASE/lib" + +# snappy +SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/" +if test -z $PIC_BUILD; then + SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a" +else + SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a" +fi +CFLAGS+=" -DSNAPPY" + +if test -z $PIC_BUILD; then + # location of zlib headers and libraries + ZLIB_INCLUDE=" -I $ZLIB_BASE/include/" + ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a" + CFLAGS+=" -DZLIB" + + # location of bzip headers and libraries + BZIP_INCLUDE=" -I $BZIP2_BASE/include/" + BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a" + CFLAGS+=" -DBZIP2" + + LZ4_INCLUDE=" -I $LZ4_BASE/include/" + LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a" + CFLAGS+=" -DLZ4" +fi + +ZSTD_INCLUDE=" -I $ZSTD_BASE/include/" +if test -z $PIC_BUILD; then + ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a" +else + ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a" +fi +CFLAGS+=" -DZSTD" + +# location of gflags headers and libraries +GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/" +if test -z $PIC_BUILD; then + GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a" +else + GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a" +fi +CFLAGS+=" -DGFLAGS=gflags" + +# location of jemalloc +JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/" +JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a" + +if test -z $PIC_BUILD; then + # location of numa + NUMA_INCLUDE=" -I $NUMA_BASE/include/" + NUMA_LIB=" $NUMA_BASE/lib/libnuma.a" + CFLAGS+=" -DNUMA" + + # location of libunwind + LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a" +fi + +# location of TBB +TBB_INCLUDE=" -isystem $TBB_BASE/include/" +if test -z $PIC_BUILD; then + TBB_LIBS="$TBB_BASE/lib/libtbb.a" +else + TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a" +fi +CFLAGS+=" -DTBB" + +# use Intel SSE support for checksum calculations +export USE_SSE=1 +export PORTABLE=1 + +BINUTILS="$BINUTILS_BASE/bin" +AR="$BINUTILS/ar" + +DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE" + +STDLIBS="-L $GCC_BASE/lib64" + +CLANG_BIN="$CLANG_BASE/bin" +CLANG_LIB="$CLANG_BASE/lib" +CLANG_SRC="$CLANG_BASE/../../src" + +CLANG_ANALYZER="$CLANG_BIN/clang++" +CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build" + +if [ -z "$USE_CLANG" ]; then + # gcc + CC="$GCC_BASE/bin/gcc" + CXX="$GCC_BASE/bin/g++" + + CFLAGS+=" -B$BINUTILS/gold" + CFLAGS+=" -isystem $LIBGCC_INCLUDE" + CFLAGS+=" -isystem $GLIBC_INCLUDE" + JEMALLOC=1 +else + # clang + CLANG_INCLUDE="$CLANG_LIB/clang/stable/include" + CC="$CLANG_BIN/clang" + CXX="$CLANG_BIN/clang++" + + KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include" + + CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib" + CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x " + CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x/x86_64-facebook-linux " + CFLAGS+=" -isystem $GLIBC_INCLUDE" + CFLAGS+=" -isystem $LIBGCC_INCLUDE" + CFLAGS+=" -isystem $CLANG_INCLUDE" + CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux " + CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE " + CFLAGS+=" -Wno-expansion-to-defined " + CXXFLAGS="-nostdinc++" +fi + +CFLAGS+=" $DEPS_INCLUDE" +CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42" +CXXFLAGS+=" $CFLAGS" + +EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS" +EXEC_LDFLAGS+=" -B$BINUTILS/gold" +EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so" +EXEC_LDFLAGS+=" $LIBUNWIND" +EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib" +# required by libtbb +EXEC_LDFLAGS+=" -ldl" + +PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++" + +EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS" + +VALGRIND_VER="$VALGRIND_BASE/bin/" + +# lua not supported because it's on track for deprecation, I think +LUA_PATH= +LUA_LIB= + +export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB diff --git a/thirdparty/rocksdb/build_tools/gnu_parallel b/thirdparty/rocksdb/build_tools/gnu_parallel index abbf8f1008..1cf164fff0 100755 --- a/thirdparty/rocksdb/build_tools/gnu_parallel +++ b/thirdparty/rocksdb/build_tools/gnu_parallel @@ -5082,8 +5082,8 @@ sub openoutputfiles { # Set reading FD if using --group (--ungroup does not need) for my $fdno (1,2) { # Re-open the file for reading - # so fdw can be closed seperately - # and fdr can be seeked seperately (for --line-buffer) + # so fdw can be closed separately + # and fdr can be seeked separately (for --line-buffer) open(my $fdr,"<", $self->fh($fdno,'name')) || ::die_bug("fdr: Cannot open ".$self->fh($fdno,'name')); $self->set_fh($fdno,'r',$fdr); diff --git a/thirdparty/rocksdb/build_tools/make_package.sh b/thirdparty/rocksdb/build_tools/make_package.sh index 58bac44739..0d86548e82 100755 --- a/thirdparty/rocksdb/build_tools/make_package.sh +++ b/thirdparty/rocksdb/build_tools/make_package.sh @@ -1,3 +1,4 @@ +# shellcheck disable=SC1113 #/usr/bin/env bash set -e @@ -28,12 +29,14 @@ function package() { if dpkg --get-selections | grep --quiet $1; then log "$1 is already installed. skipping." else + # shellcheck disable=SC2068 apt-get install $@ -y fi elif [[ $OS = "centos" ]]; then if rpm -qa | grep --quiet $1; then log "$1 is already installed. skipping." else + # shellcheck disable=SC2068 yum install $@ -y fi fi @@ -52,6 +55,7 @@ function gem_install() { if gem list | grep --quiet $1; then log "$1 is already installed. skipping." else + # shellcheck disable=SC2068 gem install $@ fi } @@ -125,4 +129,5 @@ function main() { include $LIB_DIR } +# shellcheck disable=SC2068 main $@ diff --git a/thirdparty/rocksdb/build_tools/rocksdb-lego-determinator b/thirdparty/rocksdb/build_tools/rocksdb-lego-determinator index 6e8ae9cd73..2447a19ae4 100755 --- a/thirdparty/rocksdb/build_tools/rocksdb-lego-determinator +++ b/thirdparty/rocksdb/build_tools/rocksdb-lego-determinator @@ -85,9 +85,12 @@ NON_SHM="TMPD=/tmp/rocksdb_test_tmp" GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1" ASAN="COMPILE_WITH_ASAN=1" CLANG="USE_CLANG=1" -LITE="OPT=\"-DROCKSDB_LITE -g\"" -TSAN="COMPILE_WITH_TSAN=1" +# in gcc-5 there are known problems with TSAN like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71090. +# using platform007 gives us gcc-8 or higher which has that bug fixed. +TSAN="ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007=1 COMPILE_WITH_TSAN=1" UBSAN="COMPILE_WITH_UBSAN=1" +TSAN_CRASH='CRASH_TEST_EXT_ARGS="--compression_type=zstd --log2_keys_per_lock=22"' +NON_TSAN_CRASH="CRASH_TEST_EXT_ARGS=--compression_type=zstd" DISABLE_JEMALLOC="DISABLE_JEMALLOC=1" HTTP_PROXY="https_proxy=http://fwdproxy.29.prn1:8080 http_proxy=http://fwdproxy.29.prn1:8080 ftp_proxy=http://fwdproxy.29.prn1:8080" SETUP_JAVA_ENV="export $HTTP_PROXY; export JAVA_HOME=/usr/local/jdk-8u60-64/; export PATH=\$JAVA_HOME/bin:\$PATH" @@ -343,7 +346,7 @@ LITE_BUILD_COMMANDS="[ $CLEANUP_ENV, { 'name':'Build RocksDB debug version', - 'shell':'$LITE make J=1 all check || $CONTRUN_NAME=lite $TASK_CREATION_TOOL', + 'shell':'make J=1 LITE=1 all check || $CONTRUN_NAME=lite $TASK_CREATION_TOOL', 'user':'root', $PARSER }, @@ -352,6 +355,22 @@ LITE_BUILD_COMMANDS="[ } ]" +# +# Report RocksDB lite binary size to scuba +REPORT_LITE_BINARY_SIZE_COMMANDS="[ + { + 'name':'Rocksdb Lite Binary Size', + 'oncall':'$ONCALL', + 'steps': [ + $CLEANUP_ENV, + { + 'name':'Report RocksDB Lite binary size to scuba', + 'shell':'tools/report_lite_binary_size.sh', + 'user':'root', + }, + ], +]" + # # RocksDB stress/crash test # @@ -364,14 +383,43 @@ STRESS_CRASH_TEST_COMMANDS="[ $CLEANUP_ENV, { 'name':'Build and run RocksDB debug stress tests', - 'shell':'$SHM $DEBUG make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL', + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL', 'user':'root', $PARSER }, { 'name':'Build and run RocksDB debug crash tests', 'timeout': 86400, - 'shell':'$SHM $DEBUG make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL', + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL', + 'user':'root', + $PARSER + } + ], + $ARTIFACTS, + $REPORT + } +]" + +# +# RocksDB stress/crash test with atomic flush +# +STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[ + { + 'name':'Rocksdb Stress/Crash Test (atomic flush)', + 'oncall':'$ONCALL', + 'timeout': 86400, + 'steps': [ + $CLEANUP_ENV, + { + 'name':'Build and run RocksDB debug stress tests', + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL', + 'user':'root', + $PARSER + }, + { + 'name':'Build and run RocksDB debug crash tests with atomic flush', + 'timeout': 86400, + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=crash_test_with_atomic_flush $TASK_CREATION_TOOL', 'user':'root', $PARSER } @@ -436,7 +484,29 @@ ASAN_CRASH_TEST_COMMANDS="[ { 'name':'Build and run RocksDB debug asan_crash_test', 'timeout': 86400, - 'shell':'$SHM $DEBUG make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL', + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL', + 'user':'root', + $PARSER + }, + ], + $REPORT + } +]" + +# +# RocksDB crash testing with atomic flush under address sanitizer +# +ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[ + { + 'name':'Rocksdb crash test (atomic flush) under ASAN', + 'oncall':'$ONCALL', + 'timeout': 86400, + 'steps': [ + $CLEANUP_ENV, + { + 'name':'Build and run RocksDB debug asan_crash_test_with_atomic_flush', + 'timeout': 86400, + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test_with_atomic_flush || $CONTRUN_NAME=asan_crash_test_with_atomic_flush $TASK_CREATION_TOOL', 'user':'root', $PARSER }, @@ -478,7 +548,29 @@ UBSAN_CRASH_TEST_COMMANDS="[ { 'name':'Build and run RocksDB debug ubsan_crash_test', 'timeout': 86400, - 'shell':'$SHM $DEBUG make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL', + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL', + 'user':'root', + $PARSER + }, + ], + $REPORT + } +]" + +# +# RocksDB crash testing with atomic flush under undefined behavior sanitizer +# +UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[ + { + 'name':'Rocksdb crash test (atomic flush) under UBSAN', + 'oncall':'$ONCALL', + 'timeout': 86400, + 'steps': [ + $CLEANUP_ENV, + { + 'name':'Build and run RocksDB debug ubsan_crash_test_with_atomic_flush', + 'timeout': 86400, + 'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 ubsan_crash_test_with_atomic_flush || $CONTRUN_NAME=ubsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL', 'user':'root', $PARSER }, @@ -544,7 +636,29 @@ TSAN_CRASH_TEST_COMMANDS="[ { 'name':'Compile and run', 'timeout': 86400, - 'shell':'set -o pipefail && $SHM $DEBUG $TSAN CRASH_TEST_KILL_ODD=1887 CRASH_TEST_EXT_ARGS=--log2_keys_per_lock=22 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL', + 'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL', + 'user':'root', + $PARSER + }, + ], + $REPORT + } +]" + +# +# RocksDB crash test with atomic flush under TSAN +# +TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[ + { + 'name':'Rocksdb Crash Test with atomic flush under TSAN', + 'oncall':'$ONCALL', + 'timeout': 86400, + 'steps': [ + $CLEANUP_ENV, + { + 'name':'Compile and run', + 'timeout': 86400, + 'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=tsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL', 'user':'root', $PARSER }, @@ -592,7 +706,7 @@ run_no_compression() rm -rf /dev/shm/rocksdb mkdir /dev/shm/rocksdb make clean - cat build_tools/fbcode_config.sh | grep -iv dzlib | grep -iv dlz4 | grep -iv dsnappy | grep -iv dbzip2 > .tmp.fbcode_config.sh + cat build_tools/fbcode_config.sh | grep -iv dzstd | grep -iv dzlib | grep -iv dlz4 | grep -iv dsnappy | grep -iv dbzip2 > .tmp.fbcode_config.sh mv .tmp.fbcode_config.sh build_tools/fbcode_config.sh cat Makefile | grep -v tools/ldb_test.py > .tmp.Makefile mv .tmp.Makefile Makefile @@ -645,12 +759,12 @@ run_regression() # === lite build === make clean - OPT=-DROCKSDB_LITE make -j$(nproc) static_lib + make LITE=1 -j$(nproc) static_lib send_size_to_ods static_lib_lite $(stat --printf="%s" librocksdb.a) strip librocksdb.a send_size_to_ods static_lib_lite_stripped $(stat --printf="%s" librocksdb.a) - OPT=-DROCKSDB_LITE make -j$(nproc) shared_lib + make LITE=1 -j$(nproc) shared_lib send_size_to_ods shared_lib_lite $(stat --printf="%s" `readlink -f librocksdb.so`) strip `readlink -f librocksdb.so` send_size_to_ods shared_lib_lite_stripped $(stat --printf="%s" `readlink -f librocksdb.so`) @@ -728,9 +842,15 @@ case $1 in lite) echo $LITE_BUILD_COMMANDS ;; + report_lite_binary_size) + echo $REPORT_LITE_BINARY_SIZE_COMMANDS + ;; stress_crash) echo $STRESS_CRASH_TEST_COMMANDS ;; + stress_crash_with_atomic_flush) + echo $STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS + ;; write_stress) echo $WRITE_STRESS_COMMANDS ;; @@ -740,12 +860,18 @@ case $1 in asan_crash) echo $ASAN_CRASH_TEST_COMMANDS ;; + asan_crash_with_atomic_flush) + echo $ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS + ;; ubsan) echo $UBSAN_TEST_COMMANDS ;; ubsan_crash) echo $UBSAN_CRASH_TEST_COMMANDS ;; + ubsan_crash_with_atomic_flush) + echo $UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS + ;; valgrind) echo $VALGRIND_TEST_COMMANDS ;; @@ -755,6 +881,9 @@ case $1 in tsan_crash) echo $TSAN_CRASH_TEST_COMMANDS ;; + tsan_crash_with_atomic_flush) + echo $TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS + ;; format_compatible) echo $FORMAT_COMPATIBLE_COMMANDS ;; diff --git a/thirdparty/rocksdb/build_tools/run_ci_db_test.ps1 b/thirdparty/rocksdb/build_tools/run_ci_db_test.ps1 index c8167ed957..0f8198b484 100644 --- a/thirdparty/rocksdb/build_tools/run_ci_db_test.ps1 +++ b/thirdparty/rocksdb/build_tools/run_ci_db_test.ps1 @@ -1,13 +1,16 @@ # This script enables you running RocksDB tests by running # All the tests concurrently and utilizing all the cores Param( - [switch]$EnableJE = $false, # Look for and use _je executable, append _je to listed exclusions + [switch]$EnableJE = $false, # Look for and use test executable, append _je to listed exclusions [switch]$RunAll = $false, # Will attempt discover all *_test[_je].exe binaries and run all # of them as Google suites. I.e. It will run test cases concurrently # except those mentioned as $Run, those will run as individual test cases # And any execlued with $ExcludeExes or $ExcludeCases # It will also not run any individual test cases # excluded but $ExcludeCasese + [switch]$RunAllExe = $false, # Look for and use test exdcutables, append _je to exclusions automatically + # It will attempt to run them in parallel w/o breaking them up on individual + # test cases. Those listed with $ExcludeExes will be excluded [string]$SuiteRun = "", # Split test suites in test cases and run in parallel, not compatible with $RunAll [string]$Run = "", # Run specified executables in parallel but do not split to test cases [string]$ExcludeCases = "", # Exclude test cases, expects a comma separated list, no spaces @@ -39,13 +42,18 @@ $RunOnly.Add("compact_on_deletion_collector_test") | Out-Null $RunOnly.Add("merge_test") | Out-Null $RunOnly.Add("stringappend_test") | Out-Null # Apparently incorrectly written $RunOnly.Add("backupable_db_test") | Out-Null # Disabled - +$RunOnly.Add("timer_queue_test") | Out-Null # Not a gtest if($RunAll -and $SuiteRun -ne "") { Write-Error "$RunAll and $SuiteRun are not compatible" exit 1 } +if($RunAllExe -and $Run -ne "") { + Write-Error "$RunAllExe and $Run are not compatible" + exit 1 +} + # If running under Appveyor assume that root [string]$Appveyor = $Env:APPVEYOR_BUILD_FOLDER if($Appveyor -ne "") { @@ -131,12 +139,8 @@ function ExtractTestCases([string]$GTestExe, $HashTable) { # Leading whitespace is fine $l = $l -replace '^\s+','' - # but no whitespace any other place - if($l -match "\s+") { - continue - } # Trailing dot is a test group but no whitespace - elseif ( $l -match "\.$" ) { + if ($l -match "\.$" -and $l -notmatch "\s+") { $Group = $l } else { # Otherwise it is a test name, remove leading space @@ -223,13 +227,11 @@ $TestExes = [ordered]@{} if($Run -ne "") { $test_list = $Run -split ' ' - ForEach($t in $test_list) { if($EnableJE) { $t += "_je" } - MakeAndAdd -token $t -HashTable $TestExes } @@ -237,6 +239,38 @@ if($Run -ne "") { Write-Error "Failed to extract tests from $Run" exit 1 } +} elseif($RunAllExe) { + # Discover all the test binaries + if($EnableJE) { + $pattern = "*_test_je.exe" + } else { + $pattern = "*_test.exe" + } + + $search_path = -join ($BinariesFolder, $pattern) + Write-Host "Binaries Search Path: $search_path" + + $DiscoveredExe = @() + dir -Path $search_path | ForEach-Object { + $DiscoveredExe += ($_.Name) + } + + # Remove exclusions + ForEach($e in $DiscoveredExe) { + $e = $e -replace '.exe$', '' + $bare_name = $e -replace '_je$', '' + + if($ExcludeExesSet.Contains($bare_name)) { + Write-Warning "Test $e is excluded" + continue + } + MakeAndAdd -token $e -HashTable $TestExes + } + + if($TestExes.Count -lt 1) { + Write-Error "Failed to discover test executables" + exit 1 + } } # Ordered by exe @{ Exe = @{ TestCase = LogName }} @@ -245,9 +279,7 @@ $CasesToRun = [ordered]@{} if($SuiteRun -ne "") { $suite_list = $SuiteRun -split ' ' ProcessSuites -ListOfSuites $suite_list -HashOfHashes $CasesToRun -} - -if($RunAll) { +} elseif ($RunAll) { # Discover all the test binaries if($EnableJE) { $pattern = "*_test_je.exe" @@ -255,7 +287,6 @@ if($RunAll) { $pattern = "*_test.exe" } - $search_path = -join ($BinariesFolder, $pattern) Write-Host "Binaries Search Path: $search_path" @@ -287,8 +318,6 @@ if($RunAll) { } -Write-Host "Attempting to start: $NumTestsToStart tests" - # Invoke a test with a filter and redirect all output $InvokeTestCase = { param($exe, $test, $log); @@ -307,7 +336,7 @@ $InvokeTestAsync = { # Test limiting factor here [int]$count = 0 # Overall status -[bool]$success = $true; +[bool]$script:success = $true; function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal) { @@ -365,6 +394,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal) break } + Write-Host "Starting $exe_name" [string]$Exe = -Join ($BinariesFolder, $exe_name) $job = Start-Job -Name $exe_name -ScriptBlock $InvokeTestAsync -ArgumentList @($Exe,$log_path) $JobToLog.Add($job, $log_path) @@ -395,7 +425,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal) $log_content = @(Get-Content $log) if($completed.State -ne "Completed") { - $success = $false + $script:success = $false Write-Warning $message $log_content | Write-Warning } else { @@ -419,7 +449,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal) } if(!$pass_found) { - $success = $false; + $script:success = $false; Write-Warning $message $log_content | Write-Warning } else { @@ -443,7 +473,7 @@ New-TimeSpan -Start $StartDate -End $EndDate | } -if(!$success) { +if(!$script:success) { # This does not succeed killing off jobs quick # So we simply exit # Remove-Job -Job $jobs -Force diff --git a/thirdparty/rocksdb/build_tools/setup_centos7.sh b/thirdparty/rocksdb/build_tools/setup_centos7.sh new file mode 100755 index 0000000000..c633131de8 --- /dev/null +++ b/thirdparty/rocksdb/build_tools/setup_centos7.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +ROCKSDB_VERSION="5.10.3" +ZSTD_VERSION="1.1.3" + +echo "This script configures CentOS with everything needed to build and run RocksDB" + +yum update -y && yum install epel-release -y + +yum install -y \ + wget \ + gcc-c++ \ + snappy snappy-devel \ + zlib zlib-devel \ + bzip2 bzip2-devel \ + lz4-devel \ + libasan \ + gflags + +mkdir -pv /usr/local/rocksdb-${ROCKSDB_VERSION} +ln -sfT /usr/local/rocksdb-${ROCKSDB_VERSION} /usr/local/rocksdb + +wget -qO /tmp/zstd-${ZSTD_VERSION}.tar.gz https://github.com/facebook/zstd/archive/v${ZSTD_VERSION}.tar.gz +wget -qO /tmp/rocksdb-${ROCKSDB_VERSION}.tar.gz https://github.com/facebook/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz + +cd /tmp + +tar xzvf zstd-${ZSTD_VERSION}.tar.gz +tar xzvf rocksdb-${ROCKSDB_VERSION}.tar.gz -C /usr/local/ + +echo "Installing ZSTD..." +pushd zstd-${ZSTD_VERSION} +make && make install +popd + +echo "Compiling RocksDB..." +cd /usr/local/rocksdb +chown -R vagrant:vagrant /usr/local/rocksdb/ +sudo -u vagrant make static_lib +cd examples/ +sudo -u vagrant make all +sudo -u vagrant ./c_simple_example diff --git a/thirdparty/rocksdb/build_tools/update_dependencies.sh b/thirdparty/rocksdb/build_tools/update_dependencies.sh index c7b9932646..b060544dfd 100755 --- a/thirdparty/rocksdb/build_tools/update_dependencies.sh +++ b/thirdparty/rocksdb/build_tools/update_dependencies.sh @@ -38,6 +38,7 @@ function get_lib_base() # platform is not provided, use latest gcc result=`ls -dr1v $result/gcc-*[^fb]/ | head -n1` else + echo $lib_platform result="$result/$lib_platform/" fi @@ -52,6 +53,45 @@ function get_lib_base() log_variable $__res_var } +########################################################### +# platform007 dependencies # +########################################################### + +OUTPUT="$BASEDIR/dependencies_platform007.sh" + +rm -f "$OUTPUT" +touch "$OUTPUT" + +echo "Writing dependencies to $OUTPUT" + +# Compilers locations +GCC_BASE=`readlink -f $TP2_LATEST/gcc/7.x/centos7-native/*/` +CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/` + +log_variable GCC_BASE +log_variable CLANG_BASE + +# Libraries locations +get_lib_base libgcc 7.x platform007 +get_lib_base glibc 2.26 platform007 +get_lib_base snappy LATEST platform007 +get_lib_base zlib LATEST platform007 +get_lib_base bzip2 LATEST platform007 +get_lib_base lz4 LATEST platform007 +get_lib_base zstd LATEST platform007 +get_lib_base gflags LATEST platform007 +get_lib_base jemalloc LATEST platform007 +get_lib_base numa LATEST platform007 +get_lib_base libunwind LATEST platform007 +get_lib_base tbb LATEST platform007 + +get_lib_base kernel-headers fb platform007 +get_lib_base binutils LATEST centos7-native +get_lib_base valgrind LATEST platform007 +get_lib_base lua 5.3.4 platform007 + +git diff $OUTPUT + ########################################################### # 5.x dependencies # ########################################################### @@ -64,29 +104,29 @@ touch "$OUTPUT" echo "Writing dependencies to $OUTPUT" # Compilers locations -GCC_BASE=`readlink -f $TP2_LATEST/gcc/5.x/centos6-native/*/` -CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/` +GCC_BASE=`readlink -f $TP2_LATEST/gcc/5.x/centos7-native/*/` +CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/` log_variable GCC_BASE log_variable CLANG_BASE # Libraries locations -get_lib_base libgcc 5.x -get_lib_base glibc 2.23 -get_lib_base snappy LATEST gcc-5-glibc-2.23 -get_lib_base zlib LATEST -get_lib_base bzip2 LATEST -get_lib_base lz4 LATEST -get_lib_base zstd LATEST -get_lib_base gflags LATEST -get_lib_base jemalloc LATEST -get_lib_base numa LATEST -get_lib_base libunwind LATEST -get_lib_base tbb 4.0_update2 gcc-5-glibc-2.23 - -get_lib_base kernel-headers LATEST -get_lib_base binutils LATEST centos6-native -get_lib_base valgrind 3.10.0 gcc-5-glibc-2.23 +get_lib_base libgcc 5.x gcc-5-glibc-2.23 +get_lib_base glibc 2.23 gcc-5-glibc-2.23 +get_lib_base snappy LATEST gcc-5-glibc-2.23 +get_lib_base zlib LATEST gcc-5-glibc-2.23 +get_lib_base bzip2 LATEST gcc-5-glibc-2.23 +get_lib_base lz4 LATEST gcc-5-glibc-2.23 +get_lib_base zstd LATEST gcc-5-glibc-2.23 +get_lib_base gflags LATEST gcc-5-glibc-2.23 +get_lib_base jemalloc LATEST gcc-5-glibc-2.23 +get_lib_base numa LATEST gcc-5-glibc-2.23 +get_lib_base libunwind LATEST gcc-5-glibc-2.23 +get_lib_base tbb LATEST gcc-5-glibc-2.23 + +get_lib_base kernel-headers 4.0.9-36_fbk5_2933_gd092e3f gcc-5-glibc-2.23 +get_lib_base binutils LATEST centos7-native +get_lib_base valgrind LATEST gcc-5-glibc-2.23 get_lib_base lua 5.2.3 gcc-5-glibc-2.23 git diff $OUTPUT diff --git a/thirdparty/rocksdb/build_tools/version.sh b/thirdparty/rocksdb/build_tools/version.sh index f3ca98cf61..4e3b9f20de 100755 --- a/thirdparty/rocksdb/build_tools/version.sh +++ b/thirdparty/rocksdb/build_tools/version.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash if [ "$#" = "0" ]; then echo "Usage: $0 major|minor|patch|full" exit 1 diff --git a/thirdparty/rocksdb/cache/cache_bench.cc b/thirdparty/rocksdb/cache/cache_bench.cc index 16c2ced1dd..098813d9d7 100644 --- a/thirdparty/rocksdb/cache/cache_bench.cc +++ b/thirdparty/rocksdb/cache/cache_bench.cc @@ -17,16 +17,16 @@ int main() { #include #include #include -#include -#include "rocksdb/db.h" +#include "port/port.h" #include "rocksdb/cache.h" +#include "rocksdb/db.h" #include "rocksdb/env.h" -#include "port/port.h" +#include "util/gflags_compat.h" #include "util/mutexlock.h" #include "util/random.h" -using GFLAGS::ParseCommandLineFlags; +using GFLAGS_NAMESPACE::ParseCommandLineFlags; static const uint32_t KB = 1024; @@ -52,7 +52,7 @@ namespace rocksdb { class CacheBench; namespace { -void deleter(const Slice& key, void* value) { +void deleter(const Slice& /*key*/, void* value) { delete reinterpret_cast(value); } diff --git a/thirdparty/rocksdb/cache/cache_test.cc b/thirdparty/rocksdb/cache/cache_test.cc index 8e241226d9..f9f77234cd 100644 --- a/thirdparty/rocksdb/cache/cache_test.cc +++ b/thirdparty/rocksdb/cache/cache_test.cc @@ -40,9 +40,9 @@ static int DecodeValue(void* v) { const std::string kLRU = "lru"; const std::string kClock = "clock"; -void dumbDeleter(const Slice& key, void* value) {} +void dumbDeleter(const Slice& /*key*/, void* /*value*/) {} -void eraseDeleter(const Slice& key, void* value) { +void eraseDeleter(const Slice& /*key*/, void* value) { Cache* cache = reinterpret_cast(value); cache->Erase("foo"); } @@ -64,8 +64,8 @@ class CacheTest : public testing::TestWithParam { std::vector deleted_keys_; std::vector deleted_values_; - shared_ptr cache_; - shared_ptr cache2_; + std::shared_ptr cache_; + std::shared_ptr cache2_; CacheTest() : cache_(NewCache(kCacheSize, kNumShardBits, false)), @@ -73,8 +73,7 @@ class CacheTest : public testing::TestWithParam { current_ = this; } - ~CacheTest() { - } + ~CacheTest() override {} std::shared_ptr NewCache(size_t capacity) { auto type = GetParam(); @@ -99,7 +98,7 @@ class CacheTest : public testing::TestWithParam { return nullptr; } - int Lookup(shared_ptr cache, int key) { + int Lookup(std::shared_ptr cache, int key) { Cache::Handle* handle = cache->Lookup(EncodeKey(key)); const int r = (handle == nullptr) ? -1 : DecodeValue(cache->Value(handle)); if (handle != nullptr) { @@ -108,16 +107,16 @@ class CacheTest : public testing::TestWithParam { return r; } - void Insert(shared_ptr cache, int key, int value, int charge = 1) { + void Insert(std::shared_ptr cache, int key, int value, + int charge = 1) { cache->Insert(EncodeKey(key), EncodeValue(value), charge, &CacheTest::Deleter); } - void Erase(shared_ptr cache, int key) { + void Erase(std::shared_ptr cache, int key) { cache->Erase(EncodeKey(key)); } - int Lookup(int key) { return Lookup(cache_, key); } @@ -145,7 +144,7 @@ class CacheTest : public testing::TestWithParam { CacheTest* CacheTest::current_; TEST_P(CacheTest, UsageTest) { - // cache is shared_ptr and will be automatically cleaned up. + // cache is std::shared_ptr and will be automatically cleaned up. const uint64_t kCapacity = 100000; auto cache = NewCache(kCapacity, 8, false); @@ -173,7 +172,7 @@ TEST_P(CacheTest, UsageTest) { } TEST_P(CacheTest, PinnedUsageTest) { - // cache is shared_ptr and will be automatically cleaned up. + // cache is std::shared_ptr and will be automatically cleaned up. const uint64_t kCapacity = 100000; auto cache = NewCache(kCapacity, 8, false); @@ -307,7 +306,7 @@ TEST_P(CacheTest, EvictionPolicy) { Insert(200, 201); // Frequently used entry must be kept around - for (int i = 0; i < kCacheSize + 100; i++) { + for (int i = 0; i < kCacheSize + 200; i++) { Insert(1000+i, 2000+i); ASSERT_EQ(101, Lookup(100)); } @@ -360,7 +359,7 @@ TEST_P(CacheTest, EvictionPolicyRef) { Insert(303, 104); // Insert entries much more than Cache capacity - for (int i = 0; i < kCacheSize + 100; i++) { + for (int i = 0; i < kCacheSize + 200; i++) { Insert(1000 + i, 2000 + i); } @@ -470,7 +469,7 @@ class Value { }; namespace { -void deleter(const Slice& key, void* value) { +void deleter(const Slice& /*key*/, void* value) { delete static_cast(value); } } // namespace @@ -688,7 +687,8 @@ TEST_P(CacheTest, DefaultShardBits) { } #ifdef SUPPORT_CLOCK_CACHE -shared_ptr (*new_clock_cache_func)(size_t, int, bool) = NewClockCache; +std::shared_ptr (*new_clock_cache_func)(size_t, int, + bool) = NewClockCache; INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest, testing::Values(kLRU, kClock)); #else diff --git a/thirdparty/rocksdb/cache/clock_cache.cc b/thirdparty/rocksdb/cache/clock_cache.cc index 7e42714ef1..89173834e2 100644 --- a/thirdparty/rocksdb/cache/clock_cache.cc +++ b/thirdparty/rocksdb/cache/clock_cache.cc @@ -13,8 +13,8 @@ namespace rocksdb { -std::shared_ptr NewClockCache(size_t capacity, int num_shard_bits, - bool strict_capacity_limit) { +std::shared_ptr NewClockCache(size_t /*capacity*/, int /*num_shard_bits*/, + bool /*strict_capacity_limit*/) { // Clock cache not supported. return nullptr; } @@ -234,38 +234,35 @@ struct CleanupContext { }; // A cache shard which maintains its own CLOCK cache. -class ClockCacheShard : public CacheShard { +class ClockCacheShard final : public CacheShard { public: // Hash map type. typedef tbb::concurrent_hash_map HashTable; ClockCacheShard(); - ~ClockCacheShard(); + ~ClockCacheShard() override; // Interfaces - virtual void SetCapacity(size_t capacity) override; - virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override; - virtual Status Insert(const Slice& key, uint32_t hash, void* value, - size_t charge, - void (*deleter)(const Slice& key, void* value), - Cache::Handle** handle, - Cache::Priority priority) override; - virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override; + void SetCapacity(size_t capacity) override; + void SetStrictCapacityLimit(bool strict_capacity_limit) override; + Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge, + void (*deleter)(const Slice& key, void* value), + Cache::Handle** handle, Cache::Priority priority) override; + Cache::Handle* Lookup(const Slice& key, uint32_t hash) override; // If the entry in in cache, increase reference count and return true. // Return false otherwise. // // Not necessary to hold mutex_ before being called. - virtual bool Ref(Cache::Handle* handle) override; - virtual bool Release(Cache::Handle* handle, - bool force_erase = false) override; - virtual void Erase(const Slice& key, uint32_t hash) override; + bool Ref(Cache::Handle* handle) override; + bool Release(Cache::Handle* handle, bool force_erase = false) override; + void Erase(const Slice& key, uint32_t hash) override; bool EraseAndConfirm(const Slice& key, uint32_t hash, CleanupContext* context); - virtual size_t GetUsage() const override; - virtual size_t GetPinnedUsage() const override; - virtual void EraseUnRefEntries() override; - virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t), - bool thread_safe) override; + size_t GetUsage() const override; + size_t GetPinnedUsage() const override; + void EraseUnRefEntries() override; + void ApplyToAllCacheEntries(void (*callback)(void*, size_t), + bool thread_safe) override; private: static const uint32_t kInCacheBit = 1; @@ -367,7 +364,9 @@ ClockCacheShard::~ClockCacheShard() { for (auto& handle : list_) { uint32_t flags = handle.flags.load(std::memory_order_relaxed); if (InCache(flags) || CountRefs(flags) > 0) { - (*handle.deleter)(handle.key, handle.value); + if (handle.deleter != nullptr) { + (*handle.deleter)(handle.key, handle.value); + } delete[] handle.key.data(); } } @@ -586,7 +585,7 @@ Status ClockCacheShard::Insert(const Slice& key, uint32_t hash, void* value, size_t charge, void (*deleter)(const Slice& key, void* value), Cache::Handle** out_handle, - Cache::Priority priority) { + Cache::Priority /*priority*/) { CleanupContext context; HashTable::accessor accessor; char* key_data = new char[key.size()]; @@ -673,7 +672,7 @@ void ClockCacheShard::EraseUnRefEntries() { Cleanup(context); } -class ClockCache : public ShardedCache { +class ClockCache final : public ShardedCache { public: ClockCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit) : ShardedCache(capacity, num_shard_bits, strict_capacity_limit) { @@ -683,31 +682,31 @@ class ClockCache : public ShardedCache { SetStrictCapacityLimit(strict_capacity_limit); } - virtual ~ClockCache() { delete[] shards_; } + ~ClockCache() override { delete[] shards_; } - virtual const char* Name() const override { return "ClockCache"; } + const char* Name() const override { return "ClockCache"; } - virtual CacheShard* GetShard(int shard) override { + CacheShard* GetShard(int shard) override { return reinterpret_cast(&shards_[shard]); } - virtual const CacheShard* GetShard(int shard) const override { + const CacheShard* GetShard(int shard) const override { return reinterpret_cast(&shards_[shard]); } - virtual void* Value(Handle* handle) override { + void* Value(Handle* handle) override { return reinterpret_cast(handle)->value; } - virtual size_t GetCharge(Handle* handle) const override { + size_t GetCharge(Handle* handle) const override { return reinterpret_cast(handle)->charge; } - virtual uint32_t GetHash(Handle* handle) const override { + uint32_t GetHash(Handle* handle) const override { return reinterpret_cast(handle)->hash; } - virtual void DisownData() override { shards_ = nullptr; } + void DisownData() override { shards_ = nullptr; } private: ClockCacheShard* shards_; diff --git a/thirdparty/rocksdb/cache/lru_cache.cc b/thirdparty/rocksdb/cache/lru_cache.cc index d29e709342..fdcbb4e86c 100644 --- a/thirdparty/rocksdb/cache/lru_cache.cc +++ b/thirdparty/rocksdb/cache/lru_cache.cc @@ -99,12 +99,22 @@ void LRUHandleTable::Resize() { length_ = new_length; } -LRUCacheShard::LRUCacheShard() - : high_pri_pool_usage_(0), usage_(0), lru_usage_(0) { +LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit, + double high_pri_pool_ratio, + bool use_adaptive_mutex) + : capacity_(0), + high_pri_pool_usage_(0), + strict_capacity_limit_(strict_capacity_limit), + high_pri_pool_ratio_(high_pri_pool_ratio), + high_pri_pool_capacity_(0), + usage_(0), + lru_usage_(0), + mutex_(use_adaptive_mutex) { // Make empty circular linked list lru_.next = &lru_; lru_.prev = &lru_; lru_low_pri_ = &lru_; + SetCapacity(capacity); } LRUCacheShard::~LRUCacheShard() {} @@ -167,6 +177,11 @@ size_t LRUCacheShard::TEST_GetLRUSize() { return lru_size; } +double LRUCacheShard::GetHighPriPoolRatio() { + MutexLock l(&mutex_); + return high_pri_pool_ratio_; +} + void LRUCacheShard::LRU_Remove(LRUHandle* e) { assert(e->next != nullptr); assert(e->prev != nullptr); @@ -186,7 +201,7 @@ void LRUCacheShard::LRU_Remove(LRUHandle* e) { void LRUCacheShard::LRU_Insert(LRUHandle* e) { assert(e->next == nullptr); assert(e->prev == nullptr); - if (high_pri_pool_ratio_ > 0 && e->IsHighPri()) { + if (high_pri_pool_ratio_ > 0 && (e->IsHighPri() || e->HasHit())) { // Inset "e" to head of LRU list. e->next = &lru_; e->prev = lru_.prev; @@ -233,22 +248,6 @@ void LRUCacheShard::EvictFromLRU(size_t charge, } } -void* LRUCacheShard::operator new(size_t size) { - return port::cacheline_aligned_alloc(size); -} - -void* LRUCacheShard::operator new[](size_t size) { - return port::cacheline_aligned_alloc(size); -} - -void LRUCacheShard::operator delete(void *memblock) { - port::cacheline_aligned_free(memblock); -} - -void LRUCacheShard::operator delete[](void* memblock) { - port::cacheline_aligned_free(memblock); -} - void LRUCacheShard::SetCapacity(size_t capacity) { autovector last_reference_list; { @@ -278,6 +277,7 @@ Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) { LRU_Remove(e); } e->refs++; + e->SetHit(); } return reinterpret_cast(e); } @@ -353,6 +353,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value, e->deleter = deleter; e->charge = charge; e->key_length = key.size(); + e->flags = 0; e->hash = hash; e->refs = (handle == nullptr ? 1 @@ -462,18 +463,31 @@ std::string LRUCacheShard::GetPrintableOptions() const { } LRUCache::LRUCache(size_t capacity, int num_shard_bits, - bool strict_capacity_limit, double high_pri_pool_ratio) - : ShardedCache(capacity, num_shard_bits, strict_capacity_limit) { + bool strict_capacity_limit, double high_pri_pool_ratio, + std::shared_ptr allocator, + bool use_adaptive_mutex) + : ShardedCache(capacity, num_shard_bits, strict_capacity_limit, + std::move(allocator)) { num_shards_ = 1 << num_shard_bits; - shards_ = new LRUCacheShard[num_shards_]; - SetCapacity(capacity); - SetStrictCapacityLimit(strict_capacity_limit); + shards_ = reinterpret_cast( + port::cacheline_aligned_alloc(sizeof(LRUCacheShard) * num_shards_)); + size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_; for (int i = 0; i < num_shards_; i++) { - shards_[i].SetHighPriorityPoolRatio(high_pri_pool_ratio); + new (&shards_[i]) + LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio, + use_adaptive_mutex); } } -LRUCache::~LRUCache() { delete[] shards_; } +LRUCache::~LRUCache() { + if (shards_ != nullptr) { + assert(num_shards_ > 0); + for (int i = 0; i < num_shards_; i++) { + shards_[i].~LRUCacheShard(); + } + port::cacheline_aligned_free(shards_); + } +} CacheShard* LRUCache::GetShard(int shard) { return reinterpret_cast(&shards_[shard]); @@ -497,9 +511,17 @@ uint32_t LRUCache::GetHash(Handle* handle) const { void LRUCache::DisownData() { // Do not drop data if compile with ASAN to suppress leak warning. +#if defined(__clang__) +#if !defined(__has_feature) || !__has_feature(address_sanitizer) + shards_ = nullptr; + num_shards_ = 0; +#endif +#else // __clang__ #ifndef __SANITIZE_ADDRESS__ shards_ = nullptr; + num_shards_ = 0; #endif // !__SANITIZE_ADDRESS__ +#endif // __clang__ } size_t LRUCache::TEST_GetLRUSize() { @@ -510,9 +532,27 @@ size_t LRUCache::TEST_GetLRUSize() { return lru_size_of_all_shards; } -std::shared_ptr NewLRUCache(size_t capacity, int num_shard_bits, - bool strict_capacity_limit, - double high_pri_pool_ratio) { +double LRUCache::GetHighPriPoolRatio() { + double result = 0.0; + if (num_shards_ > 0) { + result = shards_[0].GetHighPriPoolRatio(); + } + return result; +} + +std::shared_ptr NewLRUCache(const LRUCacheOptions& cache_opts) { + return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits, + cache_opts.strict_capacity_limit, + cache_opts.high_pri_pool_ratio, + cache_opts.memory_allocator, + cache_opts.use_adaptive_mutex); +} + +std::shared_ptr NewLRUCache( + size_t capacity, int num_shard_bits, bool strict_capacity_limit, + double high_pri_pool_ratio, + std::shared_ptr memory_allocator, + bool use_adaptive_mutex) { if (num_shard_bits >= 20) { return nullptr; // the cache cannot be sharded into too many fine pieces } @@ -524,7 +564,9 @@ std::shared_ptr NewLRUCache(size_t capacity, int num_shard_bits, num_shard_bits = GetDefaultCacheShardBits(capacity); } return std::make_shared(capacity, num_shard_bits, - strict_capacity_limit, high_pri_pool_ratio); + strict_capacity_limit, high_pri_pool_ratio, + std::move(memory_allocator), + use_adaptive_mutex); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/cache/lru_cache.h b/thirdparty/rocksdb/cache/lru_cache.h index abe78fd0c7..0d9a317486 100644 --- a/thirdparty/rocksdb/cache/lru_cache.h +++ b/thirdparty/rocksdb/cache/lru_cache.h @@ -55,10 +55,18 @@ struct LRUHandle { // cache itself is counted as 1 // Include the following flags: - // in_cache: whether this entry is referenced by the hash table. - // is_high_pri: whether this entry is high priority entry. - // in_high_pro_pool: whether this entry is in high-pri pool. - char flags; + // IN_CACHE: whether this entry is referenced by the hash table. + // IS_HIGH_PRI: whether this entry is high priority entry. + // IN_HIGH_PRI_POOL: whether this entry is in high-pri pool. + // HAS_HIT: whether this entry has had any lookups (hits). + enum Flags : uint8_t { + IN_CACHE = (1 << 0), + IS_HIGH_PRI = (1 << 1), + IN_HIGH_PRI_POOL = (1 << 2), + HAS_HIT = (1 << 3), + }; + + uint8_t flags; uint32_t hash; // Hash of key(); used for fast sharding and comparisons @@ -74,34 +82,37 @@ struct LRUHandle { } } - bool InCache() { return flags & 1; } - bool IsHighPri() { return flags & 2; } - bool InHighPriPool() { return flags & 4; } + bool InCache() const { return flags & IN_CACHE; } + bool IsHighPri() const { return flags & IS_HIGH_PRI; } + bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; } + bool HasHit() const { return flags & HAS_HIT; } void SetInCache(bool in_cache) { if (in_cache) { - flags |= 1; + flags |= IN_CACHE; } else { - flags &= ~1; + flags &= ~IN_CACHE; } } void SetPriority(Cache::Priority priority) { if (priority == Cache::Priority::HIGH) { - flags |= 2; + flags |= IS_HIGH_PRI; } else { - flags &= ~2; + flags &= ~IS_HIGH_PRI; } } void SetInHighPriPool(bool in_high_pri_pool) { if (in_high_pri_pool) { - flags |= 4; + flags |= IN_HIGH_PRI_POOL; } else { - flags &= ~4; + flags &= ~IN_HIGH_PRI_POOL; } } + void SetHit() { flags |= HAS_HIT; } + void Free() { assert((refs == 1 && InCache()) || (refs == 0 && !InCache())); if (deleter) { @@ -154,9 +165,10 @@ class LRUHandleTable { }; // A single shard of sharded cache. -class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard { +class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard { public: - LRUCacheShard(); + LRUCacheShard(size_t capacity, bool strict_capacity_limit, + double high_pri_pool_ratio, bool use_adaptive_mutex); virtual ~LRUCacheShard(); // Separate from constructor so caller can easily make an array of LRUCache @@ -202,14 +214,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard { // not threadsafe size_t TEST_GetLRUSize(); - // Overloading to aligned it to cache line size - void* operator new(size_t); - - void* operator new[](size_t); - - void operator delete(void *); - - void operator delete[](void*); + // Retrives high pri pool ratio + double GetHighPriPoolRatio(); private: void LRU_Remove(LRUHandle* e); @@ -278,10 +284,16 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard { mutable port::Mutex mutex_; }; -class LRUCache : public ShardedCache { +class LRUCache +#ifdef NDEBUG + final +#endif + : public ShardedCache { public: LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit, - double high_pri_pool_ratio); + double high_pri_pool_ratio, + std::shared_ptr memory_allocator = nullptr, + bool use_adaptive_mutex = kDefaultToAdaptiveMutex); virtual ~LRUCache(); virtual const char* Name() const override { return "LRUCache"; } virtual CacheShard* GetShard(int shard) override; @@ -293,9 +305,11 @@ class LRUCache : public ShardedCache { // Retrieves number of elements in LRU, for unit test purpose only size_t TEST_GetLRUSize(); + // Retrives high pri pool ratio + double GetHighPriPoolRatio(); private: - LRUCacheShard* shards_; + LRUCacheShard* shards_ = nullptr; int num_shards_ = 0; }; diff --git a/thirdparty/rocksdb/cache/lru_cache_test.cc b/thirdparty/rocksdb/cache/lru_cache_test.cc index 1b83033c36..9980dd72b7 100644 --- a/thirdparty/rocksdb/cache/lru_cache_test.cc +++ b/thirdparty/rocksdb/cache/lru_cache_test.cc @@ -7,6 +7,7 @@ #include #include +#include "port/port.h" #include "util/testharness.h" namespace rocksdb { @@ -14,22 +15,23 @@ namespace rocksdb { class LRUCacheTest : public testing::Test { public: LRUCacheTest() {} - ~LRUCacheTest() {} - - void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0) { - cache_.reset( -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable: 4316) // We've validated the alignment with the new operators -#endif - new LRUCacheShard() -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - ); - cache_->SetCapacity(capacity); - cache_->SetStrictCapacityLimit(false); - cache_->SetHighPriorityPoolRatio(high_pri_pool_ratio); + ~LRUCacheTest() override { DeleteCache(); } + + void DeleteCache() { + if (cache_ != nullptr) { + cache_->~LRUCacheShard(); + port::cacheline_aligned_free(cache_); + cache_ = nullptr; + } + } + + void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0, + bool use_adaptive_mutex = kDefaultToAdaptiveMutex) { + DeleteCache(); + cache_ = reinterpret_cast( + port::cacheline_aligned_alloc(sizeof(LRUCacheShard))); + new (cache_) LRUCacheShard(capacity, false /*strict_capcity_limit*/, + high_pri_pool_ratio, use_adaptive_mutex); } void Insert(const std::string& key, @@ -85,7 +87,7 @@ class LRUCacheTest : public testing::Test { } private: - std::unique_ptr cache_; + LRUCacheShard* cache_ = nullptr; }; TEST_F(LRUCacheTest, BasicLRU) { @@ -114,7 +116,30 @@ TEST_F(LRUCacheTest, BasicLRU) { ValidateLRUList({"e", "z", "d", "u", "v"}); } -TEST_F(LRUCacheTest, MidPointInsertion) { +TEST_F(LRUCacheTest, MidpointInsertion) { + // Allocate 2 cache entries to high-pri pool. + NewCache(5, 0.45); + + Insert("a", Cache::Priority::LOW); + Insert("b", Cache::Priority::LOW); + Insert("c", Cache::Priority::LOW); + Insert("x", Cache::Priority::HIGH); + Insert("y", Cache::Priority::HIGH); + ValidateLRUList({"a", "b", "c", "x", "y"}, 2); + + // Low-pri entries inserted to the tail of low-pri list (the midpoint). + // After lookup, it will move to the tail of the full list. + Insert("d", Cache::Priority::LOW); + ValidateLRUList({"b", "c", "d", "x", "y"}, 2); + ASSERT_TRUE(Lookup("d")); + ValidateLRUList({"b", "c", "x", "y", "d"}, 2); + + // High-pri entries will be inserted to the tail of full list. + Insert("z", Cache::Priority::HIGH); + ValidateLRUList({"c", "x", "y", "d", "z"}, 2); +} + +TEST_F(LRUCacheTest, EntriesWithPriority) { // Allocate 2 cache entries to high-pri pool. NewCache(5, 0.45); @@ -140,15 +165,15 @@ TEST_F(LRUCacheTest, MidPointInsertion) { Insert("a", Cache::Priority::LOW); ValidateLRUList({"v", "X", "a", "Y", "Z"}, 2); - // Low-pri entries will be inserted to head of low-pri pool after lookup. + // Low-pri entries will be inserted to head of high-pri pool after lookup. ASSERT_TRUE(Lookup("v")); - ValidateLRUList({"X", "a", "v", "Y", "Z"}, 2); + ValidateLRUList({"X", "a", "Y", "Z", "v"}, 2); // High-pri entries will be inserted to the head of the list after lookup. ASSERT_TRUE(Lookup("X")); - ValidateLRUList({"a", "v", "Y", "Z", "X"}, 2); + ValidateLRUList({"a", "Y", "Z", "v", "X"}, 2); ASSERT_TRUE(Lookup("Z")); - ValidateLRUList({"a", "v", "Y", "X", "Z"}, 2); + ValidateLRUList({"a", "Y", "v", "X", "Z"}, 2); Erase("Y"); ValidateLRUList({"a", "v", "X", "Z"}, 2); @@ -161,7 +186,7 @@ TEST_F(LRUCacheTest, MidPointInsertion) { Insert("g", Cache::Priority::LOW); ValidateLRUList({"d", "e", "f", "g", "Z"}, 1); ASSERT_TRUE(Lookup("d")); - ValidateLRUList({"e", "f", "g", "d", "Z"}, 1); + ValidateLRUList({"e", "f", "g", "Z", "d"}, 2); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/cache/sharded_cache.cc b/thirdparty/rocksdb/cache/sharded_cache.cc index 9bdea3a08e..a48a32185b 100644 --- a/thirdparty/rocksdb/cache/sharded_cache.cc +++ b/thirdparty/rocksdb/cache/sharded_cache.cc @@ -20,8 +20,10 @@ namespace rocksdb { ShardedCache::ShardedCache(size_t capacity, int num_shard_bits, - bool strict_capacity_limit) - : num_shard_bits_(num_shard_bits), + bool strict_capacity_limit, + std::shared_ptr allocator) + : Cache(std::move(allocator)), + num_shard_bits_(num_shard_bits), capacity_(capacity), strict_capacity_limit_(strict_capacity_limit), last_id_(1) {} @@ -53,7 +55,7 @@ Status ShardedCache::Insert(const Slice& key, void* value, size_t charge, ->Insert(key, hash, value, charge, deleter, handle, priority); } -Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* stats) { +Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* /*stats*/) { uint32_t hash = HashSlice(key); return GetShard(Shard(hash))->Lookup(key, hash); } @@ -142,6 +144,9 @@ std::string ShardedCache::GetPrintableOptions() const { strict_capacity_limit_); ret.append(buffer); } + snprintf(buffer, kBufferSize, " memory_allocator : %s\n", + memory_allocator() ? memory_allocator()->Name() : "None"); + ret.append(buffer); ret.append(GetShard(0)->GetPrintableOptions()); return ret; } diff --git a/thirdparty/rocksdb/cache/sharded_cache.h b/thirdparty/rocksdb/cache/sharded_cache.h index 4f9dea2ad0..920898b871 100644 --- a/thirdparty/rocksdb/cache/sharded_cache.h +++ b/thirdparty/rocksdb/cache/sharded_cache.h @@ -47,7 +47,8 @@ class CacheShard { // Keys are sharded by the highest num_shard_bits bits of hash value. class ShardedCache : public Cache { public: - ShardedCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit); + ShardedCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit, + std::shared_ptr memory_allocator = nullptr); virtual ~ShardedCache() = default; virtual const char* Name() const override = 0; virtual CacheShard* GetShard(int shard) = 0; @@ -82,7 +83,7 @@ class ShardedCache : public Cache { private: static inline uint32_t HashSlice(const Slice& s) { - return Hash(s.data(), s.size(), 0); + return static_cast(GetSliceNPHash64(s)); } uint32_t Shard(uint32_t hash) { diff --git a/thirdparty/rocksdb/coverage/parse_gcov_output.py b/thirdparty/rocksdb/coverage/parse_gcov_output.py index 72e8b07230..4c358610c4 100644 --- a/thirdparty/rocksdb/coverage/parse_gcov_output.py +++ b/thirdparty/rocksdb/coverage/parse_gcov_output.py @@ -1,4 +1,3 @@ -import optparse import re import sys @@ -72,7 +71,7 @@ def display_file_coverage(per_file_coverage, total_coverage): header_template = \ "%" + str(max_file_name_length) + "s\t%s\t%s" separator = "-" * (max_file_name_length + 10 + 20) - print header_template % ("Filename", "Coverage", "Lines") + print header_template % ("Filename", "Coverage", "Lines") # noqa: E999 T25377293 Grandfathered in print separator # -- Print body diff --git a/thirdparty/rocksdb/db/builder.cc b/thirdparty/rocksdb/db/builder.cc index 7cfa7800cc..a41a8ca4c3 100644 --- a/thirdparty/rocksdb/db/builder.cc +++ b/thirdparty/rocksdb/db/builder.cc @@ -18,6 +18,7 @@ #include "db/event_helpers.h" #include "db/internal_stats.h" #include "db/merge_helper.h" +#include "db/range_del_aggregator.h" #include "db/table_cache.h" #include "db/version_edit.h" #include "monitoring/iostats_context_imp.h" @@ -28,6 +29,7 @@ #include "rocksdb/options.h" #include "rocksdb/table.h" #include "table/block_based_table_builder.h" +#include "table/format.h" #include "table/internal_iterator.h" #include "util/file_reader_writer.h" #include "util/filename.h" @@ -39,23 +41,24 @@ namespace rocksdb { class TableFactory; TableBuilder* NewTableBuilder( - const ImmutableCFOptions& ioptions, + const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions, const InternalKeyComparator& internal_comparator, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, const std::string& column_family_name, WritableFileWriter* file, const CompressionType compression_type, - const CompressionOptions& compression_opts, int level, - const std::string* compression_dict, const bool skip_filters, - const uint64_t creation_time, const uint64_t oldest_key_time) { + uint64_t sample_for_compression, const CompressionOptions& compression_opts, + int level, const bool skip_filters, const uint64_t creation_time, + const uint64_t oldest_key_time, const uint64_t target_file_size) { assert((column_family_id == TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) == column_family_name.empty()); return ioptions.table_factory->NewTableBuilder( - TableBuilderOptions( - ioptions, internal_comparator, int_tbl_prop_collector_factories, - compression_type, compression_opts, compression_dict, skip_filters, - column_family_name, level, creation_time, oldest_key_time), + TableBuilderOptions(ioptions, moptions, internal_comparator, + int_tbl_prop_collector_factories, compression_type, + sample_for_compression, compression_opts, + skip_filters, column_family_name, level, + creation_time, oldest_key_time, target_file_size), column_family_id, file); } @@ -63,19 +66,21 @@ Status BuildTable( const std::string& dbname, Env* env, const ImmutableCFOptions& ioptions, const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options, TableCache* table_cache, InternalIterator* iter, - std::unique_ptr range_del_iter, FileMetaData* meta, - const InternalKeyComparator& internal_comparator, + std::vector> + range_del_iters, + FileMetaData* meta, const InternalKeyComparator& internal_comparator, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, const std::string& column_family_name, std::vector snapshots, SequenceNumber earliest_write_conflict_snapshot, - const CompressionType compression, - const CompressionOptions& compression_opts, bool paranoid_file_checks, - InternalStats* internal_stats, TableFileCreationReason reason, - EventLogger* event_logger, int job_id, const Env::IOPriority io_priority, - TableProperties* table_properties, int level, const uint64_t creation_time, - const uint64_t oldest_key_time) { + SnapshotChecker* snapshot_checker, const CompressionType compression, + uint64_t sample_for_compression, const CompressionOptions& compression_opts, + bool paranoid_file_checks, InternalStats* internal_stats, + TableFileCreationReason reason, EventLogger* event_logger, int job_id, + const Env::IOPriority io_priority, TableProperties* table_properties, + int level, const uint64_t creation_time, const uint64_t oldest_key_time, + Env::WriteLifeTimeHint write_hint) { assert((column_family_id == TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) == column_family_name.empty()); @@ -84,15 +89,13 @@ Status BuildTable( Status s; meta->fd.file_size = 0; iter->SeekToFirst(); - std::unique_ptr range_del_agg( - new RangeDelAggregator(internal_comparator, snapshots)); - s = range_del_agg->AddTombstones(std::move(range_del_iter)); - if (!s.ok()) { - // may be non-ok if a range tombstone key is unparsable - return s; + std::unique_ptr range_del_agg( + new CompactionRangeDelAggregator(&internal_comparator, snapshots)); + for (auto& range_del_iter : range_del_iters) { + range_del_agg->AddTombstones(std::move(range_del_iter)); } - std::string fname = TableFileName(ioptions.db_paths, meta->fd.GetNumber(), + std::string fname = TableFileName(ioptions.cf_paths, meta->fd.GetNumber(), meta->fd.GetPathId()); #ifndef ROCKSDB_LITE EventHelpers::NotifyTableFileCreationStarted( @@ -100,11 +103,16 @@ Status BuildTable( #endif // !ROCKSDB_LITE TableProperties tp; - if (iter->Valid() || range_del_agg->ShouldAddTombstones()) { + if (iter->Valid() || !range_del_agg->IsEmpty()) { TableBuilder* builder; - unique_ptr file_writer; + std::unique_ptr file_writer; + // Currently we only enable dictionary compression during compaction to the + // bottommost level. + CompressionOptions compression_opts_for_flush(compression_opts); + compression_opts_for_flush.max_dict_bytes = 0; + compression_opts_for_flush.zstd_max_train_bytes = 0; { - unique_ptr file; + std::unique_ptr file; #ifndef NDEBUG bool use_direct_writes = env_options.use_direct_writes; TEST_SYNC_POINT_CALLBACK("BuildTable:create_file", &use_direct_writes); @@ -117,24 +125,29 @@ Status BuildTable( return s; } file->SetIOPriority(io_priority); + file->SetWriteLifeTimeHint(write_hint); - file_writer.reset(new WritableFileWriter(std::move(file), env_options, - ioptions.statistics)); + file_writer.reset( + new WritableFileWriter(std::move(file), fname, env_options, env, + ioptions.statistics, ioptions.listeners)); builder = NewTableBuilder( - ioptions, internal_comparator, int_tbl_prop_collector_factories, - column_family_id, column_family_name, file_writer.get(), compression, - compression_opts, level, nullptr /* compression_dict */, + ioptions, mutable_cf_options, internal_comparator, + int_tbl_prop_collector_factories, column_family_id, + column_family_name, file_writer.get(), compression, + sample_for_compression, compression_opts_for_flush, level, false /* skip_filters */, creation_time, oldest_key_time); } MergeHelper merge(env, internal_comparator.user_comparator(), ioptions.merge_operator, nullptr, ioptions.info_log, true /* internal key corruption is not ok */, - snapshots.empty() ? 0 : snapshots.back()); + snapshots.empty() ? 0 : snapshots.back(), + snapshot_checker); CompactionIterator c_iter( iter, internal_comparator.user_comparator(), &merge, kMaxSequenceNumber, - &snapshots, earliest_write_conflict_snapshot, env, + &snapshots, earliest_write_conflict_snapshot, snapshot_checker, env, + ShouldReportDetailedTime(env, ioptions.statistics), true /* internal key corruption is not ok */, range_del_agg.get()); c_iter.SeekToFirst(); for (; c_iter.Valid(); c_iter.Next()) { @@ -150,12 +163,20 @@ Status BuildTable( ThreadStatus::FLUSH_BYTES_WRITTEN, IOSTATS(bytes_written)); } } - // nullptr for table_{min,max} so all range tombstones will be flushed - range_del_agg->AddToBuilder(builder, nullptr /* lower_bound */, - nullptr /* upper_bound */, meta); + + auto range_del_it = range_del_agg->NewIterator(); + for (range_del_it->SeekToFirst(); range_del_it->Valid(); + range_del_it->Next()) { + auto tombstone = range_del_it->Tombstone(); + auto kv = tombstone.Serialize(); + builder->Add(kv.first.Encode(), kv.second); + meta->UpdateBoundariesForRange(kv.first, tombstone.SerializeEndKey(), + tombstone.seq_, internal_comparator); + } // Finish and check for builder errors - bool empty = builder->NumEntries() == 0; + tp = builder->GetTableProperties(); + bool empty = builder->NumEntries() == 0 && tp.num_range_deletions == 0; s = c_iter.status(); if (!s.ok() || empty) { builder->Abandon(); @@ -168,7 +189,7 @@ Status BuildTable( meta->fd.file_size = file_size; meta->marked_for_compaction = builder->NeedCompact(); assert(meta->fd.GetFileSize() > 0); - tp = builder->GetTableProperties(); + tp = builder->GetTableProperties(); // refresh now that builder is finished if (table_properties) { *table_properties = tp; } @@ -192,8 +213,9 @@ Status BuildTable( // we will regrad this verification as user reads since the goal is // to cache it here for further user reads std::unique_ptr it(table_cache->NewIterator( - ReadOptions(), env_options, internal_comparator, meta->fd, - nullptr /* range_del_agg */, nullptr, + ReadOptions(), env_options, internal_comparator, *meta, + nullptr /* range_del_agg */, + mutable_cf_options.prefix_extractor.get(), nullptr, (internal_stats == nullptr) ? nullptr : internal_stats->GetFileReadHist(0), false /* for_compaction */, nullptr /* arena */, diff --git a/thirdparty/rocksdb/db/builder.h b/thirdparty/rocksdb/db/builder.h index 5a5081c647..c00c8273ce 100644 --- a/thirdparty/rocksdb/db/builder.h +++ b/thirdparty/rocksdb/db/builder.h @@ -9,6 +9,7 @@ #include #include #include +#include "db/range_tombstone_fragmenter.h" #include "db/table_properties_collector.h" #include "options/cf_options.h" #include "rocksdb/comparator.h" @@ -29,29 +30,27 @@ struct FileMetaData; class Env; struct EnvOptions; class Iterator; +class SnapshotChecker; class TableCache; class VersionEdit; class TableBuilder; class WritableFileWriter; class InternalStats; -class InternalIterator; // @param column_family_name Name of the column family that is also identified // by column_family_id, or empty string if unknown. It must outlive the // TableBuilder returned by this function. -// @param compression_dict Data for presetting the compression library's -// dictionary, or nullptr. TableBuilder* NewTableBuilder( - const ImmutableCFOptions& options, + const ImmutableCFOptions& options, const MutableCFOptions& moptions, const InternalKeyComparator& internal_comparator, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, const std::string& column_family_name, WritableFileWriter* file, const CompressionType compression_type, + const uint64_t sample_for_compression, const CompressionOptions& compression_opts, int level, - const std::string* compression_dict = nullptr, const bool skip_filters = false, const uint64_t creation_time = 0, - const uint64_t oldest_key_time = 0); + const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0); // Build a Table file from the contents of *iter. The generated file // will be named according to number specified in meta. On success, the rest of @@ -65,19 +64,22 @@ extern Status BuildTable( const std::string& dbname, Env* env, const ImmutableCFOptions& options, const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options, TableCache* table_cache, InternalIterator* iter, - std::unique_ptr range_del_iter, FileMetaData* meta, - const InternalKeyComparator& internal_comparator, + std::vector> + range_del_iters, + FileMetaData* meta, const InternalKeyComparator& internal_comparator, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, const std::string& column_family_name, std::vector snapshots, SequenceNumber earliest_write_conflict_snapshot, - const CompressionType compression, + SnapshotChecker* snapshot_checker, const CompressionType compression, + const uint64_t sample_for_compression, const CompressionOptions& compression_opts, bool paranoid_file_checks, InternalStats* internal_stats, TableFileCreationReason reason, EventLogger* event_logger = nullptr, int job_id = 0, const Env::IOPriority io_priority = Env::IO_HIGH, TableProperties* table_properties = nullptr, int level = -1, - const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0); + const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0, + Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET); } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/c.cc b/thirdparty/rocksdb/db/c.cc index cbfb8557d0..8610871abd 100644 --- a/thirdparty/rocksdb/db/c.cc +++ b/thirdparty/rocksdb/db/c.cc @@ -21,23 +21,30 @@ #include "rocksdb/env.h" #include "rocksdb/filter_policy.h" #include "rocksdb/iterator.h" +#include "rocksdb/memtablerep.h" #include "rocksdb/merge_operator.h" #include "rocksdb/options.h" -#include "rocksdb/status.h" -#include "rocksdb/write_batch.h" -#include "rocksdb/memtablerep.h" -#include "rocksdb/universal_compaction.h" -#include "rocksdb/statistics.h" +#include "rocksdb/rate_limiter.h" #include "rocksdb/slice_transform.h" +#include "rocksdb/statistics.h" +#include "rocksdb/status.h" #include "rocksdb/table.h" -#include "rocksdb/rate_limiter.h" +#include "rocksdb/universal_compaction.h" #include "rocksdb/utilities/backupable_db.h" -#include "rocksdb/utilities/write_batch_with_index.h" -#include "utilities/merge_operators.h" +#include "rocksdb/utilities/checkpoint.h" +#include "rocksdb/utilities/db_ttl.h" +#include "rocksdb/utilities/memory_util.h" +#include "rocksdb/utilities/optimistic_transaction_db.h" #include "rocksdb/utilities/transaction.h" #include "rocksdb/utilities/transaction_db.h" -#include "rocksdb/utilities/optimistic_transaction_db.h" -#include "rocksdb/utilities/checkpoint.h" +#include "rocksdb/utilities/write_batch_with_index.h" +#include "rocksdb/write_batch.h" +#include "rocksdb/perf_context.h" +#include "utilities/merge_operators.h" + +#include +#include +#include using rocksdb::BytewiseComparator; using rocksdb::Cache; @@ -88,8 +95,10 @@ using rocksdb::LiveFileMetaData; using rocksdb::BackupEngine; using rocksdb::BackupableDBOptions; using rocksdb::BackupInfo; +using rocksdb::BackupID; using rocksdb::RestoreOptions; using rocksdb::CompactRangeOptions; +using rocksdb::BottommostLevelCompaction; using rocksdb::RateLimiter; using rocksdb::NewGenericRateLimiter; using rocksdb::PinnableSlice; @@ -100,8 +109,16 @@ using rocksdb::OptimisticTransactionDB; using rocksdb::OptimisticTransactionOptions; using rocksdb::Transaction; using rocksdb::Checkpoint; +using rocksdb::TransactionLogIterator; +using rocksdb::BatchResult; +using rocksdb::PerfLevel; +using rocksdb::PerfContext; +using rocksdb::MemoryUtil; using std::shared_ptr; +using std::vector; +using std::unordered_set; +using std::map; extern "C" { @@ -117,7 +134,9 @@ struct rocksdb_flushoptions_t { FlushOptions rep; }; struct rocksdb_fifo_compaction_options_t { CompactionOptionsFIFO rep; }; struct rocksdb_readoptions_t { ReadOptions rep; - Slice upper_bound; // stack variable to set pointer to in ReadOptions + // stack variables to set pointers to in ReadOptions + Slice upper_bound; + Slice lower_bound; }; struct rocksdb_writeoptions_t { WriteOptions rep; }; struct rocksdb_options_t { Options rep; }; @@ -129,15 +148,24 @@ struct rocksdb_cuckoo_table_options_t { CuckooTableOptions rep; }; struct rocksdb_seqfile_t { SequentialFile* rep; }; struct rocksdb_randomfile_t { RandomAccessFile* rep; }; struct rocksdb_writablefile_t { WritableFile* rep; }; +struct rocksdb_wal_iterator_t { TransactionLogIterator* rep; }; +struct rocksdb_wal_readoptions_t { TransactionLogIterator::ReadOptions rep; }; struct rocksdb_filelock_t { FileLock* rep; }; -struct rocksdb_logger_t { shared_ptr rep; }; -struct rocksdb_cache_t { shared_ptr rep; }; +struct rocksdb_logger_t { + std::shared_ptr rep; +}; +struct rocksdb_cache_t { + std::shared_ptr rep; +}; struct rocksdb_livefiles_t { std::vector rep; }; struct rocksdb_column_family_handle_t { ColumnFamilyHandle* rep; }; struct rocksdb_envoptions_t { EnvOptions rep; }; struct rocksdb_ingestexternalfileoptions_t { IngestExternalFileOptions rep; }; struct rocksdb_sstfilewriter_t { SstFileWriter* rep; }; -struct rocksdb_ratelimiter_t { RateLimiter* rep; }; +struct rocksdb_ratelimiter_t { + std::shared_ptr rep; +}; +struct rocksdb_perfcontext_t { PerfContext* rep; }; struct rocksdb_pinnableslice_t { PinnableSlice rep; }; @@ -180,13 +208,10 @@ struct rocksdb_compactionfilter_t : public CompactionFilter { const char* (*name_)(void*); unsigned char ignore_snapshots_; - virtual ~rocksdb_compactionfilter_t() { - (*destructor_)(state_); - } + ~rocksdb_compactionfilter_t() override { (*destructor_)(state_); } - virtual bool Filter(int level, const Slice& key, const Slice& existing_value, - std::string* new_value, - bool* value_changed) const override { + bool Filter(int level, const Slice& key, const Slice& existing_value, + std::string* new_value, bool* value_changed) const override { char* c_new_value = nullptr; size_t new_value_length = 0; unsigned char c_value_changed = 0; @@ -203,9 +228,9 @@ struct rocksdb_compactionfilter_t : public CompactionFilter { return result; } - virtual const char* Name() const override { return (*name_)(state_); } + const char* Name() const override { return (*name_)(state_); } - virtual bool IgnoreSnapshots() const override { return ignore_snapshots_; } + bool IgnoreSnapshots() const override { return ignore_snapshots_; } }; struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory { @@ -215,9 +240,9 @@ struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory { void*, rocksdb_compactionfiltercontext_t* context); const char* (*name_)(void*); - virtual ~rocksdb_compactionfilterfactory_t() { (*destructor_)(state_); } + ~rocksdb_compactionfilterfactory_t() override { (*destructor_)(state_); } - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { rocksdb_compactionfiltercontext_t ccontext; ccontext.rep = context; @@ -225,7 +250,7 @@ struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory { return std::unique_ptr(cf); } - virtual const char* Name() const override { return (*name_)(state_); } + const char* Name() const override { return (*name_)(state_); } }; struct rocksdb_comparator_t : public Comparator { @@ -237,20 +262,17 @@ struct rocksdb_comparator_t : public Comparator { const char* b, size_t blen); const char* (*name_)(void*); - virtual ~rocksdb_comparator_t() { - (*destructor_)(state_); - } + ~rocksdb_comparator_t() override { (*destructor_)(state_); } - virtual int Compare(const Slice& a, const Slice& b) const override { + int Compare(const Slice& a, const Slice& b) const override { return (*compare_)(state_, a.data(), a.size(), b.data(), b.size()); } - virtual const char* Name() const override { return (*name_)(state_); } + const char* Name() const override { return (*name_)(state_); } // No-ops since the C binding does not support key shortening methods. - virtual void FindShortestSeparator(std::string*, - const Slice&) const override {} - virtual void FindShortSuccessor(std::string* key) const override {} + void FindShortestSeparator(std::string*, const Slice&) const override {} + void FindShortSuccessor(std::string* /*key*/) const override {} }; struct rocksdb_filterpolicy_t : public FilterPolicy { @@ -270,14 +292,11 @@ struct rocksdb_filterpolicy_t : public FilterPolicy { void*, const char* filter, size_t filter_length); - virtual ~rocksdb_filterpolicy_t() { - (*destructor_)(state_); - } + ~rocksdb_filterpolicy_t() override { (*destructor_)(state_); } - virtual const char* Name() const override { return (*name_)(state_); } + const char* Name() const override { return (*name_)(state_); } - virtual void CreateFilter(const Slice* keys, int n, - std::string* dst) const override { + void CreateFilter(const Slice* keys, int n, std::string* dst) const override { std::vector key_pointers(n); std::vector key_sizes(n); for (int i = 0; i < n; i++) { @@ -295,8 +314,7 @@ struct rocksdb_filterpolicy_t : public FilterPolicy { } } - virtual bool KeyMayMatch(const Slice& key, - const Slice& filter) const override { + bool KeyMayMatch(const Slice& key, const Slice& filter) const override { return (*key_match_)(state_, key.data(), key.size(), filter.data(), filter.size()); } @@ -321,14 +339,12 @@ struct rocksdb_mergeoperator_t : public MergeOperator { void*, const char* value, size_t value_length); - virtual ~rocksdb_mergeoperator_t() { - (*destructor_)(state_); - } + ~rocksdb_mergeoperator_t() override { (*destructor_)(state_); } - virtual const char* Name() const override { return (*name_)(state_); } + const char* Name() const override { return (*name_)(state_); } - virtual bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { + bool FullMergeV2(const MergeOperationInput& merge_in, + MergeOperationOutput* merge_out) const override { size_t n = merge_in.operand_list.size(); std::vector operand_pointers(n); std::vector operand_sizes(n); @@ -362,10 +378,10 @@ struct rocksdb_mergeoperator_t : public MergeOperator { return success; } - virtual bool PartialMergeMulti(const Slice& key, - const std::deque& operand_list, - std::string* new_value, - Logger* logger) const override { + bool PartialMergeMulti(const Slice& key, + const std::deque& operand_list, + std::string* new_value, + Logger* /*logger*/) const override { size_t operand_count = operand_list.size(); std::vector operand_pointers(operand_count); std::vector operand_sizes(operand_count); @@ -416,23 +432,21 @@ struct rocksdb_slicetransform_t : public SliceTransform { void*, const char* key, size_t length); - virtual ~rocksdb_slicetransform_t() { - (*destructor_)(state_); - } + ~rocksdb_slicetransform_t() override { (*destructor_)(state_); } - virtual const char* Name() const override { return (*name_)(state_); } + const char* Name() const override { return (*name_)(state_); } - virtual Slice Transform(const Slice& src) const override { + Slice Transform(const Slice& src) const override { size_t len; char* dst = (*transform_)(state_, src.data(), src.size(), &len); return Slice(dst, len); } - virtual bool InDomain(const Slice& src) const override { + bool InDomain(const Slice& src) const override { return (*in_domain_)(state_, src.data(), src.size()); } - virtual bool InRange(const Slice& src) const override { + bool InRange(const Slice& src) const override { return (*in_range_)(state_, src.data(), src.size()); } }; @@ -475,6 +489,20 @@ rocksdb_t* rocksdb_open( return result; } +rocksdb_t* rocksdb_open_with_ttl( + const rocksdb_options_t* options, + const char* name, + int ttl, + char** errptr) { + rocksdb::DBWithTTL* db; + if (SaveError(errptr, rocksdb::DBWithTTL::Open(options->rep, std::string(name), &db, ttl))) { + return nullptr; + } + rocksdb_t* result = new rocksdb_t; + result->rep = db; + return result; +} + rocksdb_t* rocksdb_open_for_read_only( const rocksdb_options_t* options, const char* name, @@ -506,10 +534,18 @@ rocksdb_backup_engine_t* rocksdb_backup_engine_open( } void rocksdb_backup_engine_create_new_backup(rocksdb_backup_engine_t* be, - rocksdb_t* db, char** errptr) { + rocksdb_t* db, + char** errptr) { SaveError(errptr, be->rep->CreateNewBackup(db->rep)); } +void rocksdb_backup_engine_create_new_backup_flush(rocksdb_backup_engine_t* be, + rocksdb_t* db, + unsigned char flush_before_backup, + char** errptr) { + SaveError(errptr, be->rep->CreateNewBackup(db->rep, flush_before_backup)); +} + void rocksdb_backup_engine_purge_old_backups(rocksdb_backup_engine_t* be, uint32_t num_backups_to_keep, char** errptr) { @@ -529,6 +565,12 @@ void rocksdb_restore_options_set_keep_log_files(rocksdb_restore_options_t* opt, opt->rep.keep_log_files = v; } + +void rocksdb_backup_engine_verify_backup(rocksdb_backup_engine_t* be, + uint32_t backup_id, char** errptr) { + SaveError(errptr, be->rep->VerifyBackup(static_cast(backup_id))); +} + void rocksdb_backup_engine_restore_db_from_latest_backup( rocksdb_backup_engine_t* be, const char* db_dir, const char* wal_dir, const rocksdb_restore_options_t* restore_options, char** errptr) { @@ -909,6 +951,54 @@ rocksdb_iterator_t* rocksdb_create_iterator( return result; } +rocksdb_wal_iterator_t* rocksdb_get_updates_since( + rocksdb_t* db, uint64_t seq_number, + const rocksdb_wal_readoptions_t* options, + char** errptr) { + std::unique_ptr iter; + TransactionLogIterator::ReadOptions ro; + if (options!=nullptr) { + ro = options->rep; + } + if (SaveError(errptr, db->rep->GetUpdatesSince(seq_number, &iter, ro))) { + return nullptr; + } + rocksdb_wal_iterator_t* result = new rocksdb_wal_iterator_t; + result->rep = iter.release(); + return result; +} + +void rocksdb_wal_iter_next(rocksdb_wal_iterator_t* iter) { + iter->rep->Next(); +} + +unsigned char rocksdb_wal_iter_valid(const rocksdb_wal_iterator_t* iter) { + return iter->rep->Valid(); +} + +void rocksdb_wal_iter_status (const rocksdb_wal_iterator_t* iter, char** errptr) { + SaveError(errptr, iter->rep->status()); +} + +void rocksdb_wal_iter_destroy (const rocksdb_wal_iterator_t* iter) { + delete iter->rep; + delete iter; +} + +rocksdb_writebatch_t* rocksdb_wal_iter_get_batch (const rocksdb_wal_iterator_t* iter, uint64_t* seq) { + rocksdb_writebatch_t* result = rocksdb_writebatch_create(); + BatchResult wal_batch = iter->rep->GetBatch(); + result->rep = * wal_batch.writeBatchPtr.release(); + if (seq != nullptr) { + *seq = wal_batch.sequence; + } + return result; +} + +uint64_t rocksdb_get_latest_sequence_number (rocksdb_t *db) { + return db->rep->GetLatestSequenceNumber(); +} + rocksdb_iterator_t* rocksdb_create_iterator_cf( rocksdb_t* db, const rocksdb_readoptions_t* options, @@ -1386,23 +1476,24 @@ void rocksdb_writebatch_put_log_data( b->rep.PutLogData(Slice(blob, len)); } +class H : public WriteBatch::Handler { + public: + void* state_; + void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen); + void (*deleted_)(void*, const char* k, size_t klen); + void Put(const Slice& key, const Slice& value) override { + (*put_)(state_, key.data(), key.size(), value.data(), value.size()); + } + void Delete(const Slice& key) override { + (*deleted_)(state_, key.data(), key.size()); + } +}; + void rocksdb_writebatch_iterate( rocksdb_writebatch_t* b, void* state, void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen), void (*deleted)(void*, const char* k, size_t klen)) { - class H : public WriteBatch::Handler { - public: - void* state_; - void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen); - void (*deleted_)(void*, const char* k, size_t klen); - virtual void Put(const Slice& key, const Slice& value) override { - (*put_)(state_, key.data(), key.size(), value.data(), value.size()); - } - virtual void Delete(const Slice& key) override { - (*deleted_)(state_, key.data(), key.size()); - } - }; H handler; handler.state_ = state; handler.put_ = put; @@ -1647,18 +1738,6 @@ void rocksdb_writebatch_wi_iterate( void* state, void (*put)(void*, const char* k, size_t klen, const char* v, size_t vlen), void (*deleted)(void*, const char* k, size_t klen)) { - class H : public WriteBatch::Handler { - public: - void* state_; - void (*put_)(void*, const char* k, size_t klen, const char* v, size_t vlen); - void (*deleted_)(void*, const char* k, size_t klen); - virtual void Put(const Slice& key, const Slice& value) override { - (*put_)(state_, key.data(), key.size(), value.data(), value.size()); - } - virtual void Delete(const Slice& key) override { - (*deleted_)(state_, key.data(), key.size()); - } - }; H handler; handler.state_ = state; handler.put_ = put; @@ -1691,11 +1770,11 @@ rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base( } rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf( - rocksdb_writebatch_wi_t* wbwi, - rocksdb_iterator_t* base_iterator, + rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator, rocksdb_column_family_handle_t* column_family) { rocksdb_iterator_t* result = new rocksdb_iterator_t; - result->rep = wbwi->rep->NewIteratorWithBase(column_family->rep, base_iterator->rep); + result->rep = + wbwi->rep->NewIteratorWithBase(column_family->rep, base_iterator->rep); delete base_iterator; return result; } @@ -1824,6 +1903,26 @@ void rocksdb_block_based_options_set_block_restart_interval( options->rep.block_restart_interval = block_restart_interval; } +void rocksdb_block_based_options_set_index_block_restart_interval( + rocksdb_block_based_table_options_t* options, int index_block_restart_interval) { + options->rep.index_block_restart_interval = index_block_restart_interval; +} + +void rocksdb_block_based_options_set_metadata_block_size( + rocksdb_block_based_table_options_t* options, uint64_t metadata_block_size) { + options->rep.metadata_block_size = metadata_block_size; +} + +void rocksdb_block_based_options_set_partition_filters( + rocksdb_block_based_table_options_t* options, unsigned char partition_filters) { + options->rep.partition_filters = partition_filters; +} + +void rocksdb_block_based_options_set_use_delta_encoding( + rocksdb_block_based_table_options_t* options, unsigned char use_delta_encoding) { + options->rep.use_delta_encoding = use_delta_encoding; +} + void rocksdb_block_based_options_set_filter_policy( rocksdb_block_based_table_options_t* options, rocksdb_filterpolicy_t* filter_policy) { @@ -1877,11 +1976,21 @@ void rocksdb_block_based_options_set_cache_index_and_filter_blocks( options->rep.cache_index_and_filter_blocks = v; } +void rocksdb_block_based_options_set_cache_index_and_filter_blocks_with_high_priority( + rocksdb_block_based_table_options_t* options, unsigned char v) { + options->rep.cache_index_and_filter_blocks_with_high_priority = v; +} + void rocksdb_block_based_options_set_pin_l0_filter_and_index_blocks_in_cache( rocksdb_block_based_table_options_t* options, unsigned char v) { options->rep.pin_l0_filter_and_index_blocks_in_cache = v; } +void rocksdb_block_based_options_set_pin_top_level_index_and_filter( + rocksdb_block_based_table_options_t* options, unsigned char v) { + options->rep.pin_top_level_index_and_filter = v; +} + void rocksdb_options_set_block_based_table_factory( rocksdb_options_t *opt, rocksdb_block_based_table_options_t* table_options) { @@ -1891,7 +2000,6 @@ void rocksdb_options_set_block_based_table_factory( } } - rocksdb_cuckoo_table_options_t* rocksdb_cuckoo_options_create() { return new rocksdb_cuckoo_table_options_t; @@ -1945,6 +2053,15 @@ void rocksdb_set_options( db->rep->SetOptions(options_map)); } +void rocksdb_set_options_cf( + rocksdb_t* db, rocksdb_column_family_handle_t* handle, int count, const char* const keys[], const char* const values[], char** errptr) { + std::unordered_map options_map; + for (int i=0; irep->SetOptions(handle->rep, options_map)); + } + rocksdb_options_t* rocksdb_options_create() { return new rocksdb_options_t; } @@ -1973,6 +2090,11 @@ void rocksdb_options_optimize_universal_style_compaction( opt->rep.OptimizeUniversalStyleCompaction(memtable_memory_budget); } +void rocksdb_options_set_allow_ingest_behind( + rocksdb_options_t* opt, unsigned char v) { + opt->rep.allow_ingest_behind = v; +} + void rocksdb_options_set_compaction_filter( rocksdb_options_t* opt, rocksdb_compactionfilter_t* filter) { @@ -2023,8 +2145,8 @@ void rocksdb_options_set_paranoid_checks( opt->rep.paranoid_checks = v; } -void rocksdb_options_set_db_paths(rocksdb_options_t* opt, - const rocksdb_dbpath_t** dbpath_values, +void rocksdb_options_set_db_paths(rocksdb_options_t* opt, + const rocksdb_dbpath_t** dbpath_values, size_t num_paths) { std::vector db_paths(num_paths); for (size_t i = 0; i < num_paths; ++i) { @@ -2111,7 +2233,8 @@ void rocksdb_options_enable_statistics(rocksdb_options_t* opt) { opt->rep.statistics = rocksdb::CreateDBStatistics(); } -void rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt, unsigned char val) { +void rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt, + unsigned char val) { opt->rep.skip_stats_update_on_db_open = val; } @@ -2134,8 +2257,8 @@ void rocksdb_options_set_level0_stop_writes_trigger( opt->rep.level0_stop_writes_trigger = n; } -void rocksdb_options_set_max_mem_compaction_level(rocksdb_options_t* opt, - int n) {} +void rocksdb_options_set_max_mem_compaction_level(rocksdb_options_t* /*opt*/, + int /*n*/) {} void rocksdb_options_set_wal_recovery_mode(rocksdb_options_t* opt,int mode) { opt->rep.wal_recovery_mode = static_cast(mode); @@ -2155,6 +2278,18 @@ void rocksdb_options_set_compression_per_level(rocksdb_options_t* opt, } } +void rocksdb_options_set_bottommost_compression_options(rocksdb_options_t* opt, + int w_bits, int level, + int strategy, + int max_dict_bytes, + bool enabled) { + opt->rep.bottommost_compression_opts.window_bits = w_bits; + opt->rep.bottommost_compression_opts.level = level; + opt->rep.bottommost_compression_opts.strategy = strategy; + opt->rep.bottommost_compression_opts.max_dict_bytes = max_dict_bytes; + opt->rep.bottommost_compression_opts.enabled = enabled; +} + void rocksdb_options_set_compression_options(rocksdb_options_t* opt, int w_bits, int level, int strategy, int max_dict_bytes) { @@ -2199,8 +2334,8 @@ void rocksdb_options_set_manifest_preallocation_size( } // noop -void rocksdb_options_set_purge_redundant_kvs_while_flush(rocksdb_options_t* opt, - unsigned char v) {} +void rocksdb_options_set_purge_redundant_kvs_while_flush( + rocksdb_options_t* /*opt*/, unsigned char /*v*/) {} void rocksdb_options_set_use_direct_reads(rocksdb_options_t* opt, unsigned char v) { @@ -2265,11 +2400,21 @@ void rocksdb_options_set_use_adaptive_mutex( opt->rep.use_adaptive_mutex = v; } +void rocksdb_options_set_wal_bytes_per_sync( + rocksdb_options_t* opt, uint64_t v) { + opt->rep.wal_bytes_per_sync = v; +} + void rocksdb_options_set_bytes_per_sync( rocksdb_options_t* opt, uint64_t v) { opt->rep.bytes_per_sync = v; } +void rocksdb_options_set_writable_file_max_buffer_size(rocksdb_options_t* opt, + uint64_t v) { + opt->rep.writable_file_max_buffer_size = static_cast(v); +} + void rocksdb_options_set_allow_concurrent_memtable_write(rocksdb_options_t* opt, unsigned char v) { opt->rep.allow_concurrent_memtable_write = v; @@ -2298,6 +2443,20 @@ void rocksdb_options_set_max_write_buffer_number_to_maintain( opt->rep.max_write_buffer_number_to_maintain = n; } +void rocksdb_options_set_enable_pipelined_write(rocksdb_options_t* opt, + unsigned char v) { + opt->rep.enable_pipelined_write = v; +} + +void rocksdb_options_set_max_subcompactions(rocksdb_options_t* opt, + uint32_t n) { + opt->rep.max_subcompactions = n; +} + +void rocksdb_options_set_max_background_jobs(rocksdb_options_t* opt, int n) { + opt->rep.max_background_jobs = n; +} + void rocksdb_options_set_max_background_compactions(rocksdb_options_t* opt, int n) { opt->rep.max_background_compactions = n; } @@ -2360,7 +2519,7 @@ void rocksdb_options_set_table_cache_numshardbits( } void rocksdb_options_set_table_cache_remove_scan_count_limit( - rocksdb_options_t* opt, int v) { + rocksdb_options_t* /*opt*/, int /*v*/) { // this option is deprecated } @@ -2474,8 +2633,9 @@ char *rocksdb_options_statistics_get_string(rocksdb_options_t *opt) { } void rocksdb_options_set_ratelimiter(rocksdb_options_t *opt, rocksdb_ratelimiter_t *limiter) { - opt->rep.rate_limiter.reset(limiter->rep); - limiter->rep = nullptr; + if (limiter) { + opt->rep.rate_limiter = limiter->rep; + } } rocksdb_ratelimiter_t* rocksdb_ratelimiter_create( @@ -2483,18 +2643,186 @@ rocksdb_ratelimiter_t* rocksdb_ratelimiter_create( int64_t refill_period_us, int32_t fairness) { rocksdb_ratelimiter_t* rate_limiter = new rocksdb_ratelimiter_t; - rate_limiter->rep = NewGenericRateLimiter(rate_bytes_per_sec, - refill_period_us, fairness); + rate_limiter->rep.reset( + NewGenericRateLimiter(rate_bytes_per_sec, + refill_period_us, fairness)); return rate_limiter; } void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t *limiter) { - if (limiter->rep) { - delete limiter->rep; - } delete limiter; } +void rocksdb_set_perf_level(int v) { + PerfLevel level = static_cast(v); + SetPerfLevel(level); +} + +rocksdb_perfcontext_t* rocksdb_perfcontext_create() { + rocksdb_perfcontext_t* context = new rocksdb_perfcontext_t; + context->rep = rocksdb::get_perf_context(); + return context; +} + +void rocksdb_perfcontext_reset(rocksdb_perfcontext_t* context) { + context->rep->Reset(); +} + +char* rocksdb_perfcontext_report(rocksdb_perfcontext_t* context, + unsigned char exclude_zero_counters) { + return strdup(context->rep->ToString(exclude_zero_counters).c_str()); +} + +uint64_t rocksdb_perfcontext_metric(rocksdb_perfcontext_t* context, + int metric) { + PerfContext* rep = context->rep; + switch (metric) { + case rocksdb_user_key_comparison_count: + return rep->user_key_comparison_count; + case rocksdb_block_cache_hit_count: + return rep->block_cache_hit_count; + case rocksdb_block_read_count: + return rep->block_read_count; + case rocksdb_block_read_byte: + return rep->block_read_byte; + case rocksdb_block_read_time: + return rep->block_read_time; + case rocksdb_block_checksum_time: + return rep->block_checksum_time; + case rocksdb_block_decompress_time: + return rep->block_decompress_time; + case rocksdb_get_read_bytes: + return rep->get_read_bytes; + case rocksdb_multiget_read_bytes: + return rep->multiget_read_bytes; + case rocksdb_iter_read_bytes: + return rep->iter_read_bytes; + case rocksdb_internal_key_skipped_count: + return rep->internal_key_skipped_count; + case rocksdb_internal_delete_skipped_count: + return rep->internal_delete_skipped_count; + case rocksdb_internal_recent_skipped_count: + return rep->internal_recent_skipped_count; + case rocksdb_internal_merge_count: + return rep->internal_merge_count; + case rocksdb_get_snapshot_time: + return rep->get_snapshot_time; + case rocksdb_get_from_memtable_time: + return rep->get_from_memtable_time; + case rocksdb_get_from_memtable_count: + return rep->get_from_memtable_count; + case rocksdb_get_post_process_time: + return rep->get_post_process_time; + case rocksdb_get_from_output_files_time: + return rep->get_from_output_files_time; + case rocksdb_seek_on_memtable_time: + return rep->seek_on_memtable_time; + case rocksdb_seek_on_memtable_count: + return rep->seek_on_memtable_count; + case rocksdb_next_on_memtable_count: + return rep->next_on_memtable_count; + case rocksdb_prev_on_memtable_count: + return rep->prev_on_memtable_count; + case rocksdb_seek_child_seek_time: + return rep->seek_child_seek_time; + case rocksdb_seek_child_seek_count: + return rep->seek_child_seek_count; + case rocksdb_seek_min_heap_time: + return rep->seek_min_heap_time; + case rocksdb_seek_max_heap_time: + return rep->seek_max_heap_time; + case rocksdb_seek_internal_seek_time: + return rep->seek_internal_seek_time; + case rocksdb_find_next_user_entry_time: + return rep->find_next_user_entry_time; + case rocksdb_write_wal_time: + return rep->write_wal_time; + case rocksdb_write_memtable_time: + return rep->write_memtable_time; + case rocksdb_write_delay_time: + return rep->write_delay_time; + case rocksdb_write_pre_and_post_process_time: + return rep->write_pre_and_post_process_time; + case rocksdb_db_mutex_lock_nanos: + return rep->db_mutex_lock_nanos; + case rocksdb_db_condition_wait_nanos: + return rep->db_condition_wait_nanos; + case rocksdb_merge_operator_time_nanos: + return rep->merge_operator_time_nanos; + case rocksdb_read_index_block_nanos: + return rep->read_index_block_nanos; + case rocksdb_read_filter_block_nanos: + return rep->read_filter_block_nanos; + case rocksdb_new_table_block_iter_nanos: + return rep->new_table_block_iter_nanos; + case rocksdb_new_table_iterator_nanos: + return rep->new_table_iterator_nanos; + case rocksdb_block_seek_nanos: + return rep->block_seek_nanos; + case rocksdb_find_table_nanos: + return rep->find_table_nanos; + case rocksdb_bloom_memtable_hit_count: + return rep->bloom_memtable_hit_count; + case rocksdb_bloom_memtable_miss_count: + return rep->bloom_memtable_miss_count; + case rocksdb_bloom_sst_hit_count: + return rep->bloom_sst_hit_count; + case rocksdb_bloom_sst_miss_count: + return rep->bloom_sst_miss_count; + case rocksdb_key_lock_wait_time: + return rep->key_lock_wait_time; + case rocksdb_key_lock_wait_count: + return rep->key_lock_wait_count; + case rocksdb_env_new_sequential_file_nanos: + return rep->env_new_sequential_file_nanos; + case rocksdb_env_new_random_access_file_nanos: + return rep->env_new_random_access_file_nanos; + case rocksdb_env_new_writable_file_nanos: + return rep->env_new_writable_file_nanos; + case rocksdb_env_reuse_writable_file_nanos: + return rep->env_reuse_writable_file_nanos; + case rocksdb_env_new_random_rw_file_nanos: + return rep->env_new_random_rw_file_nanos; + case rocksdb_env_new_directory_nanos: + return rep->env_new_directory_nanos; + case rocksdb_env_file_exists_nanos: + return rep->env_file_exists_nanos; + case rocksdb_env_get_children_nanos: + return rep->env_get_children_nanos; + case rocksdb_env_get_children_file_attributes_nanos: + return rep->env_get_children_file_attributes_nanos; + case rocksdb_env_delete_file_nanos: + return rep->env_delete_file_nanos; + case rocksdb_env_create_dir_nanos: + return rep->env_create_dir_nanos; + case rocksdb_env_create_dir_if_missing_nanos: + return rep->env_create_dir_if_missing_nanos; + case rocksdb_env_delete_dir_nanos: + return rep->env_delete_dir_nanos; + case rocksdb_env_get_file_size_nanos: + return rep->env_get_file_size_nanos; + case rocksdb_env_get_file_modification_time_nanos: + return rep->env_get_file_modification_time_nanos; + case rocksdb_env_rename_file_nanos: + return rep->env_rename_file_nanos; + case rocksdb_env_link_file_nanos: + return rep->env_link_file_nanos; + case rocksdb_env_lock_file_nanos: + return rep->env_lock_file_nanos; + case rocksdb_env_unlock_file_nanos: + return rep->env_unlock_file_nanos; + case rocksdb_env_new_logger_nanos: + return rep->env_new_logger_nanos; + default: + break; + } + return 0; +} + +void rocksdb_perfcontext_destroy(rocksdb_perfcontext_t* context) { + delete context; +} + /* TODO: DB::OpenForReadOnly @@ -2524,7 +2852,7 @@ rocksdb_compactionfilter_t* rocksdb_compactionfilter_create( result->state_ = state; result->destructor_ = destructor; result->filter_ = filter; - result->ignore_snapshots_ = false; + result->ignore_snapshots_ = true; result->name_ = name; return result; } @@ -2624,7 +2952,7 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(int bits_per_ke // supplied C functions. struct Wrapper : public rocksdb_filterpolicy_t { const FilterPolicy* rep_; - ~Wrapper() { delete rep_; } + ~Wrapper() override { delete rep_; } const char* Name() const override { return rep_->Name(); } void CreateFilter(const Slice* keys, int n, std::string* dst) const override { @@ -2717,6 +3045,18 @@ void rocksdb_readoptions_set_iterate_upper_bound( } } +void rocksdb_readoptions_set_iterate_lower_bound( + rocksdb_readoptions_t *opt, + const char* key, size_t keylen) { + if (key == nullptr) { + opt->lower_bound = Slice(); + opt->rep.iterate_lower_bound = nullptr; + } else { + opt->lower_bound = Slice(key, keylen); + opt->rep.iterate_lower_bound = &opt->lower_bound; + } +} + void rocksdb_readoptions_set_read_tier( rocksdb_readoptions_t* opt, int v) { opt->rep.read_tier = static_cast(v); @@ -2727,11 +3067,21 @@ void rocksdb_readoptions_set_tailing( opt->rep.tailing = v; } +void rocksdb_readoptions_set_managed( + rocksdb_readoptions_t* opt, unsigned char v) { + opt->rep.managed = v; +} + void rocksdb_readoptions_set_readahead_size( rocksdb_readoptions_t* opt, size_t v) { opt->rep.readahead_size = v; } +void rocksdb_readoptions_set_prefix_same_as_start( + rocksdb_readoptions_t* opt, unsigned char v) { + opt->rep.prefix_same_as_start = v; +} + void rocksdb_readoptions_set_pin_data(rocksdb_readoptions_t* opt, unsigned char v) { opt->rep.pin_data = v; @@ -2742,6 +3092,22 @@ void rocksdb_readoptions_set_total_order_seek(rocksdb_readoptions_t* opt, opt->rep.total_order_seek = v; } +void rocksdb_readoptions_set_max_skippable_internal_keys( + rocksdb_readoptions_t* opt, + uint64_t v) { + opt->rep.max_skippable_internal_keys = v; +} + +void rocksdb_readoptions_set_background_purge_on_iterator_cleanup( + rocksdb_readoptions_t* opt, unsigned char v) { + opt->rep.background_purge_on_iterator_cleanup = v; +} + +void rocksdb_readoptions_set_ignore_range_deletions( + rocksdb_readoptions_t* opt, unsigned char v) { + opt->rep.ignore_range_deletions = v; +} + rocksdb_writeoptions_t* rocksdb_writeoptions_create() { return new rocksdb_writeoptions_t; } @@ -2759,6 +3125,24 @@ void rocksdb_writeoptions_disable_WAL(rocksdb_writeoptions_t* opt, int disable) opt->rep.disableWAL = disable; } +void rocksdb_writeoptions_set_ignore_missing_column_families( + rocksdb_writeoptions_t* opt, + unsigned char v) { + opt->rep.ignore_missing_column_families = v; +} + +void rocksdb_writeoptions_set_no_slowdown( + rocksdb_writeoptions_t* opt, + unsigned char v) { + opt->rep.no_slowdown = v; +} + +void rocksdb_writeoptions_set_low_pri( + rocksdb_writeoptions_t* opt, + unsigned char v) { + opt->rep.low_pri = v; +} + rocksdb_compactoptions_t* rocksdb_compactoptions_create() { return new rocksdb_compactoptions_t; } @@ -2767,6 +3151,11 @@ void rocksdb_compactoptions_destroy(rocksdb_compactoptions_t* opt) { delete opt; } +void rocksdb_compactoptions_set_bottommost_level_compaction( + rocksdb_compactoptions_t* opt, unsigned char v) { + opt->rep.bottommost_level_compaction = static_cast(v); +} + void rocksdb_compactoptions_set_exclusive_manual_compaction( rocksdb_compactoptions_t* opt, unsigned char v) { opt->rep.exclusive_manual_compaction = v; @@ -2875,7 +3264,7 @@ rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create( rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create_with_comparator( const rocksdb_envoptions_t* env, const rocksdb_options_t* io_options, - const rocksdb_comparator_t* comparator) { + const rocksdb_comparator_t* /*comparator*/) { rocksdb_sstfilewriter_t* writer = new rocksdb_sstfilewriter_t; writer->rep = new SstFileWriter(env->rep, io_options->rep); return writer; @@ -2913,7 +3302,12 @@ void rocksdb_sstfilewriter_delete(rocksdb_sstfilewriter_t* writer, void rocksdb_sstfilewriter_finish(rocksdb_sstfilewriter_t* writer, char** errptr) { - SaveError(errptr, writer->rep->Finish(NULL)); + SaveError(errptr, writer->rep->Finish(nullptr)); +} + +void rocksdb_sstfilewriter_file_size(rocksdb_sstfilewriter_t* writer, + uint64_t* file_size) { + *file_size = writer->rep->FileSize(); } void rocksdb_sstfilewriter_destroy(rocksdb_sstfilewriter_t* writer) { @@ -2951,6 +3345,12 @@ void rocksdb_ingestexternalfileoptions_set_allow_blocking_flush( opt->rep.allow_blocking_flush = allow_blocking_flush; } +void rocksdb_ingestexternalfileoptions_set_ingest_behind( + rocksdb_ingestexternalfileoptions_t* opt, + unsigned char ingest_behind) { + opt->rep.ingest_behind = ingest_behind; +} + void rocksdb_ingestexternalfileoptions_destroy( rocksdb_ingestexternalfileoptions_t* opt) { delete opt; @@ -3005,20 +3405,21 @@ void rocksdb_slicetransform_destroy(rocksdb_slicetransform_t* st) { delete st; } +struct Wrapper : public rocksdb_slicetransform_t { + const SliceTransform* rep_; + ~Wrapper() override { delete rep_; } + const char* Name() const override { return rep_->Name(); } + Slice Transform(const Slice& src) const override { + return rep_->Transform(src); + } + bool InDomain(const Slice& src) const override { + return rep_->InDomain(src); + } + bool InRange(const Slice& src) const override { return rep_->InRange(src); } + static void DoNothing(void*) { } +}; + rocksdb_slicetransform_t* rocksdb_slicetransform_create_fixed_prefix(size_t prefixLen) { - struct Wrapper : public rocksdb_slicetransform_t { - const SliceTransform* rep_; - ~Wrapper() { delete rep_; } - const char* Name() const override { return rep_->Name(); } - Slice Transform(const Slice& src) const override { - return rep_->Transform(src); - } - bool InDomain(const Slice& src) const override { - return rep_->InDomain(src); - } - bool InRange(const Slice& src) const override { return rep_->InRange(src); } - static void DoNothing(void*) { } - }; Wrapper* wrapper = new Wrapper; wrapper->rep_ = rocksdb::NewFixedPrefixTransform(prefixLen); wrapper->state_ = nullptr; @@ -3027,19 +3428,6 @@ rocksdb_slicetransform_t* rocksdb_slicetransform_create_fixed_prefix(size_t pref } rocksdb_slicetransform_t* rocksdb_slicetransform_create_noop() { - struct Wrapper : public rocksdb_slicetransform_t { - const SliceTransform* rep_; - ~Wrapper() { delete rep_; } - const char* Name() const override { return rep_->Name(); } - Slice Transform(const Slice& src) const override { - return rep_->Transform(src); - } - bool InDomain(const Slice& src) const override { - return rep_->InDomain(src); - } - bool InRange(const Slice& src) const override { return rep_->InRange(src); } - static void DoNothing(void*) { } - }; Wrapper* wrapper = new Wrapper; wrapper->rep_ = rocksdb::NewNoopTransform(); wrapper->state_ = nullptr; @@ -3157,6 +3545,18 @@ const char* rocksdb_livefiles_largestkey( return lf->rep[index].largestkey.data(); } +uint64_t rocksdb_livefiles_entries( + const rocksdb_livefiles_t* lf, + int index) { + return lf->rep[index].num_entries; +} + +uint64_t rocksdb_livefiles_deletions( + const rocksdb_livefiles_t* lf, + int index) { + return lf->rep[index].num_deletions; +} + extern void rocksdb_livefiles_destroy( const rocksdb_livefiles_t* lf) { delete lf; @@ -3302,6 +3702,38 @@ rocksdb_transactiondb_t* rocksdb_transactiondb_open( return result; } +rocksdb_transactiondb_t* rocksdb_transactiondb_open_column_families( + const rocksdb_options_t* options, + const rocksdb_transactiondb_options_t* txn_db_options, const char* name, + int num_column_families, const char** column_family_names, + const rocksdb_options_t** column_family_options, + rocksdb_column_family_handle_t** column_family_handles, char** errptr) { + std::vector column_families; + for (int i = 0; i < num_column_families; i++) { + column_families.push_back(ColumnFamilyDescriptor( + std::string(column_family_names[i]), + ColumnFamilyOptions(column_family_options[i]->rep))); + } + + TransactionDB* txn_db; + std::vector handles; + if (SaveError(errptr, TransactionDB::Open(options->rep, txn_db_options->rep, + std::string(name), column_families, + &handles, &txn_db))) { + return nullptr; + } + + for (size_t i = 0; i < handles.size(); i++) { + rocksdb_column_family_handle_t* c_handle = + new rocksdb_column_family_handle_t; + c_handle->rep = handles[i]; + column_family_handles[i] = c_handle; + } + rocksdb_transactiondb_t* result = new rocksdb_transactiondb_t; + result->rep = txn_db; + return result; +} + const rocksdb_snapshot_t* rocksdb_transactiondb_create_snapshot( rocksdb_transactiondb_t* txn_db) { rocksdb_snapshot_t* result = new rocksdb_snapshot_t; @@ -3339,6 +3771,14 @@ void rocksdb_transaction_rollback(rocksdb_transaction_t* txn, char** errptr) { SaveError(errptr, txn->rep->Rollback()); } +void rocksdb_transaction_set_savepoint(rocksdb_transaction_t* txn) { + txn->rep->SetSavePoint(); +} + +void rocksdb_transaction_rollback_to_savepoint(rocksdb_transaction_t* txn, char** errptr) { + SaveError(errptr, txn->rep->RollbackToSavePoint()); +} + void rocksdb_transaction_destroy(rocksdb_transaction_t* txn) { delete txn->rep; delete txn; @@ -3414,6 +3854,26 @@ char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn, return result; } +char* rocksdb_transaction_get_for_update_cf( + rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options, + rocksdb_column_family_handle_t* column_family, const char* key, size_t klen, + size_t* vlen, unsigned char exclusive, char** errptr) { + char* result = nullptr; + std::string tmp; + Status s = txn->rep->GetForUpdate(options->rep, column_family->rep, + Slice(key, klen), &tmp, exclusive); + if (s.ok()) { + *vlen = tmp.size(); + result = CopyString(tmp); + } else { + *vlen = 0; + if (!s.IsNotFound()) { + SaveError(errptr, s); + } + } + return result; +} + // Read a key outside a transaction char* rocksdb_transactiondb_get( rocksdb_transactiondb_t* txn_db, @@ -3476,8 +3936,8 @@ void rocksdb_transactiondb_put(rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options, const char* key, size_t klen, const char* val, size_t vlen, char** errptr) { - SaveError(errptr, txn_db->rep->Put(options->rep, Slice(key, klen), - Slice(val, vlen))); + SaveError(errptr, + txn_db->rep->Put(options->rep, Slice(key, klen), Slice(val, vlen))); } void rocksdb_transactiondb_put_cf(rocksdb_transactiondb_t* txn_db, @@ -3506,13 +3966,29 @@ void rocksdb_transaction_merge(rocksdb_transaction_t* txn, const char* key, SaveError(errptr, txn->rep->Merge(Slice(key, klen), Slice(val, vlen))); } +void rocksdb_transaction_merge_cf(rocksdb_transaction_t* txn, + rocksdb_column_family_handle_t* column_family, + const char* key, size_t klen, const char* val, + size_t vlen, char** errptr) { + SaveError(errptr, txn->rep->Merge(column_family->rep, Slice(key, klen), + Slice(val, vlen))); +} + // Merge a key outside a transaction void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options, const char* key, size_t klen, const char* val, size_t vlen, char** errptr) { - SaveError(errptr, - txn_db->rep->Merge(options->rep, Slice(key, klen), Slice(val, vlen))); + SaveError(errptr, txn_db->rep->Merge(options->rep, Slice(key, klen), + Slice(val, vlen))); +} + +void rocksdb_transactiondb_merge_cf( + rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options, + rocksdb_column_family_handle_t* column_family, const char* key, size_t klen, + const char* val, size_t vlen, char** errptr) { + SaveError(errptr, txn_db->rep->Merge(options->rep, column_family->rep, + Slice(key, klen), Slice(val, vlen))); } // Delete a key inside a transaction @@ -3550,6 +4026,15 @@ rocksdb_iterator_t* rocksdb_transaction_create_iterator( return result; } +// Create an iterator inside a transaction with column family +rocksdb_iterator_t* rocksdb_transaction_create_iterator_cf( + rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options, + rocksdb_column_family_handle_t* column_family) { + rocksdb_iterator_t* result = new rocksdb_iterator_t; + result->rep = txn->rep->GetIterator(options->rep, column_family->rep); + return result; +} + // Create an iterator outside a transaction rocksdb_iterator_t* rocksdb_transactiondb_create_iterator( rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options) { @@ -3558,6 +4043,14 @@ rocksdb_iterator_t* rocksdb_transactiondb_create_iterator( return result; } +rocksdb_iterator_t* rocksdb_transactiondb_create_iterator_cf( + rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options, + rocksdb_column_family_handle_t* column_family) { + rocksdb_iterator_t* result = new rocksdb_iterator_t; + result->rep = txn_db->rep->NewIterator(options->rep, column_family->rep); + return result; +} + void rocksdb_transactiondb_close(rocksdb_transactiondb_t* txn_db) { delete txn_db->rep; delete txn_db; @@ -3575,8 +4068,7 @@ rocksdb_checkpoint_t* rocksdb_transactiondb_checkpoint_object_create( } rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open( - const rocksdb_options_t* options, const char* name, - char** errptr) { + const rocksdb_options_t* options, const char* name, char** errptr) { OptimisticTransactionDB* otxn_db; if (SaveError(errptr, OptimisticTransactionDB::Open( options->rep, std::string(name), &otxn_db))) { @@ -3588,6 +4080,56 @@ rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open( return result; } +rocksdb_optimistictransactiondb_t* +rocksdb_optimistictransactiondb_open_column_families( + const rocksdb_options_t* db_options, const char* name, + int num_column_families, const char** column_family_names, + const rocksdb_options_t** column_family_options, + rocksdb_column_family_handle_t** column_family_handles, char** errptr) { + std::vector column_families; + for (int i = 0; i < num_column_families; i++) { + column_families.push_back(ColumnFamilyDescriptor( + std::string(column_family_names[i]), + ColumnFamilyOptions(column_family_options[i]->rep))); + } + + OptimisticTransactionDB* otxn_db; + std::vector handles; + if (SaveError(errptr, OptimisticTransactionDB::Open( + DBOptions(db_options->rep), std::string(name), + column_families, &handles, &otxn_db))) { + return nullptr; + } + + for (size_t i = 0; i < handles.size(); i++) { + rocksdb_column_family_handle_t* c_handle = + new rocksdb_column_family_handle_t; + c_handle->rep = handles[i]; + column_family_handles[i] = c_handle; + } + rocksdb_optimistictransactiondb_t* result = + new rocksdb_optimistictransactiondb_t; + result->rep = otxn_db; + return result; +} + +rocksdb_t* rocksdb_optimistictransactiondb_get_base_db( + rocksdb_optimistictransactiondb_t* otxn_db) { + DB* base_db = otxn_db->rep->GetBaseDB(); + + if (base_db != nullptr) { + rocksdb_t* result = new rocksdb_t; + result->rep = base_db; + return result; + } + + return nullptr; +} + +void rocksdb_optimistictransactiondb_close_base_db(rocksdb_t* base_db) { + delete base_db; +} + rocksdb_transaction_t* rocksdb_optimistictransaction_begin( rocksdb_optimistictransactiondb_t* otxn_db, const rocksdb_writeoptions_t* write_options, @@ -3623,7 +4165,7 @@ rocksdb_pinnableslice_t* rocksdb_get_pinned( if (!s.IsNotFound()) { SaveError(errptr, s); } - return NULL; + return nullptr; } return v; } @@ -3640,7 +4182,7 @@ rocksdb_pinnableslice_t* rocksdb_get_pinned_cf( if (!s.IsNotFound()) { SaveError(errptr, s); } - return NULL; + return nullptr; } return v; } @@ -3651,12 +4193,104 @@ const char* rocksdb_pinnableslice_value(const rocksdb_pinnableslice_t* v, size_t* vlen) { if (!v) { *vlen = 0; - return NULL; + return nullptr; } *vlen = v->rep.size(); return v->rep.data(); } + +// container to keep databases and caches in order to use rocksdb::MemoryUtil +struct rocksdb_memory_consumers_t { + std::vector dbs; + std::unordered_set caches; +}; + +// initializes new container of memory consumers +rocksdb_memory_consumers_t* rocksdb_memory_consumers_create() { + return new rocksdb_memory_consumers_t; +} + +// adds datatabase to the container of memory consumers +void rocksdb_memory_consumers_add_db(rocksdb_memory_consumers_t* consumers, + rocksdb_t* db) { + consumers->dbs.push_back(db); +} + +// adds cache to the container of memory consumers +void rocksdb_memory_consumers_add_cache(rocksdb_memory_consumers_t* consumers, + rocksdb_cache_t* cache) { + consumers->caches.insert(cache); +} + +// deletes container with memory consumers +void rocksdb_memory_consumers_destroy(rocksdb_memory_consumers_t* consumers) { + delete consumers; +} + +// contains memory usage statistics provided by rocksdb::MemoryUtil +struct rocksdb_memory_usage_t { + uint64_t mem_table_total; + uint64_t mem_table_unflushed; + uint64_t mem_table_readers_total; + uint64_t cache_total; +}; + +// estimates amount of memory occupied by consumers (dbs and caches) +rocksdb_memory_usage_t* rocksdb_approximate_memory_usage_create( + rocksdb_memory_consumers_t* consumers, char** errptr) { + + vector dbs; + for (auto db : consumers->dbs) { + dbs.push_back(db->rep); + } + + unordered_set cache_set; + for (auto cache : consumers->caches) { + cache_set.insert(const_cast(cache->rep.get())); + } + + std::map usage_by_type; + + auto status = MemoryUtil::GetApproximateMemoryUsageByType(dbs, cache_set, + &usage_by_type); + if (SaveError(errptr, status)) { + return nullptr; + } + + auto result = new rocksdb_memory_usage_t; + result->mem_table_total = usage_by_type[MemoryUtil::kMemTableTotal]; + result->mem_table_unflushed = usage_by_type[MemoryUtil::kMemTableUnFlushed]; + result->mem_table_readers_total = usage_by_type[MemoryUtil::kTableReadersTotal]; + result->cache_total = usage_by_type[MemoryUtil::kCacheTotal]; + return result; +} + +uint64_t rocksdb_approximate_memory_usage_get_mem_table_total( + rocksdb_memory_usage_t* memory_usage) { + return memory_usage->mem_table_total; +} + +uint64_t rocksdb_approximate_memory_usage_get_mem_table_unflushed( + rocksdb_memory_usage_t* memory_usage) { + return memory_usage->mem_table_unflushed; +} + +uint64_t rocksdb_approximate_memory_usage_get_mem_table_readers_total( + rocksdb_memory_usage_t* memory_usage) { + return memory_usage->mem_table_readers_total; +} + +uint64_t rocksdb_approximate_memory_usage_get_cache_total( + rocksdb_memory_usage_t* memory_usage) { + return memory_usage->cache_total; +} + +// deletes container with memory usage estimates +void rocksdb_approximate_memory_usage_destroy(rocksdb_memory_usage_t* usage) { + delete usage; +} + } // end extern "C" #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/c_test.c b/thirdparty/rocksdb/db/c_test.c index 7b76badf1c..39b8b1cfb0 100644 --- a/thirdparty/rocksdb/db/c_test.c +++ b/thirdparty/rocksdb/db/c_test.c @@ -19,11 +19,8 @@ // Can not use port/port.h macros as this is a c file #ifdef OS_WIN - #include -#define snprintf _snprintf - // Ok for uniqueness int geteuid() { int result = 0; @@ -34,6 +31,11 @@ int geteuid() { return result; } +// VS < 2015 +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define snprintf _snprintf +#endif + #endif const char* phase = ""; @@ -47,12 +49,19 @@ static void StartPhase(const char* name) { fprintf(stderr, "=== Test %s\n", name); phase = name; } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning (disable: 4996) // getenv security warning +#endif static const char* GetTempDir(void) { const char* ret = getenv("TEST_TMPDIR"); if (ret == NULL || ret[0] == '\0') ret = "/tmp"; return ret; } +#ifdef _MSC_VER +#pragma warning(pop) +#endif #define CheckNoError(err) \ if ((err) != NULL) { \ @@ -192,10 +201,11 @@ static void CheckDel(void* ptr, const char* k, size_t klen) { (*state)++; } -static void CmpDestroy(void* arg) { } +static void CmpDestroy(void* arg) { (void)arg; } static int CmpCompare(void* arg, const char* a, size_t alen, const char* b, size_t blen) { + (void)arg; size_t n = (alen < blen) ? alen : blen; int r = memcmp(a, b, n); if (r == 0) { @@ -206,13 +216,15 @@ static int CmpCompare(void* arg, const char* a, size_t alen, } static const char* CmpName(void* arg) { + (void)arg; return "foo"; } // Custom filter policy static unsigned char fake_filter_result = 1; -static void FilterDestroy(void* arg) { } +static void FilterDestroy(void* arg) { (void)arg; } static const char* FilterName(void* arg) { + (void)arg; return "TestFilter"; } static char* FilterCreate( @@ -220,6 +232,10 @@ static char* FilterCreate( const char* const* key_array, const size_t* key_length_array, int num_keys, size_t* filter_length) { + (void)arg; + (void)key_array; + (void)key_length_array; + (void)num_keys; *filter_length = 4; char* result = malloc(4); memcpy(result, "fake", 4); @@ -229,20 +245,30 @@ static unsigned char FilterKeyMatch( void* arg, const char* key, size_t length, const char* filter, size_t filter_length) { + (void)arg; + (void)key; + (void)length; CheckCondition(filter_length == 4); CheckCondition(memcmp(filter, "fake", 4) == 0); return fake_filter_result; } // Custom compaction filter -static void CFilterDestroy(void* arg) {} -static const char* CFilterName(void* arg) { return "foo"; } +static void CFilterDestroy(void* arg) { (void)arg; } +static const char* CFilterName(void* arg) { + (void)arg; + return "foo"; +} static unsigned char CFilterFilter(void* arg, int level, const char* key, size_t key_length, const char* existing_value, size_t value_length, char** new_value, size_t* new_value_length, unsigned char* value_changed) { + (void)arg; + (void)level; + (void)existing_value; + (void)value_length; if (key_length == 3) { if (memcmp(key, "bar", key_length) == 0) { return 1; @@ -256,10 +282,15 @@ static unsigned char CFilterFilter(void* arg, int level, const char* key, return 0; } -static void CFilterFactoryDestroy(void* arg) {} -static const char* CFilterFactoryName(void* arg) { return "foo"; } +static void CFilterFactoryDestroy(void* arg) { (void)arg; } +static const char* CFilterFactoryName(void* arg) { + (void)arg; + return "foo"; +} static rocksdb_compactionfilter_t* CFilterCreate( void* arg, rocksdb_compactionfiltercontext_t* context) { + (void)arg; + (void)context; return rocksdb_compactionfilter_create(NULL, CFilterDestroy, CFilterFilter, CFilterName); } @@ -290,8 +321,9 @@ static rocksdb_t* CheckCompaction(rocksdb_t* db, rocksdb_options_t* options, } // Custom merge operator -static void MergeOperatorDestroy(void* arg) { } +static void MergeOperatorDestroy(void* arg) { (void)arg; } static const char* MergeOperatorName(void* arg) { + (void)arg; return "TestMergeOperator"; } static char* MergeOperatorFullMerge( @@ -301,6 +333,14 @@ static char* MergeOperatorFullMerge( const char* const* operands_list, const size_t* operands_list_length, int num_operands, unsigned char* success, size_t* new_value_length) { + (void)arg; + (void)key; + (void)key_length; + (void)existing_value; + (void)existing_value_length; + (void)operands_list; + (void)operands_list_length; + (void)num_operands; *new_value_length = 4; *success = 1; char* result = malloc(4); @@ -313,6 +353,12 @@ static char* MergeOperatorPartialMerge( const char* const* operands_list, const size_t* operands_list_length, int num_operands, unsigned char* success, size_t* new_value_length) { + (void)arg; + (void)key; + (void)key_length; + (void)operands_list; + (void)operands_list_length; + (void)num_operands; *new_value_length = 4; *success = 1; char* result = malloc(4); @@ -334,6 +380,20 @@ static void CheckTxnGet( Free(&val); } +static void CheckTxnGetCF(rocksdb_transaction_t* txn, + const rocksdb_readoptions_t* options, + rocksdb_column_family_handle_t* column_family, + const char* key, const char* expected) { + char* err = NULL; + size_t val_len; + char* val; + val = rocksdb_transaction_get_cf(txn, options, column_family, key, + strlen(key), &val_len, &err); + CheckNoError(err); + CheckEqual(expected, val, val_len); + Free(&val); +} + static void CheckTxnDBGet( rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options, @@ -363,6 +423,8 @@ static void CheckTxnDBGetCF(rocksdb_transactiondb_t* txn_db, } int main(int argc, char** argv) { + (void)argc; + (void)argv; rocksdb_t* db; rocksdb_comparator_t* cmp; rocksdb_cache_t* cache; @@ -378,6 +440,8 @@ int main(int argc, char** argv) { rocksdb_transactiondb_options_t* txn_db_options; rocksdb_transaction_t* txn; rocksdb_transaction_options_t* txn_options; + rocksdb_optimistictransactiondb_t* otxn_db; + rocksdb_optimistictransaction_options_t* otxn_options; char* err = NULL; int run = -1; @@ -588,7 +652,7 @@ int main(int argc, char** argv) { rocksdb_sstfilewriter_t* writer = rocksdb_sstfilewriter_create(env_opt, io_options); - unlink(sstfilename); + remove(sstfilename); rocksdb_sstfilewriter_open(writer, sstfilename, &err); CheckNoError(err); rocksdb_sstfilewriter_put(writer, "sstk1", 5, "v1", 2, &err); @@ -609,7 +673,7 @@ int main(int argc, char** argv) { CheckGet(db, roptions, "sstk2", "v2"); CheckGet(db, roptions, "sstk3", "v3"); - unlink(sstfilename); + remove(sstfilename); rocksdb_sstfilewriter_open(writer, sstfilename, &err); CheckNoError(err); rocksdb_sstfilewriter_put(writer, "sstk2", 5, "v4", 2, &err); @@ -853,7 +917,8 @@ int main(int argc, char** argv) { rocksdb_writebatch_wi_t* wbi = rocksdb_writebatch_wi_create(0, 1); rocksdb_writebatch_wi_put(wbi, "bar", 3, "b", 1); rocksdb_writebatch_wi_delete(wbi, "foo", 3); - rocksdb_iterator_t* iter = rocksdb_writebatch_wi_create_iterator_with_base(wbi, base_iter); + rocksdb_iterator_t* iter = + rocksdb_writebatch_wi_create_iterator_with_base(wbi, base_iter); CheckCondition(!rocksdb_iter_valid(iter)); rocksdb_iter_seek_to_first(iter); CheckCondition(rocksdb_iter_valid(iter)); @@ -1279,6 +1344,47 @@ int main(int argc, char** argv) { rocksdb_destroy_db(options, dbname, &err); } + // Check memory usage stats + StartPhase("approximate_memory_usage"); + { + // Create database + db = rocksdb_open(options, dbname, &err); + CheckNoError(err); + + rocksdb_memory_consumers_t* consumers; + consumers = rocksdb_memory_consumers_create(); + rocksdb_memory_consumers_add_db(consumers, db); + rocksdb_memory_consumers_add_cache(consumers, cache); + + // take memory usage report before write-read operation + rocksdb_memory_usage_t* mu1; + mu1 = rocksdb_approximate_memory_usage_create(consumers, &err); + CheckNoError(err); + + // Put data (this should affect memtables) + rocksdb_put(db, woptions, "memory", 6, "test", 4, &err); + CheckNoError(err); + CheckGet(db, roptions, "memory", "test"); + + // take memory usage report after write-read operation + rocksdb_memory_usage_t* mu2; + mu2 = rocksdb_approximate_memory_usage_create(consumers, &err); + CheckNoError(err); + + // amount of memory used within memtables should grow + CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_total(mu2) >= + rocksdb_approximate_memory_usage_get_mem_table_total(mu1)); + CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu2) >= + rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu1)); + + rocksdb_memory_consumers_destroy(consumers); + rocksdb_approximate_memory_usage_destroy(mu1); + rocksdb_approximate_memory_usage_destroy(mu2); + rocksdb_close(db); + rocksdb_destroy_db(options, dbname, &err); + CheckNoError(err); + } + StartPhase("cuckoo_options"); { rocksdb_cuckoo_table_options_t* cuckoo_options; @@ -1422,7 +1528,7 @@ int main(int argc, char** argv) { const rocksdb_snapshot_t* snapshot; snapshot = rocksdb_transactiondb_create_snapshot(txn_db); rocksdb_readoptions_set_snapshot(roptions, snapshot); - + rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hey", 3, &err); CheckNoError(err); @@ -1447,6 +1553,25 @@ int main(int argc, char** argv) { CheckNoError(err); CheckTxnDBGet(txn_db, roptions, "bar", NULL); + // save point + rocksdb_transaction_put(txn, "foo1", 4, "hi1", 3, &err); + rocksdb_transaction_set_savepoint(txn); + CheckTxnGet(txn, roptions, "foo1", "hi1"); + rocksdb_transaction_put(txn, "foo2", 4, "hi2", 3, &err); + CheckTxnGet(txn, roptions, "foo2", "hi2"); + + // rollback to savepoint + rocksdb_transaction_rollback_to_savepoint(txn, &err); + CheckNoError(err); + CheckTxnGet(txn, roptions, "foo2", NULL); + CheckTxnGet(txn, roptions, "foo1", "hi1"); + CheckTxnDBGet(txn_db, roptions, "foo1", NULL); + CheckTxnDBGet(txn_db, roptions, "foo2", NULL); + rocksdb_transaction_commit(txn, &err); + CheckNoError(err); + CheckTxnDBGet(txn_db, roptions, "foo1", "hi1"); + CheckTxnDBGet(txn_db, roptions, "foo2", NULL); + // Column families. rocksdb_column_family_handle_t* cfh; cfh = rocksdb_transactiondb_create_column_family(txn_db, options, @@ -1473,6 +1598,105 @@ int main(int argc, char** argv) { rocksdb_transactiondb_options_destroy(txn_db_options); } + StartPhase("optimistic_transactions"); + { + rocksdb_options_t* db_options = rocksdb_options_create(); + rocksdb_options_set_create_if_missing(db_options, 1); + rocksdb_options_set_allow_concurrent_memtable_write(db_options, 1); + otxn_db = rocksdb_optimistictransactiondb_open(db_options, dbname, &err); + otxn_options = rocksdb_optimistictransaction_options_create(); + rocksdb_transaction_t* txn1 = rocksdb_optimistictransaction_begin( + otxn_db, woptions, otxn_options, NULL); + rocksdb_transaction_t* txn2 = rocksdb_optimistictransaction_begin( + otxn_db, woptions, otxn_options, NULL); + rocksdb_transaction_put(txn1, "key", 3, "value", 5, &err); + CheckNoError(err); + rocksdb_transaction_put(txn2, "key1", 4, "value1", 6, &err); + CheckNoError(err); + CheckTxnGet(txn1, roptions, "key", "value"); + rocksdb_transaction_commit(txn1, &err); + CheckNoError(err); + rocksdb_transaction_commit(txn2, &err); + CheckNoError(err); + rocksdb_transaction_destroy(txn1); + rocksdb_transaction_destroy(txn2); + + // Check column family + db = rocksdb_optimistictransactiondb_get_base_db(otxn_db); + rocksdb_put(db, woptions, "key", 3, "value", 5, &err); + CheckNoError(err); + rocksdb_column_family_handle_t *cfh1, *cfh2; + cfh1 = rocksdb_create_column_family(db, db_options, "txn_db_cf1", &err); + cfh2 = rocksdb_create_column_family(db, db_options, "txn_db_cf2", &err); + txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options, + NULL); + rocksdb_transaction_put_cf(txn, cfh1, "key_cf1", 7, "val_cf1", 7, &err); + CheckNoError(err); + rocksdb_transaction_put_cf(txn, cfh2, "key_cf2", 7, "val_cf2", 7, &err); + CheckNoError(err); + rocksdb_transaction_commit(txn, &err); + CheckNoError(err); + txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options, + txn); + CheckGetCF(db, roptions, cfh1, "key_cf1", "val_cf1"); + CheckTxnGetCF(txn, roptions, cfh1, "key_cf1", "val_cf1"); + + // Check iterator with column family + rocksdb_transaction_put_cf(txn, cfh1, "key1_cf", 7, "val1_cf", 7, &err); + CheckNoError(err); + rocksdb_iterator_t* iter = + rocksdb_transaction_create_iterator_cf(txn, roptions, cfh1); + CheckCondition(!rocksdb_iter_valid(iter)); + rocksdb_iter_seek_to_first(iter); + CheckCondition(rocksdb_iter_valid(iter)); + CheckIter(iter, "key1_cf", "val1_cf"); + rocksdb_iter_get_error(iter, &err); + CheckNoError(err); + rocksdb_iter_destroy(iter); + + rocksdb_transaction_destroy(txn); + rocksdb_column_family_handle_destroy(cfh1); + rocksdb_column_family_handle_destroy(cfh2); + rocksdb_optimistictransactiondb_close_base_db(db); + rocksdb_optimistictransactiondb_close(otxn_db); + + // Check open optimistic transaction db with column families + size_t cf_len; + char** column_fams = + rocksdb_list_column_families(db_options, dbname, &cf_len, &err); + CheckNoError(err); + CheckEqual("default", column_fams[0], 7); + CheckEqual("txn_db_cf1", column_fams[1], 10); + CheckEqual("txn_db_cf2", column_fams[2], 10); + CheckCondition(cf_len == 3); + rocksdb_list_column_families_destroy(column_fams, cf_len); + + const char* cf_names[3] = {"default", "txn_db_cf1", "txn_db_cf2"}; + rocksdb_options_t* cf_options = rocksdb_options_create(); + const rocksdb_options_t* cf_opts[3] = {cf_options, cf_options, cf_options}; + + rocksdb_options_set_error_if_exists(cf_options, 0); + rocksdb_column_family_handle_t* cf_handles[3]; + otxn_db = rocksdb_optimistictransactiondb_open_column_families( + db_options, dbname, 3, cf_names, cf_opts, cf_handles, &err); + CheckNoError(err); + rocksdb_transaction_t* txn_cf = rocksdb_optimistictransaction_begin( + otxn_db, woptions, otxn_options, NULL); + CheckTxnGetCF(txn_cf, roptions, cf_handles[0], "key", "value"); + CheckTxnGetCF(txn_cf, roptions, cf_handles[1], "key_cf1", "val_cf1"); + CheckTxnGetCF(txn_cf, roptions, cf_handles[2], "key_cf2", "val_cf2"); + rocksdb_transaction_destroy(txn_cf); + rocksdb_options_destroy(cf_options); + rocksdb_column_family_handle_destroy(cf_handles[0]); + rocksdb_column_family_handle_destroy(cf_handles[1]); + rocksdb_column_family_handle_destroy(cf_handles[2]); + rocksdb_optimistictransactiondb_close(otxn_db); + rocksdb_destroy_db(db_options, dbname, &err); + rocksdb_options_destroy(db_options); + rocksdb_optimistictransaction_options_destroy(otxn_options); + CheckNoError(err); + } + // Simple sanity check that setting memtable rep works. StartPhase("memtable_reps"); { @@ -1502,7 +1726,7 @@ int main(int argc, char** argv) { db = rocksdb_open(options, dbname, &err); CheckNoError(err); } - + StartPhase("cleanup"); rocksdb_close(db); rocksdb_options_destroy(options); diff --git a/thirdparty/rocksdb/db/column_family.cc b/thirdparty/rocksdb/db/column_family.cc index 6fd0787847..f9a4ae66d8 100644 --- a/thirdparty/rocksdb/db/column_family.cc +++ b/thirdparty/rocksdb/db/column_family.cc @@ -20,10 +20,12 @@ #include #include "db/compaction_picker.h" +#include "db/compaction_picker_fifo.h" #include "db/compaction_picker_universal.h" #include "db/db_impl.h" #include "db/internal_stats.h" #include "db/job_context.h" +#include "db/range_del_aggregator.h" #include "db/table_properties_collector.h" #include "db/version_set.h" #include "db/write_controller.h" @@ -31,8 +33,10 @@ #include "monitoring/thread_status_util.h" #include "options/options_helper.h" #include "table/block_based_table_factory.h" +#include "table/merging_iterator.h" #include "util/autovector.h" #include "util/compression.h" +#include "util/sst_file_manager_impl.h" namespace rocksdb { @@ -53,6 +57,9 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() { #endif // ROCKSDB_LITE // Job id == 0 means that this is not our background process, but rather // user thread + // Need to hold some shared pointers owned by the initial_cf_options + // before final cleaning up finishes. + ColumnFamilyOptions initial_cf_options_copy = cfd_->initial_cf_options(); JobContext job_context(0); mutex_->Lock(); if (cfd_->Unref()) { @@ -61,7 +68,14 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() { db_->FindObsoleteFiles(&job_context, false, true); mutex_->Unlock(); if (job_context.HaveSomethingToDelete()) { - db_->PurgeObsoleteFiles(job_context); + bool defer_purge = + db_->immutable_db_options().avoid_unnecessary_blocking_io; + db_->PurgeObsoleteFiles(job_context, defer_purge); + if (defer_purge) { + mutex_->Lock(); + db_->SchedulePurge(); + mutex_->Unlock(); + } } job_context.Clean(); } @@ -80,6 +94,7 @@ Status ColumnFamilyHandleImpl::GetDescriptor(ColumnFamilyDescriptor* desc) { *desc = ColumnFamilyDescriptor(cfd()->GetName(), cfd()->GetLatestCFOptions()); return Status::OK(); #else + (void)desc; return Status::NotSupported(); #endif // !ROCKSDB_LITE } @@ -99,9 +114,6 @@ void GetIntTblPropCollectorFactory( int_tbl_prop_collector_factories->emplace_back( new UserKeyTablePropertiesCollectorFactory(collector_factories[i])); } - // Add collector to collect internal key statistics - int_tbl_prop_collector_factories->emplace_back( - new InternalKeyPropertiesCollectorFactory); } Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) { @@ -123,6 +135,18 @@ Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) { " is not linked with the binary."); } } + if (cf_options.compression_opts.zstd_max_train_bytes > 0) { + if (!ZSTD_TrainDictionarySupported()) { + return Status::InvalidArgument( + "zstd dictionary trainer cannot be used because ZSTD 1.1.3+ " + "is not linked with the binary."); + } + if (cf_options.compression_opts.max_dict_bytes == 0) { + return Status::InvalidArgument( + "The dictionary size limit (`CompressionOptions::max_dict_bytes`) " + "should be nonzero if we're using zstd's dictionary generator."); + } + } return Status::OK(); } @@ -139,6 +163,28 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) { return Status::OK(); } +Status CheckCFPathsSupported(const DBOptions& db_options, + const ColumnFamilyOptions& cf_options) { + // More than one cf_paths are supported only in universal + // and level compaction styles. This function also checks the case + // in which cf_paths is not specified, which results in db_paths + // being used. + if ((cf_options.compaction_style != kCompactionStyleUniversal) && + (cf_options.compaction_style != kCompactionStyleLevel)) { + if (cf_options.cf_paths.size() > 1) { + return Status::NotSupported( + "More than one CF paths are only supported in " + "universal and level compaction styles. "); + } else if (cf_options.cf_paths.empty() && + db_options.db_paths.size() > 1) { + return Status::NotSupported( + "More than one DB paths are only supported in " + "universal and level compaction styles. "); + } + } + return Status::OK(); +} + ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options, const ColumnFamilyOptions& src) { ColumnFamilyOptions result = src; @@ -257,9 +303,24 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options, result.hard_pending_compaction_bytes_limit; } +#ifndef ROCKSDB_LITE + // When the DB is stopped, it's possible that there are some .trash files that + // were not deleted yet, when we open the DB we will find these .trash files + // and schedule them to be deleted (or delete immediately if SstFileManager + // was not used) + auto sfm = static_cast(db_options.sst_file_manager.get()); + for (size_t i = 0; i < result.cf_paths.size(); i++) { + DeleteScheduler::CleanupDirectory(db_options.env, sfm, result.cf_paths[i].path); + } +#endif + + if (result.cf_paths.empty()) { + result.cf_paths = db_options.db_paths; + } + if (result.level_compaction_dynamic_level_bytes) { if (result.compaction_style != kCompactionStyleLevel || - db_options.db_paths.size() > 1U) { + result.cf_paths.size() > 1U) { // 1. level_compaction_dynamic_level_bytes only makes sense for // level-based compaction. // 2. we don't yet know how to make both of this feature and multiple @@ -328,12 +389,13 @@ void SuperVersionUnrefHandle(void* ptr) { // When latter happens, we are in ~ColumnFamilyData(), no get should happen as // well. SuperVersion* sv = static_cast(ptr); - if (sv->Unref()) { - sv->db_mutex->Lock(); - sv->Cleanup(); - sv->db_mutex->Unlock(); - delete sv; - } + bool was_last_ref __attribute__((__unused__)); + was_last_ref = sv->Unref(); + // Thread-local SuperVersions can't outlive ColumnFamilyData::super_version_. + // This is important because we can't do SuperVersion cleanup here. + // That would require locking DB mutex, which would deadlock because + // SuperVersionUnrefHandle is called with locked ThreadLocalPtr mutex. + assert(!was_last_ref); } } // anonymous namespace @@ -365,11 +427,13 @@ ColumnFamilyData::ColumnFamilyData( next_(nullptr), prev_(nullptr), log_number_(0), + flush_reason_(FlushReason::kOthers), column_family_set_(column_family_set), - pending_flush_(false), - pending_compaction_(false), + queued_for_flush_(false), + queued_for_compaction_(false), prev_compaction_needed_bytes_(0), - allow_2pc_(db_options.allow_2pc) { + allow_2pc_(db_options.allow_2pc), + last_memtable_id_(0) { Ref(); // Convert user defined table properties collector factories to internal ones. @@ -442,8 +506,8 @@ ColumnFamilyData::~ColumnFamilyData() { // It would be wrong if this ColumnFamilyData is in flush_queue_ or // compaction_queue_ and we destroyed it - assert(!pending_flush_); - assert(!pending_compaction_); + assert(!queued_for_flush_); + assert(!queued_for_compaction_); if (super_version_ != nullptr) { // Release SuperVersion reference kept in ThreadLocalPtr. @@ -452,7 +516,7 @@ ColumnFamilyData::~ColumnFamilyData() { local_sv_.reset(); super_version_->db_mutex->Lock(); - bool is_last_reference __attribute__((unused)); + bool is_last_reference __attribute__((__unused__)); is_last_reference = super_version_->Unref(); assert(is_last_reference); super_version_->Cleanup(); @@ -463,7 +527,8 @@ ColumnFamilyData::~ColumnFamilyData() { if (dummy_versions_ != nullptr) { // List must be empty assert(dummy_versions_->TEST_Next() == dummy_versions_); - bool deleted __attribute__((unused)) = dummy_versions_->Unref(); + bool deleted __attribute__((__unused__)); + deleted = dummy_versions_->Unref(); assert(deleted); } @@ -495,7 +560,9 @@ uint64_t ColumnFamilyData::OldestLogToKeep() { auto current_log = GetLogNumber(); if (allow_2pc_) { - auto imm_prep_log = imm()->GetMinLogContainingPrepSection(); + autovector empty_list; + auto imm_prep_log = + imm()->PrecomputeMinLogContainingPrepSection(empty_list); auto mem_prep_log = mem()->GetMinLogContainingPrepSection(); if (imm_prep_log > 0 && imm_prep_log < current_log) { @@ -613,58 +680,97 @@ int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger, } } // namespace -void ColumnFamilyData::RecalculateWriteStallConditions( +std::pair +ColumnFamilyData::GetWriteStallConditionAndCause( + int num_unflushed_memtables, int num_l0_files, + uint64_t num_compaction_needed_bytes, + const MutableCFOptions& mutable_cf_options) { + if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) { + return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit}; + } else if (!mutable_cf_options.disable_auto_compactions && + num_l0_files >= mutable_cf_options.level0_stop_writes_trigger) { + return {WriteStallCondition::kStopped, WriteStallCause::kL0FileCountLimit}; + } else if (!mutable_cf_options.disable_auto_compactions && + mutable_cf_options.hard_pending_compaction_bytes_limit > 0 && + num_compaction_needed_bytes >= + mutable_cf_options.hard_pending_compaction_bytes_limit) { + return {WriteStallCondition::kStopped, + WriteStallCause::kPendingCompactionBytes}; + } else if (mutable_cf_options.max_write_buffer_number > 3 && + num_unflushed_memtables >= + mutable_cf_options.max_write_buffer_number - 1) { + return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit}; + } else if (!mutable_cf_options.disable_auto_compactions && + mutable_cf_options.level0_slowdown_writes_trigger >= 0 && + num_l0_files >= + mutable_cf_options.level0_slowdown_writes_trigger) { + return {WriteStallCondition::kDelayed, WriteStallCause::kL0FileCountLimit}; + } else if (!mutable_cf_options.disable_auto_compactions && + mutable_cf_options.soft_pending_compaction_bytes_limit > 0 && + num_compaction_needed_bytes >= + mutable_cf_options.soft_pending_compaction_bytes_limit) { + return {WriteStallCondition::kDelayed, + WriteStallCause::kPendingCompactionBytes}; + } + return {WriteStallCondition::kNormal, WriteStallCause::kNone}; +} + +WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions( const MutableCFOptions& mutable_cf_options) { + auto write_stall_condition = WriteStallCondition::kNormal; if (current_ != nullptr) { auto* vstorage = current_->storage_info(); auto write_controller = column_family_set_->write_controller_; uint64_t compaction_needed_bytes = vstorage->estimated_compaction_needed_bytes(); + auto write_stall_condition_and_cause = GetWriteStallConditionAndCause( + imm()->NumNotFlushed(), vstorage->l0_delay_trigger_count(), + vstorage->estimated_compaction_needed_bytes(), mutable_cf_options); + write_stall_condition = write_stall_condition_and_cause.first; + auto write_stall_cause = write_stall_condition_and_cause.second; + bool was_stopped = write_controller->IsStopped(); bool needed_delay = write_controller->NeedsDelay(); - if (imm()->NumNotFlushed() >= mutable_cf_options.max_write_buffer_number) { + if (write_stall_condition == WriteStallCondition::kStopped && + write_stall_cause == WriteStallCause::kMemtableLimit) { write_controller_token_ = write_controller->GetStopToken(); - internal_stats_->AddCFStats(InternalStats::MEMTABLE_COMPACTION, 1); + internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_STOPS, 1); ROCKS_LOG_WARN( ioptions_.info_log, "[%s] Stopping writes because we have %d immutable memtables " "(waiting for flush), max_write_buffer_number is set to %d", name_.c_str(), imm()->NumNotFlushed(), mutable_cf_options.max_write_buffer_number); - } else if (!mutable_cf_options.disable_auto_compactions && - vstorage->l0_delay_trigger_count() >= - mutable_cf_options.level0_stop_writes_trigger) { + } else if (write_stall_condition == WriteStallCondition::kStopped && + write_stall_cause == WriteStallCause::kL0FileCountLimit) { write_controller_token_ = write_controller->GetStopToken(); - internal_stats_->AddCFStats(InternalStats::LEVEL0_NUM_FILES_TOTAL, 1); + internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_STOPS, 1); if (compaction_picker_->IsLevel0CompactionInProgress()) { internal_stats_->AddCFStats( - InternalStats::LEVEL0_NUM_FILES_WITH_COMPACTION, 1); + InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_STOPS, 1); } ROCKS_LOG_WARN(ioptions_.info_log, "[%s] Stopping writes because we have %d level-0 files", name_.c_str(), vstorage->l0_delay_trigger_count()); - } else if (!mutable_cf_options.disable_auto_compactions && - mutable_cf_options.hard_pending_compaction_bytes_limit > 0 && - compaction_needed_bytes >= - mutable_cf_options.hard_pending_compaction_bytes_limit) { + } else if (write_stall_condition == WriteStallCondition::kStopped && + write_stall_cause == WriteStallCause::kPendingCompactionBytes) { write_controller_token_ = write_controller->GetStopToken(); internal_stats_->AddCFStats( - InternalStats::HARD_PENDING_COMPACTION_BYTES_LIMIT, 1); + InternalStats::PENDING_COMPACTION_BYTES_LIMIT_STOPS, 1); ROCKS_LOG_WARN( ioptions_.info_log, "[%s] Stopping writes because of estimated pending compaction " "bytes %" PRIu64, name_.c_str(), compaction_needed_bytes); - } else if (mutable_cf_options.max_write_buffer_number > 3 && - imm()->NumNotFlushed() >= - mutable_cf_options.max_write_buffer_number - 1) { + } else if (write_stall_condition == WriteStallCondition::kDelayed && + write_stall_cause == WriteStallCause::kMemtableLimit) { write_controller_token_ = SetupDelay(write_controller, compaction_needed_bytes, prev_compaction_needed_bytes_, was_stopped, mutable_cf_options.disable_auto_compactions); - internal_stats_->AddCFStats(InternalStats::MEMTABLE_SLOWDOWN, 1); + internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_SLOWDOWNS, 1); ROCKS_LOG_WARN( ioptions_.info_log, "[%s] Stalling writes because we have %d immutable memtables " @@ -673,10 +779,8 @@ void ColumnFamilyData::RecalculateWriteStallConditions( name_.c_str(), imm()->NumNotFlushed(), mutable_cf_options.max_write_buffer_number, write_controller->delayed_write_rate()); - } else if (!mutable_cf_options.disable_auto_compactions && - mutable_cf_options.level0_slowdown_writes_trigger >= 0 && - vstorage->l0_delay_trigger_count() >= - mutable_cf_options.level0_slowdown_writes_trigger) { + } else if (write_stall_condition == WriteStallCondition::kDelayed && + write_stall_cause == WriteStallCause::kL0FileCountLimit) { // L0 is the last two files from stopping. bool near_stop = vstorage->l0_delay_trigger_count() >= mutable_cf_options.level0_stop_writes_trigger - 2; @@ -684,20 +788,19 @@ void ColumnFamilyData::RecalculateWriteStallConditions( SetupDelay(write_controller, compaction_needed_bytes, prev_compaction_needed_bytes_, was_stopped || near_stop, mutable_cf_options.disable_auto_compactions); - internal_stats_->AddCFStats(InternalStats::LEVEL0_SLOWDOWN_TOTAL, 1); + internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_SLOWDOWNS, + 1); if (compaction_picker_->IsLevel0CompactionInProgress()) { internal_stats_->AddCFStats( - InternalStats::LEVEL0_SLOWDOWN_WITH_COMPACTION, 1); + InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, 1); } ROCKS_LOG_WARN(ioptions_.info_log, "[%s] Stalling writes because we have %d level-0 files " "rate %" PRIu64, name_.c_str(), vstorage->l0_delay_trigger_count(), write_controller->delayed_write_rate()); - } else if (!mutable_cf_options.disable_auto_compactions && - mutable_cf_options.soft_pending_compaction_bytes_limit > 0 && - vstorage->estimated_compaction_needed_bytes() >= - mutable_cf_options.soft_pending_compaction_bytes_limit) { + } else if (write_stall_condition == WriteStallCondition::kDelayed && + write_stall_cause == WriteStallCause::kPendingCompactionBytes) { // If the distance to hard limit is less than 1/4 of the gap between soft // and // hard bytes limit, we think it is near stop and speed up the slowdown. @@ -714,7 +817,7 @@ void ColumnFamilyData::RecalculateWriteStallConditions( prev_compaction_needed_bytes_, was_stopped || near_stop, mutable_cf_options.disable_auto_compactions); internal_stats_->AddCFStats( - InternalStats::SOFT_PENDING_COMPACTION_BYTES_LIMIT, 1); + InternalStats::PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, 1); ROCKS_LOG_WARN( ioptions_.info_log, "[%s] Stalling writes because of estimated pending compaction " @@ -722,6 +825,7 @@ void ColumnFamilyData::RecalculateWriteStallConditions( name_.c_str(), vstorage->estimated_compaction_needed_bytes(), write_controller->delayed_write_rate()); } else { + assert(write_stall_condition == WriteStallCondition::kNormal); if (vstorage->l0_delay_trigger_count() >= GetL0ThresholdSpeedupCompaction( mutable_cf_options.level0_file_num_compaction_trigger, @@ -769,6 +873,7 @@ void ColumnFamilyData::RecalculateWriteStallConditions( } prev_compaction_needed_bytes_ = compaction_needed_bytes; } + return write_stall_condition; } const EnvOptions* ColumnFamilyData::soptions() const { @@ -787,6 +892,10 @@ uint64_t ColumnFamilyData::GetTotalSstFilesSize() const { return VersionSet::GetTotalSstFilesSize(dummy_versions_); } +uint64_t ColumnFamilyData::GetLiveSstFilesSize() const { + return current_->GetSstFilesSize(); +} + MemTable* ColumnFamilyData::ConstructNewMemtable( const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) { return new MemTable(internal_comparator_, ioptions_, mutable_cf_options, @@ -823,16 +932,70 @@ bool ColumnFamilyData::RangeOverlapWithCompaction( smallest_user_key, largest_user_key, level); } +Status ColumnFamilyData::RangesOverlapWithMemtables( + const autovector& ranges, SuperVersion* super_version, + bool* overlap) { + assert(overlap != nullptr); + *overlap = false; + // Create an InternalIterator over all unflushed memtables + Arena arena; + ReadOptions read_opts; + read_opts.total_order_seek = true; + MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena); + merge_iter_builder.AddIterator( + super_version->mem->NewIterator(read_opts, &arena)); + super_version->imm->AddIterators(read_opts, &merge_iter_builder); + ScopedArenaIterator memtable_iter(merge_iter_builder.Finish()); + + auto read_seq = super_version->current->version_set()->LastSequence(); + ReadRangeDelAggregator range_del_agg(&internal_comparator_, read_seq); + auto* active_range_del_iter = + super_version->mem->NewRangeTombstoneIterator(read_opts, read_seq); + range_del_agg.AddTombstones( + std::unique_ptr(active_range_del_iter)); + super_version->imm->AddRangeTombstoneIterators(read_opts, nullptr /* arena */, + &range_del_agg); + + Status status; + for (size_t i = 0; i < ranges.size() && status.ok() && !*overlap; ++i) { + auto* vstorage = super_version->current->storage_info(); + auto* ucmp = vstorage->InternalComparator()->user_comparator(); + InternalKey range_start(ranges[i].start, kMaxSequenceNumber, + kValueTypeForSeek); + memtable_iter->Seek(range_start.Encode()); + status = memtable_iter->status(); + ParsedInternalKey seek_result; + if (status.ok()) { + if (memtable_iter->Valid() && + !ParseInternalKey(memtable_iter->key(), &seek_result)) { + status = Status::Corruption("DB have corrupted keys"); + } + } + if (status.ok()) { + if (memtable_iter->Valid() && + ucmp->Compare(seek_result.user_key, ranges[i].limit) <= 0) { + *overlap = true; + } else if (range_del_agg.IsRangeOverlapped(ranges[i].start, + ranges[i].limit)) { + *overlap = true; + } + } + } + return status; +} + const int ColumnFamilyData::kCompactAllLevels = -1; const int ColumnFamilyData::kCompactToBaseLevel = -2; Compaction* ColumnFamilyData::CompactRange( const MutableCFOptions& mutable_cf_options, int input_level, - int output_level, uint32_t output_path_id, const InternalKey* begin, - const InternalKey* end, InternalKey** compaction_end, bool* conflict) { + int output_level, uint32_t output_path_id, uint32_t max_subcompactions, + const InternalKey* begin, const InternalKey* end, + InternalKey** compaction_end, bool* conflict) { auto* result = compaction_picker_->CompactRange( GetName(), mutable_cf_options, current_->storage_info(), input_level, - output_level, output_path_id, begin, end, compaction_end, conflict); + output_level, output_path_id, max_subcompactions, begin, end, + compaction_end, conflict); if (result != nullptr) { result->SetInputVersion(current_); } @@ -841,10 +1004,13 @@ Compaction* ColumnFamilyData::CompactRange( SuperVersion* ColumnFamilyData::GetReferencedSuperVersion( InstrumentedMutex* db_mutex) { - SuperVersion* sv = nullptr; - sv = GetThreadLocalSuperVersion(db_mutex); + SuperVersion* sv = GetThreadLocalSuperVersion(db_mutex); sv->Ref(); if (!ReturnThreadLocalSuperVersion(sv)) { + // This Unref() corresponds to the Ref() in GetThreadLocalSuperVersion() + // when the thread-local pointer was populated. So, the Ref() earlier in + // this function still prevents the returned SuperVersion* from being + // deleted out from under the caller. sv->Unref(); } return sv; @@ -852,7 +1018,6 @@ SuperVersion* ColumnFamilyData::GetReferencedSuperVersion( SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion( InstrumentedMutex* db_mutex) { - SuperVersion* sv = nullptr; // The SuperVersion is cached in thread local storage to avoid acquiring // mutex when SuperVersion does not change since the last use. When a new // SuperVersion is installed, the compaction or flush thread cleans up @@ -871,7 +1036,7 @@ SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion( // should only keep kSVInUse before ReturnThreadLocalSuperVersion call // (if no Scrape happens). assert(ptr != SuperVersion::kSVInUse); - sv = static_cast(ptr); + SuperVersion* sv = static_cast(ptr); if (sv == SuperVersion::kSVObsolete || sv->version_number != super_version_number_.load()) { RecordTick(ioptions_.statistics, NUMBER_SUPERVERSION_ACQUIRES); @@ -914,15 +1079,16 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) { return false; } -SuperVersion* ColumnFamilyData::InstallSuperVersion( - SuperVersion* new_superversion, InstrumentedMutex* db_mutex) { +void ColumnFamilyData::InstallSuperVersion( + SuperVersionContext* sv_context, InstrumentedMutex* db_mutex) { db_mutex->AssertHeld(); - return InstallSuperVersion(new_superversion, db_mutex, mutable_cf_options_); + return InstallSuperVersion(sv_context, db_mutex, mutable_cf_options_); } -SuperVersion* ColumnFamilyData::InstallSuperVersion( - SuperVersion* new_superversion, InstrumentedMutex* db_mutex, +void ColumnFamilyData::InstallSuperVersion( + SuperVersionContext* sv_context, InstrumentedMutex* db_mutex, const MutableCFOptions& mutable_cf_options) { + SuperVersion* new_superversion = sv_context->new_superversion.release(); new_superversion->db_mutex = db_mutex; new_superversion->mutable_cf_options = mutable_cf_options; new_superversion->Init(mem_, imm_.current(), current_); @@ -930,23 +1096,31 @@ SuperVersion* ColumnFamilyData::InstallSuperVersion( super_version_ = new_superversion; ++super_version_number_; super_version_->version_number = super_version_number_; + super_version_->write_stall_condition = + RecalculateWriteStallConditions(mutable_cf_options); + if (old_superversion != nullptr) { + // Reset SuperVersions cached in thread local storage. + // This should be done before old_superversion->Unref(). That's to ensure + // that local_sv_ never holds the last reference to SuperVersion, since + // it has no means to safely do SuperVersion cleanup. + ResetThreadLocalSuperVersions(); + if (old_superversion->mutable_cf_options.write_buffer_size != mutable_cf_options.write_buffer_size) { mem_->UpdateWriteBufferSize(mutable_cf_options.write_buffer_size); } + if (old_superversion->write_stall_condition != + new_superversion->write_stall_condition) { + sv_context->PushWriteStallNotification( + old_superversion->write_stall_condition, + new_superversion->write_stall_condition, GetName(), ioptions()); + } + if (old_superversion->Unref()) { + old_superversion->Cleanup(); + sv_context->superversions_to_free.push_back(old_superversion); + } } - - // Reset SuperVersions cached in thread local storage - ResetThreadLocalSuperVersions(); - - RecalculateWriteStallConditions(mutable_cf_options); - - if (old_superversion != nullptr && old_superversion->Unref()) { - old_superversion->Cleanup(); - return old_superversion; // will let caller delete outside of mutex - } - return nullptr; } void ColumnFamilyData::ResetThreadLocalSuperVersions() { @@ -958,10 +1132,12 @@ void ColumnFamilyData::ResetThreadLocalSuperVersions() { continue; } auto sv = static_cast(ptr); - if (sv->Unref()) { - sv->Cleanup(); - delete sv; - } + bool was_last_ref __attribute__((__unused__)); + was_last_ref = sv->Unref(); + // sv couldn't have been the last reference because + // ResetThreadLocalSuperVersions() is called before + // unref'ing super_version_. + assert(!was_last_ref); } } @@ -969,8 +1145,9 @@ void ColumnFamilyData::ResetThreadLocalSuperVersions() { Status ColumnFamilyData::SetOptions( const std::unordered_map& options_map) { MutableCFOptions new_mutable_cf_options; - Status s = GetMutableOptionsFromStrings(mutable_cf_options_, options_map, - &new_mutable_cf_options); + Status s = + GetMutableOptionsFromStrings(mutable_cf_options_, options_map, + ioptions_.info_log, &new_mutable_cf_options); if (s.ok()) { mutable_cf_options_ = new_mutable_cf_options; mutable_cf_options_.RefreshDerivedOptions(ioptions_); @@ -979,6 +1156,49 @@ Status ColumnFamilyData::SetOptions( } #endif // ROCKSDB_LITE +// REQUIRES: DB mutex held +Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) { + if (initial_cf_options_.compaction_style != kCompactionStyleLevel) { + return Env::WLTH_NOT_SET; + } + if (level == 0) { + return Env::WLTH_MEDIUM; + } + int base_level = current_->storage_info()->base_level(); + + // L1: medium, L2: long, ... + if (level - base_level >= 2) { + return Env::WLTH_EXTREME; + } + return static_cast(level - base_level + + static_cast(Env::WLTH_MEDIUM)); +} + +Status ColumnFamilyData::AddDirectories() { + Status s; + assert(data_dirs_.empty()); + for (auto& p : ioptions_.cf_paths) { + std::unique_ptr path_directory; + s = DBImpl::CreateAndNewDirectory(ioptions_.env, p.path, &path_directory); + if (!s.ok()) { + return s; + } + assert(path_directory != nullptr); + data_dirs_.emplace_back(path_directory.release()); + } + assert(data_dirs_.size() == ioptions_.cf_paths.size()); + return s; +} + +Directory* ColumnFamilyData::GetDataDir(size_t path_id) const { + if (data_dirs_.empty()) { + return nullptr; + } + + assert(path_id < data_dirs_.size()); + return data_dirs_[path_id].get(); +} + ColumnFamilySet::ColumnFamilySet(const std::string& dbname, const ImmutableDBOptions* db_options, const EnvOptions& env_options, @@ -1005,10 +1225,14 @@ ColumnFamilySet::~ColumnFamilySet() { while (column_family_data_.size() > 0) { // cfd destructor will delete itself from column_family_data_ auto cfd = column_family_data_.begin()->second; - cfd->Unref(); + bool last_ref __attribute__((__unused__)); + last_ref = cfd->Unref(); + assert(last_ref); delete cfd; } - dummy_cfd_->Unref(); + bool dummy_last_ref __attribute__((__unused__)); + dummy_last_ref = dummy_cfd_->Unref(); + assert(dummy_last_ref); delete dummy_cfd_; } diff --git a/thirdparty/rocksdb/db/column_family.h b/thirdparty/rocksdb/db/column_family.h index 3a807d22b9..5ed20604ac 100644 --- a/thirdparty/rocksdb/db/column_family.h +++ b/thirdparty/rocksdb/db/column_family.h @@ -30,6 +30,7 @@ namespace rocksdb { class Version; class VersionSet; +class VersionStorageInfo; class MemTable; class MemTableListVersion; class CompactionPicker; @@ -41,6 +42,7 @@ class DBImpl; class LogBuffer; class InstrumentedMutex; class InstrumentedMutexLock; +struct SuperVersionContext; extern const double kIncSlowdownRatio; @@ -76,7 +78,7 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle { class ColumnFamilyHandleInternal : public ColumnFamilyHandleImpl { public: ColumnFamilyHandleInternal() - : ColumnFamilyHandleImpl(nullptr, nullptr, nullptr) {} + : ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), internal_cfd_(nullptr) {} void SetCFD(ColumnFamilyData* _cfd) { internal_cfd_ = _cfd; } virtual ColumnFamilyData* cfd() const override { return internal_cfd_; } @@ -95,6 +97,7 @@ struct SuperVersion { MutableCFOptions mutable_cf_options; // Version number of the current SuperVersion uint64_t version_number; + WriteStallCondition write_stall_condition; InstrumentedMutex* db_mutex; @@ -136,6 +139,9 @@ extern Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options); extern Status CheckConcurrentWritesSupported( const ColumnFamilyOptions& cf_options); +extern Status CheckCFPathsSupported(const DBOptions& db_options, + const ColumnFamilyOptions& cf_options); + extern ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options, const ColumnFamilyOptions& src); // Wrap user defined table proproties collector factories `from cf_options` @@ -192,7 +198,7 @@ class ColumnFamilyData { // *) delete all memory associated with that column family // *) delete all the files associated with that column family void SetDropped(); - bool IsDropped() const { return dropped_; } + bool IsDropped() const { return dropped_.load(std::memory_order_relaxed); } // thread-safe int NumberLevels() const { return ioptions_.num_levels; } @@ -200,6 +206,10 @@ class ColumnFamilyData { void SetLogNumber(uint64_t log_number) { log_number_ = log_number; } uint64_t GetLogNumber() const { return log_number_; } + void SetFlushReason(FlushReason flush_reason) { + flush_reason_ = flush_reason; + } + FlushReason GetFlushReason() const { return flush_reason_; } // thread-safe const EnvOptions* soptions() const; const ImmutableCFOptions* ioptions() const { return &ioptions_; } @@ -237,7 +247,12 @@ class ColumnFamilyData { void SetCurrent(Version* _current); uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held - void SetMemtable(MemTable* new_mem) { mem_ = new_mem; } + uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held + void SetMemtable(MemTable* new_mem) { + uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1; + new_mem->SetID(memtable_id); + mem_ = new_mem; + } // calculate the oldest log needed for the durability of this column family uint64_t OldestLogToKeep(); @@ -263,17 +278,27 @@ class ColumnFamilyData { const Slice& largest_user_key, int level) const; + // Check if the passed ranges overlap with any unflushed memtables + // (immutable or mutable). + // + // @param super_version A referenced SuperVersion that will be held for the + // duration of this function. + // + // Thread-safe + Status RangesOverlapWithMemtables(const autovector& ranges, + SuperVersion* super_version, bool* overlap); + // A flag to tell a manual compaction is to compact all levels together - // instad of for specific level. + // instead of a specific level. static const int kCompactAllLevels; // A flag to tell a manual compaction's output is base level. static const int kCompactToBaseLevel; // REQUIRES: DB mutex held Compaction* CompactRange(const MutableCFOptions& mutable_cf_options, int input_level, int output_level, - uint32_t output_path_id, const InternalKey* begin, - const InternalKey* end, InternalKey** compaction_end, - bool* manual_conflict); + uint32_t output_path_id, uint32_t max_subcompactions, + const InternalKey* begin, const InternalKey* end, + InternalKey** compaction_end, bool* manual_conflict); CompactionPicker* compaction_picker() { return compaction_picker_.get(); } // thread-safe @@ -311,32 +336,55 @@ class ColumnFamilyData { // As argument takes a pointer to allocated SuperVersion to enable // the clients to allocate SuperVersion outside of mutex. // IMPORTANT: Only call this from DBImpl::InstallSuperVersion() - SuperVersion* InstallSuperVersion(SuperVersion* new_superversion, - InstrumentedMutex* db_mutex, - const MutableCFOptions& mutable_cf_options); - SuperVersion* InstallSuperVersion(SuperVersion* new_superversion, - InstrumentedMutex* db_mutex); + void InstallSuperVersion(SuperVersionContext* sv_context, + InstrumentedMutex* db_mutex, + const MutableCFOptions& mutable_cf_options); + void InstallSuperVersion(SuperVersionContext* sv_context, + InstrumentedMutex* db_mutex); void ResetThreadLocalSuperVersions(); // Protected by DB mutex - void set_pending_flush(bool value) { pending_flush_ = value; } - void set_pending_compaction(bool value) { pending_compaction_ = value; } - bool pending_flush() { return pending_flush_; } - bool pending_compaction() { return pending_compaction_; } + void set_queued_for_flush(bool value) { queued_for_flush_ = value; } + void set_queued_for_compaction(bool value) { queued_for_compaction_ = value; } + bool queued_for_flush() { return queued_for_flush_; } + bool queued_for_compaction() { return queued_for_compaction_; } + + enum class WriteStallCause { + kNone, + kMemtableLimit, + kL0FileCountLimit, + kPendingCompactionBytes, + }; + static std::pair + GetWriteStallConditionAndCause(int num_unflushed_memtables, int num_l0_files, + uint64_t num_compaction_needed_bytes, + const MutableCFOptions& mutable_cf_options); // Recalculate some small conditions, which are changed only during // compaction, adding new memtable and/or // recalculation of compaction score. These values are used in // DBImpl::MakeRoomForWrite function to decide, if it need to make // a write stall - void RecalculateWriteStallConditions( + WriteStallCondition RecalculateWriteStallConditions( const MutableCFOptions& mutable_cf_options); void set_initialized() { initialized_.store(true); } bool initialized() const { return initialized_.load(); } + const ColumnFamilyOptions& initial_cf_options() { + return initial_cf_options_; + } + + Env::WriteLifeTimeHint CalculateSSTWriteHint(int level); + + Status AddDirectories(); + + Directory* GetDataDir(size_t path_id) const; + + ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); } + private: friend class ColumnFamilySet; ColumnFamilyData(uint32_t id, const std::string& name, @@ -354,7 +402,7 @@ class ColumnFamilyData { std::atomic refs_; // outstanding references to ColumnFamilyData std::atomic initialized_; - bool dropped_; // true if client dropped it + std::atomic dropped_; // true if client dropped it const InternalKeyComparator internal_comparator_; std::vector> @@ -396,6 +444,8 @@ class ColumnFamilyData { // recovered from uint64_t log_number_; + std::atomic flush_reason_; + // An object that keeps all the compaction stats // and picks the next compaction std::unique_ptr compaction_picker_; @@ -405,16 +455,22 @@ class ColumnFamilyData { std::unique_ptr write_controller_token_; // If true --> this ColumnFamily is currently present in DBImpl::flush_queue_ - bool pending_flush_; + bool queued_for_flush_; // If true --> this ColumnFamily is currently present in // DBImpl::compaction_queue_ - bool pending_compaction_; + bool queued_for_compaction_; uint64_t prev_compaction_needed_bytes_; // if the database was opened with 2pc enabled bool allow_2pc_; + + // Memtable id to track flush. + std::atomic last_memtable_id_; + + // Directories corresponding to cf_paths. + std::vector> data_dirs_; }; // ColumnFamilySet has interesting thread-safety requirements diff --git a/thirdparty/rocksdb/db/column_family_test.cc b/thirdparty/rocksdb/db/column_family_test.cc index 88786d469d..bdc832bd23 100644 --- a/thirdparty/rocksdb/db/column_family_test.cc +++ b/thirdparty/rocksdb/db/column_family_test.cc @@ -14,6 +14,7 @@ #include "db/db_impl.h" #include "db/db_test_util.h" +#include "memtable/hash_skiplist_rep.h" #include "options/options_parser.h" #include "port/port.h" #include "rocksdb/db.h" @@ -47,7 +48,7 @@ class EnvCounter : public EnvWrapper { int GetNumberOfNewWritableFileCalls() { return num_new_writable_file_; } - Status NewWritableFile(const std::string& f, unique_ptr* r, + Status NewWritableFile(const std::string& f, std::unique_ptr* r, const EnvOptions& soptions) override { ++num_new_writable_file_; return EnvWrapper::NewWritableFile(f, r, soptions); @@ -57,24 +58,36 @@ class EnvCounter : public EnvWrapper { std::atomic num_new_writable_file_; }; -class ColumnFamilyTest : public testing::Test { +class ColumnFamilyTestBase : public testing::Test { public: - ColumnFamilyTest() : rnd_(139) { + ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) { env_ = new EnvCounter(Env::Default()); - dbname_ = test::TmpDir() + "/column_family_test"; + dbname_ = test::PerThreadDBPath("column_family_test"); db_options_.create_if_missing = true; db_options_.fail_if_options_file_error = true; db_options_.env = env_; DestroyDB(dbname_, Options(db_options_, column_family_options_)); } - ~ColumnFamilyTest() { + ~ColumnFamilyTestBase() override { + std::vector column_families; + for (auto h : handles_) { + ColumnFamilyDescriptor cfdescriptor; + h->GetDescriptor(&cfdescriptor); + column_families.push_back(cfdescriptor); + } Close(); rocksdb::SyncPoint::GetInstance()->DisableProcessing(); - Destroy(); + Destroy(column_families); delete env_; } + BlockBasedTableOptions GetBlockBasedTableOptions() { + BlockBasedTableOptions options; + options.format_version = format_; + return options; + } + // Return the value to associate with the specified key Slice Value(int k, std::string* storage) { if (k == 0) { @@ -236,9 +249,11 @@ class ColumnFamilyTest : public testing::Test { #endif // !ROCKSDB_LITE } - void Destroy() { + void Destroy(const std::vector& column_families = + std::vector()) { Close(); - ASSERT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_))); + ASSERT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_), + column_families)); } void CreateColumnFamilies( @@ -291,6 +306,9 @@ class ColumnFamilyTest : public testing::Test { } void PutRandomData(int cf, int num, int key_value_size, bool save = false) { + if (cf >= static_cast(keys_.size())) { + keys_.resize(cf + 1); + } for (int i = 0; i < num; ++i) { // 10 bytes for key, rest is value if (!save) { @@ -298,7 +316,7 @@ class ColumnFamilyTest : public testing::Test { RandomString(&rnd_, key_value_size - 10))); } else { std::string key = test::RandomKey(&rnd_, 11); - keys_.insert(key); + keys_[cf].insert(key); ASSERT_OK(Put(cf, key, RandomString(&rnd_, key_value_size - 10))); } } @@ -383,6 +401,9 @@ class ColumnFamilyTest : public testing::Test { void AssertFilesPerLevel(const std::string& value, int cf) { #ifndef ROCKSDB_LITE ASSERT_EQ(value, FilesPerLevel(cf)); +#else + (void) value; + (void) cf; #endif } @@ -397,6 +418,8 @@ class ColumnFamilyTest : public testing::Test { void AssertCountLiveFiles(int expected_value) { #ifndef ROCKSDB_LITE ASSERT_EQ(expected_value, CountLiveFiles()); +#else + (void) expected_value; #endif } @@ -445,6 +468,8 @@ class ColumnFamilyTest : public testing::Test { void AssertCountLiveLogFiles(int value) { #ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported ASSERT_EQ(value, CountLiveLogFiles()); +#else + (void) value; #endif // !ROCKSDB_LITE } @@ -462,9 +487,9 @@ class ColumnFamilyTest : public testing::Test { void CopyFile(const std::string& source, const std::string& destination, uint64_t size = 0) { const EnvOptions soptions; - unique_ptr srcfile; + std::unique_ptr srcfile; ASSERT_OK(env_->NewSequentialFile(source, &srcfile, soptions)); - unique_ptr destfile; + std::unique_ptr destfile; ASSERT_OK(env_->NewWritableFile(destination, &destfile, soptions)); if (size == 0) { @@ -483,18 +508,47 @@ class ColumnFamilyTest : public testing::Test { ASSERT_OK(destfile->Close()); } + int GetSstFileCount(std::string path) { + std::vector files; + DBTestBase::GetSstFiles(env_, path, &files); + return static_cast(files.size()); + } + + void RecalculateWriteStallConditions(ColumnFamilyData* cfd, + const MutableCFOptions& mutable_cf_options) { + // add lock to avoid race condition between + // `RecalculateWriteStallConditions` which writes to CFStats and + // background `DBImpl::DumpStats()` threads which read CFStats + dbfull()->TEST_LockMutex(); + cfd->RecalculateWriteStallConditions(mutable_cf_options); + dbfull()-> TEST_UnlockMutex(); + } + std::vector handles_; std::vector names_; - std::set keys_; + std::vector> keys_; ColumnFamilyOptions column_family_options_; DBOptions db_options_; std::string dbname_; DB* db_ = nullptr; EnvCounter* env_; Random rnd_; + uint32_t format_; }; -TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) { +class ColumnFamilyTest + : public ColumnFamilyTestBase, + virtual public ::testing::WithParamInterface { + public: + ColumnFamilyTest() : ColumnFamilyTestBase(GetParam()) {} +}; + +INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest, + testing::Values(test::kDefaultFormatVersion)); +INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest, + testing::Values(test::kLatestFormatVersion)); + +TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) { for (int iter = 0; iter < 3; ++iter) { Open(); CreateColumnFamilies({"one", "two", "three"}); @@ -513,7 +567,8 @@ TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) { Reopen(); } CreateColumnFamilies({"three2"}); - // ID 3 that was used for dropped column family "three" should not be reused + // ID 3 that was used for dropped column family "three" should not be + // reused auto cfh3 = reinterpret_cast(handles_[3]); ASSERT_EQ(4U, cfh3->GetID()); Close(); @@ -522,7 +577,7 @@ TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) { } #ifndef ROCKSDB_LITE -TEST_F(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) { +TEST_P(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) { Open(); rocksdb::SyncPoint::GetInstance()->LoadDependency( @@ -545,10 +600,13 @@ TEST_F(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) { } #endif // !ROCKSDB_LITE -class FlushEmptyCFTestWithParam : public ColumnFamilyTest, - public testing::WithParamInterface { +class FlushEmptyCFTestWithParam + : public ColumnFamilyTestBase, + virtual public testing::WithParamInterface> { public: - FlushEmptyCFTestWithParam() { allow_2pc_ = GetParam(); } + FlushEmptyCFTestWithParam() + : ColumnFamilyTestBase(std::get<0>(GetParam())), + allow_2pc_(std::get<1>(GetParam())) {} // Required if inheriting from testing::WithParamInterface<> static void SetUpTestCase() {} @@ -673,10 +731,16 @@ TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest2) { db_options_.env = env_; } -INSTANTIATE_TEST_CASE_P(FlushEmptyCFTestWithParam, FlushEmptyCFTestWithParam, - ::testing::Bool()); +INSTANTIATE_TEST_CASE_P( + FormatDef, FlushEmptyCFTestWithParam, + testing::Values(std::make_tuple(test::kDefaultFormatVersion, true), + std::make_tuple(test::kDefaultFormatVersion, false))); +INSTANTIATE_TEST_CASE_P( + FormatLatest, FlushEmptyCFTestWithParam, + testing::Values(std::make_tuple(test::kLatestFormatVersion, true), + std::make_tuple(test::kLatestFormatVersion, false))); -TEST_F(ColumnFamilyTest, AddDrop) { +TEST_P(ColumnFamilyTest, AddDrop) { Open(); CreateColumnFamilies({"one", "two", "three"}); ASSERT_EQ("NOT_FOUND", Get(1, "fodor")); @@ -702,7 +766,7 @@ TEST_F(ColumnFamilyTest, AddDrop) { std::vector({"default", "four", "three"})); } -TEST_F(ColumnFamilyTest, BulkAddDrop) { +TEST_P(ColumnFamilyTest, BulkAddDrop) { constexpr int kNumCF = 1000; ColumnFamilyOptions cf_options; WriteOptions write_options; @@ -740,7 +804,7 @@ TEST_F(ColumnFamilyTest, BulkAddDrop) { ASSERT_TRUE(families == std::vector({"default"})); } -TEST_F(ColumnFamilyTest, DropTest) { +TEST_P(ColumnFamilyTest, DropTest) { // first iteration - dont reopen DB before dropping // second iteration - reopen DB before dropping for (int iter = 0; iter < 2; ++iter) { @@ -764,7 +828,7 @@ TEST_F(ColumnFamilyTest, DropTest) { } } -TEST_F(ColumnFamilyTest, WriteBatchFailure) { +TEST_P(ColumnFamilyTest, WriteBatchFailure) { Open(); CreateColumnFamiliesAndReopen({"one", "two"}); WriteBatch batch; @@ -782,7 +846,7 @@ TEST_F(ColumnFamilyTest, WriteBatchFailure) { Close(); } -TEST_F(ColumnFamilyTest, ReadWrite) { +TEST_P(ColumnFamilyTest, ReadWrite) { Open(); CreateColumnFamiliesAndReopen({"one", "two"}); ASSERT_OK(Put(0, "foo", "v1")); @@ -806,7 +870,7 @@ TEST_F(ColumnFamilyTest, ReadWrite) { Close(); } -TEST_F(ColumnFamilyTest, IgnoreRecoveredLog) { +TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) { std::string backup_logs = dbname_ + "/backup_logs"; // delete old files in backup_logs directory @@ -882,7 +946,7 @@ TEST_F(ColumnFamilyTest, IgnoreRecoveredLog) { } #ifndef ROCKSDB_LITE // TEST functions used are not supported -TEST_F(ColumnFamilyTest, FlushTest) { +TEST_P(ColumnFamilyTest, FlushTest) { Open(); CreateColumnFamiliesAndReopen({"one", "two"}); ASSERT_OK(Put(0, "foo", "v1")); @@ -930,65 +994,65 @@ TEST_F(ColumnFamilyTest, FlushTest) { } // Makes sure that obsolete log files get deleted -TEST_F(ColumnFamilyTest, LogDeletionTest) { +TEST_P(ColumnFamilyTest, LogDeletionTest) { db_options_.max_total_wal_size = std::numeric_limits::max(); column_family_options_.arena_block_size = 4 * 1024; - column_family_options_.write_buffer_size = 100000; // 100KB + column_family_options_.write_buffer_size = 128000; // 128KB Open(); CreateColumnFamilies({"one", "two", "three", "four"}); // Each bracket is one log file. if number is in (), it means // we don't need it anymore (it's been flushed) // [] AssertCountLiveLogFiles(0); - PutRandomData(0, 1, 100); + PutRandomData(0, 1, 128); // [0] - PutRandomData(1, 1, 100); + PutRandomData(1, 1, 128); // [0, 1] - PutRandomData(1, 1000, 100); + PutRandomData(1, 1000, 128); WaitForFlush(1); // [0, (1)] [1] AssertCountLiveLogFiles(2); - PutRandomData(0, 1, 100); + PutRandomData(0, 1, 128); // [0, (1)] [0, 1] AssertCountLiveLogFiles(2); - PutRandomData(2, 1, 100); + PutRandomData(2, 1, 128); // [0, (1)] [0, 1, 2] - PutRandomData(2, 1000, 100); + PutRandomData(2, 1000, 128); WaitForFlush(2); // [0, (1)] [0, 1, (2)] [2] AssertCountLiveLogFiles(3); - PutRandomData(2, 1000, 100); + PutRandomData(2, 1000, 128); WaitForFlush(2); // [0, (1)] [0, 1, (2)] [(2)] [2] AssertCountLiveLogFiles(4); - PutRandomData(3, 1, 100); + PutRandomData(3, 1, 128); // [0, (1)] [0, 1, (2)] [(2)] [2, 3] - PutRandomData(1, 1, 100); + PutRandomData(1, 1, 128); // [0, (1)] [0, 1, (2)] [(2)] [1, 2, 3] AssertCountLiveLogFiles(4); - PutRandomData(1, 1000, 100); + PutRandomData(1, 1000, 128); WaitForFlush(1); // [0, (1)] [0, (1), (2)] [(2)] [(1), 2, 3] [1] AssertCountLiveLogFiles(5); - PutRandomData(0, 1000, 100); + PutRandomData(0, 1000, 128); WaitForFlush(0); // [(0), (1)] [(0), (1), (2)] [(2)] [(1), 2, 3] [1, (0)] [0] // delete obsolete logs --> // [(1), 2, 3] [1, (0)] [0] AssertCountLiveLogFiles(3); - PutRandomData(0, 1000, 100); + PutRandomData(0, 1000, 128); WaitForFlush(0); // [(1), 2, 3] [1, (0)], [(0)] [0] AssertCountLiveLogFiles(4); - PutRandomData(1, 1000, 100); + PutRandomData(1, 1000, 128); WaitForFlush(1); // [(1), 2, 3] [(1), (0)] [(0)] [0, (1)] [1] AssertCountLiveLogFiles(5); - PutRandomData(2, 1000, 100); + PutRandomData(2, 1000, 128); WaitForFlush(2); // [(1), (2), 3] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2] AssertCountLiveLogFiles(6); - PutRandomData(3, 1000, 100); + PutRandomData(3, 1000, 128); WaitForFlush(3); // [(1), (2), (3)] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2, (3)] [3] // delete obsolete logs --> @@ -998,7 +1062,7 @@ TEST_F(ColumnFamilyTest, LogDeletionTest) { } #endif // !ROCKSDB_LITE -TEST_F(ColumnFamilyTest, CrashAfterFlush) { +TEST_P(ColumnFamilyTest, CrashAfterFlush) { std::unique_ptr fault_env( new FaultInjectionTestEnv(env_)); db_options_.env = fault_env.get(); @@ -1030,7 +1094,7 @@ TEST_F(ColumnFamilyTest, CrashAfterFlush) { db_options_.env = env_; } -TEST_F(ColumnFamilyTest, OpenNonexistentColumnFamily) { +TEST_P(ColumnFamilyTest, OpenNonexistentColumnFamily) { ASSERT_OK(TryOpen({"default"})); Close(); ASSERT_TRUE(TryOpen({"default", "dne"}).IsInvalidArgument()); @@ -1038,7 +1102,7 @@ TEST_F(ColumnFamilyTest, OpenNonexistentColumnFamily) { #ifndef ROCKSDB_LITE // WaitForFlush() is not supported // Makes sure that obsolete log files get deleted -TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) { +TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) { // disable flushing stale column families db_options_.max_total_wal_size = std::numeric_limits::max(); Open(); @@ -1143,45 +1207,49 @@ TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) { } #endif // !ROCKSDB_LITE -#ifndef ROCKSDB_LITE // Cuckoo is not supported in lite -TEST_F(ColumnFamilyTest, MemtableNotSupportSnapshot) { - db_options_.allow_concurrent_memtable_write = false; - Open(); - auto* s1 = dbfull()->GetSnapshot(); - ASSERT_TRUE(s1 != nullptr); - dbfull()->ReleaseSnapshot(s1); - - // Add a column family that doesn't support snapshot - ColumnFamilyOptions first; - first.memtable_factory.reset(NewHashCuckooRepFactory(1024 * 1024)); - CreateColumnFamilies({"first"}, {first}); - auto* s2 = dbfull()->GetSnapshot(); - ASSERT_TRUE(s2 == nullptr); - - // Add a column family that supports snapshot. Snapshot stays not supported. - ColumnFamilyOptions second; - CreateColumnFamilies({"second"}, {second}); - auto* s3 = dbfull()->GetSnapshot(); - ASSERT_TRUE(s3 == nullptr); - Close(); -} -#endif // !ROCKSDB_LITE +// The test is commented out because we want to test that snapshot is +// not created for memtables not supported it, but There isn't a memtable +// that doesn't support snapshot right now. If we have one later, we can +// re-enable the test. +// +// #ifndef ROCKSDB_LITE // Cuckoo is not supported in lite +// TEST_P(ColumnFamilyTest, MemtableNotSupportSnapshot) { +// db_options_.allow_concurrent_memtable_write = false; +// Open(); +// auto* s1 = dbfull()->GetSnapshot(); +// ASSERT_TRUE(s1 != nullptr); +// dbfull()->ReleaseSnapshot(s1); + +// // Add a column family that doesn't support snapshot +// ColumnFamilyOptions first; +// first.memtable_factory.reset(new DummyMemtableNotSupportingSnapshot()); +// CreateColumnFamilies({"first"}, {first}); +// auto* s2 = dbfull()->GetSnapshot(); +// ASSERT_TRUE(s2 == nullptr); + +// // Add a column family that supports snapshot. Snapshot stays not +// supported. ColumnFamilyOptions second; CreateColumnFamilies({"second"}, +// {second}); auto* s3 = dbfull()->GetSnapshot(); ASSERT_TRUE(s3 == nullptr); +// Close(); +// } +// #endif // !ROCKSDB_LITE class TestComparator : public Comparator { - int Compare(const rocksdb::Slice& a, const rocksdb::Slice& b) const override { + int Compare(const rocksdb::Slice& /*a*/, + const rocksdb::Slice& /*b*/) const override { return 0; } const char* Name() const override { return "Test"; } - void FindShortestSeparator(std::string* start, - const rocksdb::Slice& limit) const override {} - void FindShortSuccessor(std::string* key) const override {} + void FindShortestSeparator(std::string* /*start*/, + const rocksdb::Slice& /*limit*/) const override {} + void FindShortSuccessor(std::string* /*key*/) const override {} }; static TestComparator third_comparator; static TestComparator fourth_comparator; // Test that we can retrieve the comparator from a created CF -TEST_F(ColumnFamilyTest, GetComparator) { +TEST_P(ColumnFamilyTest, GetComparator) { Open(); // Add a column family with no comparator specified CreateColumnFamilies({"first"}); @@ -1200,7 +1268,7 @@ TEST_F(ColumnFamilyTest, GetComparator) { Close(); } -TEST_F(ColumnFamilyTest, DifferentMergeOperators) { +TEST_P(ColumnFamilyTest, DifferentMergeOperators) { Open(); CreateColumnFamilies({"first", "second"}); ColumnFamilyOptions default_cf, first, second; @@ -1231,7 +1299,7 @@ TEST_F(ColumnFamilyTest, DifferentMergeOperators) { } #ifndef ROCKSDB_LITE // WaitForFlush() is not supported -TEST_F(ColumnFamilyTest, DifferentCompactionStyles) { +TEST_P(ColumnFamilyTest, DifferentCompactionStyles) { Open(); CreateColumnFamilies({"one", "two"}); ColumnFamilyOptions default_cf, one, two; @@ -1243,7 +1311,7 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) { default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = static_cast(1) << 60; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1303,7 +1371,7 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) { #ifndef ROCKSDB_LITE // Sync points not supported in RocksDB Lite -TEST_F(ColumnFamilyTest, MultipleManualCompactions) { +TEST_P(ColumnFamilyTest, MultipleManualCompactions) { Open(); CreateColumnFamilies({"one", "two"}); ColumnFamilyOptions default_cf, one, two; @@ -1315,7 +1383,7 @@ TEST_F(ColumnFamilyTest, MultipleManualCompactions) { default_cf.write_buffer_size = 64 << 10; // 64KB default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1346,7 +1414,7 @@ TEST_F(ColumnFamilyTest, MultipleManualCompactions) { {"ColumnFamilyTest::MultiManual:2", "ColumnFamilyTest::MultiManual:5"}, {"ColumnFamilyTest::MultiManual:2", "ColumnFamilyTest::MultiManual:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (cf_1_1) { TEST_SYNC_POINT("ColumnFamilyTest::MultiManual:4"); cf_1_1 = false; @@ -1392,15 +1460,17 @@ TEST_F(ColumnFamilyTest, MultipleManualCompactions) { CompactAll(2); AssertFilesPerLevel("0,1", 2); // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { + std::set::iterator key_iter = keys_[1].begin(); + while (key_iter != keys_[1].end()) { ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); key_iter++; } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); Close(); } -TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) { +TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) { Open(); CreateColumnFamilies({"one", "two"}); ColumnFamilyOptions default_cf, one, two; @@ -1412,7 +1482,8 @@ TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) { default_cf.write_buffer_size = 64 << 10; // 64KB default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + ; table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1439,7 +1510,7 @@ TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) { {"ColumnFamilyTest::AutoManual:2", "ColumnFamilyTest::AutoManual:5"}, {"ColumnFamilyTest::AutoManual:2", "ColumnFamilyTest::AutoManual:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (cf_1_1) { cf_1_1 = false; TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:4"); @@ -1486,14 +1557,16 @@ TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) { CompactAll(2); AssertFilesPerLevel("0,1", 2); // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { + std::set::iterator key_iter = keys_[1].begin(); + while (key_iter != keys_[1].end()) { ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); key_iter++; } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } -TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) { +TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) { Open(); CreateColumnFamilies({"one", "two"}); ColumnFamilyOptions default_cf, one, two; @@ -1505,7 +1578,8 @@ TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) { default_cf.write_buffer_size = 64 << 10; // 64KB default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + ; table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1540,7 +1614,7 @@ TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) { {"ColumnFamilyTest::ManualAuto:5", "ColumnFamilyTest::ManualAuto:2"}, {"ColumnFamilyTest::ManualAuto:2", "ColumnFamilyTest::ManualAuto:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (cf_1_1) { TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4"); cf_1_1 = false; @@ -1582,14 +1656,16 @@ TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) { CompactAll(2); AssertFilesPerLevel("0,1", 2); // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { + std::set::iterator key_iter = keys_[1].begin(); + while (key_iter != keys_[1].end()) { ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); key_iter++; } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } -TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) { +TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) { Open(); CreateColumnFamilies({"one"}); ColumnFamilyOptions default_cf, one; @@ -1601,7 +1677,8 @@ TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) { default_cf.write_buffer_size = 64 << 10; // 64KB default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + ; table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1633,7 +1710,7 @@ TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) { {"ColumnFamilyTest::ManualManual:1", "ColumnFamilyTest::ManualManual:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (cf_1_1) { TEST_SYNC_POINT("ColumnFamilyTest::ManualManual:4"); cf_1_1 = false; @@ -1681,14 +1758,16 @@ TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) { ASSERT_LE(NumTableFilesAtLevel(0, 1), 2); // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { + std::set::iterator key_iter = keys_[1].begin(); + while (key_iter != keys_[1].end()) { ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); key_iter++; } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } -TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) { +TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) { Open(); CreateColumnFamilies({"one"}); ColumnFamilyOptions default_cf, one; @@ -1700,7 +1779,8 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) { default_cf.write_buffer_size = 64 << 10; // 64KB default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + ; table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1731,7 +1811,7 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) { {"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:2"}, {"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (cf_1_1) { TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4"); cf_1_1 = false; @@ -1771,14 +1851,16 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) { ASSERT_LE(NumTableFilesAtLevel(0, 1), 2); // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { + std::set::iterator key_iter = keys_[1].begin(); + while (key_iter != keys_[1].end()) { ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); key_iter++; } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } -TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) { +TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) { Open(); CreateColumnFamilies({"one"}); ColumnFamilyOptions default_cf, one; @@ -1790,7 +1872,8 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) { default_cf.write_buffer_size = 64 << 10; // 64KB default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + ; table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1823,7 +1906,7 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) { "ColumnFamilyTest::ManualAuto:3"}, {"ColumnFamilyTest::ManualAuto:1", "ColumnFamilyTest::ManualAuto:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (cf_1_1) { TEST_SYNC_POINT("ColumnFamilyTest::ManualAuto:4"); cf_1_1 = false; @@ -1861,131 +1944,13 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) { AssertFilesPerLevel("0,1", 1); // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { - ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); - key_iter++; - } -} - -// This test checks for automatic getting a conflict if there is a -// manual which has not yet been scheduled. -// The manual compaction waits in NotScheduled -// We generate more files and then trigger an automatic compaction -// This will wait because there is an unscheduled manual compaction. -// Once the conflict is hit, the manual compaction starts and ends -// Then another automatic will start and end. -TEST_F(ColumnFamilyTest, SameCFManualAutomaticConflict) { - Open(); - CreateColumnFamilies({"one"}); - ColumnFamilyOptions default_cf, one; - db_options_.max_open_files = 20; // only 10 files in file cache - db_options_.max_background_compactions = 3; - - default_cf.compaction_style = kCompactionStyleLevel; - default_cf.num_levels = 3; - default_cf.write_buffer_size = 64 << 10; // 64KB - default_cf.target_file_size_base = 30 << 10; - default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; - table_options.no_block_cache = true; - default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - one.compaction_style = kCompactionStyleUniversal; - - one.num_levels = 1; - // trigger compaction if there are >= 4 files - one.level0_file_num_compaction_trigger = 4; - one.write_buffer_size = 120000; - - Reopen({default_cf, one}); - // make sure all background compaction jobs can be scheduled - auto stop_token = - dbfull()->TEST_write_controler().GetCompactionPressureToken(); - - // SETUP column family "one" -- universal style - for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(ToString(i + 1), 1); - } - bool cf_1_1 = true; - bool cf_1_2 = true; - rocksdb::SyncPoint::GetInstance()->LoadDependency( - {{"DBImpl::BackgroundCompaction()::Conflict", - "ColumnFamilyTest::ManualAutoCon:7"}, - {"ColumnFamilyTest::ManualAutoCon:9", - "ColumnFamilyTest::ManualAutoCon:8"}, - {"ColumnFamilyTest::ManualAutoCon:2", - "ColumnFamilyTest::ManualAutoCon:6"}, - {"ColumnFamilyTest::ManualAutoCon:4", - "ColumnFamilyTest::ManualAutoCon:5"}, - {"ColumnFamilyTest::ManualAutoCon:1", - "ColumnFamilyTest::ManualAutoCon:2"}, - {"ColumnFamilyTest::ManualAutoCon:1", - "ColumnFamilyTest::ManualAutoCon:3"}}); - rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { - if (cf_1_1) { - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:4"); - cf_1_1 = false; - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:3"); - } else if (cf_1_2) { - cf_1_2 = false; - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:2"); - } - }); - rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::RunManualCompaction:NotScheduled", [&](void* arg) { - InstrumentedMutex* mutex = static_cast(arg); - mutex->Unlock(); - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:9"); - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:7"); - mutex->Lock(); - }); - - rocksdb::SyncPoint::GetInstance()->EnableProcessing(); - rocksdb::port::Thread threads([&] { - CompactRangeOptions compact_options; - compact_options.exclusive_manual_compaction = false; - ASSERT_OK( - db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)); - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:6"); - }); - - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:8"); - WaitForFlush(1); - - // Add more L0 files and force automatic compaction - for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - AssertFilesPerLevel(ToString(one.level0_file_num_compaction_trigger + i), - 1); - } - - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:5"); - // Add more L0 files and force automatic compaction - for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) { - PutRandomData(1, 10, 12000, true); - PutRandomData(1, 1, 10, true); - WaitForFlush(1); - } - TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:1"); - - threads.join(); - WaitForCompaction(); - // VERIFY compaction "one" - ASSERT_LE(NumTableFilesAtLevel(0, 1), 3); - - // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { + std::set::iterator key_iter = keys_[1].begin(); + while (key_iter != keys_[1].end()) { ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); key_iter++; } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } // In this test, we generate enough files to trigger automatic compactions. @@ -1994,7 +1959,7 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticConflict) { // This will wait because the automatic compaction has files it needs. // Once the conflict is hit, the automatic compaction starts and ends // Then the manual will run and end. -TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) { +TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) { Open(); CreateColumnFamilies({"one"}); ColumnFamilyOptions default_cf, one; @@ -2006,7 +1971,8 @@ TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) { default_cf.write_buffer_size = 64 << 10; // 64KB default_cf.target_file_size_base = 30 << 10; default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + ; table_options.no_block_cache = true; default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -2030,7 +1996,7 @@ TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) { {"CompactionPicker::CompactRange:Conflict", "ColumnFamilyTest::AutoManual:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (cf_1_1) { TEST_SYNC_POINT("ColumnFamilyTest::AutoManual:4"); cf_1_1 = false; @@ -2070,15 +2036,17 @@ TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) { // VERIFY compaction "one" AssertFilesPerLevel("1", 1); // Compare against saved keys - std::set::iterator key_iter = keys_.begin(); - while (key_iter != keys_.end()) { + std::set::iterator key_iter = keys_[1].begin(); + while (key_iter != keys_[1].end()) { ASSERT_NE("NOT_FOUND", Get(1, *key_iter)); key_iter++; } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } #endif // !ROCKSDB_LITE -#ifndef ROCKSDB_LITE // Tailing interator not supported +#ifndef ROCKSDB_LITE // Tailing iterator not supported namespace { std::string IterStatus(Iterator* iter) { std::string result; @@ -2091,7 +2059,7 @@ std::string IterStatus(Iterator* iter) { } } // anonymous namespace -TEST_F(ColumnFamilyTest, NewIteratorsTest) { +TEST_P(ColumnFamilyTest, NewIteratorsTest) { // iter == 0 -- no tailing // iter == 2 -- tailing for (int iter = 0; iter < 2; ++iter) { @@ -2138,7 +2106,7 @@ TEST_F(ColumnFamilyTest, NewIteratorsTest) { #endif // !ROCKSDB_LITE #ifndef ROCKSDB_LITE // ReadOnlyDB is not supported -TEST_F(ColumnFamilyTest, ReadOnlyDBTest) { +TEST_P(ColumnFamilyTest, ReadOnlyDBTest) { Open(); CreateColumnFamiliesAndReopen({"one", "two", "three", "four"}); ASSERT_OK(Put(0, "a", "b")); @@ -2190,7 +2158,7 @@ TEST_F(ColumnFamilyTest, ReadOnlyDBTest) { #endif // !ROCKSDB_LITE #ifndef ROCKSDB_LITE // WaitForFlush() is not supported in lite -TEST_F(ColumnFamilyTest, DontRollEmptyLogs) { +TEST_P(ColumnFamilyTest, DontRollEmptyLogs) { Open(); CreateColumnFamiliesAndReopen({"one", "two", "three", "four"}); @@ -2214,7 +2182,7 @@ TEST_F(ColumnFamilyTest, DontRollEmptyLogs) { #endif // !ROCKSDB_LITE #ifndef ROCKSDB_LITE // WaitForCompaction() is not supported in lite -TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) { +TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) { Open(); CreateColumnFamilies({"one", "two"}); ColumnFamilyOptions default_cf, one, two; @@ -2249,7 +2217,7 @@ TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) { } #endif // !ROCKSDB_LITE -TEST_F(ColumnFamilyTest, CreateMissingColumnFamilies) { +TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) { Status s = TryOpen({"one", "two"}); ASSERT_TRUE(!s.ok()); db_options_.create_missing_column_families = true; @@ -2258,7 +2226,7 @@ TEST_F(ColumnFamilyTest, CreateMissingColumnFamilies) { Close(); } -TEST_F(ColumnFamilyTest, SanitizeOptions) { +TEST_P(ColumnFamilyTest, SanitizeOptions) { DBOptions db_options; for (int s = kCompactionStyleLevel; s <= kCompactionStyleUniversal; ++s) { for (int l = 0; l <= 2; l++) { @@ -2307,7 +2275,7 @@ TEST_F(ColumnFamilyTest, SanitizeOptions) { } } -TEST_F(ColumnFamilyTest, ReadDroppedColumnFamily) { +TEST_P(ColumnFamilyTest, ReadDroppedColumnFamily) { // iter 0 -- drop CF, don't reopen // iter 1 -- delete CF, reopen for (int iter = 0; iter < 2; ++iter) { @@ -2379,7 +2347,7 @@ TEST_F(ColumnFamilyTest, ReadDroppedColumnFamily) { } } -TEST_F(ColumnFamilyTest, FlushAndDropRaceCondition) { +TEST_P(ColumnFamilyTest, FlushAndDropRaceCondition) { db_options_.create_missing_column_families = true; Open({"default", "one"}); ColumnFamilyOptions options; @@ -2445,12 +2413,13 @@ TEST_F(ColumnFamilyTest, FlushAndDropRaceCondition) { // skipped as persisting options is not supported in ROCKSDB_LITE namespace { std::atomic test_stage(0); +std::atomic ordered_by_writethread(false); const int kMainThreadStartPersistingOptionsFile = 1; const int kChildThreadFinishDroppingColumnFamily = 2; -const int kChildThreadWaitingMainThreadPersistOptions = 3; void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id, std::vector* comparators) { - while (test_stage < kMainThreadStartPersistingOptionsFile) { + while (test_stage < kMainThreadStartPersistingOptionsFile && + !ordered_by_writethread) { Env::Default()->SleepForMicroseconds(100); } cf_test->DropColumnFamilies({cf_id}); @@ -2462,7 +2431,7 @@ void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id, } } // namespace -TEST_F(ColumnFamilyTest, CreateAndDropRace) { +TEST_P(ColumnFamilyTest, CreateAndDropRace) { const int kCfCount = 5; std::vector cf_opts; std::vector comparators; @@ -2477,28 +2446,25 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) { auto main_thread_id = std::this_thread::get_id(); rocksdb::SyncPoint::GetInstance()->SetCallBack("PersistRocksDBOptions:start", - [&](void* arg) { + [&](void* /*arg*/) { auto current_thread_id = std::this_thread::get_id(); // If it's the main thread hitting this sync-point, then it // will be blocked until some other thread update the test_stage. if (main_thread_id == current_thread_id) { test_stage = kMainThreadStartPersistingOptionsFile; - while (test_stage < kChildThreadFinishDroppingColumnFamily) { + while (test_stage < kChildThreadFinishDroppingColumnFamily && + !ordered_by_writethread) { Env::Default()->SleepForMicroseconds(100); } } }); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "WriteThread::EnterUnbatched:Wait", [&](void* arg) { + "WriteThread::EnterUnbatched:Wait", [&](void* /*arg*/) { // This means a thread doing DropColumnFamily() is waiting for // other thread to finish persisting options. // In such case, we update the test_stage to unblock the main thread. - test_stage = kChildThreadWaitingMainThreadPersistOptions; - - // Note that based on the test setting, this must not be the - // main thread. - ASSERT_NE(main_thread_id, std::this_thread::get_id()); + ordered_by_writethread = true; }); // Create a database with four column families @@ -2509,7 +2475,8 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) { // Start a thread that will drop the first column family // and its comparator - rocksdb::port::Thread drop_cf_thread(DropSingleColumnFamily, this, 1, &comparators); + rocksdb::port::Thread drop_cf_thread(DropSingleColumnFamily, this, 1, + &comparators); DropColumnFamilies({2}); @@ -2521,10 +2488,13 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) { delete comparator; } } + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } #endif // !ROCKSDB_LITE -TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) { +TEST_P(ColumnFamilyTest, WriteStallSingleColumnFamily) { const uint64_t kBaseRate = 800000u; db_options_.delayed_write_rate = kBaseRate; db_options_.max_background_compactions = 6; @@ -2544,139 +2514,139 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) { mutable_cf_options.disable_auto_compactions = false; vstorage->TEST_set_estimated_compaction_needed_bytes(50); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); vstorage->TEST_set_estimated_compaction_needed_bytes(201); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(400); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(500); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(450); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(205); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(202); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(201); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(198); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); vstorage->TEST_set_estimated_compaction_needed_bytes(399); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(599); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(2001); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(3001); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); vstorage->TEST_set_estimated_compaction_needed_bytes(390); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(100); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); vstorage->set_l0_delay_trigger_count(100); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(101); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); vstorage->set_l0_delay_trigger_count(0); vstorage->TEST_set_estimated_compaction_needed_bytes(300); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); vstorage->set_l0_delay_trigger_count(101); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25 / 1.25 / 1.25, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(200); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); vstorage->set_l0_delay_trigger_count(0); vstorage->TEST_set_estimated_compaction_needed_bytes(0); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); mutable_cf_options.disable_auto_compactions = true; dbfull()->TEST_write_controler().set_delayed_write_rate(kBaseRate); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); vstorage->set_l0_delay_trigger_count(50); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(0, GetDbDelayedWriteRate()); @@ -2684,7 +2654,7 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) { vstorage->set_l0_delay_trigger_count(60); vstorage->TEST_set_estimated_compaction_needed_bytes(300); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(0, GetDbDelayedWriteRate()); @@ -2693,20 +2663,20 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) { mutable_cf_options.disable_auto_compactions = false; vstorage->set_l0_delay_trigger_count(70); vstorage->TEST_set_estimated_compaction_needed_bytes(500); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); vstorage->set_l0_delay_trigger_count(71); vstorage->TEST_set_estimated_compaction_needed_bytes(501); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); } -TEST_F(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) { +TEST_P(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) { db_options_.max_background_compactions = 6; Open({"default"}); ColumnFamilyData* cfd = @@ -2725,31 +2695,31 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) { mutable_cf_options.hard_pending_compaction_bytes_limit = 2000; vstorage->TEST_set_estimated_compaction_needed_bytes(40); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(50); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(300); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(45); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(7); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(9); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(6); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); // Speed up threshold = min(4 * 2, 4 + (12 - 4)/4) = 6 @@ -2758,19 +2728,19 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) { mutable_cf_options.level0_stop_writes_trigger = 30; vstorage->set_l0_delay_trigger_count(5); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(7); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(3); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); } -TEST_F(ColumnFamilyTest, WriteStallTwoColumnFamilies) { +TEST_P(ColumnFamilyTest, WriteStallTwoColumnFamilies) { const uint64_t kBaseRate = 810000u; db_options_.delayed_write_rate = kBaseRate; Open(); @@ -2793,59 +2763,59 @@ TEST_F(ColumnFamilyTest, WriteStallTwoColumnFamilies) { mutable_cf_options1.soft_pending_compaction_bytes_limit = 500; vstorage->TEST_set_estimated_compaction_needed_bytes(50); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); vstorage1->TEST_set_estimated_compaction_needed_bytes(201); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); vstorage1->TEST_set_estimated_compaction_needed_bytes(600); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(70); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate, GetDbDelayedWriteRate()); vstorage1->TEST_set_estimated_compaction_needed_bytes(800); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(300); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); vstorage1->TEST_set_estimated_compaction_needed_bytes(700); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); vstorage->TEST_set_estimated_compaction_needed_bytes(500); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25 / 1.25, GetDbDelayedWriteRate()); vstorage1->TEST_set_estimated_compaction_needed_bytes(600); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_TRUE(!IsDbWriteStopped()); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate()); } -TEST_F(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) { +TEST_P(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) { db_options_.max_background_compactions = 6; column_family_options_.soft_pending_compaction_bytes_limit = 200; column_family_options_.hard_pending_compaction_bytes_limit = 2000; @@ -2872,46 +2842,79 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) { mutable_cf_options1.level0_slowdown_writes_trigger = 16; vstorage->TEST_set_estimated_compaction_needed_bytes(40); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(60); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage1->TEST_set_estimated_compaction_needed_bytes(30); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage1->TEST_set_estimated_compaction_needed_bytes(70); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->TEST_set_estimated_compaction_needed_bytes(20); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage1->TEST_set_estimated_compaction_needed_bytes(3); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(9); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage1->set_l0_delay_trigger_count(2); - cfd1->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd1, mutable_cf_options); ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed()); vstorage->set_l0_delay_trigger_count(0); - cfd->RecalculateWriteStallConditions(mutable_cf_options); + RecalculateWriteStallConditions(cfd, mutable_cf_options); ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed()); } +TEST_P(ColumnFamilyTest, CreateAndDestoryOptions) { + std::unique_ptr cfo(new ColumnFamilyOptions()); + ColumnFamilyHandle* cfh; + Open(); + ASSERT_OK(db_->CreateColumnFamily(*(cfo.get()), "yoyo", &cfh)); + cfo.reset(); + ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); + ASSERT_OK(db_->Flush(FlushOptions(), cfh)); + ASSERT_OK(db_->DropColumnFamily(cfh)); + ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh)); +} + +TEST_P(ColumnFamilyTest, CreateDropAndDestroy) { + ColumnFamilyHandle* cfh; + Open(); + ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh)); + ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); + ASSERT_OK(db_->Flush(FlushOptions(), cfh)); + ASSERT_OK(db_->DropColumnFamily(cfh)); + ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh)); +} + #ifndef ROCKSDB_LITE -TEST_F(ColumnFamilyTest, FlushCloseWALFiles) { +TEST_P(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) { + ColumnFamilyHandle* cfh; + Open(); + ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh)); + ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); + ASSERT_OK(db_->Flush(FlushOptions(), cfh)); + ASSERT_OK(db_->DisableFileDeletions()); + ASSERT_OK(db_->DropColumnFamily(cfh)); + ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh)); +} + +TEST_P(ColumnFamilyTest, FlushCloseWALFiles) { SpecialEnv env(Env::Default()); db_options_.env = &env; db_options_.max_background_flushes = 1; @@ -2953,7 +2956,7 @@ TEST_F(ColumnFamilyTest, FlushCloseWALFiles) { #endif // !ROCKSDB_LITE #ifndef ROCKSDB_LITE // WaitForFlush() is not supported -TEST_F(ColumnFamilyTest, IteratorCloseWALFile1) { +TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) { SpecialEnv env(Env::Default()); db_options_.env = &env; db_options_.max_background_flushes = 1; @@ -2998,7 +3001,7 @@ TEST_F(ColumnFamilyTest, IteratorCloseWALFile1) { Close(); } -TEST_F(ColumnFamilyTest, IteratorCloseWALFile2) { +TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) { SpecialEnv env(Env::Default()); // Allow both of flush and purge job to schedule. env.SetBackgroundThreads(2, Env::HIGH); @@ -3055,7 +3058,7 @@ TEST_F(ColumnFamilyTest, IteratorCloseWALFile2) { #endif // !ROCKSDB_LITE #ifndef ROCKSDB_LITE // TEST functions are not supported in lite -TEST_F(ColumnFamilyTest, ForwardIteratorCloseWALFile) { +TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) { SpecialEnv env(Env::Default()); // Allow both of flush and purge job to schedule. env.SetBackgroundThreads(2, Env::HIGH); @@ -3132,7 +3135,7 @@ TEST_F(ColumnFamilyTest, ForwardIteratorCloseWALFile) { // Disable on windows because SyncWAL requires env->IsSyncThreadSafe() // to return true which is not so in unbuffered mode. #ifndef OS_WIN -TEST_F(ColumnFamilyTest, LogSyncConflictFlush) { +TEST_P(ColumnFamilyTest, LogSyncConflictFlush) { Open(); CreateColumnFamiliesAndReopen({"one", "two"}); @@ -3167,7 +3170,7 @@ TEST_F(ColumnFamilyTest, LogSyncConflictFlush) { // test is being used to ensure a roll of wal files. // Basic idea is to test that WAL truncation is being detected and not // ignored -TEST_F(ColumnFamilyTest, DISABLED_LogTruncationTest) { +TEST_P(ColumnFamilyTest, DISABLED_LogTruncationTest) { Open(); CreateColumnFamiliesAndReopen({"one", "two"}); @@ -3236,6 +3239,77 @@ TEST_F(ColumnFamilyTest, DISABLED_LogTruncationTest) { // cleanup env_->DeleteDir(backup_logs); } + +TEST_P(ColumnFamilyTest, DefaultCfPathsTest) { + Open(); + // Leave cf_paths for one column families to be empty. + // Files should be generated according to db_paths for that + // column family. + ColumnFamilyOptions cf_opt1, cf_opt2; + cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1", + std::numeric_limits::max()); + CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2}); + Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2}); + + // Fill Column family 1. + PutRandomData(1, 100, 100); + Flush(1); + + ASSERT_EQ(1, GetSstFileCount(cf_opt1.cf_paths[0].path)); + ASSERT_EQ(0, GetSstFileCount(dbname_)); + + // Fill column family 2 + PutRandomData(2, 100, 100); + Flush(2); + + // SST from Column family 2 should be generated in + // db_paths which is dbname_ in this case. + ASSERT_EQ(1, GetSstFileCount(dbname_)); +} + +TEST_P(ColumnFamilyTest, MultipleCFPathsTest) { + Open(); + // Configure Column family specific paths. + ColumnFamilyOptions cf_opt1, cf_opt2; + cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1", + std::numeric_limits::max()); + cf_opt2.cf_paths.emplace_back(dbname_ + "_two_1", + std::numeric_limits::max()); + CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2}); + Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2}); + + PutRandomData(1, 100, 100, true /* save */); + Flush(1); + + // Check that files are generated in appropriate paths. + ASSERT_EQ(1, GetSstFileCount(cf_opt1.cf_paths[0].path)); + ASSERT_EQ(0, GetSstFileCount(dbname_)); + + PutRandomData(2, 100, 100, true /* save */); + Flush(2); + + ASSERT_EQ(1, GetSstFileCount(cf_opt2.cf_paths[0].path)); + ASSERT_EQ(0, GetSstFileCount(dbname_)); + + // Re-open and verify the keys. + Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2}); + DBImpl* dbi = reinterpret_cast(db_); + for (int cf = 1; cf != 3; ++cf) { + ReadOptions read_options; + read_options.readahead_size = 0; + auto it = dbi->NewIterator(read_options, handles_[cf]); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + Slice key(it->key()); + ASSERT_NE(keys_[cf].end(), keys_[cf].find(key.ToString())); + } + delete it; + + for (const auto& key : keys_[cf]) { + ASSERT_NE("NOT_FOUND", Get(cf, key)); + } + } +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/compact_files_test.cc b/thirdparty/rocksdb/db/compact_files_test.cc index 5aad6114f5..ce80375e0e 100644 --- a/thirdparty/rocksdb/db/compact_files_test.cc +++ b/thirdparty/rocksdb/db/compact_files_test.cc @@ -24,7 +24,7 @@ class CompactFilesTest : public testing::Test { public: CompactFilesTest() { env_ = Env::Default(); - db_name_ = test::TmpDir(env_) + "/compact_files_test"; + db_name_ = test::PerThreadDBPath("compact_files_test"); } std::string db_name_; @@ -35,10 +35,9 @@ class CompactFilesTest : public testing::Test { class FlushedFileCollector : public EventListener { public: FlushedFileCollector() {} - ~FlushedFileCollector() {} + ~FlushedFileCollector() override {} - virtual void OnFlushCompleted( - DB* db, const FlushJobInfo& info) override { + void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { std::lock_guard lock(mutex_); flushed_files_.push_back(info.file_path); } @@ -257,9 +256,9 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) { TEST_F(CompactFilesTest, CompactionFilterWithGetSv) { class FilterWithGet : public CompactionFilter { public: - virtual bool Filter(int level, const Slice& key, const Slice& value, - std::string* new_value, - bool* value_changed) const override { + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { if (db_ == nullptr) { return true; } @@ -272,7 +271,7 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) { db_ = db; } - virtual const char* Name() const override { return "FilterWithGet"; } + const char* Name() const override { return "FilterWithGet"; } private: DB* db_; @@ -309,6 +308,100 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) { delete db; } +TEST_F(CompactFilesTest, SentinelCompressionType) { + if (!Zlib_Supported()) { + fprintf(stderr, "zlib compression not supported, skip this test\n"); + return; + } + if (!Snappy_Supported()) { + fprintf(stderr, "snappy compression not supported, skip this test\n"); + return; + } + // Check that passing `CompressionType::kDisableCompressionOption` to + // `CompactFiles` causes it to use the column family compression options. + for (auto compaction_style : + {CompactionStyle::kCompactionStyleLevel, + CompactionStyle::kCompactionStyleUniversal, + CompactionStyle::kCompactionStyleNone}) { + DestroyDB(db_name_, Options()); + Options options; + options.compaction_style = compaction_style; + // L0: Snappy, L1: ZSTD, L2: Snappy + options.compression_per_level = {CompressionType::kSnappyCompression, + CompressionType::kZlibCompression, + CompressionType::kSnappyCompression}; + options.create_if_missing = true; + FlushedFileCollector* collector = new FlushedFileCollector(); + options.listeners.emplace_back(collector); + DB* db = nullptr; + ASSERT_OK(DB::Open(options, db_name_, &db)); + + db->Put(WriteOptions(), "key", "val"); + db->Flush(FlushOptions()); + + auto l0_files = collector->GetFlushedFiles(); + ASSERT_EQ(1, l0_files.size()); + + // L0->L1 compaction, so output should be ZSTD-compressed + CompactionOptions compaction_opts; + compaction_opts.compression = CompressionType::kDisableCompressionOption; + ASSERT_OK(db->CompactFiles(compaction_opts, l0_files, 1)); + + rocksdb::TablePropertiesCollection all_tables_props; + ASSERT_OK(db->GetPropertiesOfAllTables(&all_tables_props)); + for (const auto& name_and_table_props : all_tables_props) { + ASSERT_EQ(CompressionTypeToString(CompressionType::kZlibCompression), + name_and_table_props.second->compression_name); + } + delete db; + } +} + +TEST_F(CompactFilesTest, GetCompactionJobInfo) { + Options options; + options.create_if_missing = true; + // Disable RocksDB background compaction. + options.compaction_style = kCompactionStyleNone; + options.level0_slowdown_writes_trigger = 1000; + options.level0_stop_writes_trigger = 1000; + options.write_buffer_size = 65536; + options.max_write_buffer_number = 2; + options.compression = kNoCompression; + options.max_compaction_bytes = 5000; + + // Add listener + FlushedFileCollector* collector = new FlushedFileCollector(); + options.listeners.emplace_back(collector); + + DB* db = nullptr; + DestroyDB(db_name_, options); + Status s = DB::Open(options, db_name_, &db); + assert(s.ok()); + assert(db); + + // create couple files + for (int i = 0; i < 500; ++i) { + db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26))); + } + reinterpret_cast(db)->TEST_WaitForFlushMemTable(); + auto l0_files_1 = collector->GetFlushedFiles(); + CompactionOptions co; + co.compression = CompressionType::kLZ4Compression; + CompactionJobInfo compaction_job_info; + ASSERT_OK( + db->CompactFiles(co, l0_files_1, 0, -1, nullptr, &compaction_job_info)); + ASSERT_EQ(compaction_job_info.base_input_level, 0); + ASSERT_EQ(compaction_job_info.cf_id, db->DefaultColumnFamily()->GetID()); + ASSERT_EQ(compaction_job_info.cf_name, db->DefaultColumnFamily()->GetName()); + ASSERT_EQ(compaction_job_info.compaction_reason, + CompactionReason::kManualCompaction); + ASSERT_EQ(compaction_job_info.compression, CompressionType::kLZ4Compression); + ASSERT_EQ(compaction_job_info.output_level, 0); + ASSERT_OK(compaction_job_info.status); + // no assertion failure + delete db; +} + } // namespace rocksdb int main(int argc, char** argv) { @@ -319,7 +412,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as DBImpl::CompactFiles is not supported in ROCKSDB_LITE\n"); return 0; diff --git a/thirdparty/rocksdb/db/compacted_db_impl.cc b/thirdparty/rocksdb/db/compacted_db_impl.cc index d1007d972a..acdaad4ec2 100644 --- a/thirdparty/rocksdb/db/compacted_db_impl.cc +++ b/thirdparty/rocksdb/db/compacted_db_impl.cc @@ -17,29 +17,20 @@ extern bool SaveValue(void* arg, const ParsedInternalKey& parsed_key, CompactedDBImpl::CompactedDBImpl( const DBOptions& options, const std::string& dbname) - : DBImpl(options, dbname) { + : DBImpl(options, dbname), cfd_(nullptr), version_(nullptr), + user_comparator_(nullptr) { } CompactedDBImpl::~CompactedDBImpl() { } size_t CompactedDBImpl::FindFile(const Slice& key) { - size_t left = 0; size_t right = files_.num_files - 1; - while (left < right) { - size_t mid = (left + right) >> 1; - const FdWithKeyRange& f = files_.files[mid]; - if (user_comparator_->Compare(ExtractUserKey(f.largest_key), key) < 0) { - // Key at "mid.largest" is < "target". Therefore all - // files at or before "mid" are uninteresting. - left = mid + 1; - } else { - // Key at "mid.largest" is >= "target". Therefore all files - // after "mid" are uninteresting. - right = mid; - } - } - return right; + auto cmp = [&](const FdWithKeyRange& f, const Slice& k) -> bool { + return user_comparator_->Compare(ExtractUserKey(f.largest_key), k) < 0; + }; + return static_cast(std::lower_bound(files_.files, + files_.files + right, key, cmp) - files_.files); } Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*, @@ -48,8 +39,8 @@ Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*, GetContext::kNotFound, key, value, nullptr, nullptr, nullptr, nullptr); LookupKey lkey(key, kMaxSequenceNumber); - files_.files[FindFile(key)].fd.table_reader->Get( - options, lkey.internal_key(), &get_context); + files_.files[FindFile(key)].fd.table_reader->Get(options, lkey.internal_key(), + &get_context, nullptr); if (get_context.State() == GetContext::kFound) { return Status::OK(); } @@ -81,7 +72,7 @@ std::vector CompactedDBImpl::MultiGet(const ReadOptions& options, GetContext::kNotFound, keys[idx], &pinnable_val, nullptr, nullptr, nullptr, nullptr); LookupKey lkey(keys[idx], kMaxSequenceNumber); - r->Get(options, lkey.internal_key(), &get_context); + r->Get(options, lkey.internal_key(), &get_context, nullptr); value.assign(pinnable_val.data(), pinnable_val.size()); if (get_context.State() == GetContext::kFound) { statuses[idx] = Status::OK(); @@ -93,6 +84,7 @@ std::vector CompactedDBImpl::MultiGet(const ReadOptions& options, } Status CompactedDBImpl::Init(const Options& options) { + SuperVersionContext sv_context(/* create_superversion */ true); mutex_.Lock(); ColumnFamilyDescriptor cf(kDefaultColumnFamilyName, ColumnFamilyOptions(options)); @@ -100,9 +92,10 @@ Status CompactedDBImpl::Init(const Options& options) { if (s.ok()) { cfd_ = reinterpret_cast( DefaultColumnFamily())->cfd(); - delete cfd_->InstallSuperVersion(new SuperVersion(), &mutex_); + cfd_->InstallSuperVersion(&sv_context, &mutex_); } mutex_.Unlock(); + sv_context.Clean(); if (!s.ok()) { return s; } @@ -154,6 +147,7 @@ Status CompactedDBImpl::Open(const Options& options, std::unique_ptr db(new CompactedDBImpl(db_options, dbname)); Status s = db->Init(options); if (s.ok()) { + db->StartTimedTasks(); ROCKS_LOG_INFO(db->immutable_db_options_.info_log, "Opened the db as fully compacted mode"); LogFlush(db->immutable_db_options_.info_log); diff --git a/thirdparty/rocksdb/db/compacted_db_impl.h b/thirdparty/rocksdb/db/compacted_db_impl.h index de32f21e68..5c574b4b9a 100644 --- a/thirdparty/rocksdb/db/compacted_db_impl.h +++ b/thirdparty/rocksdb/db/compacted_db_impl.h @@ -32,55 +32,57 @@ class CompactedDBImpl : public DBImpl { override; using DBImpl::Put; - virtual Status Put(const WriteOptions& options, - ColumnFamilyHandle* column_family, const Slice& key, - const Slice& value) override { + virtual Status Put(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/, const Slice& /*value*/) override { return Status::NotSupported("Not supported in compacted db mode."); } using DBImpl::Merge; - virtual Status Merge(const WriteOptions& options, - ColumnFamilyHandle* column_family, const Slice& key, - const Slice& value) override { + virtual Status Merge(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/, const Slice& /*value*/) override { return Status::NotSupported("Not supported in compacted db mode."); } using DBImpl::Delete; - virtual Status Delete(const WriteOptions& options, - ColumnFamilyHandle* column_family, - const Slice& key) override { + virtual Status Delete(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/) override { return Status::NotSupported("Not supported in compacted db mode."); } - virtual Status Write(const WriteOptions& options, - WriteBatch* updates) override { + virtual Status Write(const WriteOptions& /*options*/, + WriteBatch* /*updates*/) override { return Status::NotSupported("Not supported in compacted db mode."); } using DBImpl::CompactRange; - virtual Status CompactRange(const CompactRangeOptions& options, - ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end) override { + virtual Status CompactRange(const CompactRangeOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice* /*begin*/, + const Slice* /*end*/) override { return Status::NotSupported("Not supported in compacted db mode."); } virtual Status DisableFileDeletions() override { return Status::NotSupported("Not supported in compacted db mode."); } - virtual Status EnableFileDeletions(bool force) override { + virtual Status EnableFileDeletions(bool /*force*/) override { return Status::NotSupported("Not supported in compacted db mode."); } - virtual Status GetLiveFiles(std::vector&, + virtual Status GetLiveFiles(std::vector& ret, uint64_t* manifest_file_size, - bool flush_memtable = true) override { - return Status::NotSupported("Not supported in compacted db mode."); + bool /*flush_memtable*/) override { + return DBImpl::GetLiveFiles(ret, manifest_file_size, + false /* flush_memtable */); } using DBImpl::Flush; - virtual Status Flush(const FlushOptions& options, - ColumnFamilyHandle* column_family) override { + virtual Status Flush(const FlushOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/) override { return Status::NotSupported("Not supported in compacted db mode."); } using DB::IngestExternalFile; virtual Status IngestExternalFile( - ColumnFamilyHandle* column_family, - const std::vector& external_files, - const IngestExternalFileOptions& ingestion_options) override { + ColumnFamilyHandle* /*column_family*/, + const std::vector& /*external_files*/, + const IngestExternalFileOptions& /*ingestion_options*/) override { return Status::NotSupported("Not supported in compacted db mode."); } diff --git a/thirdparty/rocksdb/db/compaction.cc b/thirdparty/rocksdb/db/compaction.cc index 706eb3be03..f8805376f1 100644 --- a/thirdparty/rocksdb/db/compaction.cc +++ b/thirdparty/rocksdb/db/compaction.cc @@ -23,6 +23,43 @@ namespace rocksdb { +const uint64_t kRangeTombstoneSentinel = + PackSequenceAndType(kMaxSequenceNumber, kTypeRangeDeletion); + +int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a, + const InternalKey& b) { + auto c = user_cmp->Compare(a.user_key(), b.user_key()); + if (c != 0) { + return c; + } + auto a_footer = ExtractInternalKeyFooter(a.Encode()); + auto b_footer = ExtractInternalKeyFooter(b.Encode()); + if (a_footer == kRangeTombstoneSentinel) { + if (b_footer != kRangeTombstoneSentinel) { + return -1; + } + } else if (b_footer == kRangeTombstoneSentinel) { + return 1; + } + return 0; +} + +int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a, + const InternalKey& b) { + if (a == nullptr) { + return -1; + } + return sstableKeyCompare(user_cmp, *a, b); +} + +int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a, + const InternalKey* b) { + if (b == nullptr) { + return -1; + } + return sstableKeyCompare(user_cmp, a, *b); +} + uint64_t TotalFileSize(const std::vector& files) { uint64_t sum = 0; for (size_t i = 0; i < files.size() && files[i]; i++) { @@ -81,40 +118,71 @@ void Compaction::GetBoundaryKeys( } } +std::vector Compaction::PopulateWithAtomicBoundaries( + VersionStorageInfo* vstorage, std::vector inputs) { + const Comparator* ucmp = vstorage->InternalComparator()->user_comparator(); + for (size_t i = 0; i < inputs.size(); i++) { + if (inputs[i].level == 0 || inputs[i].files.empty()) { + continue; + } + inputs[i].atomic_compaction_unit_boundaries.reserve(inputs[i].files.size()); + AtomicCompactionUnitBoundary cur_boundary; + size_t first_atomic_idx = 0; + auto add_unit_boundary = [&](size_t to) { + if (first_atomic_idx == to) return; + for (size_t k = first_atomic_idx; k < to; k++) { + inputs[i].atomic_compaction_unit_boundaries.push_back(cur_boundary); + } + first_atomic_idx = to; + }; + for (size_t j = 0; j < inputs[i].files.size(); j++) { + const auto* f = inputs[i].files[j]; + if (j == 0) { + // First file in a level. + cur_boundary.smallest = &f->smallest; + cur_boundary.largest = &f->largest; + } else if (sstableKeyCompare(ucmp, *cur_boundary.largest, f->smallest) == + 0) { + // SSTs overlap but the end key of the previous file was not + // artificially extended by a range tombstone. Extend the current + // boundary. + cur_boundary.largest = &f->largest; + } else { + // Atomic compaction unit has ended. + add_unit_boundary(j); + cur_boundary.smallest = &f->smallest; + cur_boundary.largest = &f->largest; + } + } + add_unit_boundary(inputs[i].files.size()); + assert(inputs[i].files.size() == + inputs[i].atomic_compaction_unit_boundaries.size()); + } + return inputs; +} + // helper function to determine if compaction is creating files at the // bottommost level bool Compaction::IsBottommostLevel( int output_level, VersionStorageInfo* vstorage, const std::vector& inputs) { - if (inputs[0].level == 0 && - inputs[0].files.back() != vstorage->LevelFiles(0).back()) { - return false; + int output_l0_idx; + if (output_level == 0) { + output_l0_idx = 0; + for (const auto* file : vstorage->LevelFiles(0)) { + if (inputs[0].files.back() == file) { + break; + } + ++output_l0_idx; + } + assert(static_cast(output_l0_idx) < vstorage->LevelFiles(0).size()); + } else { + output_l0_idx = -1; } - Slice smallest_key, largest_key; GetBoundaryKeys(vstorage, inputs, &smallest_key, &largest_key); - - // Checks whether there are files living beyond the output_level. - // If lower levels have files, it checks for overlap between files - // if the compaction process and those files. - // Bottomlevel optimizations can be made if there are no files in - // lower levels or if there is no overlap with the files in - // the lower levels. - for (int i = output_level + 1; i < vstorage->num_levels(); i++) { - // It is not the bottommost level if there are files in higher - // levels when the output level is 0 or if there are files in - // higher levels which overlap with files to be compacted. - // output_level == 0 means that we want it to be considered - // s the bottommost level only if the last file on the level - // is a part of the files to be compacted - this is verified by - // the first if condition in this function - if (vstorage->NumLevelFiles(i) > 0 && - (output_level == 0 || - vstorage->OverlapInLevel(i, &smallest_key, &largest_key))) { - return false; - } - } - return true; + return !vstorage->RangeMightExistAfterSortedRun(smallest_key, largest_key, + output_level, output_l0_idx); } // test function to validate the functionality of IsBottommostLevel() @@ -146,6 +214,8 @@ Compaction::Compaction(VersionStorageInfo* vstorage, int _output_level, uint64_t _target_file_size, uint64_t _max_compaction_bytes, uint32_t _output_path_id, CompressionType _compression, + CompressionOptions _compression_opts, + uint32_t _max_subcompactions, std::vector _grandparents, bool _manual_compaction, double _score, bool _deletion_compaction, @@ -155,6 +225,7 @@ Compaction::Compaction(VersionStorageInfo* vstorage, output_level_(_output_level), max_output_file_size_(_target_file_size), max_compaction_bytes_(_max_compaction_bytes), + max_subcompactions_(_max_subcompactions), immutable_cf_options_(_immutable_cf_options), mutable_cf_options_(_mutable_cf_options), input_version_(nullptr), @@ -162,8 +233,9 @@ Compaction::Compaction(VersionStorageInfo* vstorage, cfd_(nullptr), output_path_id_(_output_path_id), output_compression_(_compression), + output_compression_opts_(_compression_opts), deletion_compaction_(_deletion_compaction), - inputs_(std::move(_inputs)), + inputs_(PopulateWithAtomicBoundaries(vstorage, std::move(_inputs))), grandparents_(std::move(_grandparents)), score_(_score), bottommost_level_(IsBottommostLevel(output_level_, vstorage, inputs_)), @@ -175,6 +247,15 @@ Compaction::Compaction(VersionStorageInfo* vstorage, if (is_manual_compaction_) { compaction_reason_ = CompactionReason::kManualCompaction; } + if (max_subcompactions_ == 0) { + max_subcompactions_ = immutable_cf_options_.max_subcompactions; + } + if (!bottommost_level_) { + // Currently we only enable dictionary compression during compaction to the + // bottommost level. + output_compression_opts_.max_dict_bytes = 0; + output_compression_opts_.zstd_max_train_bytes = 0; + } #ifndef NDEBUG for (size_t i = 1; i < inputs_.size(); ++i) { @@ -241,7 +322,7 @@ bool Compaction::IsTrivialMove() const { // Used in universal compaction, where trivial move can be done if the // input files are non overlapping - if ((immutable_cf_options_.compaction_options_universal.allow_trivial_move) && + if ((mutable_cf_options_.compaction_options_universal.allow_trivial_move) && (output_level_ != 0)) { return is_trivial_move_; } @@ -284,10 +365,10 @@ bool Compaction::KeyNotExistsBeyondOutputLevel( assert(input_version_ != nullptr); assert(level_ptrs != nullptr); assert(level_ptrs->size() == static_cast(number_levels_)); - if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) { - if (output_level_ == 0) { - return false; - } + if (bottommost_level_) { + return true; + } else if (output_level_ != 0 && + cfd_->ioptions()->compaction_style == kCompactionStyleLevel) { // Maybe use binary search to find right entry instead of linear search? const Comparator* user_cmp = cfd_->user_comparator(); for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) { @@ -298,8 +379,8 @@ bool Compaction::KeyNotExistsBeyondOutputLevel( if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) { // We've advanced far enough if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) { - // Key falls in this file's range, so definitely - // exists beyond output level + // Key falls in this file's range, so it may + // exist beyond output level return false; } break; @@ -307,9 +388,8 @@ bool Compaction::KeyNotExistsBeyondOutputLevel( } } return true; - } else { - return bottommost_level_; } + return false; } // Mark (or clear) each file that is being compacted @@ -337,12 +417,14 @@ const char* Compaction::InputLevelSummary( if (!is_first) { len += snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, " + "); + len = std::min(len, static_cast(sizeof(scratch->buffer))); } else { is_first = false; } len += snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, "%" ROCKSDB_PRIszt "@%d", input_level.size(), input_level.level); + len = std::min(len, static_cast(sizeof(scratch->buffer))); } snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, " files to L%d", output_level()); @@ -418,20 +500,23 @@ void Compaction::Summary(char* output, int len) { uint64_t Compaction::OutputFilePreallocationSize() const { uint64_t preallocation_size = 0; + for (const auto& level_files : inputs_) { + for (const auto& file : level_files.files) { + preallocation_size += file->fd.GetFileSize(); + } + } + if (max_output_file_size_ != port::kMaxUint64 && - (cfd_->ioptions()->compaction_style == kCompactionStyleLevel || + (immutable_cf_options_.compaction_style == kCompactionStyleLevel || output_level() > 0)) { - preallocation_size = max_output_file_size_; - } else { - for (const auto& level_files : inputs_) { - for (const auto& file : level_files.files) { - preallocation_size += file->fd.GetFileSize(); - } - } + preallocation_size = std::min(max_output_file_size_, preallocation_size); } + // Over-estimate slightly so we don't end up just barely crossing // the threshold - return preallocation_size + (preallocation_size / 10); + // No point to prellocate more than 1GB. + return std::min(uint64_t{1073741824}, + preallocation_size + (preallocation_size / 10)); } std::unique_ptr Compaction::CreateCompactionFilter() const { @@ -452,11 +537,12 @@ bool Compaction::IsOutputLevelEmpty() const { } bool Compaction::ShouldFormSubcompactions() const { - if (immutable_cf_options_.max_subcompactions <= 1 || cfd_ == nullptr) { + if (max_subcompactions_ <= 1 || cfd_ == nullptr) { return false; } if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) { - return start_level_ == 0 && output_level_ > 0 && !IsOutputLevelEmpty(); + return (start_level_ == 0 || is_manual_compaction_) && output_level_ > 0 && + !IsOutputLevelEmpty(); } else if (cfd_->ioptions()->compaction_style == kCompactionStyleUniversal) { return number_levels_ > 1 && output_level_ > 0; } else { @@ -477,4 +563,8 @@ uint64_t Compaction::MaxInputFileCreationTime() const { return max_creation_time; } +int Compaction::GetInputBaseLevel() const { + return input_vstorage_->base_level(); +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/compaction.h b/thirdparty/rocksdb/db/compaction.h index 7be6df2c1e..2cf737b676 100644 --- a/thirdparty/rocksdb/db/compaction.h +++ b/thirdparty/rocksdb/db/compaction.h @@ -15,11 +15,43 @@ namespace rocksdb { +// Utility for comparing sstable boundary keys. Returns -1 if either a or b is +// null which provides the property that a==null indicates a key that is less +// than any key and b==null indicates a key that is greater than any key. Note +// that the comparison is performed primarily on the user-key portion of the +// key. If the user-keys compare equal, an additional test is made to sort +// range tombstone sentinel keys before other keys with the same user-key. The +// result is that 2 user-keys will compare equal if they differ purely on +// their sequence number and value, but the range tombstone sentinel for that +// user-key will compare not equal. This is necessary because the range +// tombstone sentinel key is set as the largest key for an sstable even though +// that key never appears in the database. We don't want adjacent sstables to +// be considered overlapping if they are separated by the range tombstone +// sentinel. +int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a, + const InternalKey& b); +int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a, + const InternalKey& b); +int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a, + const InternalKey* b); + +// An AtomicCompactionUnitBoundary represents a range of keys [smallest, +// largest] that exactly spans one ore more neighbouring SSTs on the same +// level. Every pair of SSTs in this range "overlap" (i.e., the largest +// user key of one file is the smallest user key of the next file). These +// boundaries are propagated down to RangeDelAggregator during compaction +// to provide safe truncation boundaries for range tombstones. +struct AtomicCompactionUnitBoundary { + const InternalKey* smallest = nullptr; + const InternalKey* largest = nullptr; +}; + // The structure that manages compaction input files associated // with the same physical level. struct CompactionInputFiles { int level; std::vector files; + std::vector atomic_compaction_unit_boundaries; inline bool empty() const { return files.empty(); } inline size_t size() const { return files.size(); } inline void clear() { files.clear(); } @@ -40,6 +72,7 @@ class Compaction { std::vector inputs, int output_level, uint64_t target_file_size, uint64_t max_compaction_bytes, uint32_t output_path_id, CompressionType compression, + CompressionOptions compression_opts, uint32_t max_subcompactions, std::vector grandparents, bool manual_compaction = false, double score = -1, bool deletion_compaction = false, @@ -95,6 +128,12 @@ class Compaction { return inputs_[compaction_input_level][i]; } + const std::vector* boundaries( + size_t compaction_input_level) const { + assert(compaction_input_level < inputs_.size()); + return &inputs_[compaction_input_level].atomic_compaction_unit_boundaries; + } + // Returns the list of file meta data of the specified compaction // input level. // REQUIREMENT: "compaction_input_level" must be >= 0 and @@ -118,6 +157,11 @@ class Compaction { // What compression for output CompressionType output_compression() const { return output_compression_; } + // What compression options for output + CompressionOptions output_compression_opts() const { + return output_compression_opts_; + } + // Whether need to write output file to second DB path. uint32_t output_path_id() const { return output_path_id_; } @@ -233,6 +277,8 @@ class Compaction { Slice GetLargestUserKey() const { return largest_user_key_; } + int GetInputBaseLevel() const; + CompactionReason compaction_reason() { return compaction_reason_; } const std::vector& grandparents() const { @@ -241,6 +287,8 @@ class Compaction { uint64_t max_compaction_bytes() const { return max_compaction_bytes_; } + uint32_t max_subcompactions() const { return max_subcompactions_; } + uint64_t MaxInputFileCreationTime() const; private: @@ -252,6 +300,13 @@ class Compaction { const std::vector& inputs, Slice* smallest_key, Slice* largest_key); + // Get the atomic file boundaries for all files in the compaction. Necessary + // in order to avoid the scenario described in + // https://github.com/facebook/rocksdb/pull/4432#discussion_r221072219 and plumb + // down appropriate key boundaries to RangeDelAggregator during compaction. + static std::vector PopulateWithAtomicBoundaries( + VersionStorageInfo* vstorage, std::vector inputs); + // helper function to determine if compaction with inputs and storage is // bottommost static bool IsBottommostLevel( @@ -267,6 +322,7 @@ class Compaction { const int output_level_; // levels to which output files are stored uint64_t max_output_file_size_; uint64_t max_compaction_bytes_; + uint32_t max_subcompactions_; const ImmutableCFOptions immutable_cf_options_; const MutableCFOptions mutable_cf_options_; Version* input_version_; @@ -277,6 +333,7 @@ class Compaction { const uint32_t output_path_id_; CompressionType output_compression_; + CompressionOptions output_compression_opts_; // If true, then the comaction can be done by simply deleting input files. const bool deletion_compaction_; diff --git a/thirdparty/rocksdb/db/compaction_iterator.cc b/thirdparty/rocksdb/db/compaction_iterator.cc index ae63f04d83..93c2b5fa9e 100644 --- a/thirdparty/rocksdb/db/compaction_iterator.cc +++ b/thirdparty/rocksdb/db/compaction_iterator.cc @@ -4,102 +4,105 @@ // (found in the LICENSE.Apache file in the root directory). #include "db/compaction_iterator.h" + +#include "db/snapshot_checker.h" +#include "port/likely.h" #include "rocksdb/listener.h" #include "table/internal_iterator.h" +#include "util/sync_point.h" -namespace rocksdb { +#define DEFINITELY_IN_SNAPSHOT(seq, snapshot) \ + ((seq) <= (snapshot) && \ + (snapshot_checker_ == nullptr || \ + LIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \ + SnapshotCheckerResult::kInSnapshot))) -#ifndef ROCKSDB_LITE -CompactionEventListener::CompactionListenerValueType fromInternalValueType( - ValueType vt) { - switch (vt) { - case kTypeDeletion: - return CompactionEventListener::CompactionListenerValueType::kDelete; - case kTypeValue: - return CompactionEventListener::CompactionListenerValueType::kValue; - case kTypeMerge: - return CompactionEventListener::CompactionListenerValueType:: - kMergeOperand; - case kTypeSingleDeletion: - return CompactionEventListener::CompactionListenerValueType:: - kSingleDelete; - case kTypeRangeDeletion: - return CompactionEventListener::CompactionListenerValueType::kRangeDelete; - case kTypeBlobIndex: - return CompactionEventListener::CompactionListenerValueType::kBlobIndex; - default: - assert(false); - return CompactionEventListener::CompactionListenerValueType::kInvalid; - } -} -#endif // ROCKSDB_LITE +#define DEFINITELY_NOT_IN_SNAPSHOT(seq, snapshot) \ + ((seq) > (snapshot) || \ + (snapshot_checker_ != nullptr && \ + UNLIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \ + SnapshotCheckerResult::kNotInSnapshot))) + +#define IN_EARLIEST_SNAPSHOT(seq) \ + ((seq) <= earliest_snapshot_ && \ + (snapshot_checker_ == nullptr || LIKELY(IsInEarliestSnapshot(seq)))) + +namespace rocksdb { CompactionIterator::CompactionIterator( InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper, SequenceNumber last_sequence, std::vector* snapshots, - SequenceNumber earliest_write_conflict_snapshot, Env* env, - bool expect_valid_internal_key, RangeDelAggregator* range_del_agg, - const Compaction* compaction, const CompactionFilter* compaction_filter, - CompactionEventListener* compaction_listener, - const std::atomic* shutting_down) + SequenceNumber earliest_write_conflict_snapshot, + const SnapshotChecker* snapshot_checker, Env* env, + bool report_detailed_time, bool expect_valid_internal_key, + CompactionRangeDelAggregator* range_del_agg, const Compaction* compaction, + const CompactionFilter* compaction_filter, + const std::atomic* shutting_down, + const SequenceNumber preserve_deletes_seqnum) : CompactionIterator( input, cmp, merge_helper, last_sequence, snapshots, - earliest_write_conflict_snapshot, env, expect_valid_internal_key, - range_del_agg, + earliest_write_conflict_snapshot, snapshot_checker, env, + report_detailed_time, expect_valid_internal_key, range_del_agg, std::unique_ptr( compaction ? new CompactionProxy(compaction) : nullptr), - compaction_filter, compaction_listener, shutting_down) {} + compaction_filter, shutting_down, preserve_deletes_seqnum) {} CompactionIterator::CompactionIterator( InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper, - SequenceNumber last_sequence, std::vector* snapshots, - SequenceNumber earliest_write_conflict_snapshot, Env* env, - bool expect_valid_internal_key, RangeDelAggregator* range_del_agg, + SequenceNumber /*last_sequence*/, std::vector* snapshots, + SequenceNumber earliest_write_conflict_snapshot, + const SnapshotChecker* snapshot_checker, Env* env, + bool report_detailed_time, bool expect_valid_internal_key, + CompactionRangeDelAggregator* range_del_agg, std::unique_ptr compaction, const CompactionFilter* compaction_filter, - CompactionEventListener* compaction_listener, - const std::atomic* shutting_down) + const std::atomic* shutting_down, + const SequenceNumber preserve_deletes_seqnum) : input_(input), cmp_(cmp), merge_helper_(merge_helper), snapshots_(snapshots), earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot), + snapshot_checker_(snapshot_checker), env_(env), + report_detailed_time_(report_detailed_time), expect_valid_internal_key_(expect_valid_internal_key), range_del_agg_(range_del_agg), compaction_(std::move(compaction)), compaction_filter_(compaction_filter), -#ifndef ROCKSDB_LITE - compaction_listener_(compaction_listener), -#endif // ROCKSDB_LITE shutting_down_(shutting_down), - ignore_snapshots_(false), - merge_out_iter_(merge_helper_) { + preserve_deletes_seqnum_(preserve_deletes_seqnum), + current_user_key_sequence_(0), + current_user_key_snapshot_(0), + merge_out_iter_(merge_helper_), + current_key_committed_(false) { assert(compaction_filter_ == nullptr || compaction_ != nullptr); + assert(snapshots_ != nullptr); bottommost_level_ = compaction_ == nullptr ? false : compaction_->bottommost_level(); if (compaction_ != nullptr) { level_ptrs_ = std::vector(compaction_->number_levels(), 0); } - if (snapshots_->size() == 0) { // optimize for fast path if there are no snapshots visible_at_tip_ = true; + earliest_snapshot_iter_ = snapshots_->end(); earliest_snapshot_ = kMaxSequenceNumber; latest_snapshot_ = 0; } else { visible_at_tip_ = false; + earliest_snapshot_iter_ = snapshots_->begin(); earliest_snapshot_ = snapshots_->at(0); latest_snapshot_ = snapshots_->back(); } - if (compaction_filter_ != nullptr) { - if (compaction_filter_->IgnoreSnapshots()) { - ignore_snapshots_ = true; - } - } else { - ignore_snapshots_ = false; +#ifndef NDEBUG + // findEarliestVisibleSnapshot assumes this ordering. + for (size_t i = 1; i < snapshots_->size(); ++i) { + assert(snapshots_->at(i - 1) < snapshots_->at(i)); } +#endif input_->SetPinnedItersMgr(&pinned_iters_mgr_); + TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get()); } CompactionIterator::~CompactionIterator() { @@ -131,8 +134,8 @@ void CompactionIterator::Next() { if (merge_out_iter_.Valid()) { key_ = merge_out_iter_.key(); value_ = merge_out_iter_.value(); - bool valid_key __attribute__((__unused__)) = - ParseInternalKey(key_, &ikey_); + bool valid_key __attribute__((__unused__)); + valid_key = ParseInternalKey(key_, &ikey_); // MergeUntil stops when it encounters a corrupt key and does not // include them in the result, so we expect the keys here to be valid. assert(valid_key); @@ -166,6 +169,59 @@ void CompactionIterator::Next() { PrepareOutput(); } +void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip, + Slice* skip_until) { + if (compaction_filter_ != nullptr && + (ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex)) { + // If the user has specified a compaction filter and the sequence + // number is greater than any external snapshot, then invoke the + // filter. If the return value of the compaction filter is true, + // replace the entry with a deletion marker. + CompactionFilter::Decision filter; + compaction_filter_value_.clear(); + compaction_filter_skip_until_.Clear(); + CompactionFilter::ValueType value_type = + ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue + : CompactionFilter::ValueType::kBlobIndex; + // Hack: pass internal key to BlobIndexCompactionFilter since it needs + // to get sequence number. + Slice& filter_key = ikey_.type == kTypeValue ? ikey_.user_key : key_; + { + StopWatchNano timer(env_, report_detailed_time_); + filter = compaction_filter_->FilterV2( + compaction_->level(), filter_key, value_type, value_, + &compaction_filter_value_, compaction_filter_skip_until_.rep()); + iter_stats_.total_filter_time += + env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0; + } + + if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil && + cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <= + 0) { + // Can't skip to a key smaller than the current one. + // Keep the key as per FilterV2 documentation. + filter = CompactionFilter::Decision::kKeep; + } + + if (filter == CompactionFilter::Decision::kRemove) { + // convert the current key to a delete; key_ is pointing into + // current_key_ at this point, so updating current_key_ updates key() + ikey_.type = kTypeDeletion; + current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion); + // no value associated with delete + value_.clear(); + iter_stats_.num_record_drop_user++; + } else if (filter == CompactionFilter::Decision::kChangeValue) { + value_ = compaction_filter_value_; + } else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) { + *need_skip = true; + compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber, + kValueTypeForSeek); + *skip_until = compaction_filter_skip_until_.Encode(); + } + } +} + void CompactionIterator::NextFromInput() { at_next_ = false; valid_ = false; @@ -192,6 +248,7 @@ void CompactionIterator::NextFromInput() { valid_ = true; break; } + TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_); // Update input statistics if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) { @@ -220,73 +277,14 @@ void CompactionIterator::NextFromInput() { has_outputted_key_ = false; current_user_key_sequence_ = kMaxSequenceNumber; current_user_key_snapshot_ = 0; + current_key_committed_ = KeyCommitted(ikey_.sequence); -#ifndef ROCKSDB_LITE - if (compaction_listener_) { - compaction_listener_->OnCompaction(compaction_->level(), ikey_.user_key, - fromInternalValueType(ikey_.type), - value_, ikey_.sequence, true); - } -#endif // ROCKSDB_LITE - - // apply the compaction filter to the first occurrence of the user key - if (compaction_filter_ != nullptr && - (ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex) && - (visible_at_tip_ || ikey_.sequence > latest_snapshot_ || - ignore_snapshots_)) { - // If the user has specified a compaction filter and the sequence - // number is greater than any external snapshot, then invoke the - // filter. If the return value of the compaction filter is true, - // replace the entry with a deletion marker. - CompactionFilter::Decision filter; - compaction_filter_value_.clear(); - compaction_filter_skip_until_.Clear(); - CompactionFilter::ValueType value_type = - ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue - : CompactionFilter::ValueType::kBlobIndex; - { - StopWatchNano timer(env_, true); - filter = compaction_filter_->FilterV2( - compaction_->level(), ikey_.user_key, value_type, value_, - &compaction_filter_value_, compaction_filter_skip_until_.rep()); - iter_stats_.total_filter_time += - env_ != nullptr ? timer.ElapsedNanos() : 0; - } - - if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil && - cmp_->Compare(*compaction_filter_skip_until_.rep(), - ikey_.user_key) <= 0) { - // Can't skip to a key smaller than the current one. - // Keep the key as per FilterV2 documentation. - filter = CompactionFilter::Decision::kKeep; - } - - if (filter == CompactionFilter::Decision::kRemove) { - // convert the current key to a delete; key_ is pointing into - // current_key_ at this point, so updating current_key_ updates key() - ikey_.type = kTypeDeletion; - current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion); - // no value associated with delete - value_.clear(); - iter_stats_.num_record_drop_user++; - } else if (filter == CompactionFilter::Decision::kChangeValue) { - value_ = compaction_filter_value_; - } else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) { - need_skip = true; - compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber, - kValueTypeForSeek); - skip_until = compaction_filter_skip_until_.Encode(); - } + // Apply the compaction filter to the first committed version of the user + // key. + if (current_key_committed_) { + InvokeFilterIfNeeded(&need_skip, &skip_until); } } else { -#ifndef ROCKSDB_LITE - if (compaction_listener_) { - compaction_listener_->OnCompaction(compaction_->level(), ikey_.user_key, - fromInternalValueType(ikey_.type), - value_, ikey_.sequence, false); - } -#endif // ROCKSDB_LITE - // Update the current key to reflect the new sequence number/type without // copying the user key. // TODO(rven): Compaction filter does not process keys in this path @@ -295,13 +293,32 @@ void CompactionIterator::NextFromInput() { current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type); key_ = current_key_.GetInternalKey(); ikey_.user_key = current_key_.GetUserKey(); + + // Note that newer version of a key is ordered before older versions. If a + // newer version of a key is committed, so as the older version. No need + // to query snapshot_checker_ in that case. + if (UNLIKELY(!current_key_committed_)) { + assert(snapshot_checker_ != nullptr); + current_key_committed_ = KeyCommitted(ikey_.sequence); + // Apply the compaction filter to the first committed version of the + // user key. + if (current_key_committed_) { + InvokeFilterIfNeeded(&need_skip, &skip_until); + } + } + } + + if (UNLIKELY(!current_key_committed_)) { + assert(snapshot_checker_ != nullptr); + valid_ = true; + break; } // If there are no snapshots, then this kv affect visibility at tip. // Otherwise, search though all existing snapshots to find the earliest // snapshot that is affected by this kv. - SequenceNumber last_sequence __attribute__((__unused__)) = - current_user_key_sequence_; + SequenceNumber last_sequence __attribute__((__unused__)); + last_sequence = current_user_key_sequence_; current_user_key_sequence_ = ikey_.sequence; SequenceNumber last_snapshot = current_user_key_snapshot_; SequenceNumber prev_snapshot = 0; // 0 means no previous snapshot @@ -366,7 +383,8 @@ void CompactionIterator::NextFromInput() { cmp_->Equal(ikey_.user_key, next_ikey.user_key)) { // Check whether the next key belongs to the same snapshot as the // SingleDelete. - if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot) { + if (prev_snapshot == 0 || + DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot)) { if (next_ikey.type == kTypeSingleDeletion) { // We encountered two SingleDeletes in a row. This could be due to // unexpected user input. @@ -377,8 +395,9 @@ void CompactionIterator::NextFromInput() { // input_->Next(). ++iter_stats_.num_record_drop_obsolete; ++iter_stats_.num_single_del_mismatch; - } else if ((ikey_.sequence <= earliest_write_conflict_snapshot_) || - has_outputted_key_) { + } else if (has_outputted_key_ || + DEFINITELY_IN_SNAPSHOT( + ikey_.sequence, earliest_write_conflict_snapshot_)) { // Found a matching value, we can drop the single delete and the // value. It is safe to drop both records since we've already // outputted a key in this snapshot, or there is no earlier @@ -388,7 +407,8 @@ void CompactionIterator::NextFromInput() { // is an unexpected Merge or Delete. We will compact it out // either way. We will maintain counts of how many mismatches // happened - if (next_ikey.type != kTypeValue) { + if (next_ikey.type != kTypeValue && + next_ikey.type != kTypeBlobIndex) { ++iter_stats_.num_single_del_mismatch; } @@ -425,7 +445,7 @@ void CompactionIterator::NextFromInput() { // iteration. If the next key is corrupt, we return before the // comparison, so the value of has_current_user_key does not matter. has_current_user_key_ = false; - if (compaction_ != nullptr && ikey_.sequence <= earliest_snapshot_ && + if (compaction_ != nullptr && IN_EARLIEST_SNAPSHOT(ikey_.sequence) && compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key, &level_ptrs_)) { // Key doesn't exist outside of this range. @@ -444,21 +464,38 @@ void CompactionIterator::NextFromInput() { if (valid_) { at_next_ = true; } - } else if (last_snapshot == current_user_key_snapshot_) { + } else if (last_snapshot == current_user_key_snapshot_ || + (last_snapshot > 0 && + last_snapshot < current_user_key_snapshot_)) { // If the earliest snapshot is which this key is visible in // is the same as the visibility of a previous instance of the // same key, then this kv is not visible in any snapshot. // Hidden by an newer entry for same user key - // TODO(noetzli): why not > ? // // Note: Dropping this key will not affect TransactionDB write-conflict // checking since there has already been a record returned for this key // in this snapshot. assert(last_sequence >= current_user_key_sequence_); + + // Note2: if last_snapshot < current_user_key_snapshot, it can only + // mean last_snapshot is released between we process last value and + // this value, and findEarliestVisibleSnapshot returns the next snapshot + // as current_user_key_snapshot. In this case last value and current + // value are both in current_user_key_snapshot currently. + // Although last_snapshot is released we might still get a definitive + // response when key sequence number changes, e.g., when seq is determined + // too old and visible in all snapshots. + assert(last_snapshot == current_user_key_snapshot_ || + (snapshot_checker_ != nullptr && + snapshot_checker_->CheckInSnapshot(current_user_key_sequence_, + last_snapshot) != + SnapshotCheckerResult::kNotInSnapshot)); + ++iter_stats_.num_record_drop_hidden; // (A) input_->Next(); } else if (compaction_ != nullptr && ikey_.type == kTypeDeletion && - ikey_.sequence <= earliest_snapshot_ && + IN_EARLIEST_SNAPSHOT(ikey_.sequence) && + ikeyNotNeededForIncrementalSnapshot() && compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key, &level_ptrs_)) { // TODO(noetzli): This is the only place where we use compaction_ @@ -475,11 +512,38 @@ void CompactionIterator::NextFromInput() { // // Note: Dropping this Delete will not affect TransactionDB // write-conflict checking since it is earlier than any snapshot. + // + // It seems that we can also drop deletion later than earliest snapshot + // given that: + // (1) The deletion is earlier than earliest_write_conflict_snapshot, and + // (2) No value exist earlier than the deletion. ++iter_stats_.num_record_drop_obsolete; if (!bottommost_level_) { ++iter_stats_.num_optimized_del_drop_obsolete; } input_->Next(); + } else if ((ikey_.type == kTypeDeletion) && bottommost_level_ && + ikeyNotNeededForIncrementalSnapshot()) { + // Handle the case where we have a delete key at the bottom most level + // We can skip outputting the key iff there are no subsequent puts for this + // key + ParsedInternalKey next_ikey; + input_->Next(); + // Skip over all versions of this key that happen to occur in the same snapshot + // range as the delete + while (input_->Valid() && ParseInternalKey(input_->key(), &next_ikey) && + cmp_->Equal(ikey_.user_key, next_ikey.user_key) && + (prev_snapshot == 0 || + DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot))) { + input_->Next(); + } + // If you find you still need to output a row with this key, we need to output the + // delete too + if (input_->Valid() && ParseInternalKey(input_->key(), &next_ikey) && + cmp_->Equal(ikey_.user_key, next_ikey.user_key)) { + valid_ = true; + at_next_ = true; + } } else if (ikey_.type == kTypeMerge) { if (!merge_helper_->HasOperator()) { status_ = Status::InvalidArgument( @@ -504,8 +568,8 @@ void CompactionIterator::NextFromInput() { // These will be correctly set below. key_ = merge_out_iter_.key(); value_ = merge_out_iter_.value(); - bool valid_key __attribute__((__unused__)) = - ParseInternalKey(key_, &ikey_); + bool valid_key __attribute__((__unused__)); + valid_key = ParseInternalKey(key_, &ikey_); // MergeUntil stops when it encounters a corrupt key and does not // include them in the result, so we expect the keys here to valid. assert(valid_key); @@ -529,7 +593,7 @@ void CompactionIterator::NextFromInput() { // 1. new user key -OR- // 2. different snapshot stripe bool should_delete = range_del_agg_->ShouldDelete( - key_, RangeDelAggregator::RangePositioningMode::kForwardTraversal); + key_, RangeDelPositioningMode::kForwardTraversal); if (should_delete) { ++iter_stats_.num_record_drop_hidden; ++iter_stats_.num_record_drop_range_del; @@ -555,13 +619,15 @@ void CompactionIterator::PrepareOutput() { // and the earliest snapshot is larger than this seqno // and the userkey differs from the last userkey in compaction // then we can squash the seqno to zero. - + // // This is safe for TransactionDB write-conflict checking since transactions // only care about sequence number larger than any active snapshots. + // + // Can we do the same for levels above bottom level as long as + // KeyNotExistsBeyondOutputLevel() return true? if ((compaction_ != nullptr && !compaction_->allow_ingest_behind()) && - bottommost_level_ && valid_ && ikey_.sequence <= earliest_snapshot_ && - ikey_.type != kTypeMerge && - !cmp_->Equal(compaction_->GetLargestUserKey(), ikey_.user_key)) { + ikeyNotNeededForIncrementalSnapshot() && bottommost_level_ && valid_ && + IN_EARLIEST_SNAPSHOT(ikey_.sequence) && ikey_.type != kTypeMerge) { assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion); ikey_.sequence = 0; current_key_.UpdateInternalKey(0, ikey_.type); @@ -571,18 +637,68 @@ void CompactionIterator::PrepareOutput() { inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot( SequenceNumber in, SequenceNumber* prev_snapshot) { assert(snapshots_->size()); - SequenceNumber prev __attribute__((__unused__)) = kMaxSequenceNumber; - for (const auto cur : *snapshots_) { - assert(prev == kMaxSequenceNumber || prev <= cur); - if (cur >= in) { - *prev_snapshot = prev == kMaxSequenceNumber ? 0 : prev; + auto snapshots_iter = std::lower_bound( + snapshots_->begin(), snapshots_->end(), in); + if (snapshots_iter == snapshots_->begin()) { + *prev_snapshot = 0; + } else { + *prev_snapshot = *std::prev(snapshots_iter); + assert(*prev_snapshot < in); + } + if (snapshot_checker_ == nullptr) { + return snapshots_iter != snapshots_->end() + ? *snapshots_iter : kMaxSequenceNumber; + } + bool has_released_snapshot = !released_snapshots_.empty(); + for (; snapshots_iter != snapshots_->end(); ++snapshots_iter) { + auto cur = *snapshots_iter; + assert(in <= cur); + // Skip if cur is in released_snapshots. + if (has_released_snapshot && released_snapshots_.count(cur) > 0) { + continue; + } + auto res = snapshot_checker_->CheckInSnapshot(in, cur); + if (res == SnapshotCheckerResult::kInSnapshot) { return cur; + } else if (res == SnapshotCheckerResult::kSnapshotReleased) { + released_snapshots_.insert(cur); } - prev = cur; - assert(prev < kMaxSequenceNumber); + *prev_snapshot = cur; } - *prev_snapshot = prev; return kMaxSequenceNumber; } +// used in 2 places - prevents deletion markers to be dropped if they may be +// needed and disables seqnum zero-out in PrepareOutput for recent keys. +inline bool CompactionIterator::ikeyNotNeededForIncrementalSnapshot() { + return (!compaction_->preserve_deletes()) || + (ikey_.sequence < preserve_deletes_seqnum_); +} + +bool CompactionIterator::IsInEarliestSnapshot(SequenceNumber sequence) { + assert(snapshot_checker_ != nullptr); + assert(earliest_snapshot_ == kMaxSequenceNumber || + (earliest_snapshot_iter_ != snapshots_->end() && + *earliest_snapshot_iter_ == earliest_snapshot_)); + auto in_snapshot = + snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_); + while (UNLIKELY(in_snapshot == SnapshotCheckerResult::kSnapshotReleased)) { + // Avoid the the current earliest_snapshot_ being return as + // earliest visible snapshot for the next value. So if a value's sequence + // is zero-ed out by PrepareOutput(), the next value will be compact out. + released_snapshots_.insert(earliest_snapshot_); + earliest_snapshot_iter_++; + + if (earliest_snapshot_iter_ == snapshots_->end()) { + earliest_snapshot_ = kMaxSequenceNumber; + } else { + earliest_snapshot_ = *earliest_snapshot_iter_; + } + in_snapshot = + snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_); + } + assert(in_snapshot != SnapshotCheckerResult::kSnapshotReleased); + return in_snapshot == SnapshotCheckerResult::kInSnapshot; +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/compaction_iterator.h b/thirdparty/rocksdb/db/compaction_iterator.h index cad2386669..a9e7a26207 100644 --- a/thirdparty/rocksdb/db/compaction_iterator.h +++ b/thirdparty/rocksdb/db/compaction_iterator.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "db/compaction.h" @@ -14,13 +15,12 @@ #include "db/merge_helper.h" #include "db/pinned_iterators_manager.h" #include "db/range_del_aggregator.h" +#include "db/snapshot_checker.h" #include "options/cf_options.h" #include "rocksdb/compaction_filter.h" namespace rocksdb { -class CompactionEventListener; - class CompactionIterator { public: // A wrapper around Compaction. Has a much smaller interface, only what @@ -31,7 +31,7 @@ class CompactionIterator { : compaction_(compaction) {} virtual ~CompactionProxy() = default; - virtual int level(size_t compaction_input_level = 0) const { + virtual int level(size_t /*compaction_input_level*/ = 0) const { return compaction_->level(); } virtual bool KeyNotExistsBeyondOutputLevel( @@ -48,6 +48,9 @@ class CompactionIterator { virtual bool allow_ingest_behind() const { return compaction_->immutable_cf_options()->allow_ingest_behind; } + virtual bool preserve_deletes() const { + return compaction_->immutable_cf_options()->preserve_deletes; + } protected: CompactionProxy() = default; @@ -59,25 +62,27 @@ class CompactionIterator { CompactionIterator(InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper, SequenceNumber last_sequence, std::vector* snapshots, - SequenceNumber earliest_write_conflict_snapshot, Env* env, - bool expect_valid_internal_key, - RangeDelAggregator* range_del_agg, + SequenceNumber earliest_write_conflict_snapshot, + const SnapshotChecker* snapshot_checker, Env* env, + bool report_detailed_time, bool expect_valid_internal_key, + CompactionRangeDelAggregator* range_del_agg, const Compaction* compaction = nullptr, const CompactionFilter* compaction_filter = nullptr, - CompactionEventListener* compaction_listener = nullptr, - const std::atomic* shutting_down = nullptr); + const std::atomic* shutting_down = nullptr, + const SequenceNumber preserve_deletes_seqnum = 0); // Constructor with custom CompactionProxy, used for tests. CompactionIterator(InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper, SequenceNumber last_sequence, std::vector* snapshots, - SequenceNumber earliest_write_conflict_snapshot, Env* env, - bool expect_valid_internal_key, - RangeDelAggregator* range_del_agg, + SequenceNumber earliest_write_conflict_snapshot, + const SnapshotChecker* snapshot_checker, Env* env, + bool report_detailed_time, bool expect_valid_internal_key, + CompactionRangeDelAggregator* range_del_agg, std::unique_ptr compaction, const CompactionFilter* compaction_filter = nullptr, - CompactionEventListener* compaction_listener = nullptr, - const std::atomic* shutting_down = nullptr); + const std::atomic* shutting_down = nullptr, + const SequenceNumber preserve_deletes_seqnum = 0); ~CompactionIterator(); @@ -111,6 +116,9 @@ class CompactionIterator { // compression. void PrepareOutput(); + // Invoke compaction filter if needed. + void InvokeFilterIfNeeded(bool* need_skip, Slice* skip_until); + // Given a sequence number, return the sequence number of the // earliest snapshot that this sequence number is visible in. // The snapshots themselves are arranged in ascending order of @@ -120,26 +128,45 @@ class CompactionIterator { inline SequenceNumber findEarliestVisibleSnapshot( SequenceNumber in, SequenceNumber* prev_snapshot); + // Checks whether the currently seen ikey_ is needed for + // incremental (differential) snapshot and hence can't be dropped + // or seqnum be zero-ed out even if all other conditions for it are met. + inline bool ikeyNotNeededForIncrementalSnapshot(); + + inline bool KeyCommitted(SequenceNumber sequence) { + return snapshot_checker_ == nullptr || + snapshot_checker_->CheckInSnapshot(sequence, kMaxSequenceNumber) == + SnapshotCheckerResult::kInSnapshot; + } + + bool IsInEarliestSnapshot(SequenceNumber sequence); + InternalIterator* input_; const Comparator* cmp_; MergeHelper* merge_helper_; const std::vector* snapshots_; + // List of snapshots released during compaction. + // findEarliestVisibleSnapshot() find them out from return of + // snapshot_checker, and make sure they will not be returned as + // earliest visible snapshot of an older value. + // See WritePreparedTransactionTest::ReleaseSnapshotDuringCompaction3. + std::unordered_set released_snapshots_; + std::vector::const_iterator earliest_snapshot_iter_; const SequenceNumber earliest_write_conflict_snapshot_; + const SnapshotChecker* const snapshot_checker_; Env* env_; + bool report_detailed_time_; bool expect_valid_internal_key_; - RangeDelAggregator* range_del_agg_; + CompactionRangeDelAggregator* range_del_agg_; std::unique_ptr compaction_; const CompactionFilter* compaction_filter_; -#ifndef ROCKSDB_LITE - CompactionEventListener* compaction_listener_; -#endif // ROCKSDB_LITE const std::atomic* shutting_down_; + const SequenceNumber preserve_deletes_seqnum_; bool bottommost_level_; bool valid_ = false; bool visible_at_tip_; SequenceNumber earliest_snapshot_; SequenceNumber latest_snapshot_; - bool ignore_snapshots_; // State // @@ -189,6 +216,10 @@ class CompactionIterator { std::vector level_ptrs_; CompactionIterationStats iter_stats_; + // Used to avoid purging uncommitted values. The application can specify + // uncommitted values by providing a SnapshotChecker object. + bool current_key_committed_; + bool IsShuttingDown() { // This is a best-effort facility, so memory_order_relaxed is sufficient. return shutting_down_ && shutting_down_->load(std::memory_order_relaxed); diff --git a/thirdparty/rocksdb/db/compaction_iterator_test.cc b/thirdparty/rocksdb/db/compaction_iterator_test.cc index dfc4139363..c466f6c912 100644 --- a/thirdparty/rocksdb/db/compaction_iterator_test.cc +++ b/thirdparty/rocksdb/db/compaction_iterator_test.cc @@ -9,23 +9,25 @@ #include #include "port/port.h" +#include "util/string_util.h" #include "util/testharness.h" #include "util/testutil.h" +#include "utilities/merge_operators.h" namespace rocksdb { // Expects no merging attempts. class NoMergingMergeOp : public MergeOperator { public: - bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { + bool FullMergeV2(const MergeOperationInput& /*merge_in*/, + MergeOperationOutput* /*merge_out*/) const override { ADD_FAILURE(); return false; } - bool PartialMergeMulti(const Slice& key, - const std::deque& operand_list, - std::string* new_value, - Logger* logger) const override { + bool PartialMergeMulti(const Slice& /*key*/, + const std::deque& /*operand_list*/, + std::string* /*new_value*/, + Logger* /*logger*/) const override { ADD_FAILURE(); return false; } @@ -39,9 +41,9 @@ class NoMergingMergeOp : public MergeOperator { // Always returns Decition::kRemove. class StallingFilter : public CompactionFilter { public: - virtual Decision FilterV2(int level, const Slice& key, ValueType t, - const Slice& existing_value, std::string* new_value, - std::string* skip_until) const override { + Decision FilterV2(int /*level*/, const Slice& key, ValueType /*type*/, + const Slice& /*existing_value*/, std::string* /*new_value*/, + std::string* /*skip_until*/) const override { int k = std::atoi(key.ToString().c_str()); last_seen.store(k); while (k >= stall_at.load()) { @@ -72,6 +74,18 @@ class StallingFilter : public CompactionFilter { mutable std::atomic last_seen{0}; }; +// Compaction filter that filter out all keys. +class FilterAllKeysCompactionFilter : public CompactionFilter { + public: + Decision FilterV2(int /*level*/, const Slice& /*key*/, ValueType /*type*/, + const Slice& /*existing_value*/, std::string* /*new_value*/, + std::string* /*skip_until*/) const override { + return Decision::kRemove; + } + + const char* Name() const override { return "AllKeysCompactionFilter"; } +}; + class LoggingForwardVectorIterator : public InternalIterator { public: struct Action { @@ -98,39 +112,39 @@ class LoggingForwardVectorIterator : public InternalIterator { assert(keys_.size() == values_.size()); } - virtual bool Valid() const override { return current_ < keys_.size(); } + bool Valid() const override { return current_ < keys_.size(); } - virtual void SeekToFirst() override { + void SeekToFirst() override { log.emplace_back(Action::Type::SEEK_TO_FIRST); current_ = 0; } - virtual void SeekToLast() override { assert(false); } + void SeekToLast() override { assert(false); } - virtual void Seek(const Slice& target) override { + void Seek(const Slice& target) override { log.emplace_back(Action::Type::SEEK, target.ToString()); current_ = std::lower_bound(keys_.begin(), keys_.end(), target.ToString()) - keys_.begin(); } - virtual void SeekForPrev(const Slice& target) override { assert(false); } + void SeekForPrev(const Slice& /*target*/) override { assert(false); } - virtual void Next() override { + void Next() override { assert(Valid()); log.emplace_back(Action::Type::NEXT); current_++; } - virtual void Prev() override { assert(false); } + void Prev() override { assert(false); } - virtual Slice key() const override { + Slice key() const override { assert(Valid()); return Slice(keys_[current_]); } - virtual Slice value() const override { + Slice value() const override { assert(Valid()); return Slice(values_[current_]); } - virtual Status status() const override { return Status::OK(); } + Status status() const override { return Status::OK(); } std::vector log; @@ -144,71 +158,156 @@ class FakeCompaction : public CompactionIterator::CompactionProxy { public: FakeCompaction() = default; - virtual int level(size_t compaction_input_level) const { return 0; } - virtual bool KeyNotExistsBeyondOutputLevel( - const Slice& user_key, std::vector* level_ptrs) const { - return key_not_exists_beyond_output_level; + int level(size_t /*compaction_input_level*/) const override { return 0; } + bool KeyNotExistsBeyondOutputLevel( + const Slice& /*user_key*/, + std::vector* /*level_ptrs*/) const override { + return is_bottommost_level || key_not_exists_beyond_output_level; } - virtual bool bottommost_level() const { return false; } - virtual int number_levels() const { return 1; } - virtual Slice GetLargestUserKey() const { + bool bottommost_level() const override { return is_bottommost_level; } + int number_levels() const override { return 1; } + Slice GetLargestUserKey() const override { return "\xff\xff\xff\xff\xff\xff\xff\xff\xff"; } - virtual bool allow_ingest_behind() const { return false; } + bool allow_ingest_behind() const override { return false; } + + bool preserve_deletes() const override { return false; } bool key_not_exists_beyond_output_level = false; + + bool is_bottommost_level = false; }; -class CompactionIteratorTest : public testing::Test { +// A simplifed snapshot checker which assumes each snapshot has a global +// last visible sequence. +class TestSnapshotChecker : public SnapshotChecker { + public: + explicit TestSnapshotChecker( + SequenceNumber last_committed_sequence, + const std::unordered_map& snapshots = {}) + : last_committed_sequence_(last_committed_sequence), + snapshots_(snapshots) {} + + SnapshotCheckerResult CheckInSnapshot( + SequenceNumber seq, SequenceNumber snapshot_seq) const override { + if (snapshot_seq == kMaxSequenceNumber) { + return seq <= last_committed_sequence_ + ? SnapshotCheckerResult::kInSnapshot + : SnapshotCheckerResult::kNotInSnapshot; + } + assert(snapshots_.count(snapshot_seq) > 0); + return seq <= snapshots_.at(snapshot_seq) + ? SnapshotCheckerResult::kInSnapshot + : SnapshotCheckerResult::kNotInSnapshot; + } + + private: + SequenceNumber last_committed_sequence_; + // A map of valid snapshot to last visible sequence to the snapshot. + std::unordered_map snapshots_; +}; + +// Test param: +// bool: whether to pass snapshot_checker to compaction iterator. +class CompactionIteratorTest : public testing::TestWithParam { public: CompactionIteratorTest() : cmp_(BytewiseComparator()), icmp_(cmp_), snapshots_({}) {} - void InitIterators(const std::vector& ks, - const std::vector& vs, - const std::vector& range_del_ks, - const std::vector& range_del_vs, - SequenceNumber last_sequence, - MergeOperator* merge_op = nullptr, - CompactionFilter* filter = nullptr) { - std::unique_ptr range_del_iter( + void InitIterators( + const std::vector& ks, const std::vector& vs, + const std::vector& range_del_ks, + const std::vector& range_del_vs, + SequenceNumber last_sequence, + SequenceNumber last_committed_sequence = kMaxSequenceNumber, + MergeOperator* merge_op = nullptr, CompactionFilter* filter = nullptr, + bool bottommost_level = false, + SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) { + std::unique_ptr unfragmented_range_del_iter( new test::VectorIterator(range_del_ks, range_del_vs)); - range_del_agg_.reset(new RangeDelAggregator(icmp_, snapshots_)); - ASSERT_OK(range_del_agg_->AddTombstones(std::move(range_del_iter))); + auto tombstone_list = std::make_shared( + std::move(unfragmented_range_del_iter), icmp_); + std::unique_ptr range_del_iter( + new FragmentedRangeTombstoneIterator(tombstone_list, icmp_, + kMaxSequenceNumber)); + range_del_agg_.reset(new CompactionRangeDelAggregator(&icmp_, snapshots_)); + range_del_agg_->AddTombstones(std::move(range_del_iter)); std::unique_ptr compaction; - if (filter) { + if (filter || bottommost_level) { compaction_proxy_ = new FakeCompaction(); + compaction_proxy_->is_bottommost_level = bottommost_level; compaction.reset(compaction_proxy_); } + bool use_snapshot_checker = UseSnapshotChecker() || GetParam(); + if (use_snapshot_checker || last_committed_sequence < kMaxSequenceNumber) { + snapshot_checker_.reset( + new TestSnapshotChecker(last_committed_sequence, snapshot_map_)); + } + merge_helper_.reset( + new MergeHelper(Env::Default(), cmp_, merge_op, filter, nullptr, false, + 0 /*latest_snapshot*/, snapshot_checker_.get(), + 0 /*level*/, nullptr /*statistics*/, &shutting_down_)); - merge_helper_.reset(new MergeHelper(Env::Default(), cmp_, merge_op, filter, - nullptr, false, 0, 0, nullptr, - &shutting_down_)); iter_.reset(new LoggingForwardVectorIterator(ks, vs)); iter_->SeekToFirst(); c_iter_.reset(new CompactionIterator( iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_, - kMaxSequenceNumber, Env::Default(), false, range_del_agg_.get(), - std::move(compaction), filter, nullptr, &shutting_down_)); + earliest_write_conflict_snapshot, snapshot_checker_.get(), + Env::Default(), false /* report_detailed_time */, false, + range_del_agg_.get(), std::move(compaction), filter, &shutting_down_)); + } + + void AddSnapshot(SequenceNumber snapshot, + SequenceNumber last_visible_seq = kMaxSequenceNumber) { + snapshots_.push_back(snapshot); + snapshot_map_[snapshot] = last_visible_seq; } - void AddSnapshot(SequenceNumber snapshot) { snapshots_.push_back(snapshot); } + virtual bool UseSnapshotChecker() const { return false; } + + void RunTest( + const std::vector& input_keys, + const std::vector& input_values, + const std::vector& expected_keys, + const std::vector& expected_values, + SequenceNumber last_committed_seq = kMaxSequenceNumber, + MergeOperator* merge_operator = nullptr, + CompactionFilter* compaction_filter = nullptr, + bool bottommost_level = false, + SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) { + InitIterators(input_keys, input_values, {}, {}, kMaxSequenceNumber, + last_committed_seq, merge_operator, compaction_filter, + bottommost_level, earliest_write_conflict_snapshot); + c_iter_->SeekToFirst(); + for (size_t i = 0; i < expected_keys.size(); i++) { + std::string info = "i = " + ToString(i); + ASSERT_TRUE(c_iter_->Valid()) << info; + ASSERT_OK(c_iter_->status()) << info; + ASSERT_EQ(expected_keys[i], c_iter_->key().ToString()) << info; + ASSERT_EQ(expected_values[i], c_iter_->value().ToString()) << info; + c_iter_->Next(); + } + ASSERT_FALSE(c_iter_->Valid()); + } const Comparator* cmp_; const InternalKeyComparator icmp_; std::vector snapshots_; + // A map of valid snapshot to last visible sequence to the snapshot. + std::unordered_map snapshot_map_; std::unique_ptr merge_helper_; std::unique_ptr iter_; std::unique_ptr c_iter_; - std::unique_ptr range_del_agg_; + std::unique_ptr range_del_agg_; + std::unique_ptr snapshot_checker_; std::atomic shutting_down_{false}; FakeCompaction* compaction_proxy_; }; // It is possible that the output of the compaction iterator is empty even if // the input is not. -TEST_F(CompactionIteratorTest, EmptyResult) { +TEST_P(CompactionIteratorTest, EmptyResult) { InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion), test::KeyStr("a", 3, kTypeValue)}, {"", "val"}, {}, {}, 5); @@ -218,7 +317,7 @@ TEST_F(CompactionIteratorTest, EmptyResult) { // If there is a corruption after a single deletion, the corrupted key should // be preserved. -TEST_F(CompactionIteratorTest, CorruptionAfterSingleDeletion) { +TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) { InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion), test::KeyStr("a", 3, kTypeValue, true), test::KeyStr("b", 10, kTypeValue)}, @@ -237,7 +336,7 @@ TEST_F(CompactionIteratorTest, CorruptionAfterSingleDeletion) { ASSERT_FALSE(c_iter_->Valid()); } -TEST_F(CompactionIteratorTest, SimpleRangeDeletion) { +TEST_P(CompactionIteratorTest, SimpleRangeDeletion) { InitIterators({test::KeyStr("morning", 5, kTypeValue), test::KeyStr("morning", 2, kTypeValue), test::KeyStr("night", 3, kTypeValue)}, @@ -253,7 +352,7 @@ TEST_F(CompactionIteratorTest, SimpleRangeDeletion) { ASSERT_FALSE(c_iter_->Valid()); } -TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) { +TEST_P(CompactionIteratorTest, RangeDeletionWithSnapshots) { AddSnapshot(10); std::vector ks1; ks1.push_back(test::KeyStr("ma", 28, kTypeRangeDeletion)); @@ -274,12 +373,11 @@ TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) { ASSERT_FALSE(c_iter_->Valid()); } -TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) { +TEST_P(CompactionIteratorTest, CompactionFilterSkipUntil) { class Filter : public CompactionFilter { - virtual Decision FilterV2(int level, const Slice& key, ValueType t, - const Slice& existing_value, - std::string* new_value, - std::string* skip_until) const override { + Decision FilterV2(int /*level*/, const Slice& key, ValueType t, + const Slice& existing_value, std::string* /*new_value*/, + std::string* skip_until) const override { std::string k = key.ToString(); std::string v = existing_value.ToString(); // See InitIterators() call below for the sequence of keys and their @@ -349,7 +447,7 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) { test::KeyStr("j", 99, kTypeValue)}, {"av50", "am45", "bv60", "bv40", "cv35", "dm70", "em71", "fm65", "fm30", "fv25", "gv90", "hv91", "im95", "jv99"}, - {}, {}, kMaxSequenceNumber, &merge_op, &filter); + {}, {}, kMaxSequenceNumber, kMaxSequenceNumber, &merge_op, &filter); // Compaction should output just "a", "e" and "h" keys. c_iter_->SeekToFirst(); @@ -384,13 +482,14 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) { ASSERT_EQ(expected_actions, iter_->log); } -TEST_F(CompactionIteratorTest, ShuttingDownInFilter) { +TEST_P(CompactionIteratorTest, ShuttingDownInFilter) { NoMergingMergeOp merge_op; StallingFilter filter; InitIterators( {test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeValue), test::KeyStr("3", 3, kTypeValue), test::KeyStr("4", 4, kTypeValue)}, - {"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter); + {"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber, + &merge_op, &filter); // Don't leave tombstones (kTypeDeletion) for filtered keys. compaction_proxy_->key_not_exists_beyond_output_level = true; @@ -421,13 +520,14 @@ TEST_F(CompactionIteratorTest, ShuttingDownInFilter) { // Same as ShuttingDownInFilter, but shutdown happens during filter call for // a merge operand, not for a value. -TEST_F(CompactionIteratorTest, ShuttingDownInMerge) { +TEST_P(CompactionIteratorTest, ShuttingDownInMerge) { NoMergingMergeOp merge_op; StallingFilter filter; InitIterators( {test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeMerge), test::KeyStr("3", 3, kTypeMerge), test::KeyStr("4", 4, kTypeValue)}, - {"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter); + {"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber, + &merge_op, &filter); compaction_proxy_->key_not_exists_beyond_output_level = true; std::atomic seek_done{false}; @@ -455,12 +555,11 @@ TEST_F(CompactionIteratorTest, ShuttingDownInMerge) { EXPECT_EQ(2, filter.last_seen.load()); } -TEST_F(CompactionIteratorTest, SingleMergeOperand) { +TEST_P(CompactionIteratorTest, SingleMergeOperand) { class Filter : public CompactionFilter { - virtual Decision FilterV2(int level, const Slice& key, ValueType t, - const Slice& existing_value, - std::string* new_value, - std::string* skip_until) const override { + Decision FilterV2(int /*level*/, const Slice& key, ValueType t, + const Slice& existing_value, std::string* /*new_value*/, + std::string* /*skip_until*/) const override { std::string k = key.ToString(); std::string v = existing_value.ToString(); @@ -511,7 +610,7 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) { bool PartialMergeMulti(const Slice& key, const std::deque& operand_list, std::string* new_value, - Logger* logger) const override { + Logger* /*logger*/) const override { std::string string_key = key.ToString(); EXPECT_TRUE(string_key == "a" || string_key == "b"); @@ -547,7 +646,7 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) { // c should invoke FullMerge due to kTypeValue at the beginning. test::KeyStr("c", 90, kTypeMerge), test::KeyStr("c", 80, kTypeValue)}, {"av1", "bv2", "bv1", "cv2", "cv1"}, {}, {}, kMaxSequenceNumber, - &merge_op, &filter); + kMaxSequenceNumber, &merge_op, &filter); c_iter_->SeekToFirst(); ASSERT_TRUE(c_iter_->Valid()); @@ -560,6 +659,315 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) { ASSERT_EQ("cv1cv2", c_iter_->value().ToString()); } +// In bottommost level, values earlier than earliest snapshot can be output +// with sequence = 0. +TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) { + AddSnapshot(1); + RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue)}, + {"v1", "v2"}, + {test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue)}, + {"v1", "v2"}, kMaxSequenceNumber /*last_commited_seq*/, + nullptr /*merge_operator*/, nullptr /*compaction_filter*/, + true /*bottommost_level*/); +} + +// In bottommost level, deletions earlier than earliest snapshot can be removed +// permanently. +TEST_P(CompactionIteratorTest, RemoveDeletionAtBottomLevel) { + AddSnapshot(1); + RunTest({test::KeyStr("a", 1, kTypeDeletion), + test::KeyStr("b", 3, kTypeDeletion), + test::KeyStr("b", 1, kTypeValue)}, + {"", "", ""}, + {test::KeyStr("b", 3, kTypeDeletion), + test::KeyStr("b", 0, kTypeValue)}, + {"", ""}, + kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/, + nullptr /*compaction_filter*/, true /*bottommost_level*/); +} + +// In bottommost level, single deletions earlier than earliest snapshot can be +// removed permanently. +TEST_P(CompactionIteratorTest, RemoveSingleDeletionAtBottomLevel) { + AddSnapshot(1); + RunTest({test::KeyStr("a", 1, kTypeSingleDeletion), + test::KeyStr("b", 2, kTypeSingleDeletion)}, + {"", ""}, {test::KeyStr("b", 2, kTypeSingleDeletion)}, {""}, + kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/, + nullptr /*compaction_filter*/, true /*bottommost_level*/); +} + +INSTANTIATE_TEST_CASE_P(CompactionIteratorTestInstance, CompactionIteratorTest, + testing::Values(true, false)); + +// Tests how CompactionIterator work together with SnapshotChecker. +class CompactionIteratorWithSnapshotCheckerTest + : public CompactionIteratorTest { + public: + bool UseSnapshotChecker() const override { return true; } +}; + +// Uncommitted keys (keys with seq > last_committed_seq) should be output as-is +// while committed version of these keys should get compacted as usual. + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + PreserveUncommittedKeys_Value) { + RunTest( + {test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue), + test::KeyStr("foo", 1, kTypeValue)}, + {"v3", "v2", "v1"}, + {test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue)}, + {"v3", "v2"}, 2 /*last_committed_seq*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + PreserveUncommittedKeys_Deletion) { + RunTest({test::KeyStr("foo", 2, kTypeDeletion), + test::KeyStr("foo", 1, kTypeValue)}, + {"", "v1"}, + {test::KeyStr("foo", 2, kTypeDeletion), + test::KeyStr("foo", 1, kTypeValue)}, + {"", "v1"}, 1 /*last_committed_seq*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + PreserveUncommittedKeys_Merge) { + auto merge_op = MergeOperators::CreateStringAppendOperator(); + RunTest( + {test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge), + test::KeyStr("foo", 1, kTypeValue)}, + {"v3", "v2", "v1"}, + {test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeValue)}, + {"v3", "v1,v2"}, 2 /*last_committed_seq*/, merge_op.get()); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + PreserveUncommittedKeys_SingleDelete) { + RunTest({test::KeyStr("foo", 2, kTypeSingleDeletion), + test::KeyStr("foo", 1, kTypeValue)}, + {"", "v1"}, + {test::KeyStr("foo", 2, kTypeSingleDeletion), + test::KeyStr("foo", 1, kTypeValue)}, + {"", "v1"}, 1 /*last_committed_seq*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + PreserveUncommittedKeys_BlobIndex) { + RunTest({test::KeyStr("foo", 3, kTypeBlobIndex), + test::KeyStr("foo", 2, kTypeBlobIndex), + test::KeyStr("foo", 1, kTypeBlobIndex)}, + {"v3", "v2", "v1"}, + {test::KeyStr("foo", 3, kTypeBlobIndex), + test::KeyStr("foo", 2, kTypeBlobIndex)}, + {"v3", "v2"}, 2 /*last_committed_seq*/); +} + +// Test compaction iterator dedup keys visible to the same snapshot. + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Value) { + AddSnapshot(2, 1); + RunTest( + {test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue), + test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)}, + {"v4", "v3", "v2", "v1"}, + {test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue), + test::KeyStr("foo", 1, kTypeValue)}, + {"v4", "v3", "v1"}, 3 /*last_committed_seq*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Deletion) { + AddSnapshot(2, 1); + RunTest( + {test::KeyStr("foo", 4, kTypeValue), + test::KeyStr("foo", 3, kTypeDeletion), + test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)}, + {"v4", "", "v2", "v1"}, + {test::KeyStr("foo", 4, kTypeValue), + test::KeyStr("foo", 3, kTypeDeletion), + test::KeyStr("foo", 1, kTypeValue)}, + {"v4", "", "v1"}, 3 /*last_committed_seq*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Merge) { + AddSnapshot(2, 1); + AddSnapshot(4, 3); + auto merge_op = MergeOperators::CreateStringAppendOperator(); + RunTest( + {test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge), + test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge), + test::KeyStr("foo", 1, kTypeValue)}, + {"v5", "v4", "v3", "v2", "v1"}, + {test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge), + test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 1, kTypeValue)}, + {"v5", "v4", "v2,v3", "v1"}, 4 /*last_committed_seq*/, merge_op.get()); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + DedupSameSnapshot_SingleDeletion) { + AddSnapshot(2, 1); + RunTest( + {test::KeyStr("foo", 4, kTypeValue), + test::KeyStr("foo", 3, kTypeSingleDeletion), + test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)}, + {"v4", "", "v2", "v1"}, + {test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 1, kTypeValue)}, + {"v4", "v1"}, 3 /*last_committed_seq*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_BlobIndex) { + AddSnapshot(2, 1); + RunTest({test::KeyStr("foo", 4, kTypeBlobIndex), + test::KeyStr("foo", 3, kTypeBlobIndex), + test::KeyStr("foo", 2, kTypeBlobIndex), + test::KeyStr("foo", 1, kTypeBlobIndex)}, + {"v4", "v3", "v2", "v1"}, + {test::KeyStr("foo", 4, kTypeBlobIndex), + test::KeyStr("foo", 3, kTypeBlobIndex), + test::KeyStr("foo", 1, kTypeBlobIndex)}, + {"v4", "v3", "v1"}, 3 /*last_committed_seq*/); +} + +// At bottom level, sequence numbers can be zero out, and deletions can be +// removed, but only when they are visible to earliest snapshot. + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + NotZeroOutSequenceIfNotVisibleToEarliestSnapshot) { + AddSnapshot(2, 1); + RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue), + test::KeyStr("c", 3, kTypeValue)}, + {"v1", "v2", "v3"}, + {test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue), + test::KeyStr("c", 3, kTypeValue)}, + {"v1", "v2", "v3"}, kMaxSequenceNumber /*last_commited_seq*/, + nullptr /*merge_operator*/, nullptr /*compaction_filter*/, + true /*bottommost_level*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + NotRemoveDeletionIfNotVisibleToEarliestSnapshot) { + AddSnapshot(2, 1); + RunTest( + {test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 2, kTypeDeletion), + test::KeyStr("c", 3, kTypeDeletion)}, + {"", "", ""}, + {}, + {"", ""}, kMaxSequenceNumber /*last_commited_seq*/, + nullptr /*merge_operator*/, nullptr /*compaction_filter*/, + true /*bottommost_level*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + NotRemoveDeletionIfValuePresentToEarlierSnapshot) { + AddSnapshot(2,1); + RunTest( + {test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 1, kTypeValue), + test::KeyStr("b", 3, kTypeValue)}, + {"", "", ""}, + {test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 0, kTypeValue), + test::KeyStr("b", 3, kTypeValue)}, + {"", "", ""}, kMaxSequenceNumber /*last_commited_seq*/, + nullptr /*merge_operator*/, nullptr /*compaction_filter*/, + true /*bottommost_level*/); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + NotRemoveSingleDeletionIfNotVisibleToEarliestSnapshot) { + AddSnapshot(2, 1); + RunTest({test::KeyStr("a", 1, kTypeSingleDeletion), + test::KeyStr("b", 2, kTypeSingleDeletion), + test::KeyStr("c", 3, kTypeSingleDeletion)}, + {"", "", ""}, + {test::KeyStr("b", 2, kTypeSingleDeletion), + test::KeyStr("c", 3, kTypeSingleDeletion)}, + {"", ""}, kMaxSequenceNumber /*last_commited_seq*/, + nullptr /*merge_operator*/, nullptr /*compaction_filter*/, + true /*bottommost_level*/); +} + +// Single delete should not cancel out values that not visible to the +// same set of snapshots +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + SingleDeleteAcrossSnapshotBoundary) { + AddSnapshot(2, 1); + RunTest({test::KeyStr("a", 2, kTypeSingleDeletion), + test::KeyStr("a", 1, kTypeValue)}, + {"", "v1"}, + {test::KeyStr("a", 2, kTypeSingleDeletion), + test::KeyStr("a", 1, kTypeValue)}, + {"", "v1"}, 2 /*last_committed_seq*/); +} + +// Single delete should be kept in case it is not visible to the +// earliest write conflict snapshot. If a single delete is kept for this reason, +// corresponding value can be trimmed to save space. +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + KeepSingleDeletionForWriteConflictChecking) { + AddSnapshot(2, 0); + RunTest({test::KeyStr("a", 2, kTypeSingleDeletion), + test::KeyStr("a", 1, kTypeValue)}, + {"", "v1"}, + {test::KeyStr("a", 2, kTypeSingleDeletion), + test::KeyStr("a", 1, kTypeValue)}, + {"", ""}, 2 /*last_committed_seq*/, nullptr /*merge_operator*/, + nullptr /*compaction_filter*/, false /*bottommost_level*/, + 2 /*earliest_write_conflict_snapshot*/); +} + +// Compaction filter should keep uncommitted key as-is, and +// * Convert the latest velue to deletion, and/or +// * if latest value is a merge, apply filter to all suequent merges. + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Value) { + std::unique_ptr compaction_filter( + new FilterAllKeysCompactionFilter()); + RunTest( + {test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeValue), + test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeValue)}, + {"v2", "v1", "v3", "v4"}, + {test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeDeletion), + test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeDeletion)}, + {"v2", "", "v3", ""}, 1 /*last_committed_seq*/, + nullptr /*merge_operator*/, compaction_filter.get()); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Deletion) { + std::unique_ptr compaction_filter( + new FilterAllKeysCompactionFilter()); + RunTest( + {test::KeyStr("a", 2, kTypeDeletion), test::KeyStr("a", 1, kTypeValue)}, + {"", "v1"}, + {test::KeyStr("a", 2, kTypeDeletion), + test::KeyStr("a", 1, kTypeDeletion)}, + {"", ""}, 1 /*last_committed_seq*/, nullptr /*merge_operator*/, + compaction_filter.get()); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, + CompactionFilter_PartialMerge) { + std::shared_ptr merge_op = + MergeOperators::CreateStringAppendOperator(); + std::unique_ptr compaction_filter( + new FilterAllKeysCompactionFilter()); + RunTest({test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge), + test::KeyStr("a", 1, kTypeMerge)}, + {"v3", "v2", "v1"}, {test::KeyStr("a", 3, kTypeMerge)}, {"v3"}, + 2 /*last_committed_seq*/, merge_op.get(), compaction_filter.get()); +} + +TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_FullMerge) { + std::shared_ptr merge_op = + MergeOperators::CreateStringAppendOperator(); + std::unique_ptr compaction_filter( + new FilterAllKeysCompactionFilter()); + RunTest( + {test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge), + test::KeyStr("a", 1, kTypeValue)}, + {"v3", "v2", "v1"}, + {test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 1, kTypeDeletion)}, + {"v3", ""}, 2 /*last_committed_seq*/, merge_op.get(), + compaction_filter.get()); +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/compaction_job.cc b/thirdparty/rocksdb/db/compaction_job.cc index 1d023ca456..65e9719a39 100644 --- a/thirdparty/rocksdb/db/compaction_job.cc +++ b/thirdparty/rocksdb/db/compaction_job.cc @@ -25,8 +25,10 @@ #include #include "db/builder.h" +#include "db/db_impl.h" #include "db/db_iter.h" #include "db/dbformat.h" +#include "db/error_handler.h" #include "db/event_helpers.h" #include "db/log_reader.h" #include "db/log_writer.h" @@ -34,11 +36,11 @@ #include "db/memtable_list.h" #include "db/merge_context.h" #include "db/merge_helper.h" +#include "db/range_del_aggregator.h" #include "db/version_set.h" #include "monitoring/iostats_context_imp.h" #include "monitoring/perf_context_imp.h" #include "monitoring/thread_status_util.h" -#include "port/likely.h" #include "port/port.h" #include "rocksdb/db.h" #include "rocksdb/env.h" @@ -63,6 +65,46 @@ namespace rocksdb { +const char* GetCompactionReasonString(CompactionReason compaction_reason) { + switch (compaction_reason) { + case CompactionReason::kUnknown: + return "Unknown"; + case CompactionReason::kLevelL0FilesNum: + return "LevelL0FilesNum"; + case CompactionReason::kLevelMaxLevelSize: + return "LevelMaxLevelSize"; + case CompactionReason::kUniversalSizeAmplification: + return "UniversalSizeAmplification"; + case CompactionReason::kUniversalSizeRatio: + return "UniversalSizeRatio"; + case CompactionReason::kUniversalSortedRunNum: + return "UniversalSortedRunNum"; + case CompactionReason::kFIFOMaxSize: + return "FIFOMaxSize"; + case CompactionReason::kFIFOReduceNumFiles: + return "FIFOReduceNumFiles"; + case CompactionReason::kFIFOTtl: + return "FIFOTtl"; + case CompactionReason::kManualCompaction: + return "ManualCompaction"; + case CompactionReason::kFilesMarkedForCompaction: + return "FilesMarkedForCompaction"; + case CompactionReason::kBottommostFiles: + return "BottommostFiles"; + case CompactionReason::kTtl: + return "Ttl"; + case CompactionReason::kFlush: + return "Flush"; + case CompactionReason::kExternalSstIngestion: + return "ExternalSstIngestion"; + case CompactionReason::kNumOfReasons: + // fall through + default: + assert(false); + return "Invalid"; + } +} + // Maintains state for each sub-compaction struct CompactionJob::SubcompactionState { const Compaction* compaction; @@ -115,7 +157,6 @@ struct CompactionJob::SubcompactionState { uint64_t overlapped_bytes = 0; // A flag determine whether the key has been seen in ShouldStopBefore() bool seen_key = false; - std::string compression_dict; SubcompactionState(Compaction* c, Slice* _start, Slice* _end, uint64_t size = 0) @@ -131,8 +172,7 @@ struct CompactionJob::SubcompactionState { approx_size(size), grandparent_index(0), overlapped_bytes(0), - seen_key(false), - compression_dict() { + seen_key(false) { assert(compaction != nullptr); } @@ -155,11 +195,10 @@ struct CompactionJob::SubcompactionState { grandparent_index = std::move(o.grandparent_index); overlapped_bytes = std::move(o.overlapped_bytes); seen_key = std::move(o.seen_key); - compression_dict = std::move(o.compression_dict); return *this; } - // Because member unique_ptrs do not have these. + // Because member std::unique_ptrs do not have these. SubcompactionState(const SubcompactionState&) = delete; SubcompactionState& operator=(const SubcompactionState&) = delete; @@ -264,37 +303,46 @@ void CompactionJob::AggregateStatistics() { CompactionJob::CompactionJob( int job_id, Compaction* compaction, const ImmutableDBOptions& db_options, - const EnvOptions& env_options, VersionSet* versions, - const std::atomic* shutting_down, LogBuffer* log_buffer, + const EnvOptions env_options, VersionSet* versions, + const std::atomic* shutting_down, + const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer, Directory* db_directory, Directory* output_directory, Statistics* stats, - InstrumentedMutex* db_mutex, Status* db_bg_error, + InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler, std::vector existing_snapshots, SequenceNumber earliest_write_conflict_snapshot, - std::shared_ptr table_cache, EventLogger* event_logger, - bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname, - CompactionJobStats* compaction_job_stats) + const SnapshotChecker* snapshot_checker, std::shared_ptr table_cache, + EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats, + const std::string& dbname, CompactionJobStats* compaction_job_stats, + Env::Priority thread_pri) : job_id_(job_id), compact_(new CompactionState(compaction)), compaction_job_stats_(compaction_job_stats), - compaction_stats_(1), + compaction_stats_(compaction->compaction_reason(), 1), dbname_(dbname), db_options_(db_options), env_options_(env_options), env_(db_options.env), + env_optiosn_for_read_( + env_->OptimizeForCompactionTableRead(env_options, db_options_)), versions_(versions), shutting_down_(shutting_down), + preserve_deletes_seqnum_(preserve_deletes_seqnum), log_buffer_(log_buffer), db_directory_(db_directory), output_directory_(output_directory), stats_(stats), db_mutex_(db_mutex), - db_bg_error_(db_bg_error), + db_error_handler_(db_error_handler), existing_snapshots_(std::move(existing_snapshots)), earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot), + snapshot_checker_(snapshot_checker), table_cache_(std::move(table_cache)), event_logger_(event_logger), + bottommost_level_(false), paranoid_file_checks_(paranoid_file_checks), - measure_io_stats_(measure_io_stats) { + measure_io_stats_(measure_io_stats), + write_hint_(Env::WLTH_NOT_SET), + thread_pri_(thread_pri) { assert(log_buffer_ != nullptr); const auto* cfd = compact_->compaction->column_family_data(); ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env, @@ -308,15 +356,13 @@ CompactionJob::~CompactionJob() { ThreadStatusUtil::ResetThreadStatus(); } -void CompactionJob::ReportStartedCompaction( - Compaction* compaction) { +void CompactionJob::ReportStartedCompaction(Compaction* compaction) { const auto* cfd = compact_->compaction->column_family_data(); ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env, db_options_.enable_thread_tracking); - ThreadStatusUtil::SetThreadOperationProperty( - ThreadStatus::COMPACTION_JOB_ID, - job_id_); + ThreadStatusUtil::SetThreadOperationProperty(ThreadStatus::COMPACTION_JOB_ID, + job_id_); ThreadStatusUtil::SetThreadOperationProperty( ThreadStatus::COMPACTION_INPUT_OUTPUT_LEVEL, @@ -346,8 +392,7 @@ void CompactionJob::ReportStartedCompaction( // Set the thread operation after operation properties // to ensure GetThreadList() can always show them all together. - ThreadStatusUtil::SetThreadOperation( - ThreadStatus::OP_COMPACTION); + ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION); if (compaction_job_stats_) { compaction_job_stats_->is_manual_compaction = @@ -362,18 +407,19 @@ void CompactionJob::Prepare() { // Generate file_levels_ for compaction berfore making Iterator auto* c = compact_->compaction; assert(c->column_family_data() != nullptr); - assert(c->column_family_data()->current()->storage_info() - ->NumLevelFiles(compact_->compaction->level()) > 0); + assert(c->column_family_data()->current()->storage_info()->NumLevelFiles( + compact_->compaction->level()) > 0); + write_hint_ = + c->column_family_data()->CalculateSSTWriteHint(c->output_level()); // Is this compaction producing files at the bottommost level? bottommost_level_ = c->bottommost_level(); if (c->ShouldFormSubcompactions()) { - const uint64_t start_micros = env_->NowMicros(); - GenSubcompactionBoundaries(); - MeasureTime(stats_, SUBCOMPACTION_SETUP_TIME, - env_->NowMicros() - start_micros); - + { + StopWatch sw(env_, stats_, SUBCOMPACTION_SETUP_TIME); + GenSubcompactionBoundaries(); + } assert(sizes_.size() == boundaries_.size() + 1); for (size_t i = 0; i <= boundaries_.size(); i++) { @@ -381,8 +427,8 @@ void CompactionJob::Prepare() { Slice* end = i == boundaries_.size() ? nullptr : &boundaries_[i]; compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i]); } - MeasureTime(stats_, NUM_SUBCOMPACTIONS_SCHEDULED, - compact_->sub_compact_states.size()); + RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED, + compact_->sub_compact_states.size()); } else { compact_->sub_compact_states.emplace_back(c, nullptr, nullptr); } @@ -447,20 +493,27 @@ void CompactionJob::GenSubcompactionBoundaries() { } std::sort(bounds.begin(), bounds.end(), - [cfd_comparator] (const Slice& a, const Slice& b) -> bool { - return cfd_comparator->Compare(ExtractUserKey(a), ExtractUserKey(b)) < 0; - }); + [cfd_comparator](const Slice& a, const Slice& b) -> bool { + return cfd_comparator->Compare(ExtractUserKey(a), + ExtractUserKey(b)) < 0; + }); // Remove duplicated entries from bounds - bounds.erase(std::unique(bounds.begin(), bounds.end(), - [cfd_comparator] (const Slice& a, const Slice& b) -> bool { - return cfd_comparator->Compare(ExtractUserKey(a), ExtractUserKey(b)) == 0; - }), bounds.end()); + bounds.erase( + std::unique(bounds.begin(), bounds.end(), + [cfd_comparator](const Slice& a, const Slice& b) -> bool { + return cfd_comparator->Compare(ExtractUserKey(a), + ExtractUserKey(b)) == 0; + }), + bounds.end()); // Combine consecutive pairs of boundaries into ranges with an approximate // size of data covered by keys in that range uint64_t sum = 0; std::vector ranges; - auto* v = cfd->current(); + // Get input version from CompactionState since it's already referenced + // earlier in SetInputVersioCompaction::SetInputVersion and will not change + // when db_mutex_ is released below + auto* v = compact_->compaction->input_version(); for (auto it = bounds.begin();;) { const Slice a = *it; it++; @@ -470,19 +523,28 @@ void CompactionJob::GenSubcompactionBoundaries() { } const Slice b = *it; + + // ApproximateSize could potentially create table reader iterator to seek + // to the index block and may incur I/O cost in the process. Unlock db + // mutex to reduce contention + db_mutex_->Unlock(); uint64_t size = versions_->ApproximateSize(v, a, b, start_lvl, out_lvl + 1); + db_mutex_->Lock(); ranges.emplace_back(a, b, size); sum += size; } // Group the ranges into subcompactions const double min_file_fill_percent = 4.0 / 5; - uint64_t max_output_files = static_cast( - std::ceil(sum / min_file_fill_percent / - c->mutable_cf_options()->MaxFileSizeForLevel(out_lvl))); + int base_level = v->storage_info()->base_level(); + uint64_t max_output_files = static_cast(std::ceil( + sum / min_file_fill_percent / + MaxFileSizeForLevel(*(c->mutable_cf_options()), out_lvl, + c->immutable_cf_options()->compaction_style, base_level, + c->immutable_cf_options()->level_compaction_dynamic_level_bytes))); uint64_t subcompactions = std::min({static_cast(ranges.size()), - static_cast(db_options_.max_subcompactions), + static_cast(c->max_subcompactions()), max_output_files}); if (subcompactions > 1) { @@ -539,12 +601,18 @@ Status CompactionJob::Run() { thread.join(); } - if (output_directory_) { - output_directory_->Fsync(); + compaction_stats_.micros = env_->NowMicros() - start_micros; + compaction_stats_.cpu_micros = 0; + for (size_t i = 0; i < compact_->sub_compact_states.size(); i++) { + compaction_stats_.cpu_micros += + compact_->sub_compact_states[i].compaction_job_stats.cpu_micros; } - compaction_stats_.micros = env_->NowMicros() - start_micros; - MeasureTime(stats_, COMPACTION_TIME, compaction_stats_.micros); + RecordTimeToHistogram(stats_, COMPACTION_TIME, compaction_stats_.micros); + RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME, + compaction_stats_.cpu_micros); + + TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify"); // Check if any thread encountered an error during execution Status status; @@ -555,11 +623,79 @@ Status CompactionJob::Run() { } } + if (status.ok() && output_directory_) { + status = output_directory_->Fsync(); + } + + if (status.ok()) { + thread_pool.clear(); + std::vector files_meta; + for (const auto& state : compact_->sub_compact_states) { + for (const auto& output : state.outputs) { + files_meta.emplace_back(&output.meta); + } + } + ColumnFamilyData* cfd = compact_->compaction->column_family_data(); + auto prefix_extractor = + compact_->compaction->mutable_cf_options()->prefix_extractor.get(); + std::atomic next_file_meta_idx(0); + auto verify_table = [&](Status& output_status) { + while (true) { + size_t file_idx = next_file_meta_idx.fetch_add(1); + if (file_idx >= files_meta.size()) { + break; + } + // Verify that the table is usable + // We set for_compaction to false and don't OptimizeForCompactionTableRead + // here because this is a special case after we finish the table building + // No matter whether use_direct_io_for_flush_and_compaction is true, + // we will regard this verification as user reads since the goal is + // to cache it here for further user reads + InternalIterator* iter = cfd->table_cache()->NewIterator( + ReadOptions(), env_options_, cfd->internal_comparator(), + *files_meta[file_idx], nullptr /* range_del_agg */, + prefix_extractor, nullptr, + cfd->internal_stats()->GetFileReadHist( + compact_->compaction->output_level()), + false, nullptr /* arena */, false /* skip_filters */, + compact_->compaction->output_level()); + auto s = iter->status(); + + if (s.ok() && paranoid_file_checks_) { + for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {} + s = iter->status(); + } + + delete iter; + + if (!s.ok()) { + output_status = s; + break; + } + } + }; + for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) { + thread_pool.emplace_back(verify_table, + std::ref(compact_->sub_compact_states[i].status)); + } + verify_table(compact_->sub_compact_states[0].status); + for (auto& thread : thread_pool) { + thread.join(); + } + for (const auto& state : compact_->sub_compact_states) { + if (!state.status.ok()) { + status = state.status; + break; + } + } + } + TablePropertiesCollection tp; for (const auto& state : compact_->sub_compact_states) { for (const auto& output : state.outputs) { - auto fn = TableFileName(db_options_.db_paths, output.meta.fd.GetNumber(), - output.meta.fd.GetPathId()); + auto fn = + TableFileName(state.compaction->immutable_cf_options()->cf_paths, + output.meta.fd.GetNumber(), output.meta.fd.GetPathId()); tp[fn] = output.table_properties; } } @@ -583,7 +719,7 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) { Status status = compact_->status; ColumnFamilyData* cfd = compact_->compaction->column_family_data(); cfd->internal_stats()->AddCompactionStats( - compact_->compaction->output_level(), compaction_stats_); + compact_->compaction->output_level(), thread_pri_, compaction_stats_); if (status.ok()) { status = InstallCompactionResults(mutable_cf_options); @@ -617,7 +753,8 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) { "[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, " "files in(%d, %d) out(%d) " "MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) " - "write-amplify(%.1f) %s, records in: %d, records dropped: %d\n", + "write-amplify(%.1f) %s, records in: %" PRIu64 + ", records dropped: %" PRIu64 " output_compression: %s\n", cfd->GetName().c_str(), vstorage->LevelSummary(&tmp), bytes_read_per_sec, bytes_written_per_sec, compact_->compaction->output_level(), stats.num_input_files_in_non_output_levels, @@ -626,20 +763,24 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) { stats.bytes_read_output_level / 1048576.0, stats.bytes_written / 1048576.0, read_write_amp, write_amp, status.ToString().c_str(), stats.num_input_records, - stats.num_dropped_records); + stats.num_dropped_records, + CompressionTypeToString(compact_->compaction->output_compression()) + .c_str()); UpdateCompactionJobStats(stats); auto stream = event_logger_->LogToBuffer(log_buffer_); - stream << "job" << job_id_ - << "event" << "compaction_finished" + stream << "job" << job_id_ << "event" + << "compaction_finished" << "compaction_time_micros" << compaction_stats_.micros + << "compaction_time_cpu_micros" << compaction_stats_.cpu_micros << "output_level" << compact_->compaction->output_level() << "num_output_files" << compact_->NumOutputFiles() - << "total_output_size" << compact_->total_bytes - << "num_input_records" << compact_->num_input_records - << "num_output_records" << compact_->num_output_records - << "num_subcompactions" << compact_->sub_compact_states.size(); + << "total_output_size" << compact_->total_bytes << "num_input_records" + << compact_->num_input_records << "num_output_records" + << compact_->num_output_records << "num_subcompactions" + << compact_->sub_compact_states.size() << "output_compression" + << CompressionTypeToString(compact_->compaction->output_compression()); if (compaction_job_stats_ != nullptr) { stream << "num_single_delete_mismatches" @@ -670,11 +811,35 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) { void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { assert(sub_compact != nullptr); + + uint64_t prev_cpu_micros = env_->NowCPUNanos() / 1000; + ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); - std::unique_ptr range_del_agg( - new RangeDelAggregator(cfd->internal_comparator(), existing_snapshots_)); + + // Create compaction filter and fail the compaction if + // IgnoreSnapshots() = false because it is not supported anymore + const CompactionFilter* compaction_filter = + cfd->ioptions()->compaction_filter; + std::unique_ptr compaction_filter_from_factory = nullptr; + if (compaction_filter == nullptr) { + compaction_filter_from_factory = + sub_compact->compaction->CreateCompactionFilter(); + compaction_filter = compaction_filter_from_factory.get(); + } + if (compaction_filter != nullptr && !compaction_filter->IgnoreSnapshots()) { + sub_compact->status = Status::NotSupported( + "CompactionFilter::IgnoreSnapshots() = false is not supported " + "anymore."); + return; + } + + CompactionRangeDelAggregator range_del_agg(&cfd->internal_comparator(), + existing_snapshots_); + + // Although the v2 aggregator is what the level iterator(s) know about, + // the AddTombstones calls will be propagated down to the v1 aggregator. std::unique_ptr input(versions_->MakeInputIterator( - sub_compact->compaction, range_del_agg.get())); + sub_compact->compaction, &range_del_agg, env_optiosn_for_read_)); AutoThreadOperationStageUpdater stage_updater( ThreadStatus::STAGE_COMPACTION_PROCESS_KV); @@ -686,54 +851,26 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { uint64_t prev_fsync_nanos = 0; uint64_t prev_range_sync_nanos = 0; uint64_t prev_prepare_write_nanos = 0; + uint64_t prev_cpu_write_nanos = 0; + uint64_t prev_cpu_read_nanos = 0; if (measure_io_stats_) { prev_perf_level = GetPerfLevel(); - SetPerfLevel(PerfLevel::kEnableTime); + SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); prev_write_nanos = IOSTATS(write_nanos); prev_fsync_nanos = IOSTATS(fsync_nanos); prev_range_sync_nanos = IOSTATS(range_sync_nanos); prev_prepare_write_nanos = IOSTATS(prepare_write_nanos); + prev_cpu_write_nanos = IOSTATS(cpu_write_nanos); + prev_cpu_read_nanos = IOSTATS(cpu_read_nanos); } - const MutableCFOptions* mutable_cf_options = - sub_compact->compaction->mutable_cf_options(); - - // To build compression dictionary, we sample the first output file, assuming - // it'll reach the maximum length, and then use the dictionary for compressing - // subsequent output files. The dictionary may be less than max_dict_bytes if - // the first output file's length is less than the maximum. - const int kSampleLenShift = 6; // 2^6 = 64-byte samples - std::set sample_begin_offsets; - if (bottommost_level_ && - cfd->ioptions()->compression_opts.max_dict_bytes > 0) { - const size_t kMaxSamples = - cfd->ioptions()->compression_opts.max_dict_bytes >> kSampleLenShift; - const size_t kOutFileLen = mutable_cf_options->MaxFileSizeForLevel( - compact_->compaction->output_level()); - if (kOutFileLen != port::kMaxSizet) { - const size_t kOutFileNumSamples = kOutFileLen >> kSampleLenShift; - Random64 generator{versions_->NewFileNumber()}; - for (size_t i = 0; i < kMaxSamples; ++i) { - sample_begin_offsets.insert(generator.Uniform(kOutFileNumSamples) - << kSampleLenShift); - } - } - } - - auto compaction_filter = cfd->ioptions()->compaction_filter; - std::unique_ptr compaction_filter_from_factory = nullptr; - if (compaction_filter == nullptr) { - compaction_filter_from_factory = - sub_compact->compaction->CreateCompactionFilter(); - compaction_filter = compaction_filter_from_factory.get(); - } MergeHelper merge( env_, cfd->user_comparator(), cfd->ioptions()->merge_operator, compaction_filter, db_options_.info_log.get(), false /* internal key corruption is expected */, existing_snapshots_.empty() ? 0 : existing_snapshots_.back(), - compact_->compaction->level(), db_options_.statistics.get(), - shutting_down_); + snapshot_checker_, compact_->compaction->level(), + db_options_.statistics.get(), shutting_down_); TEST_SYNC_POINT("CompactionJob::Run():Inprogress"); @@ -747,40 +884,23 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { input->SeekToFirst(); } - // we allow only 1 compaction event listener. Used by blob storage - CompactionEventListener* comp_event_listener = nullptr; -#ifndef ROCKSDB_LITE - for (auto& celitr : cfd->ioptions()->listeners) { - comp_event_listener = celitr->GetCompactionEventListener(); - if (comp_event_listener != nullptr) { - break; - } - } -#endif // ROCKSDB_LITE - Status status; sub_compact->c_iter.reset(new CompactionIterator( input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(), - &existing_snapshots_, earliest_write_conflict_snapshot_, env_, false, - range_del_agg.get(), sub_compact->compaction, compaction_filter, - comp_event_listener, shutting_down_)); + &existing_snapshots_, earliest_write_conflict_snapshot_, + snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_), false, + &range_del_agg, sub_compact->compaction, compaction_filter, + shutting_down_, preserve_deletes_seqnum_)); auto c_iter = sub_compact->c_iter.get(); c_iter->SeekToFirst(); - if (c_iter->Valid() && - sub_compact->compaction->output_level() != 0) { + if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) { // ShouldStopBefore() maintains state based on keys processed so far. The // compaction loop always calls it on the "next" key, thus won't tell it the // first key. So we do that here. - sub_compact->ShouldStopBefore( - c_iter->key(), sub_compact->current_output_file_size); + sub_compact->ShouldStopBefore(c_iter->key(), + sub_compact->current_output_file_size); } const auto& c_iter_stats = c_iter->iter_stats(); - auto sample_begin_offset_iter = sample_begin_offsets.cbegin(); - // data_begin_offset and compression_dict are only valid while generating - // dictionary from the first output file. - size_t data_begin_offset = 0; - std::string compression_dict; - compression_dict.reserve(cfd->ioptions()->compression_opts.max_dict_bytes); while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) { // Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid() @@ -816,55 +936,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { key, c_iter->ikey().sequence); sub_compact->num_output_records++; - if (sub_compact->outputs.size() == 1) { // first output file - // Check if this key/value overlaps any sample intervals; if so, appends - // overlapping portions to the dictionary. - for (const auto& data_elmt : {key, value}) { - size_t data_end_offset = data_begin_offset + data_elmt.size(); - while (sample_begin_offset_iter != sample_begin_offsets.cend() && - *sample_begin_offset_iter < data_end_offset) { - size_t sample_end_offset = - *sample_begin_offset_iter + (1 << kSampleLenShift); - // Invariant: Because we advance sample iterator while processing the - // data_elmt containing the sample's last byte, the current sample - // cannot end before the current data_elmt. - assert(data_begin_offset < sample_end_offset); - - size_t data_elmt_copy_offset, data_elmt_copy_len; - if (*sample_begin_offset_iter <= data_begin_offset) { - // The sample starts before data_elmt starts, so take bytes starting - // at the beginning of data_elmt. - data_elmt_copy_offset = 0; - } else { - // data_elmt starts before the sample starts, so take bytes starting - // at the below offset into data_elmt. - data_elmt_copy_offset = - *sample_begin_offset_iter - data_begin_offset; - } - if (sample_end_offset <= data_end_offset) { - // The sample ends before data_elmt ends, so take as many bytes as - // needed. - data_elmt_copy_len = - sample_end_offset - (data_begin_offset + data_elmt_copy_offset); - } else { - // data_elmt ends before the sample ends, so take all remaining - // bytes in data_elmt. - data_elmt_copy_len = - data_end_offset - (data_begin_offset + data_elmt_copy_offset); - } - compression_dict.append(&data_elmt.data()[data_elmt_copy_offset], - data_elmt_copy_len); - if (sample_end_offset > data_end_offset) { - // Didn't finish sample. Try to finish it with the next data_elmt. - break; - } - // Next sample may require bytes from same data_elmt. - sample_begin_offset_iter++; - } - data_begin_offset = data_end_offset; - } - } - // Close output file if it is big enough. Two possibilities determine it's // time to close it: (1) the current key should be this file's last key, (2) // the next key should not be in this file. @@ -886,8 +957,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { c_iter->Next(); if (!output_file_ended && c_iter->Valid() && sub_compact->compaction->output_level() != 0 && - sub_compact->ShouldStopBefore( - c_iter->key(), sub_compact->current_output_file_size) && + sub_compact->ShouldStopBefore(c_iter->key(), + sub_compact->current_output_file_size) && sub_compact->builder != nullptr) { // (2) this key belongs to the next file. For historical reasons, the // iterator status after advancing will be given to @@ -901,16 +972,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { next_key = &c_iter->key(); } CompactionIterationStats range_del_out_stats; - status = FinishCompactionOutputFile(input_status, sub_compact, - range_del_agg.get(), - &range_del_out_stats, next_key); + status = + FinishCompactionOutputFile(input_status, sub_compact, &range_del_agg, + &range_del_out_stats, next_key); RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats); - if (sub_compact->outputs.size() == 1) { - // Use dictionary from first output file for compression of subsequent - // files. - sub_compact->compression_dict = std::move(compression_dict); - } } } @@ -933,8 +999,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { RecordDroppedKeys(c_iter_stats, &sub_compact->compaction_job_stats); RecordCompactionIOStats(); - if (status.ok() && (shutting_down_->load(std::memory_order_relaxed) || - cfd->IsDropped())) { + if (status.ok() && + (shutting_down_->load(std::memory_order_relaxed) || cfd->IsDropped())) { status = Status::ShutdownInProgress( "Database shutdown or Column family drop during compaction"); } @@ -946,8 +1012,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { } if (status.ok() && sub_compact->builder == nullptr && - sub_compact->outputs.size() == 0 && - range_del_agg->ShouldAddTombstones(bottommost_level_)) { + sub_compact->outputs.size() == 0 && !range_del_agg.IsEmpty()) { // handle subcompaction containing only range deletions status = OpenCompactionOutputFile(sub_compact); } @@ -956,14 +1021,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { // close the output file. if (sub_compact->builder != nullptr) { CompactionIterationStats range_del_out_stats; - Status s = FinishCompactionOutputFile( - status, sub_compact, range_del_agg.get(), &range_del_out_stats); + Status s = FinishCompactionOutputFile(status, sub_compact, &range_del_agg, + &range_del_out_stats); if (status.ok()) { status = s; } RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats); } + sub_compact->compaction_job_stats.cpu_micros = + env_->NowCPUNanos() / 1000 - prev_cpu_micros; + if (measure_io_stats_) { sub_compact->compaction_job_stats.file_write_nanos += IOSTATS(write_nanos) - prev_write_nanos; @@ -973,7 +1041,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { IOSTATS(range_sync_nanos) - prev_range_sync_nanos; sub_compact->compaction_job_stats.file_prepare_write_nanos += IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos; - if (prev_perf_level != PerfLevel::kEnableTime) { + sub_compact->compaction_job_stats.cpu_micros -= + (IOSTATS(cpu_write_nanos) - prev_cpu_write_nanos + + IOSTATS(cpu_read_nanos) - prev_cpu_read_nanos) / + 1000; + if (prev_perf_level != PerfLevel::kEnableTimeAndCPUTimeExceptForMutex) { SetPerfLevel(prev_perf_level); } } @@ -1022,7 +1094,7 @@ void CompactionJob::RecordDroppedKeys( Status CompactionJob::FinishCompactionOutputFile( const Status& input_status, SubcompactionState* sub_compact, - RangeDelAggregator* range_del_agg, + CompactionRangeDelAggregator* range_del_agg, CompactionIterationStats* range_del_out_stats, const Slice* next_table_min_key /* = nullptr */) { AutoThreadOperationStageUpdater stage_updater( @@ -1035,48 +1107,185 @@ Status CompactionJob::FinishCompactionOutputFile( uint64_t output_number = sub_compact->current_output()->meta.fd.GetNumber(); assert(output_number != 0); + ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); + const Comparator* ucmp = cfd->user_comparator(); + // Check for iterator errors Status s = input_status; auto meta = &sub_compact->current_output()->meta; + assert(meta != nullptr); if (s.ok()) { Slice lower_bound_guard, upper_bound_guard; + std::string smallest_user_key; const Slice *lower_bound, *upper_bound; + bool lower_bound_from_sub_compact = false; if (sub_compact->outputs.size() == 1) { // For the first output table, include range tombstones before the min key // but after the subcompaction boundary. lower_bound = sub_compact->start; + lower_bound_from_sub_compact = true; } else if (meta->smallest.size() > 0) { // For subsequent output tables, only include range tombstones from min // key onwards since the previous file was extended to contain range // tombstones falling before min key. - lower_bound_guard = meta->smallest.user_key(); + smallest_user_key = meta->smallest.user_key().ToString(false /*hex*/); + lower_bound_guard = Slice(smallest_user_key); lower_bound = &lower_bound_guard; } else { lower_bound = nullptr; } if (next_table_min_key != nullptr) { - // This isn't the last file in the subcompaction, so extend until the next - // file starts. + // This may be the last file in the subcompaction in some cases, so we + // need to compare the end key of subcompaction with the next file start + // key. When the end key is chosen by the subcompaction, we know that + // it must be the biggest key in output file. Therefore, it is safe to + // use the smaller key as the upper bound of the output file, to ensure + // that there is no overlapping between different output files. upper_bound_guard = ExtractUserKey(*next_table_min_key); - upper_bound = &upper_bound_guard; + if (sub_compact->end != nullptr && + ucmp->Compare(upper_bound_guard, *sub_compact->end) >= 0) { + upper_bound = sub_compact->end; + } else { + upper_bound = &upper_bound_guard; + } } else { // This is the last file in the subcompaction, so extend until the // subcompaction ends. upper_bound = sub_compact->end; } - range_del_agg->AddToBuilder(sub_compact->builder.get(), lower_bound, - upper_bound, meta, range_del_out_stats, - bottommost_level_); + auto earliest_snapshot = kMaxSequenceNumber; + if (existing_snapshots_.size() > 0) { + earliest_snapshot = existing_snapshots_[0]; + } + bool has_overlapping_endpoints; + if (upper_bound != nullptr && meta->largest.size() > 0) { + has_overlapping_endpoints = + ucmp->Compare(meta->largest.user_key(), *upper_bound) == 0; + } else { + has_overlapping_endpoints = false; + } + + // The end key of the subcompaction must be bigger or equal to the upper + // bound. If the end of subcompaction is null or the upper bound is null, + // it means that this file is the last file in the compaction. So there + // will be no overlapping between this file and others. + assert(sub_compact->end == nullptr || + upper_bound == nullptr || + ucmp->Compare(*upper_bound , *sub_compact->end) <= 0); + auto it = range_del_agg->NewIterator(lower_bound, upper_bound, + has_overlapping_endpoints); + // Position the range tombstone output iterator. There may be tombstone + // fragments that are entirely out of range, so make sure that we do not + // include those. + if (lower_bound != nullptr) { + it->Seek(*lower_bound); + } else { + it->SeekToFirst(); + } + for (; it->Valid(); it->Next()) { + auto tombstone = it->Tombstone(); + if (upper_bound != nullptr) { + int cmp = ucmp->Compare(*upper_bound, tombstone.start_key_); + if ((has_overlapping_endpoints && cmp < 0) || + (!has_overlapping_endpoints && cmp <= 0)) { + // Tombstones starting after upper_bound only need to be included in + // the next table. If the current SST ends before upper_bound, i.e., + // `has_overlapping_endpoints == false`, we can also skip over range + // tombstones that start exactly at upper_bound. Such range tombstones + // will be included in the next file and are not relevant to the point + // keys or endpoints of the current file. + break; + } + } + + if (bottommost_level_ && tombstone.seq_ <= earliest_snapshot) { + // TODO(andrewkr): tombstones that span multiple output files are + // counted for each compaction output file, so lots of double counting. + range_del_out_stats->num_range_del_drop_obsolete++; + range_del_out_stats->num_record_drop_obsolete++; + continue; + } + + auto kv = tombstone.Serialize(); + assert(lower_bound == nullptr || + ucmp->Compare(*lower_bound, kv.second) < 0); + sub_compact->builder->Add(kv.first.Encode(), kv.second); + InternalKey smallest_candidate = std::move(kv.first); + if (lower_bound != nullptr && + ucmp->Compare(smallest_candidate.user_key(), *lower_bound) <= 0) { + // Pretend the smallest key has the same user key as lower_bound + // (the max key in the previous table or subcompaction) in order for + // files to appear key-space partitioned. + // + // When lower_bound is chosen by a subcompaction, we know that + // subcompactions over smaller keys cannot contain any keys at + // lower_bound. We also know that smaller subcompactions exist, because + // otherwise the subcompaction woud be unbounded on the left. As a + // result, we know that no other files on the output level will contain + // actual keys at lower_bound (an output file may have a largest key of + // lower_bound@kMaxSequenceNumber, but this only indicates a large range + // tombstone was truncated). Therefore, it is safe to use the + // tombstone's sequence number, to ensure that keys at lower_bound at + // lower levels are covered by truncated tombstones. + // + // If lower_bound was chosen by the smallest data key in the file, + // choose lowest seqnum so this file's smallest internal key comes after + // the previous file's largest. The fake seqnum is OK because the read + // path's file-picking code only considers user key. + smallest_candidate = InternalKey( + *lower_bound, lower_bound_from_sub_compact ? tombstone.seq_ : 0, + kTypeRangeDeletion); + } + InternalKey largest_candidate = tombstone.SerializeEndKey(); + if (upper_bound != nullptr && + ucmp->Compare(*upper_bound, largest_candidate.user_key()) <= 0) { + // Pretend the largest key has the same user key as upper_bound (the + // min key in the following table or subcompaction) in order for files + // to appear key-space partitioned. + // + // Choose highest seqnum so this file's largest internal key comes + // before the next file's/subcompaction's smallest. The fake seqnum is + // OK because the read path's file-picking code only considers the user + // key portion. + // + // Note Seek() also creates InternalKey with (user_key, + // kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of + // kTypeRangeDeletion (0xF), so the range tombstone comes before the + // Seek() key in InternalKey's ordering. So Seek() will look in the + // next file for the user key. + largest_candidate = + InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion); + } +#ifndef NDEBUG + SequenceNumber smallest_ikey_seqnum = kMaxSequenceNumber; + if (meta->smallest.size() > 0) { + smallest_ikey_seqnum = GetInternalKeySeqno(meta->smallest.Encode()); + } +#endif + meta->UpdateBoundariesForRange(smallest_candidate, largest_candidate, + tombstone.seq_, + cfd->internal_comparator()); + + // The smallest key in a file is used for range tombstone truncation, so + // it cannot have a seqnum of 0 (unless the smallest data key in a file + // has a seqnum of 0). Otherwise, the truncated tombstone may expose + // deleted keys at lower levels. + assert(smallest_ikey_seqnum == 0 || + ExtractInternalKeyFooter(meta->smallest.Encode()) != + PackSequenceAndType(0, kTypeRangeDeletion)); + } + meta->marked_for_compaction = sub_compact->builder->NeedCompact(); } const uint64_t current_entries = sub_compact->builder->NumEntries(); - meta->marked_for_compaction = sub_compact->builder->NeedCompact(); if (s.ok()) { s = sub_compact->builder->Finish(); } else { sub_compact->builder->Abandon(); } const uint64_t current_bytes = sub_compact->builder->FileSize(); - meta->fd.file_size = current_bytes; + if (s.ok()) { + meta->fd.file_size = current_bytes; + } sub_compact->current_output()->finished = true; sub_compact->total_bytes += current_bytes; @@ -1090,92 +1299,67 @@ Status CompactionJob::FinishCompactionOutputFile( } sub_compact->outfile.reset(); - if (s.ok() && current_entries == 0) { + TableProperties tp; + if (s.ok()) { + tp = sub_compact->builder->GetTableProperties(); + } + + if (s.ok() && current_entries == 0 && tp.num_range_deletions == 0) { // If there is nothing to output, no necessary to generate a sst file. // This happens when the output level is bottom level, at the same time // the sub_compact output nothing. - std::string fname = TableFileName( - db_options_.db_paths, meta->fd.GetNumber(), meta->fd.GetPathId()); + std::string fname = + TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths, + meta->fd.GetNumber(), meta->fd.GetPathId()); env_->DeleteFile(fname); // Also need to remove the file from outputs, or it will be added to the // VersionEdit. assert(!sub_compact->outputs.empty()); sub_compact->outputs.pop_back(); - sub_compact->builder.reset(); - sub_compact->current_output_file_size = 0; - return s; + meta = nullptr; } - ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); - TableProperties tp; - if (s.ok() && current_entries > 0) { - // Verify that the table is usable - // We set for_compaction to false and don't OptimizeForCompactionTableRead - // here because this is a special case after we finish the table building - // No matter whether use_direct_io_for_flush_and_compaction is true, - // we will regrad this verification as user reads since the goal is - // to cache it here for further user reads - InternalIterator* iter = cfd->table_cache()->NewIterator( - ReadOptions(), env_options_, cfd->internal_comparator(), meta->fd, - nullptr /* range_del_agg */, nullptr, - cfd->internal_stats()->GetFileReadHist( - compact_->compaction->output_level()), - false); - s = iter->status(); - - if (s.ok() && paranoid_file_checks_) { - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {} - s = iter->status(); - } - - delete iter; - + if (s.ok() && (current_entries > 0 || tp.num_range_deletions > 0)) { // Output to event logger and fire events. - if (s.ok()) { - tp = sub_compact->builder->GetTableProperties(); - sub_compact->current_output()->table_properties = - std::make_shared(tp); - ROCKS_LOG_INFO(db_options_.info_log, - "[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64 - " keys, %" PRIu64 " bytes%s", - cfd->GetName().c_str(), job_id_, output_number, - current_entries, current_bytes, - meta->marked_for_compaction ? " (need compaction)" : ""); - } + sub_compact->current_output()->table_properties = + std::make_shared(tp); + ROCKS_LOG_INFO(db_options_.info_log, + "[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64 + " keys, %" PRIu64 " bytes%s", + cfd->GetName().c_str(), job_id_, output_number, + current_entries, current_bytes, + meta->marked_for_compaction ? " (need compaction)" : ""); + } + std::string fname; + FileDescriptor output_fd; + if (meta != nullptr) { + fname = + TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths, + meta->fd.GetNumber(), meta->fd.GetPathId()); + output_fd = meta->fd; + } else { + fname = "(nil)"; } - std::string fname = TableFileName(db_options_.db_paths, meta->fd.GetNumber(), - meta->fd.GetPathId()); EventHelpers::LogAndNotifyTableFileCreationFinished( event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname, - job_id_, meta->fd, tp, TableFileCreationReason::kCompaction, s); + job_id_, output_fd, tp, TableFileCreationReason::kCompaction, s); #ifndef ROCKSDB_LITE // Report new file to SstFileManagerImpl auto sfm = static_cast(db_options_.sst_file_manager.get()); - if (sfm && meta->fd.GetPathId() == 0) { - auto fn = TableFileName(cfd->ioptions()->db_paths, meta->fd.GetNumber(), - meta->fd.GetPathId()); - sfm->OnAddFile(fn); + if (sfm && meta != nullptr && meta->fd.GetPathId() == 0) { + sfm->OnAddFile(fname); if (sfm->IsMaxAllowedSpaceReached()) { // TODO(ajkr): should we return OK() if max space was reached by the final // compaction output file (similarly to how flush works when full)? - s = Status::IOError("Max allowed space was reached"); + s = Status::SpaceLimit("Max allowed space was reached"); TEST_SYNC_POINT( "CompactionJob::FinishCompactionOutputFile:" "MaxAllowedSpaceReached"); InstrumentedMutexLock l(db_mutex_); - if (db_bg_error_->ok()) { - Status new_bg_error = s; - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError( - cfd->ioptions()->listeners, BackgroundErrorReason::kCompaction, - &new_bg_error, db_mutex_); - if (!new_bg_error.ok()) { - *db_bg_error_ = new_bg_error; - } - } + db_error_handler_->SetBGError(s, BackgroundErrorReason::kCompaction); } } #endif @@ -1211,7 +1395,7 @@ Status CompactionJob::InstallCompactionResults( compaction->InputLevelSummary(&inputs_summary), compact_->total_bytes); } - // Add compaction outputs + // Add compaction inputs compaction->AddInputDeletions(compact_->compaction->edit()); for (const auto& sub_compact : compact_->sub_compact_states) { @@ -1241,8 +1425,9 @@ Status CompactionJob::OpenCompactionOutputFile( assert(sub_compact->builder == nullptr); // no need to lock because VersionSet::next_file_number_ is atomic uint64_t file_number = versions_->NewFileNumber(); - std::string fname = TableFileName(db_options_.db_paths, file_number, - sub_compact->compaction->output_path_id()); + std::string fname = + TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths, + file_number, sub_compact->compaction->output_path_id()); // Fire events. ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); #ifndef ROCKSDB_LITE @@ -1251,12 +1436,13 @@ Status CompactionJob::OpenCompactionOutputFile( TableFileCreationReason::kCompaction); #endif // !ROCKSDB_LITE // Make the output file - unique_ptr writable_file; - EnvOptions opt_env_opts = - env_->OptimizeForCompactionTableWrite(env_options_, db_options_); + std::unique_ptr writable_file; +#ifndef NDEBUG + bool syncpoint_arg = env_options_.use_direct_writes; TEST_SYNC_POINT_CALLBACK("CompactionJob::OpenCompactionOutputFile", - &opt_env_opts.use_direct_writes); - Status s = NewWritableFile(env_, fname, &writable_file, opt_env_opts); + &syncpoint_arg); +#endif + Status s = NewWritableFile(env_, fname, &writable_file, env_options_); if (!s.ok()) { ROCKS_LOG_ERROR( db_options_.info_log, @@ -1279,10 +1465,14 @@ Status CompactionJob::OpenCompactionOutputFile( sub_compact->outputs.push_back(out); writable_file->SetIOPriority(Env::IO_LOW); + writable_file->SetWriteLifeTimeHint(write_hint_); writable_file->SetPreallocationBlockSize(static_cast( sub_compact->compaction->OutputFilePreallocationSize())); - sub_compact->outfile.reset(new WritableFileWriter( - std::move(writable_file), env_options_, db_options_.statistics.get())); + const auto& listeners = + sub_compact->compaction->immutable_cf_options()->listeners; + sub_compact->outfile.reset( + new WritableFileWriter(std::move(writable_file), fname, env_options_, + env_, db_options_.statistics.get(), listeners)); // If the Column family flag is to only optimize filters for hits, // we can skip creating filters if this is the bottommost_level where @@ -1294,17 +1484,28 @@ Status CompactionJob::OpenCompactionOutputFile( sub_compact->compaction->MaxInputFileCreationTime(); if (output_file_creation_time == 0) { int64_t _current_time = 0; - db_options_.env->GetCurrentTime(&_current_time); // ignore error + auto status = db_options_.env->GetCurrentTime(&_current_time); + // Safe to proceed even if GetCurrentTime fails. So, log and proceed. + if (!status.ok()) { + ROCKS_LOG_WARN( + db_options_.info_log, + "Failed to get current time to populate creation_time property. " + "Status: %s", + status.ToString().c_str()); + } output_file_creation_time = static_cast(_current_time); } sub_compact->builder.reset(NewTableBuilder( - *cfd->ioptions(), cfd->internal_comparator(), - cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(), - sub_compact->outfile.get(), sub_compact->compaction->output_compression(), - cfd->ioptions()->compression_opts, - sub_compact->compaction->output_level(), &sub_compact->compression_dict, - skip_filters, output_file_creation_time)); + *cfd->ioptions(), *(sub_compact->compaction->mutable_cf_options()), + cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(), + cfd->GetID(), cfd->GetName(), sub_compact->outfile.get(), + sub_compact->compaction->output_compression(), + 0 /*sample_for_compression */, + sub_compact->compaction->output_compression_opts(), + sub_compact->compaction->output_level(), skip_filters, + output_file_creation_time, 0 /* oldest_key_time */, + sub_compact->compaction->max_output_file_size())); LogFlush(db_options_.info_log); return s; } @@ -1334,8 +1535,7 @@ void CompactionJob::CleanupCompaction() { #ifndef ROCKSDB_LITE namespace { -void CopyPrefix( - const Slice& src, size_t prefix_length, std::string* dst) { +void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) { assert(prefix_length > 0); size_t length = src.size() > prefix_length ? prefix_length : src.size(); dst->assign(src.data(), length); @@ -1354,13 +1554,11 @@ void CompactionJob::UpdateCompactionStats() { if (compaction->level(input_level) != compaction->output_level()) { UpdateCompactionInputStatsHelper( &compaction_stats_.num_input_files_in_non_output_levels, - &compaction_stats_.bytes_read_non_output_levels, - input_level); + &compaction_stats_.bytes_read_non_output_levels, input_level); } else { UpdateCompactionInputStatsHelper( &compaction_stats_.num_input_files_in_output_level, - &compaction_stats_.bytes_read_output_level, - input_level); + &compaction_stats_.bytes_read_output_level, input_level); } } @@ -1383,8 +1581,9 @@ void CompactionJob::UpdateCompactionStats() { } } -void CompactionJob::UpdateCompactionInputStatsHelper( - int* num_files, uint64_t* bytes_read, int input_level) { +void CompactionJob::UpdateCompactionInputStatsHelper(int* num_files, + uint64_t* bytes_read, + int input_level) { const Compaction* compaction = compact_->compaction; auto num_input_files = compaction->num_input_files(input_level); *num_files += static_cast(num_input_files); @@ -1405,10 +1604,8 @@ void CompactionJob::UpdateCompactionJobStats( // input information compaction_job_stats_->total_input_bytes = - stats.bytes_read_non_output_levels + - stats.bytes_read_output_level; - compaction_job_stats_->num_input_records = - compact_->num_input_records; + stats.bytes_read_non_output_levels + stats.bytes_read_output_level; + compaction_job_stats_->num_input_records = compact_->num_input_records; compaction_job_stats_->num_input_files = stats.num_input_files_in_non_output_levels + stats.num_input_files_in_output_level; @@ -1417,21 +1614,20 @@ void CompactionJob::UpdateCompactionJobStats( // output information compaction_job_stats_->total_output_bytes = stats.bytes_written; - compaction_job_stats_->num_output_records = - compact_->num_output_records; + compaction_job_stats_->num_output_records = compact_->num_output_records; compaction_job_stats_->num_output_files = stats.num_output_files; if (compact_->NumOutputFiles() > 0U) { - CopyPrefix( - compact_->SmallestUserKey(), - CompactionJobStats::kMaxPrefixLength, - &compaction_job_stats_->smallest_output_key_prefix); - CopyPrefix( - compact_->LargestUserKey(), - CompactionJobStats::kMaxPrefixLength, - &compaction_job_stats_->largest_output_key_prefix); + CopyPrefix(compact_->SmallestUserKey(), + CompactionJobStats::kMaxPrefixLength, + &compaction_job_stats_->smallest_output_key_prefix); + CopyPrefix(compact_->LargestUserKey(), + CompactionJobStats::kMaxPrefixLength, + &compaction_job_stats_->largest_output_key_prefix); } } +#else + (void)stats; #endif // !ROCKSDB_LITE } @@ -1454,7 +1650,9 @@ void CompactionJob::LogCompaction() { // build event logger report auto stream = event_logger_->Log(); stream << "job" << job_id_ << "event" - << "compaction_started"; + << "compaction_started" + << "compaction_reason" + << GetCompactionReasonString(compaction->compaction_reason()); for (size_t i = 0; i < compaction->num_input_levels(); ++i) { stream << ("files_L" + ToString(compaction->level(i))); stream.StartArray(); diff --git a/thirdparty/rocksdb/db/compaction_job.h b/thirdparty/rocksdb/db/compaction_job.h index 6ca5d627a7..9767985f33 100644 --- a/thirdparty/rocksdb/db/compaction_job.h +++ b/thirdparty/rocksdb/db/compaction_job.h @@ -29,6 +29,7 @@ #include "db/version_edit.h" #include "db/write_controller.h" #include "db/write_thread.h" +#include "options/cf_options.h" #include "options/db_options.h" #include "port/port.h" #include "rocksdb/compaction_filter.h" @@ -45,28 +46,33 @@ namespace rocksdb { +class Arena; +class ErrorHandler; class MemTable; +class SnapshotChecker; class TableCache; class Version; class VersionEdit; class VersionSet; -class Arena; class CompactionJob { public: CompactionJob(int job_id, Compaction* compaction, const ImmutableDBOptions& db_options, - const EnvOptions& env_options, VersionSet* versions, - const std::atomic* shutting_down, LogBuffer* log_buffer, - Directory* db_directory, Directory* output_directory, - Statistics* stats, InstrumentedMutex* db_mutex, - Status* db_bg_error, + const EnvOptions env_options, VersionSet* versions, + const std::atomic* shutting_down, + const SequenceNumber preserve_deletes_seqnum, + LogBuffer* log_buffer, Directory* db_directory, + Directory* output_directory, Statistics* stats, + InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler, std::vector existing_snapshots, SequenceNumber earliest_write_conflict_snapshot, + const SnapshotChecker* snapshot_checker, std::shared_ptr table_cache, EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname, - CompactionJobStats* compaction_job_stats); + CompactionJobStats* compaction_job_stats, + Env::Priority thread_pri); ~CompactionJob(); @@ -98,7 +104,7 @@ class CompactionJob { Status FinishCompactionOutputFile( const Status& input_status, SubcompactionState* sub_compact, - RangeDelAggregator* range_del_agg, + CompactionRangeDelAggregator* range_del_agg, CompactionIterationStats* range_del_out_stats, const Slice* next_table_min_key = nullptr); Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options); @@ -127,17 +133,20 @@ class CompactionJob { // DBImpl state const std::string& dbname_; const ImmutableDBOptions& db_options_; - const EnvOptions& env_options_; + const EnvOptions env_options_; Env* env_; + // env_option optimized for compaction table reads + EnvOptions env_optiosn_for_read_; VersionSet* versions_; const std::atomic* shutting_down_; + const SequenceNumber preserve_deletes_seqnum_; LogBuffer* log_buffer_; Directory* db_directory_; Directory* output_directory_; Statistics* stats_; InstrumentedMutex* db_mutex_; - Status* db_bg_error_; + ErrorHandler* db_error_handler_; // If there were two snapshots with seq numbers s1 and // s2 and s1 < s2, and if we find two instances of a key k1 then lies // entirely within s1 and s2, then the earlier version of k1 can be safely @@ -149,6 +158,8 @@ class CompactionJob { // should make sure not to remove evidence that a write occurred. SequenceNumber earliest_write_conflict_snapshot_; + const SnapshotChecker* const snapshot_checker_; + std::shared_ptr table_cache_; EventLogger* event_logger_; @@ -160,6 +171,8 @@ class CompactionJob { std::vector boundaries_; // Stores the approx size of keys covered in the range of each subcompaction std::vector sizes_; + Env::WriteLifeTimeHint write_hint_; + Env::Priority thread_pri_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/compaction_job_stats_test.cc b/thirdparty/rocksdb/db/compaction_job_stats_test.cc index 9a8372f578..48e883bc6c 100644 --- a/thirdparty/rocksdb/db/compaction_job_stats_test.cc +++ b/thirdparty/rocksdb/db/compaction_job_stats_test.cc @@ -98,7 +98,7 @@ class CompactionJobStatsTest : public testing::Test, CompactionJobStatsTest() : env_(Env::Default()) { env_->SetBackgroundThreads(1, Env::LOW); env_->SetBackgroundThreads(1, Env::HIGH); - dbname_ = test::TmpDir(env_) + "/compaction_job_stats_test"; + dbname_ = test::PerThreadDBPath("compaction_job_stats_test"); alternative_wal_dir_ = dbname_ + "/wal"; Options options; options.create_if_missing = true; @@ -113,7 +113,7 @@ class CompactionJobStatsTest : public testing::Test, Reopen(options); } - ~CompactionJobStatsTest() { + ~CompactionJobStatsTest() override { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); rocksdb::SyncPoint::GetInstance()->LoadDependency({}); rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); @@ -426,7 +426,7 @@ class CompactionJobStatsChecker : public EventListener { // Once a compaction completed, this function will verify the returned // CompactionJobInfo with the oldest CompactionJobInfo added earlier // in "expected_stats_" which has not yet being used for verification. - virtual void OnCompactionCompleted(DB *db, const CompactionJobInfo& ci) { + void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override { if (verify_next_comp_io_stats_) { ASSERT_GT(ci.stats.file_write_nanos, 0); ASSERT_GT(ci.stats.file_range_sync_nanos, 0); @@ -523,7 +523,7 @@ class CompactionJobDeletionStatsChecker : public CompactionJobStatsChecker { public: // Verifies whether two CompactionJobStats match. void Verify(const CompactionJobStats& current_stats, - const CompactionJobStats& stats) { + const CompactionJobStats& stats) override { ASSERT_EQ( current_stats.num_input_deletion_records, stats.num_input_deletion_records); @@ -806,7 +806,7 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) { stats_checker->set_verify_next_comp_io_stats(true); std::atomic first_prepare_write(true); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Append:BeforePrepareWrite", [&](void* arg) { + "WritableFileWriter::Append:BeforePrepareWrite", [&](void* /*arg*/) { if (first_prepare_write.load()) { options.env->SleepForMicroseconds(3); first_prepare_write.store(false); @@ -815,7 +815,7 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) { std::atomic first_flush(true); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::Flush:BeforeAppend", [&](void* arg) { + "WritableFileWriter::Flush:BeforeAppend", [&](void* /*arg*/) { if (first_flush.load()) { options.env->SleepForMicroseconds(3); first_flush.store(false); @@ -824,7 +824,7 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) { std::atomic first_sync(true); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::SyncInternal:0", [&](void* arg) { + "WritableFileWriter::SyncInternal:0", [&](void* /*arg*/) { if (first_sync.load()) { options.env->SleepForMicroseconds(3); first_sync.store(false); @@ -833,7 +833,7 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) { std::atomic first_range_sync(true); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "WritableFileWriter::RangeSync:0", [&](void* arg) { + "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) { if (first_range_sync.load()) { options.env->SleepForMicroseconds(3); first_range_sync.store(false); @@ -1034,7 +1034,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED, not supported in ROCKSDB_LITE\n"); return 0; } @@ -1043,5 +1043,5 @@ int main(int argc, char** argv) { #else -int main(int argc, char** argv) { return 0; } +int main(int /*argc*/, char** /*argv*/) { return 0; } #endif // !defined(IOS_CROSS_COMPILE) diff --git a/thirdparty/rocksdb/db/compaction_job_test.cc b/thirdparty/rocksdb/db/compaction_job_test.cc index cace1814ad..f05a8ec2ff 100644 --- a/thirdparty/rocksdb/db/compaction_job_test.cc +++ b/thirdparty/rocksdb/db/compaction_job_test.cc @@ -12,6 +12,7 @@ #include "db/column_family.h" #include "db/compaction_job.h" +#include "db/error_handler.h" #include "db/version_set.h" #include "rocksdb/cache.h" #include "rocksdb/db.h" @@ -67,7 +68,7 @@ class CompactionJobTest : public testing::Test { public: CompactionJobTest() : env_(Env::Default()), - dbname_(test::TmpDir() + "/compaction_job_test"), + dbname_(test::PerThreadDBPath("compaction_job_test")), db_options_(), mutable_cf_options_(cf_options_), table_cache_(NewLRUCache(50000, 16)), @@ -76,7 +77,9 @@ class CompactionJobTest : public testing::Test { table_cache_.get(), &write_buffer_manager_, &write_controller_)), shutting_down_(false), - mock_table_factory_(new mock::MockTableFactory()) { + preserve_deletes_seqnum_(0), + mock_table_factory_(new mock::MockTableFactory()), + error_handler_(nullptr, db_options_, &mutex_) { EXPECT_OK(env_->CreateDirIfMissing(dbname_)); db_options_.db_paths.emplace_back(dbname_, std::numeric_limits::max()); @@ -142,7 +145,8 @@ class CompactionJobTest : public testing::Test { } void SetLastSequence(const SequenceNumber sequence_number) { - versions_->SetLastToBeWrittenSequence(sequence_number + 1); + versions_->SetLastAllocatedSequence(sequence_number + 1); + versions_->SetLastPublishedSequence(sequence_number + 1); versions_->SetLastSequence(sequence_number + 1); } @@ -168,7 +172,7 @@ class CompactionJobTest : public testing::Test { // This is how the key will look like once it's written in bottommost // file InternalKey bottommost_internal_key( - key, (key == "9999") ? sequence_number : 0, kTypeValue); + key, 0, kTypeValue); if (corrupt_id(k)) { test::CorruptKeyType(&internal_key); @@ -196,12 +200,12 @@ class CompactionJobTest : public testing::Test { new_db.SetLastSequence(0); const std::string manifest = DescriptorFileName(dbname_, 1); - unique_ptr file; + std::unique_ptr file; Status s = env_->NewWritableFile( manifest, &file, env_->OptimizeForManifestWrite(env_options_)); ASSERT_OK(s); - unique_ptr file_writer( - new WritableFileWriter(std::move(file), env_options_)); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(file), manifest, env_options_)); { log::Writer log(std::move(file_writer), 0, false); std::string record; @@ -244,18 +248,22 @@ class CompactionJobTest : public testing::Test { Compaction compaction(cfd->current()->storage_info(), *cfd->ioptions(), *cfd->GetLatestMutableCFOptions(), compaction_input_files, 1, 1024 * 1024, - 10 * 1024 * 1024, 0, kNoCompression, {}, true); + 10 * 1024 * 1024, 0, kNoCompression, + cfd->ioptions()->compression_opts, 0, {}, true); compaction.SetInputVersion(cfd->current()); LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get()); mutex_.Lock(); EventLogger event_logger(db_options_.info_log.get()); + // TODO(yiwu) add a mock snapshot checker and add test for it. + SnapshotChecker* snapshot_checker = nullptr; CompactionJob compaction_job( 0, &compaction, db_options_, env_options_, versions_.get(), - &shutting_down_, &log_buffer, nullptr, nullptr, nullptr, &mutex_, - &bg_error_, snapshots, earliest_write_conflict_snapshot, table_cache_, - &event_logger, false, false, dbname_, &compaction_job_stats_); - + &shutting_down_, preserve_deletes_seqnum_, &log_buffer, nullptr, + nullptr, nullptr, &mutex_, &error_handler_, snapshots, + earliest_write_conflict_snapshot, snapshot_checker, table_cache_, + &event_logger, false, false, dbname_, &compaction_job_stats_, + Env::Priority::USER); VerifyInitializationOfCompactionJobStats(compaction_job_stats_); compaction_job.Prepare(); @@ -291,12 +299,13 @@ class CompactionJobTest : public testing::Test { std::unique_ptr versions_; InstrumentedMutex mutex_; std::atomic shutting_down_; + SequenceNumber preserve_deletes_seqnum_; std::shared_ptr mock_table_factory_; CompactionJobStats compaction_job_stats_; ColumnFamilyData* cfd_; std::unique_ptr compaction_filter_; std::shared_ptr merge_op_; - Status bg_error_; + ErrorHandler error_handler_; }; TEST_F(CompactionJobTest, Simple) { @@ -371,7 +380,7 @@ TEST_F(CompactionJobTest, SimpleOverwrite) { auto expected_results = mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "val2"}, - {KeyStr("b", 4U, kTypeValue), "val3"}}); + {KeyStr("b", 0U, kTypeValue), "val3"}}); SetLastSequence(4U); auto files = cfd_->current()->storage_info()->LevelFiles(0); @@ -424,7 +433,7 @@ TEST_F(CompactionJobTest, SimpleMerge) { auto expected_results = mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"}, - {KeyStr("b", 2U, kTypeValue), "1,2"}}); + {KeyStr("b", 0U, kTypeValue), "1,2"}}); SetLastSequence(5U); auto files = cfd_->current()->storage_info()->LevelFiles(0); @@ -448,8 +457,7 @@ TEST_F(CompactionJobTest, NonAssocMerge) { auto expected_results = mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), "3,4,5"}, - {KeyStr("b", 2U, kTypeMerge), "2"}, - {KeyStr("b", 1U, kTypeMerge), "1"}}); + {KeyStr("b", 0U, kTypeValue), "1,2"}}); SetLastSequence(5U); auto files = cfd_->current()->storage_info()->LevelFiles(0); @@ -476,7 +484,7 @@ TEST_F(CompactionJobTest, MergeOperandFilter) { auto expected_results = mock::MakeMockFile({{KeyStr("a", 0U, kTypeValue), test::EncodeInt(8U)}, - {KeyStr("b", 2U, kTypeMerge), test::EncodeInt(2U)}}); + {KeyStr("b", 0U, kTypeValue), test::EncodeInt(2U)}}); SetLastSequence(5U); auto files = cfd_->current()->storage_info()->LevelFiles(0); @@ -739,7 +747,7 @@ TEST_F(CompactionJobTest, SingleDeleteZeroSeq) { AddMockFile(file2); auto expected_results = mock::MakeMockFile({ - {KeyStr("dummy", 5U, kTypeValue), "val2"}, + {KeyStr("dummy", 0U, kTypeValue), "val2"}, }); SetLastSequence(22U); @@ -923,7 +931,7 @@ TEST_F(CompactionJobTest, CorruptionAfterDeletion) { mock::MakeMockFile({{test::KeyStr("A", 0U, kTypeValue), "val3"}, {test::KeyStr("a", 0U, kTypeValue, true), "val"}, {test::KeyStr("b", 0U, kTypeValue, true), "val"}, - {test::KeyStr("c", 1U, kTypeValue), "val2"}}); + {test::KeyStr("c", 0U, kTypeValue), "val2"}}); SetLastSequence(6U); auto files = cfd_->current()->storage_info()->LevelFiles(0); @@ -940,7 +948,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as CompactionJobStats is not supported in ROCKSDB_LITE\n"); return 0; diff --git a/thirdparty/rocksdb/db/compaction_picker.cc b/thirdparty/rocksdb/db/compaction_picker.cc index 79af3ed9fe..6510d4bc0c 100644 --- a/thirdparty/rocksdb/db/compaction_picker.cc +++ b/thirdparty/rocksdb/db/compaction_picker.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include "db/column_family.h" #include "monitoring/statistics.h" #include "util/filename.h" @@ -36,12 +37,13 @@ uint64_t TotalCompensatedFileSize(const std::vector& files) { } return sum; } +} // anonymous namespace bool FindIntraL0Compaction(const std::vector& level_files, size_t min_files_to_compact, uint64_t max_compact_bytes_per_del_file, CompactionInputFiles* comp_inputs) { - size_t compact_bytes = level_files[0]->fd.file_size; + size_t compact_bytes = static_cast(level_files[0]->fd.file_size); size_t compact_bytes_per_del_file = port::kMaxSizet; // compaction range will be [0, span_len). size_t span_len; @@ -49,7 +51,7 @@ bool FindIntraL0Compaction(const std::vector& level_files, // increasing. size_t new_compact_bytes_per_del_file = 0; for (span_len = 1; span_len < level_files.size(); ++span_len) { - compact_bytes += level_files[span_len]->fd.file_size; + compact_bytes += static_cast(level_files[span_len]->fd.file_size); new_compact_bytes_per_del_file = compact_bytes / span_len; if (level_files[span_len]->being_compacted || new_compact_bytes_per_del_file > compact_bytes_per_del_file) { @@ -59,7 +61,7 @@ bool FindIntraL0Compaction(const std::vector& level_files, } if (span_len >= min_files_to_compact && - new_compact_bytes_per_del_file < max_compact_bytes_per_del_file) { + compact_bytes_per_del_file < max_compact_bytes_per_del_file) { assert(comp_inputs != nullptr); comp_inputs->level = 0; for (size_t i = 0; i < span_len; ++i) { @@ -69,7 +71,6 @@ bool FindIntraL0Compaction(const std::vector& level_files, } return false; } -} // anonymous namespace // Determine compression type, based on user options, level of the output // file and whether compression is disabled. @@ -89,7 +90,7 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions, // If bottommost_compression is set and we are compacting to the // bottommost level then we should use it. if (ioptions.bottommost_compression != kDisableCompressionOption && - level > base_level && level >= (vstorage->num_non_empty_levels() - 1)) { + level >= (vstorage->num_non_empty_levels() - 1)) { return ioptions.bottommost_compression; } // If the user has specified a different compression level for each level, @@ -110,6 +111,24 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions, } } +CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions, + const VersionStorageInfo* vstorage, + int level, + const bool enable_compression) { + if (!enable_compression) { + return ioptions.compression_opts; + } + // If bottommost_compression is set and we are compacting to the + // bottommost level then we should use the specified compression options + // for the bottmomost_compression. + if (ioptions.bottommost_compression != kDisableCompressionOption && + level >= (vstorage->num_non_empty_levels() - 1) && + ioptions.bottommost_compression_opts.enabled) { + return ioptions.bottommost_compression_opts; + } + return ioptions.compression_opts; +} + CompactionPicker::CompactionPicker(const ImmutableCFOptions& ioptions, const InternalKeyComparator* icmp) : ioptions_(ioptions), icmp_(icmp) {} @@ -199,9 +218,10 @@ void CompactionPicker::GetRange(const std::vector& inputs, assert(initialized); } -bool CompactionPicker::ExpandInputsToCleanCut(const std::string& cf_name, +bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/, VersionStorageInfo* vstorage, - CompactionInputFiles* inputs) { + CompactionInputFiles* inputs, + InternalKey** next_smallest) { // This isn't good compaction assert(!inputs->empty()); @@ -224,7 +244,8 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& cf_name, GetRange(*inputs, &smallest, &largest); inputs->clear(); vstorage->GetOverlappingInputs(level, &smallest, &largest, &inputs->files, - hint_index, &hint_index); + hint_index, &hint_index, true, + next_smallest); } while (inputs->size() > old_size); // we started off with inputs non-empty and the previous loop only grew @@ -292,25 +313,34 @@ Compaction* CompactionPicker::CompactFiles( VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options, uint32_t output_path_id) { assert(input_files.size()); - - // TODO(rven ): we might be able to run concurrent level 0 compaction - // if the key ranges of the two compactions do not overlap, but for now - // we do not allow it. - if ((input_files[0].level == 0) && !level0_compactions_in_progress_.empty()) { - return nullptr; - } - // This compaction output could overlap with a running compaction - if (FilesRangeOverlapWithCompaction(input_files, output_level)) { - return nullptr; + // This compaction output should not overlap with a running compaction as + // `SanitizeCompactionInputFiles` should've checked earlier and db mutex + // shouldn't have been released since. + assert(!FilesRangeOverlapWithCompaction(input_files, output_level)); + + CompressionType compression_type; + if (compact_options.compression == kDisableCompressionOption) { + int base_level; + if (ioptions_.compaction_style == kCompactionStyleLevel) { + base_level = vstorage->base_level(); + } else { + base_level = 1; + } + compression_type = + GetCompressionType(ioptions_, vstorage, mutable_cf_options, + output_level, base_level); + } else { + // TODO(ajkr): `CompactionOptions` offers configurable `CompressionType` + // without configurable `CompressionOptions`, which is inconsistent. + compression_type = compact_options.compression; } - auto c = - new Compaction(vstorage, ioptions_, mutable_cf_options, input_files, - output_level, compact_options.output_file_size_limit, - mutable_cf_options.max_compaction_bytes, output_path_id, - compact_options.compression, /* grandparents */ {}, true); - - // If it's level 0 compaction, make sure we don't execute any other level 0 - // compactions in parallel + auto c = new Compaction( + vstorage, ioptions_, mutable_cf_options, input_files, output_level, + compact_options.output_file_size_limit, + mutable_cf_options.max_compaction_bytes, output_path_id, compression_type, + GetCompressionOptions(ioptions_, vstorage, output_level), + compact_options.max_subcompactions, + /* grandparents */ {}, true); RegisterCompaction(c); return c; } @@ -318,7 +348,7 @@ Compaction* CompactionPicker::CompactFiles( Status CompactionPicker::GetCompactionInputsFromFileNumbers( std::vector* input_files, std::unordered_set* input_set, const VersionStorageInfo* vstorage, - const CompactionOptions& compact_options) const { + const CompactionOptions& /*compact_options*/) const { if (input_set->size() == 0U) { return Status::InvalidArgument( "Compaction must include at least one file."); @@ -373,7 +403,7 @@ bool CompactionPicker::IsRangeInCompaction(VersionStorageInfo* vstorage, assert(level < NumberLevels()); vstorage->GetOverlappingInputs(level, smallest, largest, &inputs, - *level_index, level_index); + level_index ? *level_index : 0, level_index); return AreFilesInCompaction(inputs); } @@ -396,7 +426,10 @@ bool CompactionPicker::SetupOtherInputs( assert(output_level_inputs->empty()); const int input_level = inputs->level; const int output_level = output_level_inputs->level; - assert(input_level != output_level); + if (input_level == output_level) { + // no possibility of conflict + return true; + } // For now, we only support merging two levels, start level and output level. // We need to assert other levels are empty. @@ -481,7 +514,7 @@ bool CompactionPicker::SetupOtherInputs( ROCKS_LOG_INFO(ioptions_.info_log, "[%s] Expanding@%d %" ROCKSDB_PRIszt "+%" ROCKSDB_PRIszt "(%" PRIu64 "+%" PRIu64 " bytes) to %" ROCKSDB_PRIszt - "+%" ROCKSDB_PRIszt " (%" PRIu64 "+%" PRIu64 "bytes)\n", + "+%" ROCKSDB_PRIszt " (%" PRIu64 "+%" PRIu64 " bytes)\n", cf_name.c_str(), input_level, inputs->size(), output_level_inputs->size(), inputs_size, output_level_inputs_size, expanded_inputs.size(), @@ -510,7 +543,8 @@ void CompactionPicker::GetGrandparents( Compaction* CompactionPicker::CompactRange( const std::string& cf_name, const MutableCFOptions& mutable_cf_options, VersionStorageInfo* vstorage, int input_level, int output_level, - uint32_t output_path_id, const InternalKey* begin, const InternalKey* end, + uint32_t output_path_id, uint32_t max_subcompactions, + const InternalKey* begin, const InternalKey* end, InternalKey** compaction_end, bool* manual_conflict) { // CompactionPickerFIFO has its own implementation of compact range assert(ioptions_.compaction_style != kCompactionStyleFIFO); @@ -572,11 +606,14 @@ Compaction* CompactionPicker::CompactRange( Compaction* c = new Compaction( vstorage, ioptions_, mutable_cf_options, std::move(inputs), - output_level, mutable_cf_options.MaxFileSizeForLevel(output_level), + output_level, + MaxFileSizeForLevel(mutable_cf_options, output_level, + ioptions_.compaction_style), /* max_compaction_bytes */ LLONG_MAX, output_path_id, GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level, 1), - /* grandparents */ {}, /* is manual */ true); + GetCompressionOptions(ioptions_, vstorage, output_level), + max_subcompactions, /* grandparents */ {}, /* is manual */ true); RegisterCompaction(c); return c; } @@ -615,16 +652,18 @@ Compaction* CompactionPicker::CompactRange( uint64_t s = inputs[i]->compensated_file_size; total += s; if (total >= limit) { - **compaction_end = inputs[i + 1]->smallest; covering_the_whole_range = false; inputs.files.resize(i + 1); break; } } } - assert(output_path_id < static_cast(ioptions_.db_paths.size())); + assert(output_path_id < static_cast(ioptions_.cf_paths.size())); - if (ExpandInputsToCleanCut(cf_name, vstorage, &inputs) == false) { + InternalKey key_storage; + InternalKey* next_smallest = &key_storage; + if (ExpandInputsToCleanCut(cf_name, vstorage, &inputs, &next_smallest) == + false) { // manual compaction is now multi-threaded, so it can // happen that ExpandWhileOverlapping fails // we handle it higher in RunManualCompaction @@ -632,8 +671,10 @@ Compaction* CompactionPicker::CompactRange( return nullptr; } - if (covering_the_whole_range) { + if (covering_the_whole_range || !next_smallest) { *compaction_end = nullptr; + } else { + **compaction_end = *next_smallest; } CompactionInputFiles output_level_inputs; @@ -679,11 +720,16 @@ Compaction* CompactionPicker::CompactRange( GetGrandparents(vstorage, inputs, output_level_inputs, &grandparents); Compaction* compaction = new Compaction( vstorage, ioptions_, mutable_cf_options, std::move(compaction_inputs), - output_level, mutable_cf_options.MaxFileSizeForLevel(output_level), + output_level, + MaxFileSizeForLevel(mutable_cf_options, output_level, + ioptions_.compaction_style, vstorage->base_level(), + ioptions_.level_compaction_dynamic_level_bytes), mutable_cf_options.max_compaction_bytes, output_path_id, GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level, vstorage->base_level()), - std::move(grandparents), /* is manual compaction */ true); + GetCompressionOptions(ioptions_, vstorage, output_level), + max_subcompactions, std::move(grandparents), + /* is manual compaction */ true); TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction); RegisterCompaction(compaction); @@ -730,10 +776,6 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels( auto& levels = cf_meta.levels; auto comparator = icmp_->user_comparator(); - // TODO(yhchiang): If there is any input files of L1 or up and there - // is at least one L0 files. All L0 files older than the L0 file needs - // to be included. Otherwise, it is a false conditoin - // TODO(yhchiang): add is_adjustable to CompactionOptions // the smallest and largest key of the current compaction input @@ -794,6 +836,8 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels( } last_included++; } + } else if (output_level > 0) { + last_included = static_cast(current_files.size() - 1); } // include all files between the first and the last compaction input files. @@ -853,6 +897,11 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels( } } } + if (RangeOverlapWithCompaction(smallestkey, largestkey, output_level)) { + return Status::Aborted( + "A running compaction is writing to the same output level in an " + "overlapping key range"); + } return Status::OK(); } @@ -895,8 +944,8 @@ Status CompactionPicker::SanitizeCompactionInputFiles( // any currently-existing files. for (auto file_num : *input_files) { bool found = false; - for (auto level_meta : cf_meta.levels) { - for (auto file_meta : level_meta.files) { + for (const auto& level_meta : cf_meta.levels) { + for (const auto& file_meta : level_meta.files) { if (file_num == TableFileNameToNumber(file_meta.name)) { if (file_meta.being_compacted) { return Status::Aborted("Specified compaction input file " + @@ -947,8 +996,86 @@ void CompactionPicker::UnregisterCompaction(Compaction* c) { compactions_in_progress_.erase(c); } +void CompactionPicker::PickFilesMarkedForCompaction( + const std::string& cf_name, VersionStorageInfo* vstorage, int* start_level, + int* output_level, CompactionInputFiles* start_level_inputs) { + if (vstorage->FilesMarkedForCompaction().empty()) { + return; + } + + auto continuation = [&, cf_name](std::pair level_file) { + // If it's being compacted it has nothing to do here. + // If this assert() fails that means that some function marked some + // files as being_compacted, but didn't call ComputeCompactionScore() + assert(!level_file.second->being_compacted); + *start_level = level_file.first; + *output_level = + (*start_level == 0) ? vstorage->base_level() : *start_level + 1; + + if (*start_level == 0 && !level0_compactions_in_progress()->empty()) { + return false; + } + + start_level_inputs->files = {level_file.second}; + start_level_inputs->level = *start_level; + return ExpandInputsToCleanCut(cf_name, vstorage, start_level_inputs); + }; + + // take a chance on a random file first + Random64 rnd(/* seed */ reinterpret_cast(vstorage)); + size_t random_file_index = static_cast(rnd.Uniform( + static_cast(vstorage->FilesMarkedForCompaction().size()))); + + if (continuation(vstorage->FilesMarkedForCompaction()[random_file_index])) { + // found the compaction! + return; + } + + for (auto& level_file : vstorage->FilesMarkedForCompaction()) { + if (continuation(level_file)) { + // found the compaction! + return; + } + } + start_level_inputs->files.clear(); +} + +bool CompactionPicker::GetOverlappingL0Files( + VersionStorageInfo* vstorage, CompactionInputFiles* start_level_inputs, + int output_level, int* parent_index) { + // Two level 0 compaction won't run at the same time, so don't need to worry + // about files on level 0 being compacted. + assert(level0_compactions_in_progress()->empty()); + InternalKey smallest, largest; + GetRange(*start_level_inputs, &smallest, &largest); + // Note that the next call will discard the file we placed in + // c->inputs_[0] earlier and replace it with an overlapping set + // which will include the picked file. + start_level_inputs->files.clear(); + vstorage->GetOverlappingInputs(0, &smallest, &largest, + &(start_level_inputs->files)); + + // If we include more L0 files in the same compaction run it can + // cause the 'smallest' and 'largest' key to get extended to a + // larger range. So, re-invoke GetRange to get the new key range + GetRange(*start_level_inputs, &smallest, &largest); + if (IsRangeInCompaction(vstorage, &smallest, &largest, output_level, + parent_index)) { + return false; + } + assert(!start_level_inputs->files.empty()); + + return true; +} + bool LevelCompactionPicker::NeedsCompaction( const VersionStorageInfo* vstorage) const { + if (!vstorage->ExpiredTtlFiles().empty()) { + return true; + } + if (!vstorage->BottommostFilesMarkedForCompaction().empty()) { + return true; + } if (!vstorage->FilesMarkedForCompaction().empty()) { return true; } @@ -1012,8 +1139,7 @@ class LevelCompactionBuilder { // otherwise, returns false. bool PickIntraL0Compaction(); - // If there is any file marked for compaction, put put it into inputs. - void PickFilesMarkedForCompaction(); + void PickExpiredTtlFiles(); const std::string& cf_name_; VersionStorageInfo* vstorage_; @@ -1041,8 +1167,8 @@ class LevelCompactionBuilder { static const int kMinFilesForIntraL0Compaction = 4; }; -void LevelCompactionBuilder::PickFilesMarkedForCompaction() { - if (vstorage_->FilesMarkedForCompaction().empty()) { +void LevelCompactionBuilder::PickExpiredTtlFiles() { + if (vstorage_->ExpiredTtlFiles().empty()) { return; } @@ -1055,8 +1181,9 @@ void LevelCompactionBuilder::PickFilesMarkedForCompaction() { output_level_ = (start_level_ == 0) ? vstorage_->base_level() : start_level_ + 1; - if (start_level_ == 0 && - !compaction_picker_->level0_compactions_in_progress()->empty()) { + if ((start_level_ == vstorage_->num_non_empty_levels() - 1) || + (start_level_ == 0 && + !compaction_picker_->level0_compactions_in_progress()->empty())) { return false; } @@ -1066,22 +1193,13 @@ void LevelCompactionBuilder::PickFilesMarkedForCompaction() { &start_level_inputs_); }; - // take a chance on a random file first - Random64 rnd(/* seed */ reinterpret_cast(vstorage_)); - size_t random_file_index = static_cast(rnd.Uniform( - static_cast(vstorage_->FilesMarkedForCompaction().size()))); - - if (continuation(vstorage_->FilesMarkedForCompaction()[random_file_index])) { - // found the compaction! - return; - } - - for (auto& level_file : vstorage_->FilesMarkedForCompaction()) { + for (auto& level_file : vstorage_->ExpiredTtlFiles()) { if (continuation(level_file)) { // found the compaction! return; } } + start_level_inputs_.files.clear(); } @@ -1136,40 +1254,51 @@ void LevelCompactionBuilder::SetupInitialFiles() { // if we didn't find a compaction, check if there are any files marked for // compaction if (start_level_inputs_.empty()) { - is_manual_ = true; parent_index_ = base_index_ = -1; - PickFilesMarkedForCompaction(); + + // PickFilesMarkedForCompaction(); + compaction_picker_->PickFilesMarkedForCompaction( + cf_name_, vstorage_, &start_level_, &output_level_, &start_level_inputs_); if (!start_level_inputs_.empty()) { + is_manual_ = true; compaction_reason_ = CompactionReason::kFilesMarkedForCompaction; + return; + } + + size_t i; + for (i = 0; i < vstorage_->BottommostFilesMarkedForCompaction().size(); + ++i) { + auto& level_and_file = vstorage_->BottommostFilesMarkedForCompaction()[i]; + assert(!level_and_file.second->being_compacted); + start_level_inputs_.level = output_level_ = start_level_ = + level_and_file.first; + start_level_inputs_.files = {level_and_file.second}; + if (compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_, + &start_level_inputs_)) { + break; + } + } + if (i == vstorage_->BottommostFilesMarkedForCompaction().size()) { + start_level_inputs_.clear(); + } else { + assert(!start_level_inputs_.empty()); + compaction_reason_ = CompactionReason::kBottommostFiles; + return; + } + + assert(start_level_inputs_.empty()); + PickExpiredTtlFiles(); + if (!start_level_inputs_.empty()) { + compaction_reason_ = CompactionReason::kTtl; } } } bool LevelCompactionBuilder::SetupOtherL0FilesIfNeeded() { if (start_level_ == 0 && output_level_ != 0) { - // Two level 0 compaction won't run at the same time, so don't need to worry - // about files on level 0 being compacted. - assert(compaction_picker_->level0_compactions_in_progress()->empty()); - InternalKey smallest, largest; - compaction_picker_->GetRange(start_level_inputs_, &smallest, &largest); - // Note that the next call will discard the file we placed in - // c->inputs_[0] earlier and replace it with an overlapping set - // which will include the picked file. - start_level_inputs_.files.clear(); - vstorage_->GetOverlappingInputs(0, &smallest, &largest, - &start_level_inputs_.files); - - // If we include more L0 files in the same compaction run it can - // cause the 'smallest' and 'largest' key to get extended to a - // larger range. So, re-invoke GetRange to get the new key range - compaction_picker_->GetRange(start_level_inputs_, &smallest, &largest); - if (compaction_picker_->IsRangeInCompaction( - vstorage_, &smallest, &largest, output_level_, &parent_index_)) { - return false; - } + return compaction_picker_->GetOverlappingL0Files( + vstorage_, &start_level_inputs_, output_level_, &parent_index_); } - assert(!start_level_inputs_.files.empty()); - return true; } @@ -1242,13 +1371,17 @@ Compaction* LevelCompactionBuilder::PickCompaction() { Compaction* LevelCompactionBuilder::GetCompaction() { auto c = new Compaction( vstorage_, ioptions_, mutable_cf_options_, std::move(compaction_inputs_), - output_level_, mutable_cf_options_.MaxFileSizeForLevel(output_level_), + output_level_, + MaxFileSizeForLevel(mutable_cf_options_, output_level_, + ioptions_.compaction_style, vstorage_->base_level(), + ioptions_.level_compaction_dynamic_level_bytes), mutable_cf_options_.max_compaction_bytes, GetPathId(ioptions_, mutable_cf_options_, output_level_), GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, output_level_, vstorage_->base_level()), - std::move(grandparents_), is_manual_, start_level_score_, - false /* deletion_compaction */, compaction_reason_); + GetCompressionOptions(ioptions_, vstorage_, output_level_), + /* max_subcompactions */ 0, std::move(grandparents_), is_manual_, + start_level_score_, false /* deletion_compaction */, compaction_reason_); // If it's level 0 compaction, make sure we don't execute any other level 0 // compactions in parallel @@ -1271,32 +1404,47 @@ uint32_t LevelCompactionBuilder::GetPathId( const ImmutableCFOptions& ioptions, const MutableCFOptions& mutable_cf_options, int level) { uint32_t p = 0; - assert(!ioptions.db_paths.empty()); + assert(!ioptions.cf_paths.empty()); // size remaining in the most recent path - uint64_t current_path_size = ioptions.db_paths[0].target_size; + uint64_t current_path_size = ioptions.cf_paths[0].target_size; uint64_t level_size; int cur_level = 0; + // max_bytes_for_level_base denotes L1 size. + // We estimate L0 size to be the same as L1. level_size = mutable_cf_options.max_bytes_for_level_base; // Last path is the fallback - while (p < ioptions.db_paths.size() - 1) { + while (p < ioptions.cf_paths.size() - 1) { if (level_size <= current_path_size) { if (cur_level == level) { // Does desired level fit in this path? return p; } else { current_path_size -= level_size; - level_size = static_cast( - level_size * mutable_cf_options.max_bytes_for_level_multiplier); + if (cur_level > 0) { + if (ioptions.level_compaction_dynamic_level_bytes) { + // Currently, level_compaction_dynamic_level_bytes is ignored when + // multiple db paths are specified. https://github.com/facebook/ + // rocksdb/blob/master/db/column_family.cc. + // Still, adding this check to avoid accidentally using + // max_bytes_for_level_multiplier_additional + level_size = static_cast( + level_size * mutable_cf_options.max_bytes_for_level_multiplier); + } else { + level_size = static_cast( + level_size * mutable_cf_options.max_bytes_for_level_multiplier * + mutable_cf_options.MaxBytesMultiplerAdditional(cur_level)); + } + } cur_level++; continue; } } p++; - current_path_size = ioptions.db_paths[p].target_size; + current_path_size = ioptions.cf_paths[p].target_size; } return p; } @@ -1400,195 +1548,4 @@ Compaction* LevelCompactionPicker::PickCompaction( return builder.PickCompaction(); } -#ifndef ROCKSDB_LITE -bool FIFOCompactionPicker::NeedsCompaction( - const VersionStorageInfo* vstorage) const { - const int kLevel0 = 0; - return vstorage->CompactionScore(kLevel0) >= 1; -} - -namespace { -uint64_t GetTotalFilesSize( - const std::vector& files) { - uint64_t total_size = 0; - for (const auto& f : files) { - total_size += f->fd.file_size; - } - return total_size; -} -} // anonymous namespace - -Compaction* FIFOCompactionPicker::PickTTLCompaction( - const std::string& cf_name, const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* vstorage, LogBuffer* log_buffer) { - assert(ioptions_.compaction_options_fifo.ttl > 0); - - const int kLevel0 = 0; - const std::vector& level_files = vstorage->LevelFiles(kLevel0); - uint64_t total_size = GetTotalFilesSize(level_files); - - int64_t _current_time; - auto status = ioptions_.env->GetCurrentTime(&_current_time); - if (!status.ok()) { - ROCKS_LOG_BUFFER(log_buffer, - "[%s] FIFO compaction: Couldn't get current time: %s. " - "Not doing compactions based on TTL. ", - cf_name.c_str(), status.ToString().c_str()); - return nullptr; - } - const uint64_t current_time = static_cast(_current_time); - - std::vector inputs; - inputs.emplace_back(); - inputs[0].level = 0; - - // avoid underflow - if (current_time > ioptions_.compaction_options_fifo.ttl) { - for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) { - auto f = *ritr; - if (f->fd.table_reader != nullptr && - f->fd.table_reader->GetTableProperties() != nullptr) { - auto creation_time = - f->fd.table_reader->GetTableProperties()->creation_time; - if (creation_time == 0 || - creation_time >= - (current_time - ioptions_.compaction_options_fifo.ttl)) { - break; - } - total_size -= f->compensated_file_size; - inputs[0].files.push_back(f); - } - } - } - - // Return a nullptr and proceed to size-based FIFO compaction if: - // 1. there are no files older than ttl OR - // 2. there are a few files older than ttl, but deleting them will not bring - // the total size to be less than max_table_files_size threshold. - if (inputs[0].files.empty() || - total_size > ioptions_.compaction_options_fifo.max_table_files_size) { - return nullptr; - } - - for (const auto& f : inputs[0].files) { - ROCKS_LOG_BUFFER(log_buffer, - "[%s] FIFO compaction: picking file %" PRIu64 - " with creation time %" PRIu64 " for deletion", - cf_name.c_str(), f->fd.GetNumber(), - f->fd.table_reader->GetTableProperties()->creation_time); - } - - Compaction* c = new Compaction( - vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0, - kNoCompression, {}, /* is manual */ false, vstorage->CompactionScore(0), - /* is deletion compaction */ true, CompactionReason::kFIFOTtl); - return c; -} - -Compaction* FIFOCompactionPicker::PickSizeCompaction( - const std::string& cf_name, const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* vstorage, LogBuffer* log_buffer) { - const int kLevel0 = 0; - const std::vector& level_files = vstorage->LevelFiles(kLevel0); - uint64_t total_size = GetTotalFilesSize(level_files); - - if (total_size <= ioptions_.compaction_options_fifo.max_table_files_size || - level_files.size() == 0) { - // total size not exceeded - if (ioptions_.compaction_options_fifo.allow_compaction && - level_files.size() > 0) { - CompactionInputFiles comp_inputs; - if (FindIntraL0Compaction( - level_files, - mutable_cf_options - .level0_file_num_compaction_trigger /* min_files_to_compact */, - mutable_cf_options.write_buffer_size, &comp_inputs)) { - Compaction* c = new Compaction( - vstorage, ioptions_, mutable_cf_options, {comp_inputs}, 0, - 16 * 1024 * 1024 /* output file size limit */, - 0 /* max compaction bytes, not applicable */, - 0 /* output path ID */, mutable_cf_options.compression, {}, - /* is manual */ false, vstorage->CompactionScore(0), - /* is deletion compaction */ false, - CompactionReason::kFIFOReduceNumFiles); - return c; - } - } - - ROCKS_LOG_BUFFER(log_buffer, - "[%s] FIFO compaction: nothing to do. Total size %" PRIu64 - ", max size %" PRIu64 "\n", - cf_name.c_str(), total_size, - ioptions_.compaction_options_fifo.max_table_files_size); - return nullptr; - } - - if (!level0_compactions_in_progress_.empty()) { - ROCKS_LOG_BUFFER( - log_buffer, - "[%s] FIFO compaction: Already executing compaction. No need " - "to run parallel compactions since compactions are very fast", - cf_name.c_str()); - return nullptr; - } - - std::vector inputs; - inputs.emplace_back(); - inputs[0].level = 0; - - for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) { - auto f = *ritr; - total_size -= f->compensated_file_size; - inputs[0].files.push_back(f); - char tmp_fsize[16]; - AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize)); - ROCKS_LOG_BUFFER(log_buffer, - "[%s] FIFO compaction: picking file %" PRIu64 - " with size %s for deletion", - cf_name.c_str(), f->fd.GetNumber(), tmp_fsize); - if (total_size <= ioptions_.compaction_options_fifo.max_table_files_size) { - break; - } - } - - Compaction* c = new Compaction( - vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0, - kNoCompression, {}, /* is manual */ false, vstorage->CompactionScore(0), - /* is deletion compaction */ true, CompactionReason::kFIFOMaxSize); - return c; -} - -Compaction* FIFOCompactionPicker::PickCompaction( - const std::string& cf_name, const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* vstorage, LogBuffer* log_buffer) { - assert(vstorage->num_levels() == 1); - - Compaction* c = nullptr; - if (ioptions_.compaction_options_fifo.ttl > 0) { - c = PickTTLCompaction(cf_name, mutable_cf_options, vstorage, log_buffer); - } - if (c == nullptr) { - c = PickSizeCompaction(cf_name, mutable_cf_options, vstorage, log_buffer); - } - RegisterCompaction(c); - return c; -} - -Compaction* FIFOCompactionPicker::CompactRange( - const std::string& cf_name, const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* vstorage, int input_level, int output_level, - uint32_t output_path_id, const InternalKey* begin, const InternalKey* end, - InternalKey** compaction_end, bool* manual_conflict) { - assert(input_level == 0); - assert(output_level == 0); - *compaction_end = nullptr; - LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.info_log); - Compaction* c = - PickCompaction(cf_name, mutable_cf_options, vstorage, &log_buffer); - log_buffer.FlushBufferToLog(); - return c; -} - -#endif // !ROCKSDB_LITE - } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/compaction_picker.h b/thirdparty/rocksdb/db/compaction_picker.h index f44139c2dd..c60d792852 100644 --- a/thirdparty/rocksdb/db/compaction_picker.h +++ b/thirdparty/rocksdb/db/compaction_picker.h @@ -58,7 +58,8 @@ class CompactionPicker { virtual Compaction* CompactRange( const std::string& cf_name, const MutableCFOptions& mutable_cf_options, VersionStorageInfo* vstorage, int input_level, int output_level, - uint32_t output_path_id, const InternalKey* begin, const InternalKey* end, + uint32_t output_path_id, uint32_t max_subcompactions, + const InternalKey* begin, const InternalKey* end, InternalKey** compaction_end, bool* manual_conflict); // The maximum allowed output level. Default value is NumberLevels() - 1. @@ -88,6 +89,10 @@ class CompactionPicker { // Takes a list of CompactionInputFiles and returns a (manual) Compaction // object. + // + // Caller must provide a set of input files that has been passed through + // `SanitizeCompactionInputFiles` earlier. The lock should not be released + // between that call and this one. Compaction* CompactFiles(const CompactionOptions& compact_options, const std::vector& input_files, int output_level, VersionStorageInfo* vstorage, @@ -146,7 +151,8 @@ class CompactionPicker { // Will return false if it is impossible to apply this compaction. bool ExpandInputsToCleanCut(const std::string& cf_name, VersionStorageInfo* vstorage, - CompactionInputFiles* inputs); + CompactionInputFiles* inputs, + InternalKey** next_smallest = nullptr); // Returns true if any one of the parent files are being compacted bool IsRangeInCompaction(VersionStorageInfo* vstorage, @@ -170,6 +176,15 @@ class CompactionPicker { const CompactionInputFiles& output_level_inputs, std::vector* grandparents); + void PickFilesMarkedForCompaction(const std::string& cf_name, + VersionStorageInfo* vstorage, + int* start_level, int* output_level, + CompactionInputFiles* start_level_inputs); + + bool GetOverlappingL0Files(VersionStorageInfo* vstorage, + CompactionInputFiles* start_level_inputs, + int output_level, int* parent_index); + // Register this compaction in the set of running compactions void RegisterCompaction(Compaction* c); @@ -220,41 +235,6 @@ class LevelCompactionPicker : public CompactionPicker { }; #ifndef ROCKSDB_LITE -class FIFOCompactionPicker : public CompactionPicker { - public: - FIFOCompactionPicker(const ImmutableCFOptions& ioptions, - const InternalKeyComparator* icmp) - : CompactionPicker(ioptions, icmp) {} - - virtual Compaction* PickCompaction(const std::string& cf_name, - const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* version, - LogBuffer* log_buffer) override; - - virtual Compaction* CompactRange( - const std::string& cf_name, const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* vstorage, int input_level, int output_level, - uint32_t output_path_id, const InternalKey* begin, const InternalKey* end, - InternalKey** compaction_end, bool* manual_conflict) override; - - // The maximum allowed output level. Always returns 0. - virtual int MaxOutputLevel() const override { return 0; } - - virtual bool NeedsCompaction( - const VersionStorageInfo* vstorage) const override; - - private: - Compaction* PickTTLCompaction(const std::string& cf_name, - const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* version, - LogBuffer* log_buffer); - - Compaction* PickSizeCompaction(const std::string& cf_name, - const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* version, - LogBuffer* log_buffer); -}; - class NullCompactionPicker : public CompactionPicker { public: NullCompactionPicker(const ImmutableCFOptions& ioptions, @@ -263,36 +243,49 @@ class NullCompactionPicker : public CompactionPicker { virtual ~NullCompactionPicker() {} // Always return "nullptr" - Compaction* PickCompaction(const std::string& cf_name, - const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* vstorage, - LogBuffer* log_buffer) override { + Compaction* PickCompaction(const std::string& /*cf_name*/, + const MutableCFOptions& /*mutable_cf_options*/, + VersionStorageInfo* /*vstorage*/, + LogBuffer* /*log_buffer*/) override { return nullptr; } // Always return "nullptr" - Compaction* CompactRange(const std::string& cf_name, - const MutableCFOptions& mutable_cf_options, - VersionStorageInfo* vstorage, int input_level, - int output_level, uint32_t output_path_id, - const InternalKey* begin, const InternalKey* end, - InternalKey** compaction_end, - bool* manual_conflict) override { + Compaction* CompactRange(const std::string& /*cf_name*/, + const MutableCFOptions& /*mutable_cf_options*/, + VersionStorageInfo* /*vstorage*/, + int /*input_level*/, int /*output_level*/, + uint32_t /*output_path_id*/, + uint32_t /*max_subcompactions*/, + const InternalKey* /*begin*/, + const InternalKey* /*end*/, + InternalKey** /*compaction_end*/, + bool* /*manual_conflict*/) override { return nullptr; } // Always returns false. virtual bool NeedsCompaction( - const VersionStorageInfo* vstorage) const override { + const VersionStorageInfo* /*vstorage*/) const override { return false; } }; #endif // !ROCKSDB_LITE +bool FindIntraL0Compaction(const std::vector& level_files, + size_t min_files_to_compact, + uint64_t max_compact_bytes_per_del_file, + CompactionInputFiles* comp_inputs); + CompressionType GetCompressionType(const ImmutableCFOptions& ioptions, const VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options, int level, int base_level, const bool enable_compression = true); +CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions, + const VersionStorageInfo* vstorage, + int level, + const bool enable_compression = true); + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/compaction_picker_fifo.cc b/thirdparty/rocksdb/db/compaction_picker_fifo.cc new file mode 100644 index 0000000000..9229b2cfb1 --- /dev/null +++ b/thirdparty/rocksdb/db/compaction_picker_fifo.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#include "db/compaction_picker_fifo.h" +#ifndef ROCKSDB_LITE + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include +#include +#include +#include "db/column_family.h" +#include "util/log_buffer.h" +#include "util/string_util.h" + +namespace rocksdb { +namespace { +uint64_t GetTotalFilesSize(const std::vector& files) { + uint64_t total_size = 0; + for (const auto& f : files) { + total_size += f->fd.file_size; + } + return total_size; +} +} // anonymous namespace + +bool FIFOCompactionPicker::NeedsCompaction( + const VersionStorageInfo* vstorage) const { + const int kLevel0 = 0; + return vstorage->CompactionScore(kLevel0) >= 1; +} + +Compaction* FIFOCompactionPicker::PickTTLCompaction( + const std::string& cf_name, const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* vstorage, LogBuffer* log_buffer) { + assert(mutable_cf_options.ttl > 0); + + const int kLevel0 = 0; + const std::vector& level_files = vstorage->LevelFiles(kLevel0); + uint64_t total_size = GetTotalFilesSize(level_files); + + int64_t _current_time; + auto status = ioptions_.env->GetCurrentTime(&_current_time); + if (!status.ok()) { + ROCKS_LOG_BUFFER(log_buffer, + "[%s] FIFO compaction: Couldn't get current time: %s. " + "Not doing compactions based on TTL. ", + cf_name.c_str(), status.ToString().c_str()); + return nullptr; + } + const uint64_t current_time = static_cast(_current_time); + + std::vector inputs; + inputs.emplace_back(); + inputs[0].level = 0; + + // avoid underflow + if (current_time > mutable_cf_options.ttl) { + for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) { + auto f = *ritr; + if (f->fd.table_reader != nullptr && + f->fd.table_reader->GetTableProperties() != nullptr) { + auto creation_time = + f->fd.table_reader->GetTableProperties()->creation_time; + if (creation_time == 0 || + creation_time >= (current_time - mutable_cf_options.ttl)) { + break; + } + total_size -= f->compensated_file_size; + inputs[0].files.push_back(f); + } + } + } + + // Return a nullptr and proceed to size-based FIFO compaction if: + // 1. there are no files older than ttl OR + // 2. there are a few files older than ttl, but deleting them will not bring + // the total size to be less than max_table_files_size threshold. + if (inputs[0].files.empty() || + total_size > + mutable_cf_options.compaction_options_fifo.max_table_files_size) { + return nullptr; + } + + for (const auto& f : inputs[0].files) { + ROCKS_LOG_BUFFER(log_buffer, + "[%s] FIFO compaction: picking file %" PRIu64 + " with creation time %" PRIu64 " for deletion", + cf_name.c_str(), f->fd.GetNumber(), + f->fd.table_reader->GetTableProperties()->creation_time); + } + + Compaction* c = new Compaction( + vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0, + kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0, + {}, /* is manual */ false, vstorage->CompactionScore(0), + /* is deletion compaction */ true, CompactionReason::kFIFOTtl); + return c; +} + +Compaction* FIFOCompactionPicker::PickSizeCompaction( + const std::string& cf_name, const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* vstorage, LogBuffer* log_buffer) { + const int kLevel0 = 0; + const std::vector& level_files = vstorage->LevelFiles(kLevel0); + uint64_t total_size = GetTotalFilesSize(level_files); + + if (total_size <= + mutable_cf_options.compaction_options_fifo.max_table_files_size || + level_files.size() == 0) { + // total size not exceeded + if (mutable_cf_options.compaction_options_fifo.allow_compaction && + level_files.size() > 0) { + CompactionInputFiles comp_inputs; + // try to prevent same files from being compacted multiple times, which + // could produce large files that may never TTL-expire. Achieve this by + // disallowing compactions with files larger than memtable (inflate its + // size by 10% to account for uncompressed L0 files that may have size + // slightly greater than memtable size limit). + size_t max_compact_bytes_per_del_file = + static_cast(MultiplyCheckOverflow( + static_cast(mutable_cf_options.write_buffer_size), + 1.1)); + if (FindIntraL0Compaction( + level_files, + mutable_cf_options + .level0_file_num_compaction_trigger /* min_files_to_compact */ + , + max_compact_bytes_per_del_file, &comp_inputs)) { + Compaction* c = new Compaction( + vstorage, ioptions_, mutable_cf_options, {comp_inputs}, 0, + 16 * 1024 * 1024 /* output file size limit */, + 0 /* max compaction bytes, not applicable */, + 0 /* output path ID */, mutable_cf_options.compression, + ioptions_.compression_opts, 0 /* max_subcompactions */, {}, + /* is manual */ false, vstorage->CompactionScore(0), + /* is deletion compaction */ false, + CompactionReason::kFIFOReduceNumFiles); + return c; + } + } + + ROCKS_LOG_BUFFER( + log_buffer, + "[%s] FIFO compaction: nothing to do. Total size %" PRIu64 + ", max size %" PRIu64 "\n", + cf_name.c_str(), total_size, + mutable_cf_options.compaction_options_fifo.max_table_files_size); + return nullptr; + } + + if (!level0_compactions_in_progress_.empty()) { + ROCKS_LOG_BUFFER( + log_buffer, + "[%s] FIFO compaction: Already executing compaction. No need " + "to run parallel compactions since compactions are very fast", + cf_name.c_str()); + return nullptr; + } + + std::vector inputs; + inputs.emplace_back(); + inputs[0].level = 0; + + for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) { + auto f = *ritr; + total_size -= f->compensated_file_size; + inputs[0].files.push_back(f); + char tmp_fsize[16]; + AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize)); + ROCKS_LOG_BUFFER(log_buffer, + "[%s] FIFO compaction: picking file %" PRIu64 + " with size %s for deletion", + cf_name.c_str(), f->fd.GetNumber(), tmp_fsize); + if (total_size <= + mutable_cf_options.compaction_options_fifo.max_table_files_size) { + break; + } + } + + Compaction* c = new Compaction( + vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0, + kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0, + {}, /* is manual */ false, vstorage->CompactionScore(0), + /* is deletion compaction */ true, CompactionReason::kFIFOMaxSize); + return c; +} + +Compaction* FIFOCompactionPicker::PickCompaction( + const std::string& cf_name, const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* vstorage, LogBuffer* log_buffer) { + assert(vstorage->num_levels() == 1); + + Compaction* c = nullptr; + if (mutable_cf_options.ttl > 0) { + c = PickTTLCompaction(cf_name, mutable_cf_options, vstorage, log_buffer); + } + if (c == nullptr) { + c = PickSizeCompaction(cf_name, mutable_cf_options, vstorage, log_buffer); + } + RegisterCompaction(c); + return c; +} + +Compaction* FIFOCompactionPicker::CompactRange( + const std::string& cf_name, const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* vstorage, int input_level, int output_level, + uint32_t /*output_path_id*/, uint32_t /*max_subcompactions*/, + const InternalKey* /*begin*/, const InternalKey* /*end*/, + InternalKey** compaction_end, bool* /*manual_conflict*/) { +#ifdef NDEBUG + (void)input_level; + (void)output_level; +#endif + assert(input_level == 0); + assert(output_level == 0); + *compaction_end = nullptr; + LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.info_log); + Compaction* c = + PickCompaction(cf_name, mutable_cf_options, vstorage, &log_buffer); + log_buffer.FlushBufferToLog(); + return c; +} + +} // namespace rocksdb +#endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/compaction_picker_fifo.h b/thirdparty/rocksdb/db/compaction_picker_fifo.h new file mode 100644 index 0000000000..015fd42ddb --- /dev/null +++ b/thirdparty/rocksdb/db/compaction_picker_fifo.h @@ -0,0 +1,52 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#pragma once +#ifndef ROCKSDB_LITE + +#include "db/compaction_picker.h" + +namespace rocksdb { +class FIFOCompactionPicker : public CompactionPicker { + public: + FIFOCompactionPicker(const ImmutableCFOptions& ioptions, + const InternalKeyComparator* icmp) + : CompactionPicker(ioptions, icmp) {} + + virtual Compaction* PickCompaction(const std::string& cf_name, + const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* version, + LogBuffer* log_buffer) override; + + virtual Compaction* CompactRange( + const std::string& cf_name, const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* vstorage, int input_level, int output_level, + uint32_t output_path_id, uint32_t max_subcompactions, + const InternalKey* begin, const InternalKey* end, + InternalKey** compaction_end, bool* manual_conflict) override; + + // The maximum allowed output level. Always returns 0. + virtual int MaxOutputLevel() const override { return 0; } + + virtual bool NeedsCompaction( + const VersionStorageInfo* vstorage) const override; + + private: + Compaction* PickTTLCompaction(const std::string& cf_name, + const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* version, + LogBuffer* log_buffer); + + Compaction* PickSizeCompaction(const std::string& cf_name, + const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* version, + LogBuffer* log_buffer); +}; +} // namespace rocksdb +#endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/compaction_picker_test.cc b/thirdparty/rocksdb/db/compaction_picker_test.cc index bba2d073d8..31325c1289 100644 --- a/thirdparty/rocksdb/db/compaction_picker_test.cc +++ b/thirdparty/rocksdb/db/compaction_picker_test.cc @@ -4,10 +4,12 @@ // (found in the LICENSE.Apache file in the root directory). #include "db/compaction_picker.h" + #include #include #include #include "db/compaction.h" +#include "db/compaction_picker_fifo.h" #include "db/compaction_picker_universal.h" #include "util/logging.h" @@ -20,7 +22,7 @@ namespace rocksdb { class CountingLogger : public Logger { public: using Logger::Logv; - virtual void Logv(const char* format, va_list ap) override { log_count++; } + void Logv(const char* /*format*/, va_list /*ap*/) override { log_count++; } size_t log_count; }; @@ -55,14 +57,16 @@ class CompactionPickerTest : public testing::Test { log_buffer_(InfoLogLevel::INFO_LEVEL, &logger_), file_num_(1), vstorage_(nullptr) { + // ioptions_.compaction_pri = kMinOverlappingRatio has its own set of + // tests to cover. + ioptions_.compaction_pri = kByCompensatedSize; fifo_options_.max_table_files_size = 1; mutable_cf_options_.RefreshDerivedOptions(ioptions_); - ioptions_.db_paths.emplace_back("dummy", + ioptions_.cf_paths.emplace_back("dummy", std::numeric_limits::max()); } - ~CompactionPickerTest() { - } + ~CompactionPickerTest() override {} void NewVersionStorage(int num_levels, CompactionStyle style) { DeleteVersionStorage(); @@ -81,16 +85,17 @@ class CompactionPickerTest : public testing::Test { void Add(int level, uint32_t file_number, const char* smallest, const char* largest, uint64_t file_size = 1, uint32_t path_id = 0, - SequenceNumber smallest_seq = 100, - SequenceNumber largest_seq = 100) { + SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100, + size_t compensated_file_size = 0) { assert(level < vstorage_->num_levels()); FileMetaData* f = new FileMetaData; f->fd = FileDescriptor(file_number, path_id, file_size); f->smallest = InternalKey(smallest, smallest_seq, kTypeValue); f->largest = InternalKey(largest, largest_seq, kTypeValue); - f->smallest_seqno = smallest_seq; - f->largest_seqno = largest_seq; - f->compensated_file_size = file_size; + f->fd.smallest_seqno = smallest_seq; + f->fd.largest_seqno = largest_seq; + f->compensated_file_size = + (compensated_file_size != 0) ? compensated_file_size : file_size; f->refs = 0; vstorage_->AddFile(level, f); files_.emplace_back(f); @@ -175,6 +180,8 @@ TEST_F(CompactionPickerTest, Level1Trigger) { } TEST_F(CompactionPickerTest, Level1Trigger2) { + mutable_cf_options_.target_file_size_base = 10000000000; + mutable_cf_options_.RefreshDerivedOptions(ioptions_); NewVersionStorage(6, kCompactionStyleLevel); Add(1, 66U, "150", "200", 1000000001U); Add(1, 88U, "201", "300", 1000000000U); @@ -191,13 +198,14 @@ TEST_F(CompactionPickerTest, Level1Trigger2) { ASSERT_EQ(66U, compaction->input(0, 0)->fd.GetNumber()); ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber()); ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber()); + ASSERT_EQ(uint64_t{1073741824}, compaction->OutputFilePreallocationSize()); } TEST_F(CompactionPickerTest, LevelMaxScore) { NewVersionStorage(6, kCompactionStyleLevel); mutable_cf_options_.target_file_size_base = 10000000; - mutable_cf_options_.target_file_size_multiplier = 10; mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024; + mutable_cf_options_.RefreshDerivedOptions(ioptions_); Add(0, 1U, "150", "200", 1000000U); // Level 1 score 1.2 Add(1, 66U, "150", "200", 6000000U); @@ -218,6 +226,9 @@ TEST_F(CompactionPickerTest, LevelMaxScore) { ASSERT_TRUE(compaction.get() != nullptr); ASSERT_EQ(1U, compaction->num_input_files(0)); ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber()); + ASSERT_EQ(mutable_cf_options_.target_file_size_base + + mutable_cf_options_.target_file_size_base / 10, + compaction->OutputFilePreallocationSize()); } TEST_F(CompactionPickerTest, NeedsCompactionLevel) { @@ -383,10 +394,10 @@ TEST_F(CompactionPickerTest, NeedsCompactionUniversal) { NewVersionStorage(1, kCompactionStyleUniversal); UniversalCompactionPicker universal_compaction_picker( ioptions_, &icmp_); + UpdateVersionStorageInfo(); // must return false when there's no files. ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()), false); - UpdateVersionStorageInfo(); // verify the trigger given different number of L0 files. for (int i = 1; @@ -407,6 +418,7 @@ TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) { ioptions_.allow_ingest_behind = true; ioptions_.num_levels = 3; UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_); + UpdateVersionStorageInfo(); // must return false when there's no files. ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()), false); @@ -437,9 +449,10 @@ TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) { TEST_F(CompactionPickerTest, CannotTrivialMoveUniversal) { const uint64_t kFileSize = 100000; - ioptions_.compaction_options_universal.allow_trivial_move = true; + mutable_cf_options_.compaction_options_universal.allow_trivial_move = true; NewVersionStorage(1, kCompactionStyleUniversal); UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_); + UpdateVersionStorageInfo(); // must return false when there's no files. ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()), false); @@ -468,7 +481,7 @@ TEST_F(CompactionPickerTest, CannotTrivialMoveUniversal) { TEST_F(CompactionPickerTest, AllowsTrivialMoveUniversal) { const uint64_t kFileSize = 100000; - ioptions_.compaction_options_universal.allow_trivial_move = true; + mutable_cf_options_.compaction_options_universal.allow_trivial_move = true; UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_); NewVersionStorage(3, kCompactionStyleUniversal); @@ -496,7 +509,7 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) { const uint64_t kMaxSize = kFileSize * kFileCount / 2; fifo_options_.max_table_files_size = kMaxSize; - ioptions_.compaction_options_fifo = fifo_options_; + mutable_cf_options_.compaction_options_fifo = fifo_options_; FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_); UpdateVersionStorageInfo(); // must return false when there's no files. @@ -521,9 +534,10 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) { TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) { NewVersionStorage(6, kCompactionStyleLevel); ioptions_.compaction_pri = kMinOverlappingRatio; - mutable_cf_options_.target_file_size_base = 10000000; + mutable_cf_options_.target_file_size_base = 100000000000; mutable_cf_options_.target_file_size_multiplier = 10; mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024; + mutable_cf_options_.RefreshDerivedOptions(ioptions_); Add(2, 6U, "150", "179", 50000000U); Add(2, 7U, "180", "220", 50000000U); @@ -543,6 +557,8 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) { ASSERT_EQ(1U, compaction->num_input_files(0)); // Pick file 8 because it overlaps with 0 files on level 3. ASSERT_EQ(8U, compaction->input(0, 0)->fd.GetNumber()); + // Compaction input size * 1.1 + ASSERT_GE(uint64_t{55000000}, compaction->OutputFilePreallocationSize()); } TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) { @@ -602,6 +618,35 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping3) { ASSERT_EQ(8U, compaction->input(0, 0)->fd.GetNumber()); } +TEST_F(CompactionPickerTest, CompactionPriMinOverlapping4) { + NewVersionStorage(6, kCompactionStyleLevel); + ioptions_.compaction_pri = kMinOverlappingRatio; + mutable_cf_options_.max_bytes_for_level_base = 10000000; + mutable_cf_options_.max_bytes_for_level_multiplier = 10; + + // file 7 and 8 over lap with the same file, but file 8 is smaller so + // it will be picked. + // Overlaps with file 26, 27. And the file is compensated so will be + // picked up. + Add(2, 6U, "150", "167", 60000000U, 0, 100, 100, 180000000U); + Add(2, 7U, "168", "169", 60000000U); // Overlaps with file 27 + Add(2, 8U, "201", "300", 61000000U); // Overlaps with file 28 + + Add(3, 26U, "160", "165", 60000000U); + // Boosted file size in output level is not considered. + Add(3, 27U, "166", "170", 60000000U, 0, 100, 100, 260000000U); + Add(3, 28U, "180", "400", 60000000U); + Add(3, 29U, "401", "500", 60000000U); + UpdateVersionStorageInfo(); + + std::unique_ptr compaction(level_compaction_picker.PickCompaction( + cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_)); + ASSERT_TRUE(compaction.get() != nullptr); + ASSERT_EQ(1U, compaction->num_input_files(0)); + // Picking file 8 because overlapping ratio is the biggest. + ASSERT_EQ(6U, compaction->input(0, 0)->fd.GetNumber()); +} + // This test exhibits the bug where we don't properly reset parent_index in // PickCompaction() TEST_F(CompactionPickerTest, ParentIndexResetBug) { diff --git a/thirdparty/rocksdb/db/compaction_picker_universal.cc b/thirdparty/rocksdb/db/compaction_picker_universal.cc index 14533fbcdd..9291178585 100644 --- a/thirdparty/rocksdb/db/compaction_picker_universal.cc +++ b/thirdparty/rocksdb/db/compaction_picker_universal.cc @@ -35,7 +35,7 @@ namespace { // and the index of the file in that level struct InputFileInfo { - InputFileInfo() : f(nullptr) {} + InputFileInfo() : f(nullptr), level(0), index(0) {} FileMetaData* f; size_t level; @@ -97,17 +97,17 @@ void GetSmallestLargestSeqno(const std::vector& files, SequenceNumber* largest_seqno) { bool is_first = true; for (FileMetaData* f : files) { - assert(f->smallest_seqno <= f->largest_seqno); + assert(f->fd.smallest_seqno <= f->fd.largest_seqno); if (is_first) { is_first = false; - *smallest_seqno = f->smallest_seqno; - *largest_seqno = f->largest_seqno; + *smallest_seqno = f->fd.smallest_seqno; + *largest_seqno = f->fd.largest_seqno; } else { - if (f->smallest_seqno < *smallest_seqno) { - *smallest_seqno = f->smallest_seqno; + if (f->fd.smallest_seqno < *smallest_seqno) { + *smallest_seqno = f->fd.smallest_seqno; } - if (f->largest_seqno > *largest_seqno) { - *largest_seqno = f->largest_seqno; + if (f->fd.largest_seqno > *largest_seqno) { + *largest_seqno = f->fd.largest_seqno; } } } @@ -162,7 +162,13 @@ bool UniversalCompactionPicker::IsInputFilesNonOverlapping(Compaction* c) { bool UniversalCompactionPicker::NeedsCompaction( const VersionStorageInfo* vstorage) const { const int kLevel0 = 0; - return vstorage->CompactionScore(kLevel0) >= 1; + if (vstorage->CompactionScore(kLevel0) >= 1) { + return true; + } + if (!vstorage->FilesMarkedForCompaction().empty()) { + return true; + } + return false; } void UniversalCompactionPicker::SortedRun::Dump(char* out_buf, @@ -204,7 +210,8 @@ void UniversalCompactionPicker::SortedRun::DumpSizeInfo( std::vector UniversalCompactionPicker::CalculateSortedRuns( - const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions) { + const VersionStorageInfo& vstorage, const ImmutableCFOptions& /*ioptions*/, + const MutableCFOptions& mutable_cf_options) { std::vector ret; for (FileMetaData* f : vstorage.LevelFiles(0)) { ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size, @@ -218,7 +225,8 @@ UniversalCompactionPicker::CalculateSortedRuns( for (FileMetaData* f : vstorage.LevelFiles(level)) { total_compensated_size += f->compensated_file_size; total_size += f->fd.GetFileSize(); - if (ioptions.compaction_options_universal.allow_trivial_move == true) { + if (mutable_cf_options.compaction_options_universal.allow_trivial_move == + true) { if (f->being_compacted) { being_compacted = f->being_compacted; } @@ -227,7 +235,8 @@ UniversalCompactionPicker::CalculateSortedRuns( // non-zero level, all the files should share the same being_compacted // value. // This assumption is only valid when - // ioptions.compaction_options_universal.allow_trivial_move is false + // mutable_cf_options.compaction_options_universal.allow_trivial_move is + // false assert(is_first || f->being_compacted == being_compacted); } if (is_first) { @@ -245,18 +254,18 @@ UniversalCompactionPicker::CalculateSortedRuns( // Universal style of compaction. Pick files that are contiguous in // time-range to compact. -// Compaction* UniversalCompactionPicker::PickCompaction( const std::string& cf_name, const MutableCFOptions& mutable_cf_options, VersionStorageInfo* vstorage, LogBuffer* log_buffer) { const int kLevel0 = 0; double score = vstorage->CompactionScore(kLevel0); std::vector sorted_runs = - CalculateSortedRuns(*vstorage, ioptions_); + CalculateSortedRuns(*vstorage, ioptions_, mutable_cf_options); if (sorted_runs.size() == 0 || - sorted_runs.size() < - (unsigned int)mutable_cf_options.level0_file_num_compaction_trigger) { + (vstorage->FilesMarkedForCompaction().empty() && + sorted_runs.size() < (unsigned int)mutable_cf_options + .level0_file_num_compaction_trigger)) { ROCKS_LOG_BUFFER(log_buffer, "[%s] Universal: nothing to do\n", cf_name.c_str()); TEST_SYNC_POINT_CALLBACK("UniversalCompactionPicker::PickCompaction:Return", @@ -270,64 +279,81 @@ Compaction* UniversalCompactionPicker::PickCompaction( cf_name.c_str(), sorted_runs.size(), vstorage->LevelSummary(&tmp)); // Check for size amplification first. - Compaction* c; - if ((c = PickCompactionToReduceSizeAmp(cf_name, mutable_cf_options, vstorage, - score, sorted_runs, log_buffer)) != - nullptr) { - ROCKS_LOG_BUFFER(log_buffer, "[%s] Universal: compacting for size amp\n", - cf_name.c_str()); - } else { - // Size amplification is within limits. Try reducing read - // amplification while maintaining file size ratios. - unsigned int ratio = ioptions_.compaction_options_universal.size_ratio; - - if ((c = PickCompactionToReduceSortedRuns( - cf_name, mutable_cf_options, vstorage, score, ratio, UINT_MAX, - sorted_runs, log_buffer)) != nullptr) { - ROCKS_LOG_BUFFER(log_buffer, - "[%s] Universal: compacting for size ratio\n", + Compaction* c = nullptr; + if (sorted_runs.size() >= + static_cast( + mutable_cf_options.level0_file_num_compaction_trigger)) { + if ((c = PickCompactionToReduceSizeAmp(cf_name, mutable_cf_options, + vstorage, score, sorted_runs, + log_buffer)) != nullptr) { + ROCKS_LOG_BUFFER(log_buffer, "[%s] Universal: compacting for size amp\n", cf_name.c_str()); } else { - // Size amplification and file size ratios are within configured limits. - // If max read amplification is exceeding configured limits, then force - // compaction without looking at filesize ratios and try to reduce - // the number of files to fewer than level0_file_num_compaction_trigger. - // This is guaranteed by NeedsCompaction() - assert(sorted_runs.size() >= - static_cast( - mutable_cf_options.level0_file_num_compaction_trigger)); - // Get the total number of sorted runs that are not being compacted - int num_sr_not_compacted = 0; - for (size_t i = 0; i < sorted_runs.size(); i++) { - if (sorted_runs[i].being_compacted == false) { - num_sr_not_compacted++; + // Size amplification is within limits. Try reducing read + // amplification while maintaining file size ratios. + unsigned int ratio = + mutable_cf_options.compaction_options_universal.size_ratio; + + if ((c = PickCompactionToReduceSortedRuns( + cf_name, mutable_cf_options, vstorage, score, ratio, UINT_MAX, + sorted_runs, log_buffer)) != nullptr) { + ROCKS_LOG_BUFFER(log_buffer, + "[%s] Universal: compacting for size ratio\n", + cf_name.c_str()); + } else { + // Size amplification and file size ratios are within configured limits. + // If max read amplification is exceeding configured limits, then force + // compaction without looking at filesize ratios and try to reduce + // the number of files to fewer than level0_file_num_compaction_trigger. + // This is guaranteed by NeedsCompaction() + assert(sorted_runs.size() >= + static_cast( + mutable_cf_options.level0_file_num_compaction_trigger)); + // Get the total number of sorted runs that are not being compacted + int num_sr_not_compacted = 0; + for (size_t i = 0; i < sorted_runs.size(); i++) { + if (sorted_runs[i].being_compacted == false) { + num_sr_not_compacted++; + } } - } - // The number of sorted runs that are not being compacted is greater than - // the maximum allowed number of sorted runs - if (num_sr_not_compacted > - mutable_cf_options.level0_file_num_compaction_trigger) { - unsigned int num_files = - num_sr_not_compacted - - mutable_cf_options.level0_file_num_compaction_trigger + 1; - if ((c = PickCompactionToReduceSortedRuns( - cf_name, mutable_cf_options, vstorage, score, UINT_MAX, - num_files, sorted_runs, log_buffer)) != nullptr) { - ROCKS_LOG_BUFFER(log_buffer, - "[%s] Universal: compacting for file num -- %u\n", - cf_name.c_str(), num_files); + // The number of sorted runs that are not being compacted is greater + // than the maximum allowed number of sorted runs + if (num_sr_not_compacted > + mutable_cf_options.level0_file_num_compaction_trigger) { + unsigned int num_files = + num_sr_not_compacted - + mutable_cf_options.level0_file_num_compaction_trigger + 1; + if ((c = PickCompactionToReduceSortedRuns( + cf_name, mutable_cf_options, vstorage, score, UINT_MAX, + num_files, sorted_runs, log_buffer)) != nullptr) { + ROCKS_LOG_BUFFER(log_buffer, + "[%s] Universal: compacting for file num -- %u\n", + cf_name.c_str(), num_files); + } } } } } + + if (c == nullptr) { + if ((c = PickDeleteTriggeredCompaction(cf_name, mutable_cf_options, + vstorage, score, sorted_runs, + log_buffer)) != nullptr) { + ROCKS_LOG_BUFFER(log_buffer, + "[%s] Universal: delete triggered compaction\n", + cf_name.c_str()); + } + } + if (c == nullptr) { TEST_SYNC_POINT_CALLBACK("UniversalCompactionPicker::PickCompaction:Return", nullptr); return nullptr; } - if (ioptions_.compaction_options_universal.allow_trivial_move == true) { + if (mutable_cf_options.compaction_options_universal.allow_trivial_move == + true) { c->set_is_trivial_move(IsInputFilesNonOverlapping(c)); } @@ -339,11 +365,11 @@ Compaction* UniversalCompactionPicker::PickCompaction( size_t level_index = 0U; if (c->start_level() == 0) { for (auto f : *c->inputs(0)) { - assert(f->smallest_seqno <= f->largest_seqno); + assert(f->fd.smallest_seqno <= f->fd.largest_seqno); if (is_first) { is_first = false; } - prev_smallest_seqno = f->smallest_seqno; + prev_smallest_seqno = f->fd.smallest_seqno; } level_index = 1U; } @@ -369,8 +395,8 @@ Compaction* UniversalCompactionPicker::PickCompaction( } #endif // update statistics - MeasureTime(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION, - c->inputs(0)->size()); + RecordInHistogram(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION, + c->inputs(0)->size()); RegisterCompaction(c); vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options); @@ -381,7 +407,8 @@ Compaction* UniversalCompactionPicker::PickCompaction( } uint32_t UniversalCompactionPicker::GetPathId( - const ImmutableCFOptions& ioptions, uint64_t file_size) { + const ImmutableCFOptions& ioptions, + const MutableCFOptions& mutable_cf_options, uint64_t file_size) { // Two conditions need to be satisfied: // (1) the target path needs to be able to hold the file's size // (2) Total size left in this and previous paths need to be not @@ -398,12 +425,12 @@ uint32_t UniversalCompactionPicker::GetPathId( // that case. We need to improve it. uint64_t accumulated_size = 0; uint64_t future_size = - file_size * (100 - ioptions.compaction_options_universal.size_ratio) / - 100; + file_size * + (100 - mutable_cf_options.compaction_options_universal.size_ratio) / 100; uint32_t p = 0; - assert(!ioptions.db_paths.empty()); - for (; p < ioptions.db_paths.size() - 1; p++) { - uint64_t target_size = ioptions.db_paths[p].target_size; + assert(!ioptions.cf_paths.empty()); + for (; p < ioptions.cf_paths.size() - 1; p++) { + uint64_t target_size = ioptions.cf_paths[p].target_size; if (target_size > file_size && accumulated_size + (target_size - file_size) > future_size) { return p; @@ -423,9 +450,9 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns( unsigned int max_number_of_files_to_compact, const std::vector& sorted_runs, LogBuffer* log_buffer) { unsigned int min_merge_width = - ioptions_.compaction_options_universal.min_merge_width; + mutable_cf_options.compaction_options_universal.min_merge_width; unsigned int max_merge_width = - ioptions_.compaction_options_universal.max_merge_width; + mutable_cf_options.compaction_options_universal.max_merge_width; const SortedRun* sr = nullptr; bool done = false; @@ -492,7 +519,7 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns( if (sz < static_cast(succeeding_sr->size)) { break; } - if (ioptions_.compaction_options_universal.stop_style == + if (mutable_cf_options.compaction_options_universal.stop_style == kCompactionStopStyleSimilarSize) { // Similar-size stopping rule: also check the last picked file isn't // far larger than the next candidate file. @@ -535,7 +562,7 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns( // size ratio of compression. bool enable_compression = true; int ratio_to_compress = - ioptions_.compaction_options_universal.compression_size_percent; + mutable_cf_options.compaction_options_universal.compression_size_percent; if (ratio_to_compress >= 0) { uint64_t total_size = 0; for (auto& sorted_run : sorted_runs) { @@ -556,7 +583,8 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns( for (unsigned int i = 0; i < first_index_after; i++) { estimated_total_size += sorted_runs[i].size; } - uint32_t path_id = GetPathId(ioptions_, estimated_total_size); + uint32_t path_id = + GetPathId(ioptions_, mutable_cf_options, estimated_total_size); int start_level = sorted_runs[start_index].level; int output_level; if (first_index_after == sorted_runs.size()) { @@ -597,17 +625,21 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns( CompactionReason compaction_reason; if (max_number_of_files_to_compact == UINT_MAX) { - compaction_reason = CompactionReason::kUniversalSortedRunNum; - } else { compaction_reason = CompactionReason::kUniversalSizeRatio; + } else { + compaction_reason = CompactionReason::kUniversalSortedRunNum; } return new Compaction( vstorage, ioptions_, mutable_cf_options, std::move(inputs), output_level, - mutable_cf_options.MaxFileSizeForLevel(output_level), LLONG_MAX, path_id, + MaxFileSizeForLevel(mutable_cf_options, output_level, + kCompactionStyleUniversal), + LLONG_MAX, path_id, GetCompressionType(ioptions_, vstorage, mutable_cf_options, start_level, 1, enable_compression), - /* grandparents */ {}, /* is manual */ false, score, - false /* deletion_compaction */, compaction_reason); + GetCompressionOptions(ioptions_, vstorage, start_level, + enable_compression), + /* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false, + score, false /* deletion_compaction */, compaction_reason); } // Look at overall size amplification. If size amplification @@ -621,14 +653,18 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSizeAmp( VersionStorageInfo* vstorage, double score, const std::vector& sorted_runs, LogBuffer* log_buffer) { // percentage flexibility while reducing size amplification - uint64_t ratio = - ioptions_.compaction_options_universal.max_size_amplification_percent; + uint64_t ratio = mutable_cf_options.compaction_options_universal + .max_size_amplification_percent; unsigned int candidate_count = 0; uint64_t candidate_size = 0; size_t start_index = 0; const SortedRun* sr = nullptr; + if (sorted_runs.back().being_compacted) { + return nullptr; + } + // Skip files that are already being compacted for (size_t loop = 0; loop < sorted_runs.size() - 1; loop++) { sr = &sorted_runs[loop]; @@ -700,7 +736,8 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSizeAmp( for (size_t loop = start_index; loop < sorted_runs.size(); loop++) { estimated_total_size += sorted_runs[loop].size; } - uint32_t path_id = GetPathId(ioptions_, estimated_total_size); + uint32_t path_id = + GetPathId(ioptions_, mutable_cf_options, estimated_total_size); int start_level = sorted_runs[start_index].level; std::vector inputs(vstorage->num_levels()); @@ -734,15 +771,137 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSizeAmp( } return new Compaction( - vstorage, ioptions_, mutable_cf_options, std::move(inputs), - output_level, mutable_cf_options.MaxFileSizeForLevel(output_level), + vstorage, ioptions_, mutable_cf_options, std::move(inputs), output_level, + MaxFileSizeForLevel(mutable_cf_options, output_level, + kCompactionStyleUniversal), /* max_grandparent_overlap_bytes */ LLONG_MAX, path_id, - GetCompressionType(ioptions_, vstorage, mutable_cf_options, - output_level, 1), - /* grandparents */ {}, /* is manual */ false, score, - false /* deletion_compaction */, + GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level, + 1), + GetCompressionOptions(ioptions_, vstorage, output_level), + /* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false, + score, false /* deletion_compaction */, CompactionReason::kUniversalSizeAmplification); } + +// Pick files marked for compaction. Typically, files are marked by +// CompactOnDeleteCollector due to the presence of tombstones. +Compaction* UniversalCompactionPicker::PickDeleteTriggeredCompaction( + const std::string& cf_name, const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* vstorage, double score, + const std::vector& /*sorted_runs*/, LogBuffer* /*log_buffer*/) { + CompactionInputFiles start_level_inputs; + int output_level; + std::vector inputs; + + if (vstorage->num_levels() == 1) { + // This is single level universal. Since we're basically trying to reclaim + // space by processing files marked for compaction due to high tombstone + // density, let's do the same thing as compaction to reduce size amp which + // has the same goals. + bool compact = false; + + start_level_inputs.level = 0; + start_level_inputs.files.clear(); + output_level = 0; + for (FileMetaData* f : vstorage->LevelFiles(0)) { + if (f->marked_for_compaction) { + compact = true; + } + if (compact) { + start_level_inputs.files.push_back(f); + } + } + if (start_level_inputs.size() <= 1) { + // If only the last file in L0 is marked for compaction, ignore it + return nullptr; + } + inputs.push_back(start_level_inputs); + } else { + int start_level; + + // For multi-level universal, the strategy is to make this look more like + // leveled. We pick one of the files marked for compaction and compact with + // overlapping files in the adjacent level. + PickFilesMarkedForCompaction(cf_name, vstorage, &start_level, &output_level, + &start_level_inputs); + if (start_level_inputs.empty()) { + return nullptr; + } + + // Pick the first non-empty level after the start_level + for (output_level = start_level + 1; output_level < vstorage->num_levels(); + output_level++) { + if (vstorage->NumLevelFiles(output_level) != 0) { + break; + } + } + + // If all higher levels are empty, pick the highest level as output level + if (output_level == vstorage->num_levels()) { + if (start_level == 0) { + output_level = vstorage->num_levels() - 1; + } else { + // If start level is non-zero and all higher levels are empty, this + // compaction will translate into a trivial move. Since the idea is + // to reclaim space and trivial move doesn't help with that, we + // skip compaction in this case and return nullptr + return nullptr; + } + } + if (ioptions_.allow_ingest_behind && + output_level == vstorage->num_levels() - 1) { + assert(output_level > 1); + output_level--; + } + + if (output_level != 0) { + if (start_level == 0) { + if (!GetOverlappingL0Files(vstorage, &start_level_inputs, output_level, + nullptr)) { + return nullptr; + } + } + + CompactionInputFiles output_level_inputs; + int parent_index = -1; + + output_level_inputs.level = output_level; + if (!SetupOtherInputs(cf_name, mutable_cf_options, vstorage, + &start_level_inputs, &output_level_inputs, + &parent_index, -1)) { + return nullptr; + } + inputs.push_back(start_level_inputs); + if (!output_level_inputs.empty()) { + inputs.push_back(output_level_inputs); + } + if (FilesRangeOverlapWithCompaction(inputs, output_level)) { + return nullptr; + } + } else { + inputs.push_back(start_level_inputs); + } + } + + uint64_t estimated_total_size = 0; + // Use size of the output level as estimated file size + for (FileMetaData* f : vstorage->LevelFiles(output_level)) { + estimated_total_size += f->fd.GetFileSize(); + } + uint32_t path_id = + GetPathId(ioptions_, mutable_cf_options, estimated_total_size); + return new Compaction( + vstorage, ioptions_, mutable_cf_options, std::move(inputs), output_level, + MaxFileSizeForLevel(mutable_cf_options, output_level, + kCompactionStyleUniversal), + /* max_grandparent_overlap_bytes */ LLONG_MAX, path_id, + GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level, + 1), + GetCompressionOptions(ioptions_, vstorage, output_level), + /* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ true, + score, false /* deletion_compaction */, + CompactionReason::kFilesMarkedForCompaction); +} } // namespace rocksdb #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/compaction_picker_universal.h b/thirdparty/rocksdb/db/compaction_picker_universal.h index 3f2bed3e62..375e5998e2 100644 --- a/thirdparty/rocksdb/db/compaction_picker_universal.h +++ b/thirdparty/rocksdb/db/compaction_picker_universal.h @@ -73,6 +73,11 @@ class UniversalCompactionPicker : public CompactionPicker { VersionStorageInfo* vstorage, double score, const std::vector& sorted_runs, LogBuffer* log_buffer); + Compaction* PickDeleteTriggeredCompaction( + const std::string& cf_name, const MutableCFOptions& mutable_cf_options, + VersionStorageInfo* vstorage, double score, + const std::vector& sorted_runs, LogBuffer* log_buffer); + // Used in universal compaction when the enabled_trivial_move // option is set. Checks whether there are any overlapping files // in the input. Returns true if the input files are non @@ -80,11 +85,13 @@ class UniversalCompactionPicker : public CompactionPicker { bool IsInputFilesNonOverlapping(Compaction* c); static std::vector CalculateSortedRuns( - const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions); + const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions, + const MutableCFOptions& mutable_cf_options); // Pick a path ID to place a newly generated file, with its estimated file // size. static uint32_t GetPathId(const ImmutableCFOptions& ioptions, + const MutableCFOptions& mutable_cf_options, uint64_t file_size); }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/comparator_db_test.cc b/thirdparty/rocksdb/db/comparator_db_test.cc index 28a2a5658e..a7ff587949 100644 --- a/thirdparty/rocksdb/db/comparator_db_test.cc +++ b/thirdparty/rocksdb/db/comparator_db_test.cc @@ -2,6 +2,7 @@ // 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). +#include #include #include @@ -26,24 +27,24 @@ class KVIter : public Iterator { public: explicit KVIter(const stl_wrappers::KVMap* map) : map_(map), iter_(map_->end()) {} - virtual bool Valid() const override { return iter_ != map_->end(); } - virtual void SeekToFirst() override { iter_ = map_->begin(); } - virtual void SeekToLast() override { + bool Valid() const override { return iter_ != map_->end(); } + void SeekToFirst() override { iter_ = map_->begin(); } + void SeekToLast() override { if (map_->empty()) { iter_ = map_->end(); } else { iter_ = map_->find(map_->rbegin()->first); } } - virtual void Seek(const Slice& k) override { + void Seek(const Slice& k) override { iter_ = map_->lower_bound(k.ToString()); } - virtual void SeekForPrev(const Slice& k) override { + void SeekForPrev(const Slice& k) override { iter_ = map_->upper_bound(k.ToString()); Prev(); } - virtual void Next() override { ++iter_; } - virtual void Prev() override { + void Next() override { ++iter_; } + void Prev() override { if (iter_ == map_->begin()) { iter_ = map_->end(); return; @@ -51,9 +52,9 @@ class KVIter : public Iterator { --iter_; } - virtual Slice key() const override { return iter_->first; } - virtual Slice value() const override { return iter_->second; } - virtual Status status() const override { return Status::OK(); } + Slice key() const override { return iter_->first; } + Slice value() const override { return iter_->second; } + Status status() const override { return Status::OK(); } private: const stl_wrappers::KVMap* const map_; @@ -170,9 +171,9 @@ class DoubleComparator : public Comparator { public: DoubleComparator() {} - virtual const char* Name() const override { return "DoubleComparator"; } + const char* Name() const override { return "DoubleComparator"; } - virtual int Compare(const Slice& a, const Slice& b) const override { + int Compare(const Slice& a, const Slice& b) const override { #ifndef CYGWIN double da = std::stod(a.ToString()); double db = std::stod(b.ToString()); @@ -188,19 +189,19 @@ class DoubleComparator : public Comparator { return -1; } } - virtual void FindShortestSeparator(std::string* start, - const Slice& limit) const override {} + void FindShortestSeparator(std::string* /*start*/, + const Slice& /*limit*/) const override {} - virtual void FindShortSuccessor(std::string* key) const override {} + void FindShortSuccessor(std::string* /*key*/) const override {} }; class HashComparator : public Comparator { public: HashComparator() {} - virtual const char* Name() const override { return "HashComparator"; } + const char* Name() const override { return "HashComparator"; } - virtual int Compare(const Slice& a, const Slice& b) const override { + int Compare(const Slice& a, const Slice& b) const override { uint32_t ha = Hash(a.data(), a.size(), 66); uint32_t hb = Hash(b.data(), b.size(), 66); if (ha == hb) { @@ -211,19 +212,19 @@ class HashComparator : public Comparator { return -1; } } - virtual void FindShortestSeparator(std::string* start, - const Slice& limit) const override {} + void FindShortestSeparator(std::string* /*start*/, + const Slice& /*limit*/) const override {} - virtual void FindShortSuccessor(std::string* key) const override {} + void FindShortSuccessor(std::string* /*key*/) const override {} }; class TwoStrComparator : public Comparator { public: TwoStrComparator() {} - virtual const char* Name() const override { return "TwoStrComparator"; } + const char* Name() const override { return "TwoStrComparator"; } - virtual int Compare(const Slice& a, const Slice& b) const override { + int Compare(const Slice& a, const Slice& b) const override { assert(a.size() >= 2); assert(b.size() >= 2); size_t size_a1 = static_cast(a[0]); @@ -243,14 +244,16 @@ class TwoStrComparator : public Comparator { } return a2.compare(b2); } - virtual void FindShortestSeparator(std::string* start, - const Slice& limit) const override {} + void FindShortestSeparator(std::string* /*start*/, + const Slice& /*limit*/) const override {} - virtual void FindShortSuccessor(std::string* key) const override {} + void FindShortSuccessor(std::string* /*key*/) const override {} }; } // namespace -class ComparatorDBTest : public testing::Test { +class ComparatorDBTest + : public testing::Test, + virtual public ::testing::WithParamInterface { private: std::string dbname_; Env* env_; @@ -261,11 +264,15 @@ class ComparatorDBTest : public testing::Test { public: ComparatorDBTest() : env_(Env::Default()), db_(nullptr) { comparator = BytewiseComparator(); - dbname_ = test::TmpDir() + "/comparator_db_test"; + dbname_ = test::PerThreadDBPath("comparator_db_test"); + BlockBasedTableOptions toptions; + toptions.format_version = GetParam(); + last_options_.table_factory.reset( + rocksdb::NewBlockBasedTableFactory(toptions)); EXPECT_OK(DestroyDB(dbname_, last_options_)); } - ~ComparatorDBTest() { + ~ComparatorDBTest() override { delete db_; EXPECT_OK(DestroyDB(dbname_, last_options_)); comparator = BytewiseComparator(); @@ -273,8 +280,12 @@ class ComparatorDBTest : public testing::Test { DB* GetDB() { return db_; } - void SetOwnedComparator(const Comparator* cmp) { - comparator_guard.reset(cmp); + void SetOwnedComparator(const Comparator* cmp, bool owner = true) { + if (owner) { + comparator_guard.reset(cmp); + } else { + comparator_guard.reset(); + } comparator = cmp; last_options_.comparator = cmp; } @@ -303,7 +314,12 @@ class ComparatorDBTest : public testing::Test { } }; -TEST_F(ComparatorDBTest, Bytewise) { +INSTANTIATE_TEST_CASE_P(FormatDef, ComparatorDBTest, + testing::Values(test::kDefaultFormatVersion)); +INSTANTIATE_TEST_CASE_P(FormatLatest, ComparatorDBTest, + testing::Values(test::kLatestFormatVersion)); + +TEST_P(ComparatorDBTest, Bytewise) { for (int rand_seed = 301; rand_seed < 306; rand_seed++) { DestroyAndReopen(); Random rnd(rand_seed); @@ -313,7 +329,7 @@ TEST_F(ComparatorDBTest, Bytewise) { } } -TEST_F(ComparatorDBTest, SimpleSuffixReverseComparator) { +TEST_P(ComparatorDBTest, SimpleSuffixReverseComparator) { SetOwnedComparator(new test::SimpleSuffixReverseComparator()); for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { @@ -339,8 +355,8 @@ TEST_F(ComparatorDBTest, SimpleSuffixReverseComparator) { } } -TEST_F(ComparatorDBTest, Uint64Comparator) { - SetOwnedComparator(test::Uint64Comparator()); +TEST_P(ComparatorDBTest, Uint64Comparator) { + SetOwnedComparator(test::Uint64Comparator(), false /* owner */); for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { Options* opt = GetOptions(); @@ -363,7 +379,7 @@ TEST_F(ComparatorDBTest, Uint64Comparator) { } } -TEST_F(ComparatorDBTest, DoubleComparator) { +TEST_P(ComparatorDBTest, DoubleComparator) { SetOwnedComparator(new DoubleComparator()); for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { @@ -388,7 +404,7 @@ TEST_F(ComparatorDBTest, DoubleComparator) { } } -TEST_F(ComparatorDBTest, HashComparator) { +TEST_P(ComparatorDBTest, HashComparator) { SetOwnedComparator(new HashComparator()); for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { @@ -407,7 +423,7 @@ TEST_F(ComparatorDBTest, HashComparator) { } } -TEST_F(ComparatorDBTest, TwoStrComparator) { +TEST_P(ComparatorDBTest, TwoStrComparator) { SetOwnedComparator(new TwoStrComparator()); for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { @@ -433,6 +449,209 @@ TEST_F(ComparatorDBTest, TwoStrComparator) { } } +TEST_P(ComparatorDBTest, IsSameLengthImmediateSuccessor) { + { + // different length + Slice s("abcxy"); + Slice t("abcxyz"); + ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + Slice s("abcxyz"); + Slice t("abcxy"); + ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + // not last byte different + Slice s("abc1xyz"); + Slice t("abc2xyz"); + ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + // same string + Slice s("abcxyz"); + Slice t("abcxyz"); + ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + Slice s("abcxy"); + Slice t("abcxz"); + ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + Slice s("abcxz"); + Slice t("abcxy"); + ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + const char s_array[] = "\x50\x8a\xac"; + const char t_array[] = "\x50\x8a\xad"; + Slice s(s_array); + Slice t(t_array); + ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + const char s_array[] = "\x50\x8a\xff"; + const char t_array[] = "\x50\x8b\x00"; + Slice s(s_array, 3); + Slice t(t_array, 3); + ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + const char s_array[] = "\x50\x8a\xff\xff"; + const char t_array[] = "\x50\x8b\x00\x00"; + Slice s(s_array, 4); + Slice t(t_array, 4); + ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } + { + const char s_array[] = "\x50\x8a\xff\xff"; + const char t_array[] = "\x50\x8b\x00\x01"; + Slice s(s_array, 4); + Slice t(t_array, 4); + ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t)); + } +} + +TEST_P(ComparatorDBTest, FindShortestSeparator) { + std::string s1 = "abc1xyz"; + std::string s2 = "abc3xy"; + + BytewiseComparator()->FindShortestSeparator(&s1, s2); + ASSERT_EQ("abc2", s1); + + s1 = "abc5xyztt"; + + ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); + ASSERT_EQ("abc5", s1); + + s1 = "abc3"; + s2 = "abc2xy"; + ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); + ASSERT_EQ("abc3", s1); + + s1 = "abc3xyz"; + s2 = "abc2xy"; + ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); + ASSERT_EQ("abc3", s1); + + s1 = "abc3xyz"; + s2 = "abc2"; + ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); + ASSERT_EQ("abc3", s1); + + std::string old_s1 = s1 = "abc2xy"; + s2 = "abc2"; + ReverseBytewiseComparator()->FindShortestSeparator(&s1, s2); + ASSERT_TRUE(old_s1 >= s1); + ASSERT_TRUE(s1 > s2); +} + +TEST_P(ComparatorDBTest, SeparatorSuccessorRandomizeTest) { + // Char list for boundary cases. + std::array char_list{{0, 1, 2, 253, 254, 255}}; + Random rnd(301); + + for (int attempts = 0; attempts < 1000; attempts++) { + uint32_t size1 = rnd.Skewed(4); + uint32_t size2; + + if (rnd.OneIn(2)) { + // size2 to be random size + size2 = rnd.Skewed(4); + } else { + // size1 is within [-2, +2] of size1 + int diff = static_cast(rnd.Uniform(5)) - 2; + int tmp_size2 = static_cast(size1) + diff; + if (tmp_size2 < 0) { + tmp_size2 = 0; + } + size2 = static_cast(tmp_size2); + } + + std::string s1; + std::string s2; + for (uint32_t i = 0; i < size1; i++) { + if (rnd.OneIn(2)) { + // Use random byte + s1 += static_cast(rnd.Uniform(256)); + } else { + // Use one byte in char_list + char c = static_cast(char_list[rnd.Uniform(sizeof(char_list))]); + s1 += c; + } + } + + // First set s2 to be the same as s1, and then modify s2. + s2 = s1; + s2.resize(size2); + // We start from the back of the string + if (size2 > 0) { + uint32_t pos = size2 - 1; + do { + if (pos >= size1 || rnd.OneIn(4)) { + // For 1/4 chance, use random byte + s2[pos] = static_cast(rnd.Uniform(256)); + } else if (rnd.OneIn(4)) { + // In 1/4 chance, stop here. + break; + } else { + // Create a char within [-2, +2] of the matching char of s1. + int diff = static_cast(rnd.Uniform(5)) - 2; + // char may be signed or unsigned based on platform. + int s1_char = static_cast(static_cast(s1[pos])); + int s2_char = s1_char + diff; + if (s2_char < 0) { + s2_char = 0; + } + if (s2_char > 255) { + s2_char = 255; + } + s2[pos] = static_cast(s2_char); + } + } while (pos-- != 0); + } + + // Test separators + for (int rev = 0; rev < 2; rev++) { + if (rev == 1) { + // switch s1 and s2 + std::string t = s1; + s1 = s2; + s2 = t; + } + std::string separator = s1; + BytewiseComparator()->FindShortestSeparator(&separator, s2); + std::string rev_separator = s1; + ReverseBytewiseComparator()->FindShortestSeparator(&rev_separator, s2); + + if (s1 == s2) { + ASSERT_EQ(s1, separator); + ASSERT_EQ(s2, rev_separator); + } else if (s1 < s2) { + ASSERT_TRUE(s1 <= separator); + ASSERT_TRUE(s2 > separator); + ASSERT_LE(separator.size(), std::max(s1.size(), s2.size())); + ASSERT_EQ(s1, rev_separator); + } else { + ASSERT_TRUE(s1 >= rev_separator); + ASSERT_TRUE(s2 < rev_separator); + ASSERT_LE(rev_separator.size(), std::max(s1.size(), s2.size())); + ASSERT_EQ(s1, separator); + } + } + + // Test successors + std::string succ = s1; + BytewiseComparator()->FindShortSuccessor(&succ); + ASSERT_TRUE(succ >= s1); + + succ = s1; + ReverseBytewiseComparator()->FindShortSuccessor(&succ); + ASSERT_TRUE(succ <= s1); + } +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/convenience.cc b/thirdparty/rocksdb/db/convenience.cc index 8ee31cacab..71c237f60c 100644 --- a/thirdparty/rocksdb/db/convenience.cc +++ b/thirdparty/rocksdb/db/convenience.cc @@ -19,15 +19,23 @@ void CancelAllBackgroundWork(DB* db, bool wait) { } Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end) { + const Slice* begin, const Slice* end, + bool include_end) { + RangePtr range(begin, end); + return DeleteFilesInRanges(db, column_family, &range, 1, include_end); +} + +Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family, + const RangePtr* ranges, size_t n, + bool include_end) { return (static_cast_with_check(db->GetRootDB())) - ->DeleteFilesInRange(column_family, begin, end); + ->DeleteFilesInRanges(column_family, ranges, n, include_end); } Status VerifySstFileChecksum(const Options& options, const EnvOptions& env_options, const std::string& file_path) { - unique_ptr file; + std::unique_ptr file; uint64_t file_size; InternalKeyComparator internal_comparator(options.comparator); ImmutableCFOptions ioptions(options); @@ -38,12 +46,14 @@ Status VerifySstFileChecksum(const Options& options, } else { return s; } - unique_ptr table_reader; + std::unique_ptr table_reader; std::unique_ptr file_reader( new RandomAccessFileReader(std::move(file), file_path)); + const bool kImmortal = true; s = ioptions.table_factory->NewTableReader( - TableReaderOptions(ioptions, env_options, internal_comparator, - false /* skip_filters */, -1 /* level */), + TableReaderOptions(ioptions, options.prefix_extractor.get(), env_options, + internal_comparator, false /* skip_filters */, + !kImmortal, -1 /* level */), std::move(file_reader), file_size, &table_reader, false /* prefetch_index_and_filter_in_cache */); if (!s.ok()) { diff --git a/thirdparty/rocksdb/db/corruption_test.cc b/thirdparty/rocksdb/db/corruption_test.cc index 56e157832c..1ccb1aa2b0 100644 --- a/thirdparty/rocksdb/db/corruption_test.cc +++ b/thirdparty/rocksdb/db/corruption_test.cc @@ -24,6 +24,8 @@ #include "rocksdb/env.h" #include "rocksdb/table.h" #include "rocksdb/write_batch.h" +#include "table/block_based_table_builder.h" +#include "table/meta_blocks.h" #include "util/filename.h" #include "util/string_util.h" #include "util/testharness.h" @@ -37,7 +39,7 @@ class CorruptionTest : public testing::Test { public: test::ErrorEnv env_; std::string dbname_; - shared_ptr tiny_cache_; + std::shared_ptr tiny_cache_; Options options_; DB* db_; @@ -48,7 +50,7 @@ class CorruptionTest : public testing::Test { tiny_cache_ = NewLRUCache(100, 4); options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords; options_.env = &env_; - dbname_ = test::TmpDir() + "/corruption_test"; + dbname_ = test::PerThreadDBPath("corruption_test"); DestroyDB(dbname_, options_); db_ = nullptr; @@ -60,9 +62,9 @@ class CorruptionTest : public testing::Test { options_.create_if_missing = false; } - ~CorruptionTest() { - delete db_; - DestroyDB(dbname_, Options()); + ~CorruptionTest() override { + delete db_; + DestroyDB(dbname_, Options()); } void CloseDb() { @@ -333,9 +335,9 @@ TEST_F(CorruptionTest, TableFileIndexData) { Corrupt(kTableFile, -2000, 500); Reopen(); dbi = reinterpret_cast(db_); - // one full file should be readable, since only one was corrupted + // one full file may be readable, since only one was corrupted // the other file should be fully non-readable, since index was corrupted - Check(5000, 5000); + Check(0, 5000); ASSERT_NOK(dbi->VerifyChecksum()); } @@ -467,6 +469,39 @@ TEST_F(CorruptionTest, UnrelatedKeys) { ASSERT_EQ(Value(1000, &tmp2).ToString(), v); } +TEST_F(CorruptionTest, RangeDeletionCorrupted) { + ASSERT_OK( + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b")); + ASSERT_OK(db_->Flush(FlushOptions())); + std::vector metadata; + db_->GetLiveFilesMetaData(&metadata); + ASSERT_EQ(static_cast(1), metadata.size()); + std::string filename = dbname_ + metadata[0].name; + + std::unique_ptr file; + ASSERT_OK(options_.env->NewRandomAccessFile(filename, &file, EnvOptions())); + std::unique_ptr file_reader( + new RandomAccessFileReader(std::move(file), filename)); + + uint64_t file_size; + ASSERT_OK(options_.env->GetFileSize(filename, &file_size)); + + BlockHandle range_del_handle; + ASSERT_OK(FindMetaBlock( + file_reader.get(), file_size, kBlockBasedTableMagicNumber, + ImmutableCFOptions(options_), kRangeDelBlock, &range_del_handle)); + + ASSERT_OK(TryReopen()); + CorruptFile(filename, static_cast(range_del_handle.offset()), 1); + // The test case does not fail on TryReopen because failure to preload table + // handlers is not considered critical. + ASSERT_OK(TryReopen()); + std::string val; + // However, it does fail on any read involving that file since that file + // cannot be opened with a corrupt range deletion meta-block. + ASSERT_TRUE(db_->Get(ReadOptions(), "a", &val).IsCorruption()); +} + TEST_F(CorruptionTest, FileSystemStateCorrupted) { for (int iter = 0; iter < 2; ++iter) { Options options; @@ -485,7 +520,7 @@ TEST_F(CorruptionTest, FileSystemStateCorrupted) { db_ = nullptr; if (iter == 0) { // corrupt file size - unique_ptr file; + std::unique_ptr file; env_.NewWritableFile(filename, &file, EnvOptions()); file->Append(Slice("corrupted sst")); file.reset(); @@ -510,7 +545,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as RepairDB() is not supported in ROCKSDB_LITE\n"); return 0; } diff --git a/thirdparty/rocksdb/db/cuckoo_table_db_test.cc b/thirdparty/rocksdb/db/cuckoo_table_db_test.cc index e7c2d279a4..2d4487ff45 100644 --- a/thirdparty/rocksdb/db/cuckoo_table_db_test.cc +++ b/thirdparty/rocksdb/db/cuckoo_table_db_test.cc @@ -25,13 +25,13 @@ class CuckooTableDBTest : public testing::Test { public: CuckooTableDBTest() : env_(Env::Default()) { - dbname_ = test::TmpDir() + "/cuckoo_table_db_test"; + dbname_ = test::PerThreadDBPath("cuckoo_table_db_test"); EXPECT_OK(DestroyDB(dbname_, Options())); db_ = nullptr; Reopen(); } - ~CuckooTableDBTest() { + ~CuckooTableDBTest() override { delete db_; EXPECT_OK(DestroyDB(dbname_, Options())); } @@ -241,7 +241,7 @@ TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) { // Write 28 values, each 10016 B ~ 10KB for (int idx = 0; idx < 28; ++idx) { - ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx))); + ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx)))); } dbfull()->TEST_WaitForFlushMemTable(); ASSERT_EQ("1", FilesPerLevel()); @@ -250,7 +250,7 @@ TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) { true /* disallow trivial move */); ASSERT_EQ("0,2", FilesPerLevel()); for (int idx = 0; idx < 28; ++idx) { - ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx))); + ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx))); } } @@ -271,14 +271,14 @@ TEST_F(CuckooTableDBTest, SameKeyInsertedInTwoDifferentFilesAndCompacted) { // Generate one more file in level-0, and should trigger level-0 compaction for (int idx = 0; idx < 11; ++idx) { - ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx))); + ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx)))); } dbfull()->TEST_WaitForFlushMemTable(); dbfull()->TEST_CompactRange(0, nullptr, nullptr); ASSERT_EQ("0,1", FilesPerLevel()); for (int idx = 0; idx < 11; ++idx) { - ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx))); + ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx))); } } @@ -333,7 +333,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n"); return 0; } diff --git a/thirdparty/rocksdb/db/db_basic_test.cc b/thirdparty/rocksdb/db/db_basic_test.cc index 654a457ef5..c93a5e4364 100644 --- a/thirdparty/rocksdb/db/db_basic_test.cc +++ b/thirdparty/rocksdb/db/db_basic_test.cc @@ -6,9 +6,11 @@ // 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. +// #include #include "db/db_test_util.h" #include "port/stack_trace.h" #include "rocksdb/perf_context.h" +#include "util/fault_injection_test_env.h" #if !defined(ROCKSDB_LITE) #include "util/sync_point.h" #endif @@ -41,7 +43,7 @@ TEST_F(DBBasicTest, ReadOnlyDB) { Close(); auto options = CurrentOptions(); - assert(options.env = env_); + assert(options.env == env_); ASSERT_OK(ReadOnlyReopen(options)); ASSERT_EQ("v3", Get("foo")); ASSERT_EQ("v2", Get("bar")); @@ -213,11 +215,11 @@ TEST_F(DBBasicTest, PutSingleDeleteGet) { ASSERT_EQ("v2", Get(1, "foo2")); ASSERT_OK(SingleDelete(1, "foo")); ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - // Skip HashCuckooRep as it does not support single delete. FIFO and - // universal compaction do not apply to the test case. Skip MergePut - // because single delete does not get removed when it encounters a merge. - } while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction | - kSkipUniversalCompaction | kSkipMergePut)); + // Ski FIFO and universal compaction because they do not apply to the test + // case. Skip MergePut because single delete does not get removed when it + // encounters a merge. + } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | + kSkipMergePut)); } TEST_F(DBBasicTest, EmptyFlush) { @@ -235,11 +237,11 @@ TEST_F(DBBasicTest, EmptyFlush) { ASSERT_OK(Flush(1)); ASSERT_EQ("[ ]", AllEntriesFor("a", 1)); - // Skip HashCuckooRep as it does not support single delete. FIFO and - // universal compaction do not apply to the test case. Skip MergePut - // because merges cannot be combined with single deletions. - } while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction | - kSkipUniversalCompaction | kSkipMergePut)); + // Skip FIFO and universal compaction as they do not apply to the test + // case. Skip MergePut because merges cannot be combined with single + // deletions. + } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | + kSkipMergePut)); } TEST_F(DBBasicTest, GetFromVersions) { @@ -263,11 +265,6 @@ TEST_F(DBBasicTest, GetSnapshot) { std::string key = (i == 0) ? std::string("foo") : std::string(200, 'x'); ASSERT_OK(Put(1, key, "v1")); const Snapshot* s1 = db_->GetSnapshot(); - if (option_config_ == kHashCuckoo) { - // Unsupported case. - ASSERT_TRUE(s1 == nullptr); - break; - } ASSERT_OK(Put(1, key, "v2")); ASSERT_EQ("v2", Get(1, key)); ASSERT_EQ("v1", Get(1, key, s1)); @@ -508,7 +505,7 @@ TEST_F(DBBasicTest, Snapshot) { ASSERT_EQ(0U, GetNumSnapshots()); ASSERT_EQ("0v4", Get(0, "foo")); ASSERT_EQ("1v4", Get(1, "foo")); - } while (ChangeOptions(kSkipHashCuckoo)); + } while (ChangeOptions()); } #endif // ROCKSDB_LITE @@ -564,25 +561,24 @@ TEST_F(DBBasicTest, CompactBetweenSnapshots) { nullptr); ASSERT_EQ("sixth", Get(1, "foo")); ASSERT_EQ(AllEntriesFor("foo", 1), "[ sixth ]"); - // skip HashCuckooRep as it does not support snapshot - } while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction)); + } while (ChangeOptions(kSkipFIFOCompaction)); } TEST_F(DBBasicTest, DBOpen_Options) { Options options = CurrentOptions(); - std::string dbname = test::TmpDir(env_) + "/db_options_test"; - ASSERT_OK(DestroyDB(dbname, options)); + Close(); + Destroy(options); // Does not exist, and create_if_missing == false: error DB* db = nullptr; options.create_if_missing = false; - Status s = DB::Open(options, dbname, &db); + Status s = DB::Open(options, dbname_, &db); ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr); ASSERT_TRUE(db == nullptr); // Does not exist, and create_if_missing == true: OK options.create_if_missing = true; - s = DB::Open(options, dbname, &db); + s = DB::Open(options, dbname_, &db); ASSERT_OK(s); ASSERT_TRUE(db != nullptr); @@ -592,14 +588,14 @@ TEST_F(DBBasicTest, DBOpen_Options) { // Does exist, and error_if_exists == true: error options.create_if_missing = false; options.error_if_exists = true; - s = DB::Open(options, dbname, &db); + s = DB::Open(options, dbname_, &db); ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr); ASSERT_TRUE(db == nullptr); // Does exist, and error_if_exists == false: OK options.create_if_missing = true; options.error_if_exists = false; - s = DB::Open(options, dbname, &db); + s = DB::Open(options, dbname_, &db); ASSERT_OK(s); ASSERT_TRUE(db != nullptr); @@ -793,7 +789,7 @@ TEST_F(DBBasicTest, ChecksumTest) { BlockBasedTableOptions table_options; Options options = CurrentOptions(); // change when new checksum type added - int max_checksum = static_cast(kxxHash); + int max_checksum = static_cast(kxxHash64); const int kNumPerFile = 2; // generate one table with each type of checksum @@ -808,7 +804,7 @@ TEST_F(DBBasicTest, ChecksumTest) { } // verify data with each type of checksum - for (int i = 0; i <= kxxHash; ++i) { + for (int i = 0; i <= kxxHash64; ++i) { table_options.checksum = static_cast(i); options.table_factory.reset(NewBlockBasedTableFactory(table_options)); Reopen(options); @@ -847,6 +843,291 @@ TEST_F(DBBasicTest, MmapAndBufferOptions) { } #endif +class TestEnv : public EnvWrapper { + public: + explicit TestEnv() : EnvWrapper(Env::Default()), + close_count(0) { } + + class TestLogger : public Logger { + public: + using Logger::Logv; + TestLogger(TestEnv *env_ptr) : Logger() { env = env_ptr; } + ~TestLogger() override { + if (!closed_) { + CloseHelper(); + } + } + void Logv(const char* /*format*/, va_list /*ap*/) override{}; + + protected: + Status CloseImpl() override { return CloseHelper(); } + + private: + Status CloseHelper() { + env->CloseCountInc();; + return Status::IOError(); + } + TestEnv *env; + }; + + void CloseCountInc() { close_count++; } + + int GetCloseCount() { return close_count; } + + Status NewLogger(const std::string& /*fname*/, + std::shared_ptr* result) override { + result->reset(new TestLogger(this)); + return Status::OK(); + } + + private: + int close_count; +}; + +TEST_F(DBBasicTest, DBClose) { + Options options = GetDefaultOptions(); + std::string dbname = test::PerThreadDBPath("db_close_test"); + ASSERT_OK(DestroyDB(dbname, options)); + + DB* db = nullptr; + TestEnv* env = new TestEnv(); + options.create_if_missing = true; + options.env = env; + Status s = DB::Open(options, dbname, &db); + ASSERT_OK(s); + ASSERT_TRUE(db != nullptr); + + s = db->Close(); + ASSERT_EQ(env->GetCloseCount(), 1); + ASSERT_EQ(s, Status::IOError()); + + delete db; + ASSERT_EQ(env->GetCloseCount(), 1); + + // Do not call DB::Close() and ensure our logger Close() still gets called + s = DB::Open(options, dbname, &db); + ASSERT_OK(s); + ASSERT_TRUE(db != nullptr); + delete db; + ASSERT_EQ(env->GetCloseCount(), 2); + + // Provide our own logger and ensure DB::Close() does not close it + options.info_log.reset(new TestEnv::TestLogger(env)); + options.create_if_missing = false; + s = DB::Open(options, dbname, &db); + ASSERT_OK(s); + ASSERT_TRUE(db != nullptr); + + s = db->Close(); + ASSERT_EQ(s, Status::OK()); + delete db; + ASSERT_EQ(env->GetCloseCount(), 2); + options.info_log.reset(); + ASSERT_EQ(env->GetCloseCount(), 3); + + delete options.env; +} + +TEST_F(DBBasicTest, DBCloseFlushError) { + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(Env::Default())); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.manual_wal_flush = true; + options.write_buffer_size=100; + options.env = fault_injection_env.get(); + + Reopen(options); + ASSERT_OK(Put("key1", "value1")); + ASSERT_OK(Put("key2", "value2")); + ASSERT_OK(dbfull()->TEST_SwitchMemtable()); + ASSERT_OK(Put("key3", "value3")); + fault_injection_env->SetFilesystemActive(false); + Status s = dbfull()->Close(); + fault_injection_env->SetFilesystemActive(true); + ASSERT_NE(s, Status::OK()); + + Destroy(options); +} + +TEST_F(DBBasicTest, MultiGetMultiCF) { + Options options = CurrentOptions(); + CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich", + "alyosha", "popovich"}, + options); + + for (int i = 0; i < 8; ++i) { + ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", + "cf" + std::to_string(i) + "_val")); + } + + int get_sv_count = 0; + rocksdb::DBImpl* db = reinterpret_cast(db_); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) { + if (++get_sv_count == 2) { + // After MultiGet refs a couple of CFs, flush all CFs so MultiGet + // is forced to repeat the process + for (int i = 0; i < 8; ++i) { + ASSERT_OK(Flush(i)); + ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", + "cf" + std::to_string(i) + "_val2")); + } + } + if (get_sv_count == 11) { + for (int i = 0; i < 8; ++i) { + auto* cfd = reinterpret_cast( + db->GetColumnFamilyHandle(i)) + ->cfd(); + ASSERT_EQ(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); + } + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + std::vector cfs; + std::vector keys; + std::vector values; + + for (int i = 0; i < 8; ++i) { + cfs.push_back(i); + keys.push_back("cf" + std::to_string(i) + "_key"); + } + + values = MultiGet(cfs, keys); + ASSERT_EQ(values.size(), 8); + for (unsigned int j = 0; j < values.size(); ++j) { + ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val2"); + } + for (int i = 0; i < 8; ++i) { + auto* cfd = reinterpret_cast( + reinterpret_cast(db_)->GetColumnFamilyHandle(i)) + ->cfd(); + ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); + ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete); + } +} + +TEST_F(DBBasicTest, MultiGetMultiCFMutex) { + Options options = CurrentOptions(); + CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich", + "alyosha", "popovich"}, + options); + + for (int i = 0; i < 8; ++i) { + ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", + "cf" + std::to_string(i) + "_val")); + } + + int get_sv_count = 0; + int retries = 0; + bool last_try = false; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::MultiGet::LastTry", [&](void* /*arg*/) { + last_try = true; + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + }); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) { + if (last_try) { + return; + } + if (++get_sv_count == 2) { + ++retries; + get_sv_count = 0; + for (int i = 0; i < 8; ++i) { + ASSERT_OK(Flush(i)); + ASSERT_OK(Put( + i, "cf" + std::to_string(i) + "_key", + "cf" + std::to_string(i) + "_val" + std::to_string(retries))); + } + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + std::vector cfs; + std::vector keys; + std::vector values; + + for (int i = 0; i < 8; ++i) { + cfs.push_back(i); + keys.push_back("cf" + std::to_string(i) + "_key"); + } + + values = MultiGet(cfs, keys); + ASSERT_TRUE(last_try); + ASSERT_EQ(values.size(), 8); + for (unsigned int j = 0; j < values.size(); ++j) { + ASSERT_EQ(values[j], + "cf" + std::to_string(j) + "_val" + std::to_string(retries)); + } + for (int i = 0; i < 8; ++i) { + auto* cfd = reinterpret_cast( + reinterpret_cast(db_)->GetColumnFamilyHandle(i)) + ->cfd(); + ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); + } +} + +TEST_F(DBBasicTest, MultiGetMultiCFSnapshot) { + Options options = CurrentOptions(); + CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich", + "alyosha", "popovich"}, + options); + + for (int i = 0; i < 8; ++i) { + ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", + "cf" + std::to_string(i) + "_val")); + } + + int get_sv_count = 0; + rocksdb::DBImpl* db = reinterpret_cast(db_); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) { + if (++get_sv_count == 2) { + for (int i = 0; i < 8; ++i) { + ASSERT_OK(Flush(i)); + ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key", + "cf" + std::to_string(i) + "_val2")); + } + } + if (get_sv_count == 8) { + for (int i = 0; i < 8; ++i) { + auto* cfd = reinterpret_cast( + db->GetColumnFamilyHandle(i)) + ->cfd(); + ASSERT_TRUE( + (cfd->TEST_GetLocalSV()->Get() == SuperVersion::kSVInUse) || + (cfd->TEST_GetLocalSV()->Get() == SuperVersion::kSVObsolete)); + } + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + std::vector cfs; + std::vector keys; + std::vector values; + + for (int i = 0; i < 8; ++i) { + cfs.push_back(i); + keys.push_back("cf" + std::to_string(i) + "_key"); + } + + const Snapshot* snapshot = db_->GetSnapshot(); + values = MultiGet(cfs, keys, snapshot); + db_->ReleaseSnapshot(snapshot); + ASSERT_EQ(values.size(), 8); + for (unsigned int j = 0; j < values.size(); ++j) { + ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val"); + } + for (int i = 0; i < 8; ++i) { + auto* cfd = reinterpret_cast( + reinterpret_cast(db_)->GetColumnFamilyHandle(i)) + ->cfd(); + ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse); + } +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_blob_index_test.cc b/thirdparty/rocksdb/db/db_blob_index_test.cc index e71b511df5..005a23d63b 100644 --- a/thirdparty/rocksdb/db/db_blob_index_test.cc +++ b/thirdparty/rocksdb/db/db_blob_index_test.cc @@ -64,7 +64,8 @@ class DBBlobIndexTest : public DBTestBase { read_options.snapshot = snapshot; PinnableSlice value; auto s = dbfull()->GetImpl(read_options, cfh(), key, &value, - nullptr /*value_found*/, is_blob_index); + nullptr /*value_found*/, nullptr /*callback*/, + is_blob_index); if (s.IsNotFound()) { return "NOT_FOUND"; } @@ -88,9 +89,9 @@ class DBBlobIndexTest : public DBTestBase { } ArenaWrappedDBIter* GetBlobIterator() { - return dbfull()->NewIteratorImpl(ReadOptions(), cfd(), - dbfull()->GetLatestSequenceNumber(), - true /*allow_blob*/); + return dbfull()->NewIteratorImpl( + ReadOptions(), cfd(), dbfull()->GetLatestSequenceNumber(), + nullptr /*read_callback*/, true /*allow_blob*/); } Options GetTestOptions() { diff --git a/thirdparty/rocksdb/db/db_block_cache_test.cc b/thirdparty/rocksdb/db/db_block_cache_test.cc index 169cadc85c..ad906dbcb5 100644 --- a/thirdparty/rocksdb/db/db_block_cache_test.cc +++ b/thirdparty/rocksdb/db/db_block_cache_test.cc @@ -47,7 +47,7 @@ class DBBlockCacheTest : public DBTestBase { return options; } - void InitTable(const Options& options) { + void InitTable(const Options& /*options*/) { std::string value(kValueSize, 'a'); for (size_t i = 0; i < kNumBlocks; i++) { ASSERT_OK(Put(ToString(i), value.c_str())); @@ -111,6 +111,31 @@ class DBBlockCacheTest : public DBTestBase { } }; +TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) { + ReadOptions read_options; + read_options.fill_cache = false; + auto table_options = GetTableOptions(); + auto options = GetOptions(table_options); + InitTable(options); + + std::shared_ptr cache = NewLRUCache(0, 0, false); + table_options.block_cache = cache; + options.table_factory.reset(new BlockBasedTableFactory(table_options)); + Reopen(options); + RecordCacheCounters(options); + + std::vector> iterators(kNumBlocks - 1); + Iterator* iter = nullptr; + + ASSERT_EQ(0, cache->GetUsage()); + iter = db_->NewIterator(read_options); + iter->Seek(ToString(0)); + ASSERT_LT(0, cache->GetUsage()); + delete iter; + iter = nullptr; + ASSERT_EQ(0, cache->GetUsage()); +} + TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) { ReadOptions read_options; auto table_options = GetTableOptions(); @@ -148,7 +173,7 @@ TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) { delete iter; iter = nullptr; - // Release interators and access cache again. + // Release iterators and access cache again. for (size_t i = 0; i < kNumBlocks - 1; i++) { iterators[i].reset(); CheckCacheCounters(options, 0, 0, 0, 0); @@ -280,6 +305,41 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksOfNewTableAddedToCache) { TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); } +// With fill_cache = false, fills up the cache, then iterates over the entire +// db, verify dummy entries inserted in `BlockBasedTable::NewDataBlockIterator` +// does not cause heap-use-after-free errors in COMPILE_WITH_ASAN=1 runs +TEST_F(DBBlockCacheTest, FillCacheAndIterateDB) { + ReadOptions read_options; + read_options.fill_cache = false; + auto table_options = GetTableOptions(); + auto options = GetOptions(table_options); + InitTable(options); + + std::shared_ptr cache = NewLRUCache(10, 0, true); + table_options.block_cache = cache; + options.table_factory.reset(new BlockBasedTableFactory(table_options)); + Reopen(options); + ASSERT_OK(Put("key1", "val1")); + ASSERT_OK(Put("key2", "val2")); + ASSERT_OK(Flush()); + ASSERT_OK(Put("key3", "val3")); + ASSERT_OK(Put("key4", "val4")); + ASSERT_OK(Flush()); + ASSERT_OK(Put("key5", "val5")); + ASSERT_OK(Put("key6", "val6")); + ASSERT_OK(Flush()); + + Iterator* iter = nullptr; + + iter = db_->NewIterator(read_options); + iter->Seek(ToString(0)); + while (iter->Valid()) { + iter->Next(); + } + delete iter; + iter = nullptr; +} + TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) { Options options = CurrentOptions(); options.create_if_missing = true; @@ -289,7 +349,7 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) { // 200 bytes are enough to hold the first two blocks std::shared_ptr cache = NewLRUCache(200, 0, false); table_options.block_cache = cache; - table_options.filter_policy.reset(NewBloomFilterPolicy(20)); + table_options.filter_policy.reset(NewBloomFilterPolicy(20, true)); options.table_factory.reset(new BlockBasedTableFactory(table_options)); CreateAndReopenWithCF({"pikachu"}, options); @@ -330,11 +390,14 @@ class MockCache : public LRUCache { static uint32_t high_pri_insert_count; static uint32_t low_pri_insert_count; - MockCache() : LRUCache(1 << 25, 0, false, 0.0) {} + MockCache() + : LRUCache((size_t)1 << 25 /*capacity*/, 0 /*num_shard_bits*/, + false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/) { + } - virtual Status Insert(const Slice& key, void* value, size_t charge, - void (*deleter)(const Slice& key, void* value), - Handle** handle, Priority priority) override { + Status Insert(const Slice& key, void* value, size_t charge, + void (*deleter)(const Slice& key, void* value), Handle** handle, + Priority priority) override { if (priority == Priority::LOW) { low_pri_insert_count++; } else { @@ -568,6 +631,74 @@ TEST_F(DBBlockCacheTest, CompressedCache) { } } +TEST_F(DBBlockCacheTest, CacheCompressionDict) { + const int kNumFiles = 4; + const int kNumEntriesPerFile = 128; + const int kNumBytesPerEntry = 1024; + + // Try all the available libraries that support dictionary compression + std::vector compression_types; +#ifdef ZLIB + compression_types.push_back(kZlibCompression); +#endif // ZLIB +#if LZ4_VERSION_NUMBER >= 10400 + compression_types.push_back(kLZ4Compression); + compression_types.push_back(kLZ4HCCompression); +#endif // LZ4_VERSION_NUMBER >= 10400 +#if ZSTD_VERSION_NUMBER >= 500 + compression_types.push_back(kZSTD); +#endif // ZSTD_VERSION_NUMBER >= 500 + Random rnd(301); + for (auto compression_type : compression_types) { + Options options = CurrentOptions(); + options.compression = compression_type; + options.compression_opts.max_dict_bytes = 4096; + options.create_if_missing = true; + options.num_levels = 2; + options.statistics = rocksdb::CreateDBStatistics(); + options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry; + BlockBasedTableOptions table_options; + table_options.cache_index_and_filter_blocks = true; + table_options.block_cache.reset(new MockCache()); + options.table_factory.reset(new BlockBasedTableFactory(table_options)); + DestroyAndReopen(options); + + for (int i = 0; i < kNumFiles; ++i) { + ASSERT_EQ(i, NumTableFilesAtLevel(0, 0)); + for (int j = 0; j < kNumEntriesPerFile; ++j) { + std::string value = RandomString(&rnd, kNumBytesPerEntry); + ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str())); + } + ASSERT_OK(Flush()); + } + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1)); + + // Seek to a key in a file. It should cause the SST's dictionary meta-block + // to be read. + RecordCacheCounters(options); + ASSERT_EQ(0, + TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS)); + ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD)); + ASSERT_EQ( + TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT), + 0); + ReadOptions read_options; + ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1))); + // Two blocks missed/added: dictionary and data block + // One block hit: index since it's prefetched + CheckCacheCounters(options, 2 /* expected_misses */, 1 /* expected_hits */, + 2 /* expected_inserts */, 0 /* expected_failures */); + ASSERT_EQ(1, + TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS)); + ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD)); + ASSERT_GT( + TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT), + 0); + } +} + #endif // ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_bloom_filter_test.cc b/thirdparty/rocksdb/db/db_bloom_filter_test.cc index e6248a0401..39dd20bb25 100644 --- a/thirdparty/rocksdb/db/db_bloom_filter_test.cc +++ b/thirdparty/rocksdb/db/db_bloom_filter_test.cc @@ -22,27 +22,51 @@ class DBBloomFilterTest : public DBTestBase { class DBBloomFilterTestWithParam : public DBTestBase, - public testing::WithParamInterface> { + public testing::WithParamInterface> { // public testing::WithParamInterface { protected: bool use_block_based_filter_; bool partition_filters_; + uint32_t format_version_; public: DBBloomFilterTestWithParam() : DBTestBase("/db_bloom_filter_tests") {} - ~DBBloomFilterTestWithParam() {} + ~DBBloomFilterTestWithParam() override {} void SetUp() override { use_block_based_filter_ = std::get<0>(GetParam()); partition_filters_ = std::get<1>(GetParam()); + format_version_ = std::get<2>(GetParam()); + } +}; + +class DBBloomFilterTestDefFormatVersion : public DBBloomFilterTestWithParam {}; + +class SliceTransformLimitedDomainGeneric : public SliceTransform { + const char* Name() const override { + return "SliceTransformLimitedDomainGeneric"; + } + + Slice Transform(const Slice& src) const override { + return Slice(src.data(), 5); + } + + bool InDomain(const Slice& src) const override { + // prefix will be x???? + return src.size() >= 5; + } + + bool InRange(const Slice& dst) const override { + // prefix will be x???? + return dst.size() == 5; } }; // 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 -TEST_P(DBBloomFilterTestWithParam, KeyMayExist) { +TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) { do { ReadOptions ropts; std::string value; @@ -117,11 +141,79 @@ TEST_P(DBBloomFilterTestWithParam, KeyMayExist) { ChangeOptions(kSkipPlainTable | kSkipHashIndex | kSkipFIFOCompaction)); } +TEST_F(DBBloomFilterTest, GetFilterByPrefixBloomCustomPrefixExtractor) { + for (bool partition_filters : {true, false}) { + Options options = last_options_; + options.prefix_extractor = + std::make_shared(); + options.statistics = rocksdb::CreateDBStatistics(); + get_perf_context()->EnablePerLevelPerfContext(); + BlockBasedTableOptions bbto; + bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); + if (partition_filters) { + bbto.partition_filters = true; + bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; + } + 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( + 0, + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + ASSERT_EQ("foo2", Get("barbarbar2")); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); + ASSERT_EQ( + 0, + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + ASSERT_EQ("NOT_FOUND", Get("barbarbar3")); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0); + ASSERT_EQ( + 0, + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + + ASSERT_EQ("NOT_FOUND", Get("barfoofoo")); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1); + ASSERT_EQ( + 1, + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + + ASSERT_EQ("NOT_FOUND", Get("foobarbar")); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2); + ASSERT_EQ( + 2, + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + + ro.total_order_seek = true; + ASSERT_TRUE(db_->Get(ro, "foobarbar", &value).IsNotFound()); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2); + ASSERT_EQ( + 2, + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + get_perf_context()->Reset(); + } +} + TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) { for (bool partition_filters : {true, false}) { Options options = last_options_; options.prefix_extractor.reset(NewFixedPrefixTransform(8)); options.statistics = rocksdb::CreateDBStatistics(); + get_perf_context()->EnablePerLevelPerfContext(); BlockBasedTableOptions bbto; bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); if (partition_filters) { @@ -160,6 +252,10 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) { ro.total_order_seek = true; ASSERT_TRUE(db_->Get(ro, "foobarbar", &value).IsNotFound()); ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2); + ASSERT_EQ( + 2, + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + get_perf_context()->Reset(); } } @@ -168,6 +264,7 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) { Options options = last_options_; options.prefix_extractor.reset(NewFixedPrefixTransform(3)); options.statistics = rocksdb::CreateDBStatistics(); + get_perf_context()->EnablePerLevelPerfContext(); BlockBasedTableOptions bbto; bbto.filter_policy.reset(NewBloomFilterPolicy(10, false)); @@ -315,6 +412,14 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) { ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12); ASSERT_EQ("bar", Get("barfoo")); ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 12); + uint64_t bloom_filter_useful_all_levels = 0; + for (auto& kv : (*(get_perf_context()->level_to_perf_context))) { + if (kv.second.bloom_filter_useful > 0) { + bloom_filter_useful_all_levels += kv.second.bloom_filter_useful; + } + } + ASSERT_EQ(12, bloom_filter_useful_all_levels); + get_perf_context()->Reset(); } } @@ -334,6 +439,11 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) { table_options.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; } + table_options.format_version = format_version_; + if (format_version_ >= 4) { + // value delta encoding challenged more with index interval > 1 + table_options.index_block_restart_interval = 8; + } table_options.metadata_block_size = 32; options.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -389,15 +499,34 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) { } while (ChangeCompactOptions()); } -INSTANTIATE_TEST_CASE_P(DBBloomFilterTestWithParam, DBBloomFilterTestWithParam, - ::testing::Values(std::make_tuple(true, false), - std::make_tuple(false, true), - std::make_tuple(false, false))); +#ifndef ROCKSDB_VALGRIND_RUN +INSTANTIATE_TEST_CASE_P( + FormatDef, DBBloomFilterTestDefFormatVersion, + ::testing::Values(std::make_tuple(true, false, test::kDefaultFormatVersion), + std::make_tuple(false, true, test::kDefaultFormatVersion), + std::make_tuple(false, false, + test::kDefaultFormatVersion))); + +INSTANTIATE_TEST_CASE_P( + FormatDef, DBBloomFilterTestWithParam, + ::testing::Values(std::make_tuple(true, false, test::kDefaultFormatVersion), + std::make_tuple(false, true, test::kDefaultFormatVersion), + std::make_tuple(false, false, + test::kDefaultFormatVersion))); + +INSTANTIATE_TEST_CASE_P( + FormatLatest, DBBloomFilterTestWithParam, + ::testing::Values(std::make_tuple(true, false, test::kLatestFormatVersion), + std::make_tuple(false, true, test::kLatestFormatVersion), + std::make_tuple(false, false, + test::kLatestFormatVersion))); +#endif // ROCKSDB_VALGRIND_RUN TEST_F(DBBloomFilterTest, BloomFilterRate) { while (ChangeFilterOptions()) { Options options = CurrentOptions(); options.statistics = rocksdb::CreateDBStatistics(); + get_perf_context()->EnablePerLevelPerfContext(); CreateAndReopenWithCF({"pikachu"}, options); const int maxKey = 10000; @@ -419,6 +548,10 @@ TEST_F(DBBloomFilterTest, BloomFilterRate) { ASSERT_EQ("NOT_FOUND", Get(1, Key(i + 33333))); } ASSERT_GE(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), maxKey * 0.98); + ASSERT_GE( + (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful, + maxKey * 0.98); + get_perf_context()->Reset(); } } @@ -509,7 +642,7 @@ class WrappedBloom : public FilterPolicy { explicit WrappedBloom(int bits_per_key) : filter_(NewBloomFilterPolicy(bits_per_key)), counter_(0) {} - ~WrappedBloom() { delete filter_; } + ~WrappedBloom() override { delete filter_; } const char* Name() const override { return "WrappedRocksDbFilterPolicy"; } @@ -653,6 +786,56 @@ TEST_F(DBBloomFilterTest, PrefixExtractorBlockFilter) { delete iter; } +TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilter) { + // regression test for #2743. the range delete tombstones in memtable should + // be added even when Get() skips searching due to its prefix bloom filter + const int kMemtableSize = 1 << 20; // 1MB + const int kMemtablePrefixFilterSize = 1 << 13; // 8KB + const int kPrefixLen = 4; + Options options = CurrentOptions(); + options.memtable_prefix_bloom_size_ratio = + static_cast(kMemtablePrefixFilterSize) / kMemtableSize; + options.prefix_extractor.reset(rocksdb::NewFixedPrefixTransform(kPrefixLen)); + options.write_buffer_size = kMemtableSize; + options.memtable_whole_key_filtering = false; + Reopen(options); + std::string key1("AAAABBBB"); + std::string key2("AAAACCCC"); // not in DB + std::string key3("AAAADDDD"); + std::string key4("AAAAEEEE"); + std::string value1("Value1"); + std::string value3("Value3"); + std::string value4("Value4"); + + ASSERT_OK(Put(key1, value1, WriteOptions())); + + // check memtable bloom stats + ASSERT_EQ("NOT_FOUND", Get(key2)); + ASSERT_EQ(0, get_perf_context()->bloom_memtable_miss_count); + // same prefix, bloom filter false positive + ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); + + // enable whole key bloom filter + options.memtable_whole_key_filtering = true; + Reopen(options); + // check memtable bloom stats + ASSERT_OK(Put(key3, value3, WriteOptions())); + ASSERT_EQ("NOT_FOUND", Get(key2)); + // whole key bloom filter kicks in and determines it's a miss + ASSERT_EQ(1, get_perf_context()->bloom_memtable_miss_count); + ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); + + // verify whole key filtering does not depend on prefix_extractor + options.prefix_extractor.reset(); + Reopen(options); + // check memtable bloom stats + ASSERT_OK(Put(key4, value4, WriteOptions())); + ASSERT_EQ("NOT_FOUND", Get(key2)); + // whole key bloom filter kicks in and determines it's a miss + ASSERT_EQ(2, get_perf_context()->bloom_memtable_miss_count); + ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count); +} + #ifndef ROCKSDB_LITE class BloomStatsTestWithParam : public DBBloomFilterTest, @@ -690,7 +873,7 @@ class BloomStatsTestWithParam DestroyAndReopen(options_); } - ~BloomStatsTestWithParam() { + ~BloomStatsTestWithParam() override { get_perf_context()->Reset(); Destroy(options_); } @@ -764,7 +947,7 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) { ASSERT_OK(Put(key1, value1, WriteOptions())); ASSERT_OK(Put(key3, value3, WriteOptions())); - unique_ptr iter(dbfull()->NewIterator(ReadOptions())); + std::unique_ptr iter(dbfull()->NewIterator(ReadOptions())); // check memtable bloom stats iter->Seek(key1); @@ -940,6 +1123,7 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) { options.table_factory.reset(NewBlockBasedTableFactory(bbto)); options.optimize_filters_for_hits = true; options.statistics = rocksdb::CreateDBStatistics(); + get_perf_context()->EnablePerLevelPerfContext(); CreateAndReopenWithCF({"mypikachu"}, options); int numkeys = 200000; @@ -986,6 +1170,14 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) { // no bloom filter. Most keys be checked bloom filters twice. ASSERT_GT(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 65000 * 2); ASSERT_LT(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 120000 * 2); + uint64_t bloom_filter_useful_all_levels = 0; + for (auto& kv : (*(get_perf_context()->level_to_perf_context))) { + if (kv.second.bloom_filter_useful > 0) { + bloom_filter_useful_all_levels += kv.second.bloom_filter_useful; + } + } + ASSERT_GT(bloom_filter_useful_all_levels, 65000 * 2); + ASSERT_LT(bloom_filter_useful_all_levels, 120000 * 2); for (int i = 0; i < numkeys; i += 2) { ASSERT_EQ(Get(1, Key(i)), "val"); @@ -1057,10 +1249,10 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) { int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial_move++; }); + [&](void* /*arg*/) { non_trivial_move++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); CompactRangeOptions compact_options; @@ -1095,6 +1287,414 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) { ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); ASSERT_EQ(2 /* index and data block */, TestGetTickerCount(options, BLOCK_CACHE_ADD)); + get_perf_context()->Reset(); +} + +int CountIter(std::unique_ptr& iter, const Slice& key) { + int count = 0; + for (iter->Seek(key); iter->Valid() && iter->status() == Status::OK(); + iter->Next()) { + count++; + } + return count; +} + +// use iterate_upper_bound to hint compatiability of existing bloom filters. +// The BF is considered compatible if 1) upper bound and seek key transform +// into the same string, or 2) the transformed seek key is of the same length +// as the upper bound and two keys are adjacent according to the comparator. +TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) { + int iteration = 0; + for (bool use_block_based_builder : {true, false}) { + Options options; + options.create_if_missing = true; + options.prefix_extractor.reset(NewCappedPrefixTransform(4)); + options.disable_auto_compactions = true; + options.statistics = CreateDBStatistics(); + // Enable prefix bloom for SST files + BlockBasedTableOptions table_options; + table_options.cache_index_and_filter_blocks = true; + table_options.filter_policy.reset( + NewBloomFilterPolicy(10, use_block_based_builder)); + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + DestroyAndReopen(options); + + ASSERT_OK(Put("abcdxxx0", "val1")); + ASSERT_OK(Put("abcdxxx1", "val2")); + ASSERT_OK(Put("abcdxxx2", "val3")); + ASSERT_OK(Put("abcdxxx3", "val4")); + dbfull()->Flush(FlushOptions()); + { + // prefix_extractor has not changed, BF will always be read + Slice upper_bound("abce"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "abcd0000"), 4); + } + { + Slice upper_bound("abcdzzzz"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "abcd0000"), 4); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:5"}})); + ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(), + "rocksdb.FixedPrefix.5")); + { + // BF changed, [abcdxx00, abce) is a valid bound, will trigger BF read + Slice upper_bound("abce"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "abcdxx00"), 4); + // should check bloom filter since upper bound meets requirement + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 2 + iteration); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + { + // [abcdxx01, abcey) is not valid bound since upper bound is too long for + // the BF in SST (capped:4) + Slice upper_bound("abcey"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "abcdxx01"), 4); + // should skip bloom filter since upper bound is too long + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 2 + iteration); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + { + // [abcdxx02, abcdy) is a valid bound since the prefix is the same + Slice upper_bound("abcdy"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "abcdxx02"), 4); + // should check bloom filter since upper bound matches transformed seek + // key + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 2 + iteration * 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + { + // [aaaaaaaa, abce) is not a valid bound since 1) they don't share the + // same prefix, 2) the prefixes are not consecutive + Slice upper_bound("abce"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "aaaaaaaa"), 0); + // should skip bloom filter since mismatch is found + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 2 + iteration * 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:3"}})); + { + // [abc, abd) is not a valid bound since the upper bound is too short + // for BF (capped:4) + Slice upper_bound("abd"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "abc"), 4); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 2 + iteration * 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:4"}})); + { + // set back to capped:4 and verify BF is always read + Slice upper_bound("abd"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "abc"), 0); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 3 + iteration * 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1); + } + iteration++; + } +} + +// Create multiple SST files each with a different prefix_extractor config, +// verify iterators can read all SST files using the latest config. +TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) { + int iteration = 0; + for (bool use_block_based_builder : {true, false}) { + Options options; + options.create_if_missing = true; + options.prefix_extractor.reset(NewFixedPrefixTransform(1)); + options.disable_auto_compactions = true; + options.statistics = CreateDBStatistics(); + // Enable prefix bloom for SST files + BlockBasedTableOptions table_options; + table_options.filter_policy.reset( + NewBloomFilterPolicy(10, use_block_based_builder)); + table_options.cache_index_and_filter_blocks = true; + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + DestroyAndReopen(options); + + Slice upper_bound("foz90000"); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + + // first SST with fixed:1 BF + ASSERT_OK(Put("foo2", "bar2")); + ASSERT_OK(Put("foo", "bar")); + ASSERT_OK(Put("foq1", "bar1")); + ASSERT_OK(Put("fpa", "0")); + dbfull()->Flush(FlushOptions()); + std::unique_ptr iter_old(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter_old, "foo"), 4); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 1); + + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}})); + ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(), + "rocksdb.CappedPrefix.3")); + read_options.iterate_upper_bound = &upper_bound; + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "foo"), 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 1 + iteration); + ASSERT_EQ(CountIter(iter, "gpk"), 0); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 1 + iteration); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + + // second SST with capped:3 BF + ASSERT_OK(Put("foo3", "bar3")); + ASSERT_OK(Put("foo4", "bar4")); + ASSERT_OK(Put("foq5", "bar5")); + ASSERT_OK(Put("fpb", "1")); + dbfull()->Flush(FlushOptions()); + { + // BF is cappped:3 now + std::unique_ptr iter_tmp(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter_tmp, "foo"), 4); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 2 + iteration * 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0); + // both counters are incremented because BF is "not changed" for 1 of the + // 2 SST files, so filter is checked once and found no match. + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 3 + iteration * 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1); + } + + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}})); + ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(), + "rocksdb.FixedPrefix.2")); + // third SST with fixed:2 BF + ASSERT_OK(Put("foo6", "bar6")); + ASSERT_OK(Put("foo7", "bar7")); + ASSERT_OK(Put("foq8", "bar8")); + ASSERT_OK(Put("fpc", "2")); + dbfull()->Flush(FlushOptions()); + { + // BF is fixed:2 now + std::unique_ptr iter_tmp(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter_tmp, "foo"), 9); + // the first and last BF are checked + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 4 + iteration * 3); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1); + ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0); + // only last BF is checked and not found + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 5 + iteration * 3); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2); + } + + // iter_old can only see the first SST, so checked plus 1 + ASSERT_EQ(CountIter(iter_old, "foo"), 4); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 6 + iteration * 3); + // iter was created after the first setoptions call so only full filter + // will check the filter + ASSERT_EQ(CountIter(iter, "foo"), 2); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 6 + iteration * 4); + + { + // keys in all three SSTs are visible to iterator + // The range of [foo, foz90000] is compatible with (fixed:1) and (fixed:2) + // so +2 for checked counter + std::unique_ptr iter_all(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter_all, "foo"), 9); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 7 + iteration * 5); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2); + ASSERT_EQ(CountIter(iter_all, "gpk"), 0); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 8 + iteration * 5); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3); + } + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}})); + ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(), + "rocksdb.CappedPrefix.3")); + { + std::unique_ptr iter_all(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter_all, "foo"), 6); + // all three SST are checked because the current options has the same as + // the remaining SST (capped:3) + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 9 + iteration * 7); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3); + ASSERT_EQ(CountIter(iter_all, "gpk"), 0); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), + 10 + iteration * 7); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 4); + } + // TODO(Zhongyi): Maybe also need to add Get calls to test point look up? + iteration++; + } +} + +// Create a new column family in a running DB, change prefix_extractor +// dynamically, verify the iterator created on the new column family behaves +// as expected +TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) { + int iteration = 0; + for (bool use_block_based_builder : {true, false}) { + Options options = CurrentOptions(); + options.create_if_missing = true; + options.prefix_extractor.reset(NewFixedPrefixTransform(1)); + options.disable_auto_compactions = true; + options.statistics = CreateDBStatistics(); + // Enable prefix bloom for SST files + BlockBasedTableOptions table_options; + table_options.cache_index_and_filter_blocks = true; + table_options.filter_policy.reset( + NewBloomFilterPolicy(10, use_block_based_builder)); + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + CreateAndReopenWithCF({"pikachu" + std::to_string(iteration)}, options); + ReadOptions read_options; + read_options.prefix_same_as_start = true; + // create a new CF and set prefix_extractor dynamically + options.prefix_extractor.reset(NewCappedPrefixTransform(3)); + CreateColumnFamilies({"ramen_dojo_" + std::to_string(iteration)}, options); + ASSERT_EQ(0, + strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(), + "rocksdb.CappedPrefix.3")); + ASSERT_OK(Put(2, "foo3", "bar3")); + ASSERT_OK(Put(2, "foo4", "bar4")); + ASSERT_OK(Put(2, "foo5", "bar5")); + ASSERT_OK(Put(2, "foq6", "bar6")); + ASSERT_OK(Put(2, "fpq7", "bar7")); + dbfull()->Flush(FlushOptions()); + { + std::unique_ptr iter( + db_->NewIterator(read_options, handles_[2])); + ASSERT_EQ(CountIter(iter, "foo"), 3); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 0); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + ASSERT_OK( + dbfull()->SetOptions(handles_[2], {{"prefix_extractor", "fixed:2"}})); + ASSERT_EQ(0, + strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(), + "rocksdb.FixedPrefix.2")); + { + std::unique_ptr iter( + db_->NewIterator(read_options, handles_[2])); + ASSERT_EQ(CountIter(iter, "foo"), 4); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 0); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + ASSERT_OK(dbfull()->DropColumnFamily(handles_[2])); + dbfull()->DestroyColumnFamilyHandle(handles_[2]); + handles_[2] = nullptr; + ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); + dbfull()->DestroyColumnFamilyHandle(handles_[1]); + handles_[1] = nullptr; + iteration++; + } +} + +// Verify it's possible to change prefix_extractor at runtime and iterators +// behaves as expected +TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) { + int iteration = 0; + for (bool use_block_based_builder : {true, false}) { + Options options; + options.create_if_missing = true; + options.prefix_extractor.reset(NewFixedPrefixTransform(1)); + options.disable_auto_compactions = true; + options.statistics = CreateDBStatistics(); + // Enable prefix bloom for SST files + BlockBasedTableOptions table_options; + table_options.cache_index_and_filter_blocks = true; + table_options.filter_policy.reset( + NewBloomFilterPolicy(10, use_block_based_builder)); + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + DestroyAndReopen(options); + + ASSERT_OK(Put("foo2", "bar2")); + ASSERT_OK(Put("foo", "bar")); + ASSERT_OK(Put("foo1", "bar1")); + ASSERT_OK(Put("fpa", "0")); + dbfull()->Flush(FlushOptions()); + ASSERT_OK(Put("foo3", "bar3")); + ASSERT_OK(Put("foo4", "bar4")); + ASSERT_OK(Put("foo5", "bar5")); + ASSERT_OK(Put("fpb", "1")); + dbfull()->Flush(FlushOptions()); + ASSERT_OK(Put("foo6", "bar6")); + ASSERT_OK(Put("foo7", "bar7")); + ASSERT_OK(Put("foo8", "bar8")); + ASSERT_OK(Put("fpc", "2")); + dbfull()->Flush(FlushOptions()); + + ReadOptions read_options; + read_options.prefix_same_as_start = true; + { + std::unique_ptr iter(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter, "foo"), 12); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + std::unique_ptr iter_old(db_->NewIterator(read_options)); + ASSERT_EQ(CountIter(iter_old, "foo"), 12); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}})); + ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(), + "rocksdb.CappedPrefix.3")); + { + std::unique_ptr iter(db_->NewIterator(read_options)); + // "fp*" should be skipped + ASSERT_EQ(CountIter(iter, "foo"), 9); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + } + + // iterator created before should not be affected and see all keys + ASSERT_EQ(CountIter(iter_old, "foo"), 12); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 9); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0); + ASSERT_EQ(CountIter(iter_old, "abc"), 0); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 12); + ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3); + iteration++; + } } #endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/db_compaction_filter_test.cc b/thirdparty/rocksdb/db/db_compaction_filter_test.cc index 9f751f059f..37e80048e6 100644 --- a/thirdparty/rocksdb/db/db_compaction_filter_test.cc +++ b/thirdparty/rocksdb/db/db_compaction_filter_test.cc @@ -24,35 +24,72 @@ class DBTestCompactionFilter : public DBTestBase { DBTestCompactionFilter() : DBTestBase("/db_compaction_filter_test") {} }; +// Param variant of DBTestBase::ChangeCompactOptions +class DBTestCompactionFilterWithCompactParam + : public DBTestCompactionFilter, + public ::testing::WithParamInterface { + public: + DBTestCompactionFilterWithCompactParam() : DBTestCompactionFilter() { + option_config_ = GetParam(); + Destroy(last_options_); + auto options = CurrentOptions(); + if (option_config_ == kDefault || option_config_ == kUniversalCompaction || + option_config_ == kUniversalCompactionMultiLevel) { + options.create_if_missing = true; + } + if (option_config_ == kLevelSubcompactions || + option_config_ == kUniversalSubcompactions) { + assert(options.max_subcompactions > 1); + } + TryReopen(options); + } +}; + +#ifndef ROCKSDB_VALGRIND_RUN +INSTANTIATE_TEST_CASE_P( + DBTestCompactionFilterWithCompactOption, + DBTestCompactionFilterWithCompactParam, + ::testing::Values(DBTestBase::OptionConfig::kDefault, + DBTestBase::OptionConfig::kUniversalCompaction, + DBTestBase::OptionConfig::kUniversalCompactionMultiLevel, + DBTestBase::OptionConfig::kLevelSubcompactions, + DBTestBase::OptionConfig::kUniversalSubcompactions)); +#else +// Run fewer cases in valgrind +INSTANTIATE_TEST_CASE_P(DBTestCompactionFilterWithCompactOption, + DBTestCompactionFilterWithCompactParam, + ::testing::Values(DBTestBase::OptionConfig::kDefault)); +#endif // ROCKSDB_VALGRIND_RUN + 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 { + 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"; } + 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 { + 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"; } + const char* Name() const override { return "DeleteFilter"; } }; class DeleteISFilter : public CompactionFilter { public: - virtual bool Filter(int level, const Slice& key, const Slice& value, - std::string* new_value, - bool* value_changed) const override { + bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { cfilter_count++; int i = std::stoi(key.ToString()); if (i > 5 && i <= 105) { @@ -61,23 +98,23 @@ class DeleteISFilter : public CompactionFilter { return false; } - virtual bool IgnoreSnapshots() const override { return true; } + bool IgnoreSnapshots() const override { return true; } - virtual const char* Name() const override { return "DeleteFilter"; } + const char* Name() const override { return "DeleteFilter"; } }; // Skip x if floor(x/10) is even, use range skips. Requires that keys are // zero-padded to length 10. class SkipEvenFilter : public CompactionFilter { public: - virtual Decision FilterV2(int level, const Slice& key, ValueType value_type, - const Slice& existing_value, std::string* new_value, - std::string* skip_until) const override { + Decision FilterV2(int /*level*/, const Slice& key, ValueType /*value_type*/, + const Slice& /*existing_value*/, std::string* /*new_value*/, + std::string* skip_until) const override { cfilter_count++; int i = std::stoi(key.ToString()); if (i / 10 % 2 == 0) { char key_str[100]; - snprintf(key_str, sizeof(key), "%010d", i / 10 * 10 + 10); + snprintf(key_str, sizeof(key_str), "%010d", i / 10 * 10 + 10); *skip_until = key_str; ++cfilter_skips; return Decision::kRemoveAndSkipUntil; @@ -85,22 +122,22 @@ class SkipEvenFilter : public CompactionFilter { return Decision::kKeep; } - virtual bool IgnoreSnapshots() const override { return true; } + bool IgnoreSnapshots() const override { return true; } - virtual const char* Name() const override { return "DeleteFilter"; } + const char* Name() const override { return "DeleteFilter"; } }; class DelayFilter : public CompactionFilter { public: explicit DelayFilter(DBTestBase* d) : db_test(d) {} - virtual bool Filter(int level, const Slice& key, const Slice& value, - std::string* new_value, - bool* value_changed) const override { + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { db_test->env_->addon_time_.fetch_add(1000); return true; } - virtual const char* Name() const override { return "DelayFilter"; } + const char* Name() const override { return "DelayFilter"; } private: DBTestBase* db_test; @@ -110,13 +147,13 @@ 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 { + 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"; } + const char* Name() const override { return "ConditionalFilter"; } private: const std::string* filtered_value_; @@ -126,16 +163,15 @@ 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 { + 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"; } + const char* Name() const override { return "ChangeFilter"; } }; class KeepFilterFactory : public CompactionFilterFactory { @@ -146,7 +182,7 @@ class KeepFilterFactory : public CompactionFilterFactory { check_context_cf_id_(check_context_cf_id), compaction_filter_created_(false) {} - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { if (check_context_) { EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction); @@ -161,7 +197,7 @@ class KeepFilterFactory : public CompactionFilterFactory { bool compaction_filter_created() const { return compaction_filter_created_; } - virtual const char* Name() const override { return "KeepFilterFactory"; } + const char* Name() const override { return "KeepFilterFactory"; } bool check_context_; bool check_context_cf_id_; std::atomic_bool expect_full_compaction_; @@ -172,7 +208,7 @@ class KeepFilterFactory : public CompactionFilterFactory { class DeleteFilterFactory : public CompactionFilterFactory { public: - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { if (context.is_manual_compaction) { return std::unique_ptr(new DeleteFilter()); @@ -181,13 +217,13 @@ class DeleteFilterFactory : public CompactionFilterFactory { } } - virtual const char* Name() const override { return "DeleteFilterFactory"; } + const char* Name() const override { return "DeleteFilterFactory"; } }; // Delete Filter Factory which ignores snapshots class DeleteISFilterFactory : public CompactionFilterFactory { public: - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { if (context.is_manual_compaction) { return std::unique_ptr(new DeleteISFilter()); @@ -196,12 +232,12 @@ class DeleteISFilterFactory : public CompactionFilterFactory { } } - virtual const char* Name() const override { return "DeleteFilterFactory"; } + const char* Name() const override { return "DeleteFilterFactory"; } }; class SkipEvenFilterFactory : public CompactionFilterFactory { public: - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { if (context.is_manual_compaction) { return std::unique_ptr(new SkipEvenFilter()); @@ -210,18 +246,18 @@ class SkipEvenFilterFactory : public CompactionFilterFactory { } } - virtual const char* Name() const override { return "SkipEvenFilterFactory"; } + const char* Name() const override { return "SkipEvenFilterFactory"; } }; class DelayFilterFactory : public CompactionFilterFactory { public: explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {} - virtual std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { + std::unique_ptr CreateCompactionFilter( + const CompactionFilter::Context& /*context*/) override { return std::unique_ptr(new DelayFilter(db_test)); } - virtual const char* Name() const override { return "DelayFilterFactory"; } + const char* Name() const override { return "DelayFilterFactory"; } private: DBTestBase* db_test; @@ -232,15 +268,13 @@ class ConditionalFilterFactory : public CompactionFilterFactory { explicit ConditionalFilterFactory(const Slice& filtered_value) : filtered_value_(filtered_value.ToString()) {} - virtual std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { + std::unique_ptr CreateCompactionFilter( + const CompactionFilter::Context& /*context*/) override { return std::unique_ptr( new ConditionalFilter(&filtered_value_)); } - virtual const char* Name() const override { - return "ConditionalFilterFactory"; - } + const char* Name() const override { return "ConditionalFilterFactory"; } private: std::string filtered_value_; @@ -250,12 +284,12 @@ class ChangeFilterFactory : public CompactionFilterFactory { public: explicit ChangeFilterFactory() {} - virtual std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { + std::unique_ptr CreateCompactionFilter( + const CompactionFilter::Context& /*context*/) override { return std::unique_ptr(new ChangeFilter()); } - virtual const char* Name() const override { return "ChangeFilterFactory"; } + const char* Name() const override { return "ChangeFilterFactory"; } }; #ifndef ROCKSDB_LITE @@ -301,14 +335,14 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) { Arena arena; { InternalKeyComparator icmp(options.comparator); - RangeDelAggregator range_del_agg(icmp, {} /* snapshots */); - ScopedArenaIterator iter( - dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[1])); + ReadRangeDelAggregator range_del_agg(&icmp, + kMaxSequenceNumber /* upper_bound */); + ScopedArenaIterator iter(dbfull()->NewInternalIterator( + &arena, &range_del_agg, kMaxSequenceNumber, 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) { @@ -318,7 +352,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) { } } ASSERT_EQ(total, 100000); - ASSERT_EQ(count, 1); + ASSERT_EQ(count, 0); // overwrite all the 100K keys once again. for (int i = 0; i < 100000; i++) { @@ -391,9 +425,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) { count = 0; { InternalKeyComparator icmp(options.comparator); - RangeDelAggregator range_del_agg(icmp, {} /* snapshots */); - ScopedArenaIterator iter( - dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[1])); + ReadRangeDelAggregator range_del_agg(&icmp, + kMaxSequenceNumber /* upper_bound */); + ScopedArenaIterator iter(dbfull()->NewInternalIterator( + &arena, &range_del_agg, kMaxSequenceNumber, handles_[1])); iter->SeekToFirst(); ASSERT_OK(iter->status()); while (iter->Valid()) { @@ -440,65 +475,63 @@ TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) { } #endif // ROCKSDB_LITE -TEST_F(DBTestCompactionFilter, CompactionFilterWithValueChange) { - do { - Options options = CurrentOptions(); - options.num_levels = 3; - options.compaction_filter_factory = - std::make_shared(); - CreateAndReopenWithCF({"pikachu"}, options); - - // 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); - Put(1, key, value); - } +TEST_P(DBTestCompactionFilterWithCompactParam, + CompactionFilterWithValueChange) { + Options options = CurrentOptions(); + options.num_levels = 3; + options.compaction_filter_factory = std::make_shared(); + CreateAndReopenWithCF({"pikachu"}, options); - // push all files to lower levels - ASSERT_OK(Flush(1)); - if (option_config_ != kUniversalCompactionMultiLevel && - option_config_ != kUniversalSubcompactions) { - dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]); - dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]); - } else { - dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr, - nullptr); - } + // 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); + Put(1, key, value); + } - // re-write all data again - for (int i = 0; i < 100001; i++) { - char key[100]; - snprintf(key, sizeof(key), "B%010d", i); - Put(1, key, value); - } + // push all files to lower levels + ASSERT_OK(Flush(1)); + if (option_config_ != kUniversalCompactionMultiLevel && + option_config_ != kUniversalSubcompactions) { + dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]); + dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]); + } else { + dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr, + nullptr); + } - // push all files to lower levels. This should - // invoke the compaction filter for all 100000 keys. - ASSERT_OK(Flush(1)); - if (option_config_ != kUniversalCompactionMultiLevel && - option_config_ != kUniversalSubcompactions) { - dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]); - dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]); - } else { - dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr, - nullptr); - } + // re-write all data again + for (int i = 0; i < 100001; i++) { + char key[100]; + snprintf(key, sizeof(key), "B%010d", i); + Put(1, key, value); + } - // 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%010d", i); - std::string newvalue = Get(1, key); - ASSERT_EQ(newvalue.compare(NEW_VALUE), 0); - } - } while (ChangeCompactOptions()); + // push all files to lower levels. This should + // invoke the compaction filter for all 100000 keys. + ASSERT_OK(Flush(1)); + if (option_config_ != kUniversalCompactionMultiLevel && + option_config_ != kUniversalSubcompactions) { + dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]); + dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]); + } else { + dbfull()->CompactRange(CompactRangeOptions(), handles_[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%010d", i); + std::string newvalue = Get(1, key); + ASSERT_EQ(newvalue.compare(NEW_VALUE), 0); + } } TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) { @@ -610,14 +643,14 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) { int total = 0; Arena arena; InternalKeyComparator icmp(options.comparator); - RangeDelAggregator range_del_agg(icmp, {} /* snapshots */); - ScopedArenaIterator iter( - dbfull()->NewInternalIterator(&arena, &range_del_agg)); + ReadRangeDelAggregator range_del_agg(&icmp, + kMaxSequenceNumber /* snapshots */); + ScopedArenaIterator iter(dbfull()->NewInternalIterator( + &arena, &range_del_agg, kMaxSequenceNumber)); 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) { @@ -626,7 +659,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) { iter->Next(); } ASSERT_EQ(total, 700); - ASSERT_EQ(count, 1); + ASSERT_EQ(count, 0); } } #endif // ROCKSDB_LITE @@ -661,44 +694,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextCfId) { } #ifndef ROCKSDB_LITE -// Compaction filters should only be applied to records that are newer than the -// latest snapshot. This test inserts records and applies a delete filter. -TEST_F(DBTestCompactionFilter, CompactionFilterSnapshot) { - Options options = CurrentOptions(); - options.compaction_filter_factory = std::make_shared(); - options.disable_auto_compactions = true; - options.create_if_missing = true; - DestroyAndReopen(options); - - // Put some data. - const Snapshot* snapshot = nullptr; - for (int table = 0; table < 4; ++table) { - for (int i = 0; i < 10; ++i) { - Put(ToString(table * 100 + i), "val"); - } - Flush(); - - if (table == 0) { - snapshot = db_->GetSnapshot(); - } - } - assert(snapshot != nullptr); - - cfilter_count = 0; - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - // The filter should delete 10 records. - ASSERT_EQ(30U, cfilter_count); - - // Release the snapshot and compact again -> now all records should be - // removed. - db_->ReleaseSnapshot(snapshot); - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - ASSERT_EQ(0U, CountLiveFiles()); -} - -// Compaction filters should only be applied to records that are newer than the -// latest snapshot. However, if the compaction filter asks to ignore snapshots -// records newer than the snapshot will also be processed +// Compaction filters aplies to all records, regardless snapshots. TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) { std::string five = ToString(5); Options options = CurrentOptions(); @@ -739,7 +735,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) { iter->Next(); } ASSERT_EQ(count, 6); - read_options.snapshot = 0; + read_options.snapshot = nullptr; std::unique_ptr iter1(db_->NewIterator(read_options)); iter1->SeekToFirst(); count = 0; @@ -813,7 +809,7 @@ TEST_F(DBTestCompactionFilter, SkipUntilWithBloomFilter) { DestroyAndReopen(options); Put("0000000010", "v10"); - Put("0000000020", "v20"); // skipped + Put("0000000020", "v20"); // skipped Put("0000000050", "v50"); Flush(); @@ -836,6 +832,38 @@ TEST_F(DBTestCompactionFilter, SkipUntilWithBloomFilter) { EXPECT_EQ("v50", val); } +class TestNotSupportedFilter : public CompactionFilter { + public: + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { + return true; + } + + const char* Name() const override { return "NotSupported"; } + bool IgnoreSnapshots() const override { return false; } +}; + +TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalse) { + Options options = CurrentOptions(); + options.compaction_filter = new TestNotSupportedFilter(); + DestroyAndReopen(options); + + Put("a", "v10"); + Put("z", "v20"); + Flush(); + + Put("a", "v10"); + Put("z", "v20"); + Flush(); + + // Comapction should fail because IgnoreSnapshots() = false + EXPECT_TRUE(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr) + .IsNotSupported()); + + delete options.compaction_filter; +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_compaction_test.cc b/thirdparty/rocksdb/db/db_compaction_test.cc index ca77d5b939..df51ef2ca2 100644 --- a/thirdparty/rocksdb/db/db_compaction_test.cc +++ b/thirdparty/rocksdb/db/db_compaction_test.cc @@ -8,11 +8,15 @@ // found in the LICENSE file. See the AUTHORS file for names of contributors. #include "db/db_test_util.h" -#include "port/stack_trace.h" #include "port/port.h" +#include "port/stack_trace.h" +#include "rocksdb/concurrent_task_limiter.h" #include "rocksdb/experimental.h" #include "rocksdb/utilities/convenience.h" +#include "util/concurrent_task_limiter_impl.h" +#include "util/fault_injection_test_env.h" #include "util/sync_point.h" + namespace rocksdb { // SYNC_POINT is not supported in released Windows mode. @@ -51,9 +55,9 @@ namespace { class FlushedFileCollector : public EventListener { public: FlushedFileCollector() {} - ~FlushedFileCollector() {} + ~FlushedFileCollector() override {} - virtual void OnFlushCompleted(DB* db, const FlushJobInfo& info) override { + void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { std::lock_guard lock(mutex_); flushed_files_.push_back(info.file_path); } @@ -74,6 +78,62 @@ class FlushedFileCollector : public EventListener { std::mutex mutex_; }; +class CompactionStatsCollector : public EventListener { +public: + CompactionStatsCollector() + : compaction_completed_(static_cast(CompactionReason::kNumOfReasons)) { + for (auto& v : compaction_completed_) { + v.store(0); + } + } + + ~CompactionStatsCollector() override {} + + void OnCompactionCompleted(DB* /* db */, + const CompactionJobInfo& info) override { + int k = static_cast(info.compaction_reason); + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + assert(k >= 0 && k < num_of_reasons); + compaction_completed_[k]++; + } + + void OnExternalFileIngested( + DB* /* db */, const ExternalFileIngestionInfo& /* info */) override { + int k = static_cast(CompactionReason::kExternalSstIngestion); + compaction_completed_[k]++; + } + + void OnFlushCompleted(DB* /* db */, const FlushJobInfo& /* info */) override { + int k = static_cast(CompactionReason::kFlush); + compaction_completed_[k]++; + } + + int NumberOfCompactions(CompactionReason reason) const { + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + int k = static_cast(reason); + assert(k >= 0 && k < num_of_reasons); + return compaction_completed_.at(k).load(); + } + +private: + std::vector> compaction_completed_; +}; + +class SstStatsCollector : public EventListener { + public: + SstStatsCollector() : num_ssts_creation_started_(0) {} + + void OnTableFileCreationStarted( + const TableFileCreationBriefInfo& /* info */) override { + ++num_ssts_creation_started_; + } + + int num_ssts_creation_started() { return num_ssts_creation_started_; } + + private: + std::atomic num_ssts_creation_started_; +}; + static const int kCDTValueSize = 1000; static const int kCDTKeysPerBuffer = 4; static const int kCDTNumLevels = 8; @@ -154,6 +214,40 @@ void VerifyCompactionResult( #endif } +/* + * Verifies compaction stats of cfd are valid. + * + * For each level of cfd, its compaction stats are valid if + * 1) sum(stat.counts) == stat.count, and + * 2) stat.counts[i] == collector.NumberOfCompactions(i) + */ +void VerifyCompactionStats(ColumnFamilyData& cfd, + const CompactionStatsCollector& collector) { +#ifndef NDEBUG + InternalStats* internal_stats_ptr = cfd.internal_stats(); + ASSERT_TRUE(internal_stats_ptr != nullptr); + const std::vector& comp_stats = + internal_stats_ptr->TEST_GetCompactionStats(); + const int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + std::vector counts(num_of_reasons, 0); + // Count the number of compactions caused by each CompactionReason across + // all levels. + for (const auto& stat : comp_stats) { + int sum = 0; + for (int i = 0; i < num_of_reasons; i++) { + counts[i] += stat.counts[i]; + sum += stat.counts[i]; + } + ASSERT_EQ(sum, stat.count); + } + // Verify InternalStats bookkeeping matches that of CompactionStatsCollector, + // assuming that all compactions complete. + for (int i = 0; i < num_of_reasons; i++) { + ASSERT_EQ(collector.NumberOfCompactions(static_cast(i)), counts[i]); + } +#endif /* NDEBUG */ +} + const SstFileMetaData* PickFileRandomly( const ColumnFamilyMetaData& cf_meta, Random* rand, @@ -175,6 +269,7 @@ const SstFileMetaData* PickFileRandomly( } } // anonymous namespace +#ifndef ROCKSDB_VALGRIND_RUN // All the TEST_P tests run once with sub_compactions disabled (i.e. // options.max_subcompactions = 1) and once with it enabled TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) { @@ -217,6 +312,85 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) { ASSERT_GT(db_size[0] / 3, db_size[1]); } } +#endif // ROCKSDB_VALGRIND_RUN + +TEST_P(DBCompactionTestWithParam, CompactionsPreserveDeletes) { + // For each options type we test following + // - Enable preserve_deletes + // - write bunch of keys and deletes + // - Set start_seqnum to the beginning; compact; check that keys are present + // - rewind start_seqnum way forward; compact; check that keys are gone + + for (int tid = 0; tid < 3; ++tid) { + Options options = DeletionTriggerOptions(CurrentOptions()); + options.max_subcompactions = max_subcompactions_; + options.preserve_deletes=true; + options.num_levels = 2; + + if (tid == 1) { + options.skip_stats_update_on_db_open = true; + } else if (tid == 2) { + // third pass with universal compaction + options.compaction_style = kCompactionStyleUniversal; + } + + DestroyAndReopen(options); + Random rnd(301); + // highlight the default; all deletes should be preserved + SetPreserveDeletesSequenceNumber(0); + + const int kTestSize = kCDTKeysPerBuffer; + std::vector values; + for (int k = 0; k < kTestSize; ++k) { + values.push_back(RandomString(&rnd, kCDTValueSize)); + ASSERT_OK(Put(Key(k), values[k])); + } + + for (int k = 0; k < kTestSize; ++k) { + ASSERT_OK(Delete(Key(k))); + } + // to ensure we tackle all tombstones + CompactRangeOptions cro; + cro.change_level = true; + cro.target_level = 2; + cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; + + dbfull()->TEST_WaitForFlushMemTable(); + dbfull()->CompactRange(cro, nullptr, nullptr); + + // check that normal user iterator doesn't see anything + Iterator* db_iter = dbfull()->NewIterator(ReadOptions()); + int i = 0; + for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { + i++; + } + ASSERT_EQ(i, 0); + delete db_iter; + + // check that iterator that sees internal keys sees tombstones + ReadOptions ro; + ro.iter_start_seqnum=1; + db_iter = dbfull()->NewIterator(ro); + i = 0; + for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { + i++; + } + ASSERT_EQ(i, 4); + delete db_iter; + + // now all deletes should be gone + SetPreserveDeletesSequenceNumber(100000000); + dbfull()->CompactRange(cro, nullptr, nullptr); + + db_iter = dbfull()->NewIterator(ro); + i = 0; + for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { + i++; + } + ASSERT_EQ(i, 0); + delete db_iter; + } +} TEST_F(DBCompactionTest, SkipStatsUpdateTest) { // This test verify UpdateAccumulatedStats is not on @@ -239,6 +413,7 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) { // Reopen the DB with stats-update disabled options.skip_stats_update_on_db_open = true; + options.max_open_files = 20; env_->random_file_open_counter_.store(0); Reopen(options); @@ -264,7 +439,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) { Options options = CurrentOptions(); options.env = env_; options.new_table_reader_for_compaction_inputs = true; - options.max_open_files = 100; + options.max_open_files = 20; options.level0_file_num_compaction_trigger = 3; DestroyAndReopen(options); Random rnd(301); @@ -282,7 +457,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) { }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "TableCache::GetTableReader:0", - [&](void* arg) { num_new_table_reader++; }); + [&](void* /*arg*/) { num_new_table_reader++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); for (int k = 0; k < options.level0_file_num_compaction_trigger; ++k) { @@ -293,15 +468,16 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) { Flush(); dbfull()->TEST_WaitForCompact(); // preloading iterator issues one table cache lookup and create - // a new table reader. - ASSERT_EQ(num_table_cache_lookup, 1); + // a new table reader, if not preloaded. + int old_num_table_cache_lookup = num_table_cache_lookup; + ASSERT_GE(num_table_cache_lookup, 1); ASSERT_EQ(num_new_table_reader, 1); num_table_cache_lookup = 0; num_new_table_reader = 0; ASSERT_EQ(Key(k), Get(Key(k))); // lookup iterator from table cache and no need to create a new one. - ASSERT_EQ(num_table_cache_lookup, 1); + ASSERT_EQ(old_num_table_cache_lookup + num_table_cache_lookup, 2); ASSERT_EQ(num_new_table_reader, 0); } } @@ -314,7 +490,10 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) { // a new table reader. One file is created for flush and one for compaction. // Compaction inputs make no table cache look-up for data/range deletion // iterators - ASSERT_EQ(num_table_cache_lookup, 2); + // May preload table cache too. + ASSERT_GE(num_table_cache_lookup, 2); + int old_num_table_cache_lookup2 = num_table_cache_lookup; + // Create new iterator for: // (1) 1 for verifying flush results // (2) 3 for compaction input files @@ -324,7 +503,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) { num_table_cache_lookup = 0; num_new_table_reader = 0; ASSERT_EQ(Key(1), Get(Key(1))); - ASSERT_EQ(num_table_cache_lookup, 1); + ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 3); ASSERT_EQ(num_new_table_reader, 0); num_table_cache_lookup = 0; @@ -336,14 +515,16 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) { db_->CompactRange(cro, nullptr, nullptr); // Only verifying compaction outputs issues one table cache lookup // for both data block and range deletion block). - ASSERT_EQ(num_table_cache_lookup, 1); + // May preload table cache too. + ASSERT_GE(num_table_cache_lookup, 1); + old_num_table_cache_lookup2 = num_table_cache_lookup; // One for compaction input, one for verifying compaction results. ASSERT_EQ(num_new_table_reader, 2); num_table_cache_lookup = 0; num_new_table_reader = 0; ASSERT_EQ(Key(1), Get(Key(1))); - ASSERT_EQ(num_table_cache_lookup, 1); + ASSERT_EQ(num_table_cache_lookup + old_num_table_cache_lookup2, 2); ASSERT_EQ(num_new_table_reader, 0); rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); @@ -838,7 +1019,7 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) { int32_t trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Options options = CurrentOptions(); @@ -895,10 +1076,10 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveNonOverlappingFiles) { int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial_move++; }); + [&](void* /*arg*/) { non_trivial_move++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Options options = CurrentOptions(); @@ -994,10 +1175,10 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveTargetLevel) { int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial_move++; }); + [&](void* /*arg*/) { non_trivial_move++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Options options = CurrentOptions(); @@ -1053,10 +1234,10 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) { int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial_move++; }); + [&](void* /*arg*/) { non_trivial_move++; }); bool first = true; // Purpose of dependencies: // 4 -> 1: ensure the order of two non-trivial compactions @@ -1067,7 +1248,7 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) { {"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:2"}, {"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (first) { first = false; TEST_SYNC_POINT("DBCompaction::ManualPartial:4"); @@ -1198,17 +1379,17 @@ TEST_F(DBCompactionTest, DISABLED_ManualPartialFill) { int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial_move++; }); + [&](void* /*arg*/) { non_trivial_move++; }); bool first = true; bool second = true; rocksdb::SyncPoint::GetInstance()->LoadDependency( {{"DBCompaction::PartialFill:4", "DBCompaction::PartialFill:1"}, {"DBCompaction::PartialFill:2", "DBCompaction::PartialFill:3"}}); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) { + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* /*arg*/) { if (first) { TEST_SYNC_POINT("DBCompaction::PartialFill:4"); first = false; @@ -1421,7 +1602,8 @@ TEST_F(DBCompactionTest, DeleteFileRange) { // Note that we don't delete level 0 files compact_options.change_level = true; compact_options.target_level = 1; - ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); + ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); + dbfull()->TEST_WaitForCompact(); ASSERT_OK( DeleteFilesInRange(db_, db_->DefaultColumnFamily(), nullptr, nullptr)); @@ -1439,15 +1621,185 @@ TEST_F(DBCompactionTest, DeleteFileRange) { ASSERT_GT(old_num_files, new_num_files); } +TEST_F(DBCompactionTest, DeleteFilesInRanges) { + Options options = CurrentOptions(); + options.write_buffer_size = 10 * 1024 * 1024; + options.max_bytes_for_level_multiplier = 2; + options.num_levels = 4; + options.max_background_compactions = 3; + options.disable_auto_compactions = true; + + DestroyAndReopen(options); + int32_t value_size = 10 * 1024; // 10 KB + + Random rnd(301); + std::map values; + + // file [0 => 100), [100 => 200), ... [900, 1000) + for (auto i = 0; i < 10; i++) { + for (auto j = 0; j < 100; j++) { + auto k = i * 100 + j; + values[k] = RandomString(&rnd, value_size); + ASSERT_OK(Put(Key(k), values[k])); + } + ASSERT_OK(Flush()); + } + ASSERT_EQ("10", FilesPerLevel(0)); + CompactRangeOptions compact_options; + compact_options.change_level = true; + compact_options.target_level = 2; + ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); + ASSERT_EQ("0,0,10", FilesPerLevel(0)); + + // file [0 => 100), [200 => 300), ... [800, 900) + for (auto i = 0; i < 10; i+=2) { + for (auto j = 0; j < 100; j++) { + auto k = i * 100 + j; + ASSERT_OK(Put(Key(k), values[k])); + } + ASSERT_OK(Flush()); + } + ASSERT_EQ("5,0,10", FilesPerLevel(0)); + ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr)); + ASSERT_EQ("0,5,10", FilesPerLevel(0)); + + // Delete files in range [0, 299] (inclusive) + { + auto begin_str1 = Key(0), end_str1 = Key(100); + auto begin_str2 = Key(100), end_str2 = Key(200); + auto begin_str3 = Key(200), end_str3 = Key(299); + Slice begin1(begin_str1), end1(end_str1); + Slice begin2(begin_str2), end2(end_str2); + Slice begin3(begin_str3), end3(end_str3); + std::vector ranges; + ranges.push_back(RangePtr(&begin1, &end1)); + ranges.push_back(RangePtr(&begin2, &end2)); + ranges.push_back(RangePtr(&begin3, &end3)); + ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), + ranges.data(), ranges.size())); + ASSERT_EQ("0,3,7", FilesPerLevel(0)); + + // Keys [0, 300) should not exist. + for (auto i = 0; i < 300; i++) { + ReadOptions ropts; + std::string result; + auto s = db_->Get(ropts, Key(i), &result); + ASSERT_TRUE(s.IsNotFound()); + } + for (auto i = 300; i < 1000; i++) { + ASSERT_EQ(Get(Key(i)), values[i]); + } + } + + // Delete files in range [600, 999) (exclusive) + { + auto begin_str1 = Key(600), end_str1 = Key(800); + auto begin_str2 = Key(700), end_str2 = Key(900); + auto begin_str3 = Key(800), end_str3 = Key(999); + Slice begin1(begin_str1), end1(end_str1); + Slice begin2(begin_str2), end2(end_str2); + Slice begin3(begin_str3), end3(end_str3); + std::vector ranges; + ranges.push_back(RangePtr(&begin1, &end1)); + ranges.push_back(RangePtr(&begin2, &end2)); + ranges.push_back(RangePtr(&begin3, &end3)); + ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), + ranges.data(), ranges.size(), false)); + ASSERT_EQ("0,1,4", FilesPerLevel(0)); + + // Keys [600, 900) should not exist. + for (auto i = 600; i < 900; i++) { + ReadOptions ropts; + std::string result; + auto s = db_->Get(ropts, Key(i), &result); + ASSERT_TRUE(s.IsNotFound()); + } + for (auto i = 300; i < 600; i++) { + ASSERT_EQ(Get(Key(i)), values[i]); + } + for (auto i = 900; i < 1000; i++) { + ASSERT_EQ(Get(Key(i)), values[i]); + } + } + + // Delete all files. + { + RangePtr range; + ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1)); + ASSERT_EQ("", FilesPerLevel(0)); + + for (auto i = 0; i < 1000; i++) { + ReadOptions ropts; + std::string result; + auto s = db_->Get(ropts, Key(i), &result); + ASSERT_TRUE(s.IsNotFound()); + } + } +} + +TEST_F(DBCompactionTest, DeleteFileRangeFileEndpointsOverlapBug) { + // regression test for #2833: groups of files whose user-keys overlap at the + // endpoints could be split by `DeleteFilesInRange`. This caused old data to + // reappear, either because a new version of the key was removed, or a range + // deletion was partially dropped. It could also cause non-overlapping + // invariant to be violated if the files dropped by DeleteFilesInRange were + // a subset of files that a range deletion spans. + const int kNumL0Files = 2; + const int kValSize = 8 << 10; // 8KB + Options options = CurrentOptions(); + options.level0_file_num_compaction_trigger = kNumL0Files; + options.target_file_size_base = 1 << 10; // 1KB + DestroyAndReopen(options); + + // The snapshot prevents key 1 from having its old version dropped. The low + // `target_file_size_base` ensures two keys will be in each output file. + const Snapshot* snapshot = nullptr; + Random rnd(301); + // The value indicates which flush the key belonged to, which is enough + // for us to determine the keys' relative ages. After L0 flushes finish, + // files look like: + // + // File 0: 0 -> vals[0], 1 -> vals[0] + // File 1: 1 -> vals[1], 2 -> vals[1] + // + // Then L0->L1 compaction happens, which outputs keys as follows: + // + // File 0: 0 -> vals[0], 1 -> vals[1] + // File 1: 1 -> vals[0], 2 -> vals[1] + // + // DeleteFilesInRange shouldn't be allowed to drop just file 0, as that + // would cause `1 -> vals[0]` (an older key) to reappear. + std::string vals[kNumL0Files]; + for (int i = 0; i < kNumL0Files; ++i) { + vals[i] = RandomString(&rnd, kValSize); + Put(Key(i), vals[i]); + Put(Key(i + 1), vals[i]); + Flush(); + if (i == 0) { + snapshot = db_->GetSnapshot(); + } + } + dbfull()->TEST_WaitForCompact(); + + // Verify `DeleteFilesInRange` can't drop only file 0 which would cause + // "1 -> vals[0]" to reappear. + std::string begin_str = Key(0), end_str = Key(1); + Slice begin = begin_str, end = end_str; + ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end)); + ASSERT_EQ(vals[1], Get(Key(1))); + + db_->ReleaseSnapshot(snapshot); +} + TEST_P(DBCompactionTestWithParam, TrivialMoveToLastLevelWithFiles) { int32_t trivial_move = 0; int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial_move++; }); + [&](void* /*arg*/) { non_trivial_move++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Options options = CurrentOptions(); @@ -1735,6 +2087,125 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionPathUse) { Destroy(options); } +TEST_P(DBCompactionTestWithParam, LevelCompactionCFPathUse) { + Options options = CurrentOptions(); + 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.memtable_factory.reset( + new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); + options.compaction_style = kCompactionStyleLevel; + options.write_buffer_size = 110 << 10; // 110KB + options.arena_block_size = 4 << 10; + options.level0_file_num_compaction_trigger = 2; + options.num_levels = 4; + options.max_bytes_for_level_base = 400 * 1024; + options.max_subcompactions = max_subcompactions_; + + std::vector option_vector; + option_vector.emplace_back(options); + ColumnFamilyOptions cf_opt1(options), cf_opt2(options); + // Configure CF1 specific paths. + cf_opt1.cf_paths.emplace_back(dbname_ + "cf1", 500 * 1024); + cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_2", 4 * 1024 * 1024); + cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_3", 1024 * 1024 * 1024); + option_vector.emplace_back(DBOptions(options), cf_opt1); + CreateColumnFamilies({"one"},option_vector[1]); + + // Configura CF2 specific paths. + cf_opt2.cf_paths.emplace_back(dbname_ + "cf2", 500 * 1024); + cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_2", 4 * 1024 * 1024); + cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_3", 1024 * 1024 * 1024); + option_vector.emplace_back(DBOptions(options), cf_opt2); + CreateColumnFamilies({"two"},option_vector[2]); + + ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); + + Random rnd(301); + int key_idx = 0; + int key_idx1 = 0; + int key_idx2 = 0; + + auto generate_file = [&]() { + GenerateNewFile(0, &rnd, &key_idx); + GenerateNewFile(1, &rnd, &key_idx1); + GenerateNewFile(2, &rnd, &key_idx2); + }; + + auto check_sstfilecount = [&](int path_id, int expected) { + ASSERT_EQ(expected, GetSstFileCount(options.db_paths[path_id].path)); + ASSERT_EQ(expected, GetSstFileCount(cf_opt1.cf_paths[path_id].path)); + ASSERT_EQ(expected, GetSstFileCount(cf_opt2.cf_paths[path_id].path)); + }; + + auto check_filesperlevel = [&](const std::string& expected) { + ASSERT_EQ(expected, FilesPerLevel(0)); + ASSERT_EQ(expected, FilesPerLevel(1)); + ASSERT_EQ(expected, FilesPerLevel(2)); + }; + + auto check_getvalues = [&]() { + for (int i = 0; i < key_idx; i++) { + auto v = Get(0, Key(i)); + ASSERT_NE(v, "NOT_FOUND"); + ASSERT_TRUE(v.size() == 1 || v.size() == 990); + } + + for (int i = 0; i < key_idx1; i++) { + auto v = Get(1, Key(i)); + ASSERT_NE(v, "NOT_FOUND"); + ASSERT_TRUE(v.size() == 1 || v.size() == 990); + } + + for (int i = 0; i < key_idx2; i++) { + auto v = Get(2, Key(i)); + ASSERT_NE(v, "NOT_FOUND"); + ASSERT_TRUE(v.size() == 1 || v.size() == 990); + } + }; + + // Check that default column family uses db_paths. + // And Column family "one" uses cf_paths. + + // First three 110KB files are not going to second path. + // After that, (100K, 200K) + for (int num = 0; num < 3; num++) { + generate_file(); + } + + // Another 110KB triggers a compaction to 400K file to fill up first path + generate_file(); + check_sstfilecount(1, 3); + + // (1, 4) + generate_file(); + check_filesperlevel("1,4"); + check_sstfilecount(1, 4); + check_sstfilecount(0, 1); + + // (1, 4, 1) + generate_file(); + check_filesperlevel("1,4,1"); + check_sstfilecount(2, 1); + check_sstfilecount(1, 4); + check_sstfilecount(0, 1); + + // (1, 4, 2) + generate_file(); + check_filesperlevel("1,4,2"); + check_sstfilecount(2, 2); + check_sstfilecount(1, 4); + check_sstfilecount(0, 1); + + check_getvalues(); + + ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); + + check_getvalues(); + + Destroy(options, true); +} + TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) { Random rnd(301); int max_key_level_insert = 200; @@ -2017,6 +2488,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) { // Compaction range overlaps files Compact(1, "p1", "p9", 1); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); ASSERT_EQ("0,1", FilesPerLevel(1)); ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path)); @@ -2032,6 +2504,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) { // Compact just the new range Compact(1, "b", "f", 1); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); ASSERT_EQ("0,2", FilesPerLevel(1)); ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path)); ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path)); @@ -2048,6 +2521,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) { compact_options.target_path_id = 1; compact_options.exclusive_manual_compaction = exclusive_manual_compaction_; db_->CompactRange(compact_options, handles_[1], nullptr, nullptr); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); ASSERT_EQ("0,1", FilesPerLevel(1)); ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path)); @@ -2325,16 +2799,16 @@ TEST_P(DBCompactionTestWithParam, CompressLevelCompaction) { rocksdb::SyncPoint::GetInstance()->SetCallBack( "Compaction::InputCompressionMatchesOutput:Matches", - [&](void* arg) { matches++; }); + [&](void* /*arg*/) { matches++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "Compaction::InputCompressionMatchesOutput:DidntMatch", - [&](void* arg) { didnt_match++; }); + [&](void* /*arg*/) { didnt_match++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial++; }); + [&](void* /*arg*/) { non_trivial++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Reopen(options); @@ -2490,21 +2964,46 @@ TEST_F(DBCompactionTest, SuggestCompactRangeNoTwoLevel0Compactions) { dbfull()->TEST_WaitForCompact(); } +static std::string ShortKey(int i) { + assert(i < 10000); + char buf[100]; + snprintf(buf, sizeof(buf), "key%04d", i); + return std::string(buf); +} TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) { int32_t trivial_move = 0; int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial_move++; }); + [&](void* /*arg*/) { non_trivial_move++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + // The key size is guaranteed to be <= 8 + class ShortKeyComparator : public Comparator { + int Compare(const rocksdb::Slice& a, + const rocksdb::Slice& b) const override { + assert(a.size() <= 8); + assert(b.size() <= 8); + return BytewiseComparator()->Compare(a, b); + } + const char* Name() const override { return "ShortKeyComparator"; } + void FindShortestSeparator(std::string* start, + const rocksdb::Slice& limit) const override { + return BytewiseComparator()->FindShortestSeparator(start, limit); + } + void FindShortSuccessor(std::string* key) const override { + return BytewiseComparator()->FindShortSuccessor(key); + } + } short_key_cmp; Options options = CurrentOptions(); + options.target_file_size_base = 100000000; options.write_buffer_size = 100000000; options.max_subcompactions = max_subcompactions_; + options.comparator = &short_key_cmp; DestroyAndReopen(options); int32_t value_size = 10 * 1024; // 10 KB @@ -2514,7 +3013,7 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) { // File with keys [ 0 => 99 ] for (int i = 0; i < 100; i++) { values.push_back(RandomString(&rnd, value_size)); - ASSERT_OK(Put(Key(i), values[i])); + ASSERT_OK(Put(ShortKey(i), values[i])); } ASSERT_OK(Flush()); @@ -2531,7 +3030,7 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) { // File with keys [ 100 => 199 ] for (int i = 100; i < 200; i++) { values.push_back(RandomString(&rnd, value_size)); - ASSERT_OK(Put(Key(i), values[i])); + ASSERT_OK(Put(ShortKey(i), values[i])); } ASSERT_OK(Flush()); @@ -2549,7 +3048,7 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) { // File with keys [ 200 => 299 ] for (int i = 200; i < 300; i++) { values.push_back(RandomString(&rnd, value_size)); - ASSERT_OK(Put(Key(i), values[i])); + ASSERT_OK(Put(ShortKey(i), values[i])); } ASSERT_OK(Flush()); @@ -2567,7 +3066,7 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) { ASSERT_EQ(non_trivial_move, 0); for (int i = 0; i < 300; i++) { - ASSERT_EQ(Get(Key(i)), values[i]); + ASSERT_EQ(Get(ShortKey(i)), values[i]); } rocksdb::SyncPoint::GetInstance()->DisableProcessing(); @@ -2684,6 +3183,48 @@ TEST_P(DBCompactionTestWithParam, IntraL0CompactionDoesNotObsoleteDeletions) { ASSERT_TRUE(db_->Get(roptions, Key(0), &result).IsNotFound()); } +TEST_P(DBCompactionTestWithParam, FullCompactionInBottomPriThreadPool) { + const int kNumFilesTrigger = 3; + Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM); + for (bool use_universal_compaction : {false, true}) { + Options options = CurrentOptions(); + if (use_universal_compaction) { + options.compaction_style = kCompactionStyleUniversal; + } else { + options.compaction_style = kCompactionStyleLevel; + options.level_compaction_dynamic_level_bytes = true; + } + options.num_levels = 4; + options.write_buffer_size = 100 << 10; // 100KB + options.target_file_size_base = 32 << 10; // 32KB + options.level0_file_num_compaction_trigger = kNumFilesTrigger; + // Trigger compaction if size amplification exceeds 110% + options.compaction_options_universal.max_size_amplification_percent = 110; + DestroyAndReopen(options); + + int num_bottom_pri_compactions = 0; + SyncPoint::GetInstance()->SetCallBack( + "DBImpl::BGWorkBottomCompaction", + [&](void* /*arg*/) { ++num_bottom_pri_compactions; }); + SyncPoint::GetInstance()->EnableProcessing(); + + Random rnd(301); + for (int num = 0; num < kNumFilesTrigger; num++) { + ASSERT_EQ(NumSortedRuns(), num); + int key_idx = 0; + GenerateNewFile(&rnd, &key_idx); + } + dbfull()->TEST_WaitForCompact(); + + ASSERT_EQ(1, num_bottom_pri_compactions); + + // Verify that size amplification did occur + ASSERT_EQ(NumSortedRuns(), 1); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + } + Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM); +} + TEST_F(DBCompactionTest, OptimizedDeletionObsoleting) { // Deletions can be dropped when compacted to non-last level if they fall // outside the lower-level files' key-ranges. @@ -2724,52 +3265,874 @@ TEST_F(DBCompactionTest, OptimizedDeletionObsoleting) { options.statistics->getTickerCount(COMPACTION_KEY_DROP_OBSOLETE)); } -INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam, - ::testing::Values(std::make_tuple(1, true), - std::make_tuple(1, false), - std::make_tuple(4, true), - std::make_tuple(4, false))); - -TEST_P(DBCompactionDirectIOTest, DirectIO) { +TEST_F(DBCompactionTest, CompactFilesPendingL0Bug) { + // https://www.facebook.com/groups/rocksdb.dev/permalink/1389452781153232/ + // CompactFiles() had a bug where it failed to pick a compaction when an L0 + // compaction existed, but marked it as scheduled anyways. It'd never be + // unmarked as scheduled, so future compactions or DB close could hang. + const int kNumL0Files = 5; Options options = CurrentOptions(); - Destroy(options); - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.use_direct_io_for_flush_and_compaction = GetParam(); - options.env = new MockEnv(Env::Default()); - Reopen(options); - bool readahead = false; - SyncPoint::GetInstance()->SetCallBack( - "TableCache::NewIterator:for_compaction", [&](void* arg) { - bool* use_direct_reads = static_cast(arg); - ASSERT_EQ(*use_direct_reads, - options.use_direct_io_for_flush_and_compaction); - }); - SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::OpenCompactionOutputFile", [&](void* arg) { - bool* use_direct_writes = static_cast(arg); - ASSERT_EQ(*use_direct_writes, - options.use_direct_io_for_flush_and_compaction); - }); - if (options.use_direct_io_for_flush_and_compaction) { - SyncPoint::GetInstance()->SetCallBack( - "SanitizeOptions:direct_io", [&](void* arg) { - readahead = true; - }); - } - SyncPoint::GetInstance()->EnableProcessing(); - CreateAndReopenWithCF({"pikachu"}, options); - MakeTables(3, "p", "q", 1); - ASSERT_EQ("1,1,1", FilesPerLevel(1)); - Compact(1, "p1", "p9"); - ASSERT_FALSE(readahead ^ options.use_direct_io_for_flush_and_compaction); - ASSERT_EQ("0,0,1", FilesPerLevel(1)); - Destroy(options); - delete options.env; -} + options.level0_file_num_compaction_trigger = kNumL0Files - 1; + options.max_background_compactions = 2; + DestroyAndReopen(options); -INSTANTIATE_TEST_CASE_P(DBCompactionDirectIOTest, DBCompactionDirectIOTest, - testing::Bool()); + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"LevelCompactionPicker::PickCompaction:Return", + "DBCompactionTest::CompactFilesPendingL0Bug:Picked"}, + {"DBCompactionTest::CompactFilesPendingL0Bug:ManualCompacted", + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}}); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + auto schedule_multi_compaction_token = + dbfull()->TEST_write_controler().GetCompactionPressureToken(); + + // Files 0-3 will be included in an L0->L1 compaction. + // + // File 4 will be included in a call to CompactFiles() while the first + // compaction is running. + for (int i = 0; i < kNumL0Files - 1; ++i) { + ASSERT_OK(Put(Key(0), "val")); // sentinel to prevent trivial move + ASSERT_OK(Put(Key(i + 1), "val")); + ASSERT_OK(Flush()); + } + TEST_SYNC_POINT("DBCompactionTest::CompactFilesPendingL0Bug:Picked"); + // file 4 flushed after 0-3 picked + ASSERT_OK(Put(Key(kNumL0Files), "val")); + ASSERT_OK(Flush()); + + // previously DB close would hang forever as this situation caused scheduled + // compactions count to never decrement to zero. + ColumnFamilyMetaData cf_meta; + dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta); + ASSERT_EQ(kNumL0Files, cf_meta.levels[0].files.size()); + std::vector input_filenames; + input_filenames.push_back(cf_meta.levels[0].files.front().name); + ASSERT_OK(dbfull() + ->CompactFiles(CompactionOptions(), input_filenames, + 0 /* output_level */)); + TEST_SYNC_POINT("DBCompactionTest::CompactFilesPendingL0Bug:ManualCompacted"); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBCompactionTest, CompactFilesOverlapInL0Bug) { + // Regression test for bug of not pulling in L0 files that overlap the user- + // specified input files in time- and key-ranges. + Put(Key(0), "old_val"); + Flush(); + Put(Key(0), "new_val"); + Flush(); + + ColumnFamilyMetaData cf_meta; + dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta); + ASSERT_GE(cf_meta.levels.size(), 2); + ASSERT_EQ(2, cf_meta.levels[0].files.size()); + + // Compacting {new L0 file, L1 file} should pull in the old L0 file since it + // overlaps in key-range and time-range. + std::vector input_filenames; + input_filenames.push_back(cf_meta.levels[0].files.front().name); + ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), input_filenames, + 1 /* output_level */)); + ASSERT_EQ("new_val", Get(Key(0))); +} + +TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) { + // bottom-level files may contain deletions due to snapshots protecting the + // deleted keys. Once the snapshot is released, we should see files with many + // such deletions undergo single-file compactions. + const int kNumKeysPerFile = 1024; + const int kNumLevelFiles = 4; + const int kValueSize = 128; + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.level0_file_num_compaction_trigger = kNumLevelFiles; + // inflate it a bit to account for key/metadata overhead + options.target_file_size_base = 120 * kNumKeysPerFile * kValueSize / 100; + CreateAndReopenWithCF({"one"}, options); + + Random rnd(301); + const Snapshot* snapshot = nullptr; + for (int i = 0; i < kNumLevelFiles; ++i) { + for (int j = 0; j < kNumKeysPerFile; ++j) { + ASSERT_OK( + Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize))); + } + if (i == kNumLevelFiles - 1) { + snapshot = db_->GetSnapshot(); + // delete every other key after grabbing a snapshot, so these deletions + // and the keys they cover can't be dropped until after the snapshot is + // released. + for (int j = 0; j < kNumLevelFiles * kNumKeysPerFile; j += 2) { + ASSERT_OK(Delete(Key(j))); + } + } + Flush(); + if (i < kNumLevelFiles - 1) { + ASSERT_EQ(i + 1, NumTableFilesAtLevel(0)); + } + } + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1)); + + std::vector pre_release_metadata, post_release_metadata; + db_->GetLiveFilesMetaData(&pre_release_metadata); + // just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST + // files does not need to be preserved in case of a future snapshot. + ASSERT_OK(Put(Key(0), "val")); + ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_); + // release snapshot and wait for compactions to finish. Single-file + // compactions should be triggered, which reduce the size of each bottom-level + // file without changing file count. + db_->ReleaseSnapshot(snapshot); + ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { + Compaction* compaction = reinterpret_cast(arg); + ASSERT_TRUE(compaction->compaction_reason() == + CompactionReason::kBottommostFiles); + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + dbfull()->TEST_WaitForCompact(); + db_->GetLiveFilesMetaData(&post_release_metadata); + ASSERT_EQ(pre_release_metadata.size(), post_release_metadata.size()); + + for (size_t i = 0; i < pre_release_metadata.size(); ++i) { + const auto& pre_file = pre_release_metadata[i]; + const auto& post_file = post_release_metadata[i]; + ASSERT_EQ(1, pre_file.level); + ASSERT_EQ(1, post_file.level); + // each file is smaller than it was before as it was rewritten without + // deletion markers/deleted keys. + ASSERT_LT(post_file.size, pre_file.size); + } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) { + const int kNumKeysPerFile = 32; + const int kNumLevelFiles = 2; + const int kValueSize = 1024; + + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.ttl = 24 * 60 * 60; // 24 hours + options.max_open_files = -1; + env_->time_elapse_only_sleep_ = false; + options.env = env_; + + env_->addon_time_.store(0); + DestroyAndReopen(options); + + Random rnd(301); + for (int i = 0; i < kNumLevelFiles; ++i) { + for (int j = 0; j < kNumKeysPerFile; ++j) { + ASSERT_OK( + Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize))); + } + Flush(); + } + dbfull()->TEST_WaitForCompact(); + MoveFilesToLevel(3); + ASSERT_EQ("0,0,0,2", FilesPerLevel()); + + // Delete previously written keys. + for (int i = 0; i < kNumLevelFiles; ++i) { + for (int j = 0; j < kNumKeysPerFile; ++j) { + ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j))); + } + Flush(); + } + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ("2,0,0,2", FilesPerLevel()); + MoveFilesToLevel(1); + ASSERT_EQ("0,2,0,2", FilesPerLevel()); + + env_->addon_time_.fetch_add(36 * 60 * 60); // 36 hours + ASSERT_EQ("0,2,0,2", FilesPerLevel()); + + // Just do a simple write + flush so that the Ttl expired files get + // compacted. + ASSERT_OK(Put("a", "1")); + Flush(); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { + Compaction* compaction = reinterpret_cast(arg); + ASSERT_TRUE(compaction->compaction_reason() == CompactionReason::kTtl); + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + dbfull()->TEST_WaitForCompact(); + // All non-L0 files are deleted, as they contained only deleted data. + ASSERT_EQ("1", FilesPerLevel()); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + + // Test dynamically changing ttl. + + env_->addon_time_.store(0); + DestroyAndReopen(options); + + for (int i = 0; i < kNumLevelFiles; ++i) { + for (int j = 0; j < kNumKeysPerFile; ++j) { + ASSERT_OK( + Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize))); + } + Flush(); + } + dbfull()->TEST_WaitForCompact(); + MoveFilesToLevel(3); + ASSERT_EQ("0,0,0,2", FilesPerLevel()); + + // Delete previously written keys. + for (int i = 0; i < kNumLevelFiles; ++i) { + for (int j = 0; j < kNumKeysPerFile; ++j) { + ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j))); + } + Flush(); + } + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ("2,0,0,2", FilesPerLevel()); + MoveFilesToLevel(1); + ASSERT_EQ("0,2,0,2", FilesPerLevel()); + + // Move time forward by 12 hours, and make sure that compaction still doesn't + // trigger as ttl is set to 24 hours. + env_->addon_time_.fetch_add(12 * 60 * 60); + ASSERT_OK(Put("a", "1")); + Flush(); + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ("1,2,0,2", FilesPerLevel()); + + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "LevelCompactionPicker::PickCompaction:Return", [&](void* arg) { + Compaction* compaction = reinterpret_cast(arg); + ASSERT_TRUE(compaction->compaction_reason() == CompactionReason::kTtl); + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + // Dynamically change ttl to 10 hours. + // This should trigger a ttl compaction, as 12 hours have already passed. + ASSERT_OK(dbfull()->SetOptions({{"ttl", "36000"}})); + dbfull()->TEST_WaitForCompact(); + // All non-L0 files are deleted, as they contained only deleted data. + ASSERT_EQ("1", FilesPerLevel()); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) { + // Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual + // compaction only triggers flush after it's sure stall won't be triggered for + // L0 file count going too high. + const int kNumL0FilesTrigger = 4; + const int kNumL0FilesLimit = 8; + // i == 0: verifies normal case where stall is avoided by delay + // i == 1: verifies no delay in edge case where stall trigger is same as + // compaction trigger, so stall can't be avoided + for (int i = 0; i < 2; ++i) { + Options options = CurrentOptions(); + options.level0_slowdown_writes_trigger = kNumL0FilesLimit; + if (i == 0) { + options.level0_file_num_compaction_trigger = kNumL0FilesTrigger; + } else { + options.level0_file_num_compaction_trigger = kNumL0FilesLimit; + } + Reopen(options); + + if (i == 0) { + // ensure the auto compaction doesn't finish until manual compaction has + // had a chance to be delayed. + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", + "CompactionJob::Run():End"}}); + } else { + // ensure the auto-compaction doesn't finish until manual compaction has + // continued without delay. + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::FlushMemTable:StallWaitDone", "CompactionJob::Run():End"}}); + } + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Random rnd(301); + for (int j = 0; j < kNumL0FilesLimit - 1; ++j) { + for (int k = 0; k < 2; ++k) { + ASSERT_OK(Put(Key(k), RandomString(&rnd, 1024))); + } + Flush(); + } + auto manual_compaction_thread = port::Thread([this]() { + CompactRangeOptions cro; + cro.allow_write_stall = false; + db_->CompactRange(cro, nullptr, nullptr); + }); + + manual_compaction_thread.join(); + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(1), 0); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + } +} + +TEST_F(DBCompactionTest, CompactRangeDelayedByImmMemTableCount) { + // Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual + // compaction only triggers flush after it's sure stall won't be triggered for + // immutable memtable count going too high. + const int kNumImmMemTableLimit = 8; + // i == 0: verifies normal case where stall is avoided by delay + // i == 1: verifies no delay in edge case where stall trigger is same as flush + // trigger, so stall can't be avoided + for (int i = 0; i < 2; ++i) { + Options options = CurrentOptions(); + options.disable_auto_compactions = true; + // the delay limit is one less than the stop limit. This test focuses on + // avoiding delay limit, but this option sets stop limit, so add one. + options.max_write_buffer_number = kNumImmMemTableLimit + 1; + if (i == 1) { + options.min_write_buffer_number_to_merge = kNumImmMemTableLimit; + } + Reopen(options); + + if (i == 0) { + // ensure the flush doesn't finish until manual compaction has had a + // chance to be delayed. + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", + "FlushJob::WriteLevel0Table"}}); + } else { + // ensure the flush doesn't finish until manual compaction has continued + // without delay. + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::FlushMemTable:StallWaitDone", + "FlushJob::WriteLevel0Table"}}); + } + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Random rnd(301); + for (int j = 0; j < kNumImmMemTableLimit - 1; ++j) { + ASSERT_OK(Put(Key(0), RandomString(&rnd, 1024))); + FlushOptions flush_opts; + flush_opts.wait = false; + flush_opts.allow_write_stall = true; + dbfull()->Flush(flush_opts); + } + + auto manual_compaction_thread = port::Thread([this]() { + CompactRangeOptions cro; + cro.allow_write_stall = false; + db_->CompactRange(cro, nullptr, nullptr); + }); + + manual_compaction_thread.join(); + dbfull()->TEST_WaitForFlushMemTable(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(1), 0); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + } +} + +TEST_F(DBCompactionTest, CompactRangeShutdownWhileDelayed) { + // Verify that, when `CompactRangeOptions::allow_write_stall == false`, delay + // does not hang if CF is dropped or DB is closed + const int kNumL0FilesTrigger = 4; + const int kNumL0FilesLimit = 8; + Options options = CurrentOptions(); + options.level0_file_num_compaction_trigger = kNumL0FilesTrigger; + options.level0_slowdown_writes_trigger = kNumL0FilesLimit; + // i == 0: DB::DropColumnFamily() on CompactRange's target CF unblocks it + // i == 1: DB::CancelAllBackgroundWork() unblocks CompactRange. This is to + // simulate what happens during Close as we can't call Close (it + // blocks on the auto-compaction, making a cycle). + for (int i = 0; i < 2; ++i) { + CreateAndReopenWithCF({"one"}, options); + // The calls to close CF/DB wait until the manual compaction stalls. + // The auto-compaction waits until the manual compaction finishes to ensure + // the signal comes from closing CF/DB, not from compaction making progress. + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", + "DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown"}, + {"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual", + "CompactionJob::Run():End"}}); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Random rnd(301); + for (int j = 0; j < kNumL0FilesLimit - 1; ++j) { + for (int k = 0; k < 2; ++k) { + ASSERT_OK(Put(1, Key(k), RandomString(&rnd, 1024))); + } + Flush(1); + } + auto manual_compaction_thread = port::Thread([this]() { + CompactRangeOptions cro; + cro.allow_write_stall = false; + ASSERT_TRUE(db_->CompactRange(cro, handles_[1], nullptr, nullptr) + .IsShutdownInProgress()); + }); + + TEST_SYNC_POINT( + "DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown"); + if (i == 0) { + ASSERT_OK(db_->DropColumnFamily(handles_[1])); + } else { + dbfull()->CancelAllBackgroundWork(false /* wait */); + } + manual_compaction_thread.join(); + TEST_SYNC_POINT( + "DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual"); + dbfull()->TEST_WaitForCompact(); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + } +} + +TEST_F(DBCompactionTest, CompactRangeSkipFlushAfterDelay) { + // Verify that, when `CompactRangeOptions::allow_write_stall == false`, + // CompactRange skips its flush if the delay is long enough that the memtables + // existing at the beginning of the call have already been flushed. + const int kNumL0FilesTrigger = 4; + const int kNumL0FilesLimit = 8; + Options options = CurrentOptions(); + options.level0_slowdown_writes_trigger = kNumL0FilesLimit; + options.level0_file_num_compaction_trigger = kNumL0FilesTrigger; + Reopen(options); + + Random rnd(301); + // The manual flush includes the memtable that was active when CompactRange + // began. So it unblocks CompactRange and precludes its flush. Throughout the + // test, stall conditions are upheld via high L0 file count. + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait", + "DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush"}, + {"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush", + "DBImpl::FlushMemTable:StallWaitDone"}, + {"DBImpl::FlushMemTable:StallWaitDone", "CompactionJob::Run():End"}}); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + //used for the delayable flushes + FlushOptions flush_opts; + flush_opts.allow_write_stall = true; + for (int i = 0; i < kNumL0FilesLimit - 1; ++i) { + for (int j = 0; j < 2; ++j) { + ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024))); + } + dbfull()->Flush(flush_opts); + } + auto manual_compaction_thread = port::Thread([this]() { + CompactRangeOptions cro; + cro.allow_write_stall = false; + db_->CompactRange(cro, nullptr, nullptr); + }); + + TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush"); + Put(ToString(0), RandomString(&rnd, 1024)); + dbfull()->Flush(flush_opts); + Put(ToString(0), RandomString(&rnd, 1024)); + TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush"); + manual_compaction_thread.join(); + + // If CompactRange's flush was skipped, the final Put above will still be + // in the active memtable. + std::string num_keys_in_memtable; + db_->GetProperty(DB::Properties::kNumEntriesActiveMemTable, &num_keys_in_memtable); + ASSERT_EQ(ToString(1), num_keys_in_memtable); + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBCompactionTest, CompactRangeFlushOverlappingMemtable) { + // Verify memtable only gets flushed if it contains data overlapping the range + // provided to `CompactRange`. Tests all kinds of overlap/non-overlap. + const int kNumEndpointKeys = 5; + std::string keys[kNumEndpointKeys] = {"a", "b", "c", "d", "e"}; + Options options = CurrentOptions(); + options.disable_auto_compactions = true; + Reopen(options); + + // One extra iteration for nullptr, which means left side of interval is + // unbounded. + for (int i = 0; i <= kNumEndpointKeys; ++i) { + Slice begin; + Slice* begin_ptr; + if (i == 0) { + begin_ptr = nullptr; + } else { + begin = keys[i - 1]; + begin_ptr = &begin; + } + // Start at `i` so right endpoint comes after left endpoint. One extra + // iteration for nullptr, which means right side of interval is unbounded. + for (int j = std::max(0, i - 1); j <= kNumEndpointKeys; ++j) { + Slice end; + Slice* end_ptr; + if (j == kNumEndpointKeys) { + end_ptr = nullptr; + } else { + end = keys[j]; + end_ptr = &end; + } + ASSERT_OK(Put("b", "val")); + ASSERT_OK(Put("d", "val")); + CompactRangeOptions compact_range_opts; + ASSERT_OK(db_->CompactRange(compact_range_opts, begin_ptr, end_ptr)); + + uint64_t get_prop_tmp, num_memtable_entries = 0; + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesImmMemTables, + &get_prop_tmp)); + num_memtable_entries += get_prop_tmp; + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, + &get_prop_tmp)); + num_memtable_entries += get_prop_tmp; + if (begin_ptr == nullptr || end_ptr == nullptr || + (i <= 4 && j >= 1 && (begin != "c" || end != "c"))) { + // In this case `CompactRange`'s range overlapped in some way with the + // memtable's range, so flush should've happened. Then "b" and "d" won't + // be in the memtable. + ASSERT_EQ(0, num_memtable_entries); + } else { + ASSERT_EQ(2, num_memtable_entries); + // flush anyways to prepare for next iteration + db_->Flush(FlushOptions()); + } + } + } +} + +TEST_F(DBCompactionTest, CompactionStatsTest) { + Options options = CurrentOptions(); + options.level0_file_num_compaction_trigger = 2; + CompactionStatsCollector* collector = new CompactionStatsCollector(); + options.listeners.emplace_back(collector); + DestroyAndReopen(options); + + for (int i = 0; i < 32; i++) { + for (int j = 0; j < 5000; j++) { + Put(std::to_string(j), std::string(1, 'A')); + } + ASSERT_OK(Flush()); + ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); + } + dbfull()->TEST_WaitForCompact(); + ColumnFamilyHandleImpl* cfh = + static_cast(dbfull()->DefaultColumnFamily()); + ColumnFamilyData* cfd = cfh->cfd(); + + VerifyCompactionStats(*cfd, *collector); +} + +TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) { + // LSM setup: + // L1: [ba bz] + // L2: [a b] [c d] + // L3: [a b] [c d] + // + // Thread 1: Thread 2: + // Begin compacting all L2->L3 + // Compact [ba bz] L1->L3 + // End compacting all L2->L3 + // + // The compaction operation in thread 2 should be disallowed because the range + // overlaps with the compaction in thread 1, which also covers that range in + // L3. + Options options = CurrentOptions(); + FlushedFileCollector* collector = new FlushedFileCollector(); + options.listeners.emplace_back(collector); + Reopen(options); + + for (int level = 3; level >= 2; --level) { + ASSERT_OK(Put("a", "val")); + ASSERT_OK(Put("b", "val")); + ASSERT_OK(Flush()); + ASSERT_OK(Put("c", "val")); + ASSERT_OK(Put("d", "val")); + ASSERT_OK(Flush()); + MoveFilesToLevel(level); + } + ASSERT_OK(Put("ba", "val")); + ASSERT_OK(Put("bz", "val")); + ASSERT_OK(Flush()); + MoveFilesToLevel(1); + + SyncPoint::GetInstance()->LoadDependency({ + {"CompactFilesImpl:0", + "DBCompactionTest::CompactFilesOutputRangeConflict:Thread2Begin"}, + {"DBCompactionTest::CompactFilesOutputRangeConflict:Thread2End", + "CompactFilesImpl:1"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + + auto bg_thread = port::Thread([&]() { + // Thread 1 + std::vector filenames = collector->GetFlushedFiles(); + filenames.pop_back(); + ASSERT_OK(db_->CompactFiles(CompactionOptions(), filenames, + 3 /* output_level */)); + }); + + // Thread 2 + TEST_SYNC_POINT( + "DBCompactionTest::CompactFilesOutputRangeConflict:Thread2Begin"); + std::string filename = collector->GetFlushedFiles().back(); + ASSERT_FALSE( + db_->CompactFiles(CompactionOptions(), {filename}, 3 /* output_level */) + .ok()); + TEST_SYNC_POINT( + "DBCompactionTest::CompactFilesOutputRangeConflict:Thread2End"); + + bg_thread.join(); +} + +TEST_F(DBCompactionTest, CompactionHasEmptyOutput) { + Options options = CurrentOptions(); + SstStatsCollector* collector = new SstStatsCollector(); + options.level0_file_num_compaction_trigger = 2; + options.listeners.emplace_back(collector); + Reopen(options); + + // Make sure the L0 files overlap to prevent trivial move. + ASSERT_OK(Put("a", "val")); + ASSERT_OK(Put("b", "val")); + ASSERT_OK(Flush()); + ASSERT_OK(Delete("a")); + ASSERT_OK(Delete("b")); + ASSERT_OK(Flush()); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(NumTableFilesAtLevel(0), 0); + ASSERT_EQ(NumTableFilesAtLevel(1), 0); + + // Expect one file creation to start for each flush, and zero for compaction + // since no keys are written. + ASSERT_EQ(2, collector->num_ssts_creation_started()); +} + +TEST_F(DBCompactionTest, CompactionLimiter) { + const int kNumKeysPerFile = 10; + const int kMaxBackgroundThreads = 64; + + struct CompactionLimiter { + std::string name; + int limit_tasks; + int max_tasks; + int tasks; + std::shared_ptr limiter; + }; + + std::vector limiter_settings; + limiter_settings.push_back({"limiter_1", 1, 0, 0, nullptr}); + limiter_settings.push_back({"limiter_2", 2, 0, 0, nullptr}); + limiter_settings.push_back({"limiter_3", 3, 0, 0, nullptr}); + + for (auto& ls : limiter_settings) { + ls.limiter.reset(NewConcurrentTaskLimiter(ls.name, ls.limit_tasks)); + } + + std::shared_ptr unique_limiter( + NewConcurrentTaskLimiter("unique_limiter", -1)); + + const char* cf_names[] = {"default", "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; + const int cf_count = sizeof cf_names / sizeof cf_names[0]; + + std::unordered_map cf_to_limiter; + + Options options = CurrentOptions(); + options.write_buffer_size = 110 * 1024; // 110KB + options.arena_block_size = 4096; + options.num_levels = 3; + options.level0_file_num_compaction_trigger = 4; + options.level0_slowdown_writes_trigger = 64; + options.level0_stop_writes_trigger = 64; + options.max_background_jobs = kMaxBackgroundThreads; // Enough threads + options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile)); + options.max_write_buffer_number = 10; // Enough memtables + DestroyAndReopen(options); + + std::vector option_vector; + option_vector.reserve(cf_count); + + for (int cf = 0; cf < cf_count; cf++) { + ColumnFamilyOptions cf_opt(options); + if (cf == 0) { + // "Default" CF does't use compaction limiter + cf_opt.compaction_thread_limiter = nullptr; + } else if (cf == 1) { + // "1" CF uses bypass compaction limiter + unique_limiter->SetMaxOutstandingTask(-1); + cf_opt.compaction_thread_limiter = unique_limiter; + } else { + // Assign limiter by mod + auto& ls = limiter_settings[cf % 3]; + cf_opt.compaction_thread_limiter = ls.limiter; + cf_to_limiter[cf_names[cf]] = &ls; + } + option_vector.emplace_back(DBOptions(options), cf_opt); + } + + for (int cf = 1; cf < cf_count; cf++) { + CreateColumnFamilies({cf_names[cf]}, option_vector[cf]); + } + + ReopenWithColumnFamilies(std::vector(cf_names, + cf_names + cf_count), + option_vector); + + port::Mutex mutex; + + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::BackgroundCompaction:BeforeCompaction", [&](void* arg) { + const auto& cf_name = static_cast(arg)->GetName(); + auto iter = cf_to_limiter.find(cf_name); + if (iter != cf_to_limiter.end()) { + MutexLock l(&mutex); + ASSERT_GE(iter->second->limit_tasks, ++iter->second->tasks); + iter->second->max_tasks = std::max(iter->second->max_tasks, + iter->second->limit_tasks); + } + }); + + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::BackgroundCompaction:AfterCompaction", [&](void* arg) { + const auto& cf_name = static_cast(arg)->GetName(); + auto iter = cf_to_limiter.find(cf_name); + if (iter != cf_to_limiter.end()) { + MutexLock l(&mutex); + ASSERT_GE(--iter->second->tasks, 0); + } + }); + + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + // Block all compact threads in thread pool. + const size_t kTotalFlushTasks = kMaxBackgroundThreads / 4; + const size_t kTotalCompactTasks = kMaxBackgroundThreads - kTotalFlushTasks; + env_->SetBackgroundThreads((int)kTotalFlushTasks, Env::HIGH); + env_->SetBackgroundThreads((int)kTotalCompactTasks, Env::LOW); + + test::SleepingBackgroundTask sleeping_compact_tasks[kTotalCompactTasks]; + + // Block all compaction threads in thread pool. + for (size_t i = 0; i < kTotalCompactTasks; i++) { + env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, + &sleeping_compact_tasks[i], Env::LOW); + sleeping_compact_tasks[i].WaitUntilSleeping(); + } + + int keyIndex = 0; + + for (int n = 0; n < options.level0_file_num_compaction_trigger; n++) { + for (int cf = 0; cf < cf_count; cf++) { + for (int i = 0; i < kNumKeysPerFile; i++) { + ASSERT_OK(Put(cf, Key(keyIndex++), "")); + } + // put extra key to trigger flush + ASSERT_OK(Put(cf, "", "")); + } + + for (int cf = 0; cf < cf_count; cf++) { + dbfull()->TEST_WaitForFlushMemTable(handles_[cf]); + } + } + + // Enough L0 files to trigger compaction + for (int cf = 0; cf < cf_count; cf++) { + ASSERT_EQ(NumTableFilesAtLevel(0, cf), + options.level0_file_num_compaction_trigger); + } + + // Create more files for one column family, which triggers speed up + // condition, all compactions will be scheduled. + for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { + for (int i = 0; i < kNumKeysPerFile; i++) { + ASSERT_OK(Put(0, Key(i), "")); + } + // put extra key to trigger flush + ASSERT_OK(Put(0, "", "")); + dbfull()->TEST_WaitForFlushMemTable(handles_[0]); + ASSERT_EQ(options.level0_file_num_compaction_trigger + num + 1, + NumTableFilesAtLevel(0, 0)); + } + + // All CFs are pending compaction + ASSERT_EQ(cf_count, env_->GetThreadPoolQueueLen(Env::LOW)); + + // Unblock all compaction threads + for (size_t i = 0; i < kTotalCompactTasks; i++) { + sleeping_compact_tasks[i].WakeUp(); + sleeping_compact_tasks[i].WaitUntilDone(); + } + + for (int cf = 0; cf < cf_count; cf++) { + dbfull()->TEST_WaitForFlushMemTable(handles_[cf]); + } + + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + + // Max outstanding compact tasks reached limit + for (auto& ls : limiter_settings) { + ASSERT_EQ(ls.limit_tasks, ls.max_tasks); + ASSERT_EQ(0, ls.limiter->GetOutstandingTask()); + } + + // test manual compaction under a fully throttled limiter + int cf_test = 1; + unique_limiter->SetMaxOutstandingTask(0); + + // flush one more file to cf 1 + for (int i = 0; i < kNumKeysPerFile; i++) { + ASSERT_OK(Put(cf_test, Key(keyIndex++), "")); + } + // put extra key to trigger flush + ASSERT_OK(Put(cf_test, "", "")); + + dbfull()->TEST_WaitForFlushMemTable(handles_[cf_test]); + ASSERT_EQ(1, NumTableFilesAtLevel(0, cf_test)); + + Compact(cf_test, Key(0), Key(keyIndex)); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); +} + +INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam, + ::testing::Values(std::make_tuple(1, true), + std::make_tuple(1, false), + std::make_tuple(4, true), + std::make_tuple(4, false))); + +TEST_P(DBCompactionDirectIOTest, DirectIO) { + Options options = CurrentOptions(); + Destroy(options); + options.create_if_missing = true; + options.disable_auto_compactions = true; + options.use_direct_io_for_flush_and_compaction = GetParam(); + options.env = new MockEnv(Env::Default()); + Reopen(options); + bool readahead = false; + SyncPoint::GetInstance()->SetCallBack( + "TableCache::NewIterator:for_compaction", [&](void* arg) { + bool* use_direct_reads = static_cast(arg); + ASSERT_EQ(*use_direct_reads, + options.use_direct_reads); + }); + SyncPoint::GetInstance()->SetCallBack( + "CompactionJob::OpenCompactionOutputFile", [&](void* arg) { + bool* use_direct_writes = static_cast(arg); + ASSERT_EQ(*use_direct_writes, + options.use_direct_io_for_flush_and_compaction); + }); + if (options.use_direct_io_for_flush_and_compaction) { + SyncPoint::GetInstance()->SetCallBack( + "SanitizeOptions:direct_io", [&](void* /*arg*/) { + readahead = true; + }); + } + SyncPoint::GetInstance()->EnableProcessing(); + CreateAndReopenWithCF({"pikachu"}, options); + MakeTables(3, "p", "q", 1); + ASSERT_EQ("1,1,1", FilesPerLevel(1)); + Compact(1, "p1", "p9"); + ASSERT_EQ(readahead, options.use_direct_reads); + ASSERT_EQ("0,0,1", FilesPerLevel(1)); + Destroy(options); + delete options.env; +} + +INSTANTIATE_TEST_CASE_P(DBCompactionDirectIOTest, DBCompactionDirectIOTest, + testing::Bool()); class CompactionPriTest : public DBTestBase, public testing::WithParamInterface { @@ -2821,6 +4184,167 @@ INSTANTIATE_TEST_CASE_P( CompactionPri::kOldestSmallestSeqFirst, CompactionPri::kMinOverlappingRatio)); +class NoopMergeOperator : public MergeOperator { + public: + NoopMergeOperator() {} + + bool FullMergeV2(const MergeOperationInput& /*merge_in*/, + MergeOperationOutput* merge_out) const override { + std::string val("bar"); + merge_out->new_value = val; + return true; + } + + const char* Name() const override { return "Noop"; } +}; + +TEST_F(DBCompactionTest, PartialManualCompaction) { + Options opts = CurrentOptions(); + opts.num_levels = 3; + opts.level0_file_num_compaction_trigger = 10; + opts.compression = kNoCompression; + opts.merge_operator.reset(new NoopMergeOperator()); + opts.target_file_size_base = 10240; + DestroyAndReopen(opts); + + Random rnd(301); + for (auto i = 0; i < 8; ++i) { + for (auto j = 0; j < 10; ++j) { + Merge("foo", RandomString(&rnd, 1024)); + } + Flush(); + } + + MoveFilesToLevel(2); + + std::string prop; + EXPECT_TRUE(dbfull()->GetProperty(DB::Properties::kLiveSstFilesSize, &prop)); + uint64_t max_compaction_bytes = atoi(prop.c_str()) / 2; + ASSERT_OK(dbfull()->SetOptions( + {{"max_compaction_bytes", std::to_string(max_compaction_bytes)}})); + + CompactRangeOptions cro; + cro.bottommost_level_compaction = BottommostLevelCompaction::kForce; + dbfull()->CompactRange(cro, nullptr, nullptr); +} + +TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) { + // Regression test for bug where manual compaction hangs forever when the DB + // is in read-only mode. Verify it now at least returns, despite failing. + const int kNumL0Files = 4; + std::unique_ptr mock_env( + new FaultInjectionTestEnv(Env::Default())); + Options opts = CurrentOptions(); + opts.disable_auto_compactions = true; + opts.env = mock_env.get(); + DestroyAndReopen(opts); + + Random rnd(301); + for (int i = 0; i < kNumL0Files; ++i) { + // Make sure files are overlapping in key-range to prevent trivial move. + Put("key1", RandomString(&rnd, 1024)); + Put("key2", RandomString(&rnd, 1024)); + Flush(); + } + ASSERT_EQ(kNumL0Files, NumTableFilesAtLevel(0)); + + // Enter read-only mode by failing a write. + mock_env->SetFilesystemActive(false); + // Make sure this is outside `CompactRange`'s range so that it doesn't fail + // early trying to flush memtable. + ASSERT_NOK(Put("key3", RandomString(&rnd, 1024))); + + // In the bug scenario, the first manual compaction would fail and forget to + // unregister itself, causing the second one to hang forever due to conflict + // with a non-running compaction. + CompactRangeOptions cro; + cro.exclusive_manual_compaction = false; + Slice begin_key("key1"); + Slice end_key("key2"); + ASSERT_NOK(dbfull()->CompactRange(cro, &begin_key, &end_key)); + ASSERT_NOK(dbfull()->CompactRange(cro, &begin_key, &end_key)); + + // Close before mock_env destruct. + Close(); +} + +// FixFileIngestionCompactionDeadlock tests and verifies that compaction and +// file ingestion do not cause deadlock in the event of write stall triggered +// by number of L0 files reaching level0_stop_writes_trigger. +TEST_P(DBCompactionTestWithParam, FixFileIngestionCompactionDeadlock) { + const int kNumKeysPerFile = 100; + // Generate SST files. + Options options = CurrentOptions(); + + // Generate an external SST file containing a single key, i.e. 99 + std::string sst_files_dir = dbname_ + "/sst_files/"; + test::DestroyDir(env_, sst_files_dir); + ASSERT_OK(env_->CreateDir(sst_files_dir)); + SstFileWriter sst_writer(EnvOptions(), options); + const std::string sst_file_path = sst_files_dir + "test.sst"; + ASSERT_OK(sst_writer.Open(sst_file_path)); + ASSERT_OK(sst_writer.Put(Key(kNumKeysPerFile - 1), "value")); + ASSERT_OK(sst_writer.Finish()); + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->LoadDependency({ + {"DBImpl::IngestExternalFile:AfterIncIngestFileCounter", + "BackgroundCallCompaction:0"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + + options.write_buffer_size = 110 << 10; // 110KB + options.level0_file_num_compaction_trigger = + options.level0_stop_writes_trigger; + options.max_subcompactions = max_subcompactions_; + options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile)); + DestroyAndReopen(options); + Random rnd(301); + + // Generate level0_stop_writes_trigger L0 files to trigger write stop + for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) { + for (int j = 0; j != kNumKeysPerFile; ++j) { + ASSERT_OK(Put(Key(j), RandomString(&rnd, 990))); + } + if (0 == i) { + // When we reach here, the memtables have kNumKeysPerFile keys. Note that + // flush is not yet triggered. We need to write an extra key so that the + // write path will call PreprocessWrite and flush the previous key-value + // pairs to e flushed. After that, there will be the newest key in the + // memtable, and a bunch of L0 files. Since there is already one key in + // the memtable, then for i = 1, 2, ..., we do not have to write this + // extra key to trigger flush. + ASSERT_OK(Put("", "")); + } + dbfull()->TEST_WaitForFlushMemTable(); + ASSERT_EQ(NumTableFilesAtLevel(0 /*level*/, 0 /*cf*/), i + 1); + } + // When we reach this point, there will be level0_stop_writes_trigger L0 + // files and one extra key (99) in memory, which overlaps with the external + // SST file. Write stall triggers, and can be cleared only after compaction + // reduces the number of L0 files. + + // Compaction will also be triggered since we have reached the threshold for + // auto compaction. Note that compaction may begin after the following file + // ingestion thread and waits for ingestion to finish. + + // Thread to ingest file with overlapping key range with the current + // memtable. Consequently ingestion will trigger a flush. The flush MUST + // proceed without waiting for the write stall condition to clear, otherwise + // deadlock can happen. + port::Thread ingestion_thr([&]() { + IngestExternalFileOptions ifo; + Status s = db_->IngestExternalFile({sst_file_path}, ifo); + ASSERT_OK(s); + }); + + // More write to trigger write stop + ingestion_thr.join(); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + Close(); +} + #endif // !defined(ROCKSDB_LITE) } // namespace rocksdb @@ -2830,6 +4354,8 @@ int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); #else + (void) argc; + (void) argv; return 0; #endif } diff --git a/thirdparty/rocksdb/db/db_dynamic_level_test.cc b/thirdparty/rocksdb/db/db_dynamic_level_test.cc index f968e7fc05..8fac82851e 100644 --- a/thirdparty/rocksdb/db/db_dynamic_level_test.cc +++ b/thirdparty/rocksdb/db/db_dynamic_level_test.cc @@ -27,7 +27,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase) { return; } // Use InMemoryEnv, or it would be too slow. - unique_ptr env(new MockEnv(env_)); + std::unique_ptr env(new MockEnv(env_)); const int kNKeys = 1000; int keys[kNKeys]; @@ -125,6 +125,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) { int kMaxKey = 1000000; Options options = CurrentOptions(); + options.compression = kNoCompression; options.create_if_missing = true; options.write_buffer_size = 20480; options.max_write_buffer_number = 2; @@ -167,8 +168,8 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) { ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); ASSERT_EQ(4U, int_prop); - // Insert extra about 28K to L0. After they are compacted to L4, base level - // should be changed to L3. + // Insert extra about 28K to L0. After they are compacted to L4, the base + // level should be changed to L3. ASSERT_OK(dbfull()->SetOptions({ {"disable_auto_compactions", "true"}, })); @@ -189,13 +190,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) { ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level2", &str_prop)); 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( - "CompactionJob::Run():Start", - [&](void* arg) { env_->SleepForMicroseconds(100000); }); - rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + // Write even more data while leaving the base level at L3. ASSERT_OK(dbfull()->SetOptions({ {"disable_auto_compactions", "true"}, })); @@ -208,18 +203,12 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) { {"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 40K, 160K, 640K, add triggers - // another compaction from 40K->160K. + // Fill up L0, and then run an (auto) L0->Lmax compaction to raise the base + // level to 2. ASSERT_OK(dbfull()->SetOptions({ {"disable_auto_compactions", "true"}, })); @@ -229,23 +218,31 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) { ASSERT_OK(Put(Key(static_cast(rnd.Uniform(kMaxKey))), RandomString(&rnd, 380))); } + + // Make sure that the compaction starts before the last bit of data is + // flushed, so that the base level isn't raised to L1. + rocksdb::SyncPoint::GetInstance()->LoadDependency({ + {"CompactionJob::Run():Start", "DynamicLevelMaxBytesBase2:0"}, + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + ASSERT_OK(dbfull()->SetOptions({ {"disable_auto_compactions", "false"}, })); + + TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:0"); Flush(); dbfull()->TEST_WaitForCompact(); ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop)); ASSERT_EQ(2U, int_prop); - - // A manual compaction will trigger the base level to become L2 - // Keep Writing data until base level changed 2->1. There will be L0->L2 - // compaction going on at the same time. rocksdb::SyncPoint::GetInstance()->DisableProcessing(); rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); + // Write more data until the base level changes to L1. There will be + // a manual compaction going on at the same time. rocksdb::SyncPoint::GetInstance()->LoadDependency({ - {"CompactionJob::Run():Start", "DynamicLevelMaxBytesBase2:0"}, - {"DynamicLevelMaxBytesBase2:1", "CompactionJob::Run():End"}, + {"CompactionJob::Run():Start", "DynamicLevelMaxBytesBase2:1"}, + {"DynamicLevelMaxBytesBase2:2", "CompactionJob::Run():End"}, {"DynamicLevelMaxBytesBase2:compact_range_finish", "FlushJob::WriteLevel0Table"}, }); @@ -257,12 +254,12 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) { TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:compact_range_finish"); }); - TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:0"); + TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:1"); for (int i = 0; i < 2; i++) { ASSERT_OK(Put(Key(static_cast(rnd.Uniform(kMaxKey))), RandomString(&rnd, 380))); } - TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:1"); + TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:2"); Flush(); @@ -378,7 +375,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBaseInc) { int non_trivial = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", - [&](void* arg) { non_trivial++; }); + [&](void* /*arg*/) { non_trivial++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Random rnd(301); @@ -501,6 +498,8 @@ int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); #else + (void) argc; + (void) argv; return 0; #endif } diff --git a/thirdparty/rocksdb/db/db_encryption_test.cc b/thirdparty/rocksdb/db/db_encryption_test.cc index 38eee56459..46ba411b6f 100644 --- a/thirdparty/rocksdb/db/db_encryption_test.cc +++ b/thirdparty/rocksdb/db/db_encryption_test.cc @@ -40,7 +40,7 @@ TEST_F(DBEncryptionTest, CheckEncrypted) { continue; } auto filePath = dbname_ + "/" + *it; - unique_ptr seqFile; + std::unique_ptr seqFile; auto envOptions = EnvOptions(CurrentOptions()); status = defaultEnv->NewSequentialFile(filePath, &seqFile, envOptions); ASSERT_OK(status); diff --git a/thirdparty/rocksdb/db/db_filesnapshot.cc b/thirdparty/rocksdb/db/db_filesnapshot.cc index e266bf1ae1..ace0befb6d 100644 --- a/thirdparty/rocksdb/db/db_filesnapshot.cc +++ b/thirdparty/rocksdb/db/db_filesnapshot.cc @@ -44,7 +44,7 @@ Status DBImpl::EnableFileDeletions(bool force) { // Job id == 0 means that this is not our background process, but rather // user thread JobContext job_context(0); - bool should_purge_files = false; + bool file_deletion_enabled = false; { InstrumentedMutexLock l(&mutex_); if (force) { @@ -54,18 +54,18 @@ Status DBImpl::EnableFileDeletions(bool force) { --disable_delete_obsolete_files_; } if (disable_delete_obsolete_files_ == 0) { - ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled"); - should_purge_files = true; + file_deletion_enabled = true; FindObsoleteFiles(&job_context, true); - } else { - ROCKS_LOG_WARN( - immutable_db_options_.info_log, - "File Deletions Enable, but not really enabled. Counter: %d", - disable_delete_obsolete_files_); + bg_cv_.SignalAll(); } } - if (should_purge_files) { + if (file_deletion_enabled) { + ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled"); PurgeObsoleteFiles(job_context); + } else { + ROCKS_LOG_WARN(immutable_db_options_.info_log, + "File Deletions Enable, but not really enabled. Counter: %d", + disable_delete_obsolete_files_); } job_context.Clean(); LogFlush(immutable_db_options_.info_log); @@ -73,7 +73,7 @@ Status DBImpl::EnableFileDeletions(bool force) { } int DBImpl::IsFileDeletionsEnabled() const { - return disable_delete_obsolete_files_; + return !disable_delete_obsolete_files_; } Status DBImpl::GetLiveFiles(std::vector& ret, @@ -86,19 +86,28 @@ Status DBImpl::GetLiveFiles(std::vector& ret, if (flush_memtable) { // flush all dirty data to disk. Status status; - for (auto cfd : *versions_->GetColumnFamilySet()) { - if (cfd->IsDropped()) { - continue; - } - cfd->Ref(); + if (immutable_db_options_.atomic_flush) { + autovector cfds; + SelectColumnFamiliesForAtomicFlush(&cfds); mutex_.Unlock(); - status = FlushMemTable(cfd, FlushOptions()); - TEST_SYNC_POINT("DBImpl::GetLiveFiles:1"); - TEST_SYNC_POINT("DBImpl::GetLiveFiles:2"); + status = AtomicFlushMemTables(cfds, FlushOptions(), + FlushReason::kGetLiveFiles); mutex_.Lock(); - cfd->Unref(); - if (!status.ok()) { - break; + } else { + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (cfd->IsDropped()) { + continue; + } + cfd->Ref(); + mutex_.Unlock(); + status = FlushMemTable(cfd, FlushOptions(), FlushReason::kGetLiveFiles); + TEST_SYNC_POINT("DBImpl::GetLiveFiles:1"); + TEST_SYNC_POINT("DBImpl::GetLiveFiles:2"); + mutex_.Lock(); + cfd->Unref(); + if (!status.ok()) { + break; + } } } versions_->GetColumnFamilySet()->FreeDeadColumnFamilies(); @@ -125,7 +134,7 @@ Status DBImpl::GetLiveFiles(std::vector& ret, // create names of the live files. The names are not absolute // paths, instead they are relative to dbname_; - for (auto live_file : live) { + for (const auto& live_file : live) { ret.push_back(MakeTableFileName("", live_file.GetNumber())); } @@ -141,6 +150,18 @@ Status DBImpl::GetLiveFiles(std::vector& ret, } Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) { + { + // If caller disabled deletions, this function should return files that are + // guaranteed not to be deleted until deletions are re-enabled. We need to + // wait for pending purges to finish since WalManager doesn't know which + // files are going to be purged. Additional purges won't be scheduled as + // long as deletions are disabled (so the below loop must terminate). + InstrumentedMutexLock l(&mutex_); + while (disable_delete_obsolete_files_ > 0 && + pending_purge_obsolete_files_ > 0) { + bg_cv_.Wait(); + } + } return wal_manager_.GetSortedWalFiles(files); } diff --git a/thirdparty/rocksdb/db/db_flush_test.cc b/thirdparty/rocksdb/db/db_flush_test.cc index 107e82467c..8a4d8fc63a 100644 --- a/thirdparty/rocksdb/db/db_flush_test.cc +++ b/thirdparty/rocksdb/db/db_flush_test.cc @@ -25,6 +25,12 @@ class DBFlushDirectIOTest : public DBFlushTest, DBFlushDirectIOTest() : DBFlushTest() {} }; +class DBAtomicFlushTest : public DBFlushTest, + public ::testing::WithParamInterface { + public: + DBAtomicFlushTest() : DBFlushTest() {} +}; + // We had issue when two background threads trying to flush at the same time, // only one of them get committed. The test verifies the issue is fixed. TEST_F(DBFlushTest, FlushWhileWritingManifest) { @@ -35,11 +41,12 @@ TEST_F(DBFlushTest, FlushWhileWritingManifest) { Reopen(options); FlushOptions no_wait; no_wait.wait = false; + no_wait.allow_write_stall=true; SyncPoint::GetInstance()->LoadDependency( {{"VersionSet::LogAndApply:WriteManifest", "DBFlushTest::FlushWhileWritingManifest:1"}, - {"MemTableList::InstallMemtableFlushResults:InProgress", + {"MemTableList::TryInstallMemtableFlushResults:InProgress", "VersionSet::LogAndApply:WriteManifestDone"}}); SyncPoint::GetInstance()->EnableProcessing(); @@ -55,6 +62,8 @@ TEST_F(DBFlushTest, FlushWhileWritingManifest) { #endif // ROCKSDB_LITE } +// Disable this test temporarily on Travis as it fails intermittently. +// Github issue: #4151 TEST_F(DBFlushTest, SyncFail) { std::unique_ptr fault_injection_env( new FaultInjectionTestEnv(env_)); @@ -63,32 +72,68 @@ TEST_F(DBFlushTest, SyncFail) { options.env = fault_injection_env.get(); SyncPoint::GetInstance()->LoadDependency( - {{"DBFlushTest::SyncFail:1", "DBImpl::SyncClosedLogs:Start"}, + {{"DBFlushTest::SyncFail:GetVersionRefCount:1", + "DBImpl::FlushMemTableToOutputFile:BeforePickMemtables"}, + {"DBImpl::FlushMemTableToOutputFile:AfterPickMemtables", + "DBFlushTest::SyncFail:GetVersionRefCount:2"}, + {"DBFlushTest::SyncFail:1", "DBImpl::SyncClosedLogs:Start"}, {"DBImpl::SyncClosedLogs:Failed", "DBFlushTest::SyncFail:2"}}); SyncPoint::GetInstance()->EnableProcessing(); - Reopen(options); + CreateAndReopenWithCF({"pikachu"}, options); Put("key", "value"); auto* cfd = reinterpret_cast(db_->DefaultColumnFamily()) ->cfd(); - int refs_before = cfd->current()->TEST_refs(); FlushOptions flush_options; flush_options.wait = false; ASSERT_OK(dbfull()->Flush(flush_options)); + // Flush installs a new super-version. Get the ref count after that. + auto current_before = cfd->current(); + int refs_before = cfd->current()->TEST_refs(); + TEST_SYNC_POINT("DBFlushTest::SyncFail:GetVersionRefCount:1"); + TEST_SYNC_POINT("DBFlushTest::SyncFail:GetVersionRefCount:2"); + int refs_after_picking_memtables = cfd->current()->TEST_refs(); + ASSERT_EQ(refs_before + 1, refs_after_picking_memtables); fault_injection_env->SetFilesystemActive(false); TEST_SYNC_POINT("DBFlushTest::SyncFail:1"); TEST_SYNC_POINT("DBFlushTest::SyncFail:2"); fault_injection_env->SetFilesystemActive(true); + // Now the background job will do the flush; wait for it. dbfull()->TEST_WaitForFlushMemTable(); #ifndef ROCKSDB_LITE ASSERT_EQ("", FilesPerLevel()); // flush failed. #endif // ROCKSDB_LITE - // Flush job should release ref count to current version. + // Backgroun flush job should release ref count to current version. + ASSERT_EQ(current_before, cfd->current()); ASSERT_EQ(refs_before, cfd->current()->TEST_refs()); Destroy(options); } +TEST_F(DBFlushTest, SyncSkip) { + Options options = CurrentOptions(); + + SyncPoint::GetInstance()->LoadDependency( + {{"DBFlushTest::SyncSkip:1", "DBImpl::SyncClosedLogs:Skip"}, + {"DBImpl::SyncClosedLogs:Skip", "DBFlushTest::SyncSkip:2"}}); + SyncPoint::GetInstance()->EnableProcessing(); + + Reopen(options); + Put("key", "value"); + + FlushOptions flush_options; + flush_options.wait = false; + ASSERT_OK(dbfull()->Flush(flush_options)); + + TEST_SYNC_POINT("DBFlushTest::SyncSkip:1"); + TEST_SYNC_POINT("DBFlushTest::SyncSkip:2"); + + // Now the background job will do the flush; wait for it. + dbfull()->TEST_WaitForFlushMemTable(); + + Destroy(options); +} + TEST_F(DBFlushTest, FlushInLowPriThreadPool) { // Verify setting an empty high-pri (flush) thread pool causes flushes to be // scheduled in the low-pri (compaction) thread pool. @@ -101,7 +146,7 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) { std::thread::id tid; int num_flushes = 0, num_compactions = 0; SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkFlush", [&](void* arg) { + "DBImpl::BGWorkFlush", [&](void* /*arg*/) { if (tid == std::thread::id()) { tid = std::this_thread::get_id(); } else { @@ -110,7 +155,7 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) { ++num_flushes; }); SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkCompaction", [&](void* arg) { + "DBImpl::BGWorkCompaction", [&](void* /*arg*/) { ASSERT_EQ(tid, std::this_thread::get_id()); ++num_compactions; }); @@ -126,6 +171,41 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) { ASSERT_EQ(1, num_compactions); } +TEST_F(DBFlushTest, ManualFlushWithMinWriteBufferNumberToMerge) { + Options options = CurrentOptions(); + options.write_buffer_size = 100; + options.max_write_buffer_number = 4; + options.min_write_buffer_number_to_merge = 3; + Reopen(options); + + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::BGWorkFlush", + "DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1"}, + {"DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2", + "FlushJob::WriteLevel0Table"}}); + SyncPoint::GetInstance()->EnableProcessing(); + + ASSERT_OK(Put("key1", "value1")); + + port::Thread t([&]() { + // The call wait for flush to finish, i.e. with flush_options.wait = true. + ASSERT_OK(Flush()); + }); + + // Wait for flush start. + TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1"); + // Insert a second memtable before the manual flush finish. + // At the end of the manual flush job, it will check if further flush + // is needed, but it will not trigger flush of the second memtable because + // min_write_buffer_number_to_merge is not reached. + ASSERT_OK(Put("key2", "value2")); + ASSERT_OK(dbfull()->TEST_SwitchMemtable()); + TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2"); + + // Manual flush should return, without waiting for flush indefinitely. + t.join(); +} + TEST_P(DBFlushDirectIOTest, DirectIO) { Options options; options.create_if_missing = true; @@ -150,9 +230,269 @@ TEST_P(DBFlushDirectIOTest, DirectIO) { delete options.env; } +TEST_F(DBFlushTest, FlushError) { + Options options; + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + options.write_buffer_size = 100; + options.max_write_buffer_number = 4; + options.min_write_buffer_number_to_merge = 3; + options.disable_auto_compactions = true; + options.env = fault_injection_env.get(); + Reopen(options); + + ASSERT_OK(Put("key1", "value1")); + ASSERT_OK(Put("key2", "value2")); + fault_injection_env->SetFilesystemActive(false); + Status s = dbfull()->TEST_SwitchMemtable(); + fault_injection_env->SetFilesystemActive(true); + Destroy(options); + ASSERT_NE(s, Status::OK()); +} + +TEST_F(DBFlushTest, ManualFlushFailsInReadOnlyMode) { + // Regression test for bug where manual flush hangs forever when the DB + // is in read-only mode. Verify it now at least returns, despite failing. + Options options; + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + options.env = fault_injection_env.get(); + options.max_write_buffer_number = 2; + Reopen(options); + + // Trigger a first flush but don't let it run + ASSERT_OK(db_->PauseBackgroundWork()); + ASSERT_OK(Put("key1", "value1")); + FlushOptions flush_opts; + flush_opts.wait = false; + ASSERT_OK(db_->Flush(flush_opts)); + + // Write a key to the second memtable so we have something to flush later + // after the DB is in read-only mode. + ASSERT_OK(Put("key2", "value2")); + + // Let the first flush continue, hit an error, and put the DB in read-only + // mode. + fault_injection_env->SetFilesystemActive(false); + ASSERT_OK(db_->ContinueBackgroundWork()); + dbfull()->TEST_WaitForFlushMemTable(); +#ifndef ROCKSDB_LITE + uint64_t num_bg_errors; + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBackgroundErrors, + &num_bg_errors)); + ASSERT_GT(num_bg_errors, 0); +#endif // ROCKSDB_LITE + + // In the bug scenario, triggering another flush would cause the second flush + // to hang forever. After the fix we expect it to return an error. + ASSERT_NOK(db_->Flush(FlushOptions())); + + Close(); +} + +TEST_P(DBAtomicFlushTest, ManualAtomicFlush) { + Options options = CurrentOptions(); + options.create_if_missing = true; + options.atomic_flush = GetParam(); + options.write_buffer_size = (static_cast(64) << 20); + + CreateAndReopenWithCF({"pikachu", "eevee"}, options); + size_t num_cfs = handles_.size(); + ASSERT_EQ(3, num_cfs); + WriteOptions wopts; + wopts.disableWAL = true; + for (size_t i = 0; i != num_cfs; ++i) { + ASSERT_OK(Put(static_cast(i) /*cf*/, "key", "value", wopts)); + } + std::vector cf_ids; + for (size_t i = 0; i != num_cfs; ++i) { + cf_ids.emplace_back(static_cast(i)); + } + ASSERT_OK(Flush(cf_ids)); + for (size_t i = 0; i != num_cfs; ++i) { + auto cfh = static_cast(handles_[i]); + ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); + ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty()); + } +} + +TEST_P(DBAtomicFlushTest, AtomicFlushTriggeredByMemTableFull) { + Options options = CurrentOptions(); + options.create_if_missing = true; + options.atomic_flush = GetParam(); + // 4KB so that we can easily trigger auto flush. + options.write_buffer_size = 4096; + + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::BackgroundCallFlush:FlushFinish:0", + "DBAtomicFlushTest::AtomicFlushTriggeredByMemTableFull:BeforeCheck"}}); + SyncPoint::GetInstance()->EnableProcessing(); + + CreateAndReopenWithCF({"pikachu", "eevee"}, options); + size_t num_cfs = handles_.size(); + ASSERT_EQ(3, num_cfs); + WriteOptions wopts; + wopts.disableWAL = true; + for (size_t i = 0; i != num_cfs; ++i) { + ASSERT_OK(Put(static_cast(i) /*cf*/, "key", "value", wopts)); + } + // Keep writing to one of them column families to trigger auto flush. + for (int i = 0; i != 4000; ++i) { + ASSERT_OK(Put(static_cast(num_cfs) - 1 /*cf*/, + "key" + std::to_string(i), "value" + std::to_string(i), + wopts)); + } + + TEST_SYNC_POINT( + "DBAtomicFlushTest::AtomicFlushTriggeredByMemTableFull:BeforeCheck"); + if (options.atomic_flush) { + for (size_t i = 0; i != num_cfs - 1; ++i) { + auto cfh = static_cast(handles_[i]); + ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); + ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty()); + } + } else { + for (size_t i = 0; i != num_cfs - 1; ++i) { + auto cfh = static_cast(handles_[i]); + ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed()); + ASSERT_FALSE(cfh->cfd()->mem()->IsEmpty()); + } + } + SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_P(DBAtomicFlushTest, AtomicFlushRollbackSomeJobs) { + bool atomic_flush = GetParam(); + if (!atomic_flush) { + return; + } + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + Options options = CurrentOptions(); + options.create_if_missing = true; + options.atomic_flush = atomic_flush; + options.env = fault_injection_env.get(); + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:1", + "DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:1"}, + {"DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:2", + "DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:2"}}); + SyncPoint::GetInstance()->EnableProcessing(); + + CreateAndReopenWithCF({"pikachu", "eevee"}, options); + size_t num_cfs = handles_.size(); + ASSERT_EQ(3, num_cfs); + WriteOptions wopts; + wopts.disableWAL = true; + for (size_t i = 0; i != num_cfs; ++i) { + int cf_id = static_cast(i); + ASSERT_OK(Put(cf_id, "key", "value", wopts)); + } + FlushOptions flush_opts; + flush_opts.wait = false; + ASSERT_OK(dbfull()->Flush(flush_opts, handles_)); + TEST_SYNC_POINT("DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:1"); + fault_injection_env->SetFilesystemActive(false); + TEST_SYNC_POINT("DBAtomicFlushTest::AtomicFlushRollbackSomeJobs:2"); + for (auto* cfh : handles_) { + dbfull()->TEST_WaitForFlushMemTable(cfh); + } + for (size_t i = 0; i != num_cfs; ++i) { + auto cfh = static_cast(handles_[i]); + ASSERT_EQ(1, cfh->cfd()->imm()->NumNotFlushed()); + ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty()); + } + fault_injection_env->SetFilesystemActive(true); + Destroy(options); +} + +TEST_P(DBAtomicFlushTest, FlushMultipleCFs_DropSomeBeforeRequestFlush) { + bool atomic_flush = GetParam(); + if (!atomic_flush) { + return; + } + Options options = CurrentOptions(); + options.create_if_missing = true; + options.atomic_flush = atomic_flush; + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->EnableProcessing(); + + CreateAndReopenWithCF({"pikachu", "eevee"}, options); + size_t num_cfs = handles_.size(); + ASSERT_EQ(3, num_cfs); + WriteOptions wopts; + wopts.disableWAL = true; + std::vector cf_ids; + for (size_t i = 0; i != num_cfs; ++i) { + int cf_id = static_cast(i); + ASSERT_OK(Put(cf_id, "key", "value", wopts)); + cf_ids.push_back(cf_id); + } + ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); + ASSERT_TRUE(Flush(cf_ids).IsShutdownInProgress()); + Destroy(options); +} + +TEST_P(DBAtomicFlushTest, + FlushMultipleCFs_DropSomeAfterScheduleFlushBeforeFlushJobRun) { + bool atomic_flush = GetParam(); + if (!atomic_flush) { + return; + } + Options options = CurrentOptions(); + options.create_if_missing = true; + options.atomic_flush = atomic_flush; + + CreateAndReopenWithCF({"pikachu", "eevee"}, options); + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::AtomicFlushMemTables:AfterScheduleFlush", + "DBAtomicFlushTest::BeforeDropCF"}, + {"DBAtomicFlushTest::AfterDropCF", + "DBImpl::BackgroundCallFlush:start"}}); + SyncPoint::GetInstance()->EnableProcessing(); + + size_t num_cfs = handles_.size(); + ASSERT_EQ(3, num_cfs); + WriteOptions wopts; + wopts.disableWAL = true; + for (size_t i = 0; i != num_cfs; ++i) { + int cf_id = static_cast(i); + ASSERT_OK(Put(cf_id, "key", "value", wopts)); + } + port::Thread user_thread([&]() { + TEST_SYNC_POINT("DBAtomicFlushTest::BeforeDropCF"); + ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); + TEST_SYNC_POINT("DBAtomicFlushTest::AfterDropCF"); + }); + FlushOptions flush_opts; + flush_opts.wait = true; + ASSERT_OK(dbfull()->Flush(flush_opts, handles_)); + user_thread.join(); + for (size_t i = 0; i != num_cfs; ++i) { + int cf_id = static_cast(i); + ASSERT_EQ("value", Get(cf_id, "key")); + } + + ReopenWithColumnFamilies({kDefaultColumnFamilyName, "eevee"}, options); + num_cfs = handles_.size(); + ASSERT_EQ(2, num_cfs); + for (size_t i = 0; i != num_cfs; ++i) { + int cf_id = static_cast(i); + ASSERT_EQ("value", Get(cf_id, "key")); + } + Destroy(options); +} + INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest, testing::Bool()); +INSTANTIATE_TEST_CASE_P(DBAtomicFlushTest, DBAtomicFlushTest, testing::Bool()); + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_impl.cc b/thirdparty/rocksdb/db/db_impl.cc index d1bfe41e8c..8180564c2a 100644 --- a/thirdparty/rocksdb/db/db_impl.cc +++ b/thirdparty/rocksdb/db/db_impl.cc @@ -11,14 +11,12 @@ #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif -#include #include #ifdef OS_SOLARIS #include #endif #include -#include #include #include #include @@ -34,20 +32,21 @@ #include "db/db_info_dumper.h" #include "db/db_iter.h" #include "db/dbformat.h" +#include "db/error_handler.h" #include "db/event_helpers.h" #include "db/external_sst_file_ingestion_job.h" #include "db/flush_job.h" #include "db/forward_iterator.h" +#include "db/in_memory_stats_history.h" #include "db/job_context.h" #include "db/log_reader.h" #include "db/log_writer.h" #include "db/malloc_stats.h" -#include "db/managed_iterator.h" #include "db/memtable.h" #include "db/memtable_list.h" #include "db/merge_context.h" #include "db/merge_helper.h" -#include "db/range_del_aggregator.h" +#include "db/range_tombstone_fragmenter.h" #include "db/table_cache.h" #include "db/table_properties_collector.h" #include "db/transaction_log_impl.h" @@ -63,7 +62,6 @@ #include "options/cf_options.h" #include "options/options_helper.h" #include "options/options_parser.h" -#include "port/likely.h" #include "port/port.h" #include "rocksdb/cache.h" #include "rocksdb/compaction_filter.h" @@ -72,9 +70,9 @@ #include "rocksdb/env.h" #include "rocksdb/merge_operator.h" #include "rocksdb/statistics.h" +#include "rocksdb/stats_history.h" #include "rocksdb/status.h" #include "rocksdb/table.h" -#include "rocksdb/version.h" #include "rocksdb/write_buffer_manager.h" #include "table/block.h" #include "table/block_based_table_factory.h" @@ -101,7 +99,7 @@ namespace rocksdb { const std::string kDefaultColumnFamilyName("default"); -void DumpRocksDBBuildVersion(Logger * log); +void DumpRocksDBBuildVersion(Logger* log); CompressionType GetCompressionFlush( const ImmutableCFOptions& ioptions, @@ -110,7 +108,8 @@ CompressionType GetCompressionFlush( // optimization is used for leveled compaction. Otherwise the CPU and // latency overhead is not offset by saving much space. if (ioptions.compaction_style == kCompactionStyleUniversal) { - if (ioptions.compaction_options_universal.compression_size_percent < 0) { + if (mutable_cf_options.compaction_options_universal + .compression_size_percent < 0) { return mutable_cf_options.compression; } else { return kNoCompression; @@ -126,37 +125,44 @@ CompressionType GetCompressionFlush( namespace { void DumpSupportInfo(Logger* logger) { ROCKS_LOG_HEADER(logger, "Compression algorithms supported:"); - ROCKS_LOG_HEADER(logger, "\tSnappy supported: %d", Snappy_Supported()); - ROCKS_LOG_HEADER(logger, "\tZlib supported: %d", Zlib_Supported()); - ROCKS_LOG_HEADER(logger, "\tBzip supported: %d", BZip2_Supported()); - ROCKS_LOG_HEADER(logger, "\tLZ4 supported: %d", LZ4_Supported()); - ROCKS_LOG_HEADER(logger, "\tZSTD supported: %d", ZSTD_Supported()); - ROCKS_LOG_HEADER(logger, "Fast CRC32 supported: %d", - crc32c::IsFastCrc32Supported()); + for (auto& compression : OptionsHelper::compression_type_string_map) { + if (compression.second != kNoCompression && + compression.second != kDisableCompressionOption) { + ROCKS_LOG_HEADER(logger, "\t%s supported: %d", compression.first.c_str(), + CompressionTypeSupported(compression.second)); + } + } + ROCKS_LOG_HEADER(logger, "Fast CRC32 supported: %s", + crc32c::IsFastCrc32Supported().c_str()); } int64_t kDefaultLowPriThrottledRate = 2 * 1024 * 1024; -} // namespace +} // namespace -DBImpl::DBImpl(const DBOptions& options, const std::string& dbname) +DBImpl::DBImpl(const DBOptions& options, const std::string& dbname, + const bool seq_per_batch, const bool batch_per_txn) : env_(options.env), dbname_(dbname), + own_info_log_(options.info_log == nullptr), initial_db_options_(SanitizeOptions(dbname, options)), immutable_db_options_(initial_db_options_), mutable_db_options_(initial_db_options_), stats_(immutable_db_options_.statistics.get()), - db_lock_(nullptr), mutex_(stats_, env_, DB_MUTEX_WAIT_MICROS, immutable_db_options_.use_adaptive_mutex), + default_cf_handle_(nullptr), + max_total_in_memory_state_(0), + env_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)), + env_options_for_compaction_(env_->OptimizeForCompactionTableWrite( + env_options_, immutable_db_options_)), + db_lock_(nullptr), shutting_down_(false), bg_cv_(&mutex_), logfile_number_(0), log_dir_synced_(false), log_empty_(true), - default_cf_handle_(nullptr), log_sync_cv_(&mutex_), total_log_size_(0), - max_total_in_memory_state_(0), is_snapshot_supported_(true), write_buffer_manager_(immutable_db_options_.write_buffer_manager.get()), write_thread_(immutable_db_options_), @@ -177,23 +183,49 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname) num_running_flushes_(0), bg_purge_scheduled_(0), disable_delete_obsolete_files_(0), + pending_purge_obsolete_files_(0), delete_obsolete_files_last_run_(env_->NowMicros()), last_stats_dump_time_microsec_(0), next_job_id_(1), has_unpersisted_data_(false), - unable_to_flush_oldest_log_(false), - env_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)), + unable_to_release_oldest_log_(false), num_running_ingest_file_(0), #ifndef ROCKSDB_LITE - wal_manager_(immutable_db_options_, env_options_), + wal_manager_(immutable_db_options_, env_options_, seq_per_batch), #endif // ROCKSDB_LITE event_logger_(immutable_db_options_.info_log.get()), bg_work_paused_(0), bg_compaction_paused_(0), refitting_level_(false), opened_successfully_(false), - concurrent_prepare_(options.concurrent_prepare), - manual_wal_flush_(options.manual_wal_flush) { + two_write_queues_(options.two_write_queues), + manual_wal_flush_(options.manual_wal_flush), + seq_per_batch_(seq_per_batch), + batch_per_txn_(batch_per_txn), + // last_sequencee_ is always maintained by the main queue that also writes + // to the memtable. When two_write_queues_ is disabled last seq in + // memtable is the same as last seq published to the readers. When it is + // enabled but seq_per_batch_ is disabled, last seq in memtable still + // indicates last published seq since wal-only writes that go to the 2nd + // queue do not consume a sequence number. Otherwise writes performed by + // the 2nd queue could change what is visible to the readers. In this + // cases, last_seq_same_as_publish_seq_==false, the 2nd queue maintains a + // separate variable to indicate the last published sequence. + last_seq_same_as_publish_seq_( + !(seq_per_batch && options.two_write_queues)), + // Since seq_per_batch_ is currently set only by WritePreparedTxn which + // requires a custom gc for compaction, we use that to set use_custom_gc_ + // as well. + use_custom_gc_(seq_per_batch), + shutdown_initiated_(false), + own_sfm_(options.sst_file_manager == nullptr), + preserve_deletes_(options.preserve_deletes), + closed_(false), + error_handler_(this, immutable_db_options_, &mutex_), + atomic_flush_install_cv_(&mutex_) { + // !batch_per_trx_ implies seq_per_batch_ because it is only unset for + // WriteUnprepared, which should use seq_per_batch_. + assert(batch_per_txn_ || seq_per_batch_); env_->GetAbsolutePath(dbname, &db_absolute_path_); // Reserve ten files or so for other uses and give the rest to TableCache. @@ -215,25 +247,175 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname) immutable_db_options_.Dump(immutable_db_options_.info_log.get()); mutable_db_options_.Dump(immutable_db_options_.info_log.get()); DumpSupportInfo(immutable_db_options_.info_log.get()); + + // always open the DB with 0 here, which means if preserve_deletes_==true + // we won't drop any deletion markers until SetPreserveDeletesSequenceNumber() + // is called by client and this seqnum is advanced. + preserve_deletes_seqnum_.store(0); +} + +Status DBImpl::Resume() { + ROCKS_LOG_INFO(immutable_db_options_.info_log, "Resuming DB"); + + InstrumentedMutexLock db_mutex(&mutex_); + + if (!error_handler_.IsDBStopped() && !error_handler_.IsBGWorkStopped()) { + // Nothing to do + return Status::OK(); + } + + if (error_handler_.IsRecoveryInProgress()) { + // Don't allow a mix of manual and automatic recovery + return Status::Busy(); + } + + mutex_.Unlock(); + Status s = error_handler_.RecoverFromBGError(true); + mutex_.Lock(); + return s; +} + +// This function implements the guts of recovery from a background error. It +// is eventually called for both manual as well as automatic recovery. It does +// the following - +// 1. Wait for currently scheduled background flush/compaction to exit, in +// order to inadvertently causing an error and thinking recovery failed +// 2. Flush memtables if there's any data for all the CFs. This may result +// another error, which will be saved by error_handler_ and reported later +// as the recovery status +// 3. Find and delete any obsolete files +// 4. Schedule compactions if needed for all the CFs. This is needed as the +// flush in the prior step might have been a no-op for some CFs, which +// means a new super version wouldn't have been installed +Status DBImpl::ResumeImpl() { + mutex_.AssertHeld(); + WaitForBackgroundWork(); + + Status bg_error = error_handler_.GetBGError(); + Status s; + if (shutdown_initiated_) { + // Returning shutdown status to SFM during auto recovery will cause it + // to abort the recovery and allow the shutdown to progress + s = Status::ShutdownInProgress(); + } + if (s.ok() && bg_error.severity() > Status::Severity::kHardError) { + ROCKS_LOG_INFO( + immutable_db_options_.info_log, + "DB resume requested but failed due to Fatal/Unrecoverable error"); + s = bg_error; + } + + // We cannot guarantee consistency of the WAL. So force flush Memtables of + // all the column families + if (s.ok()) { + FlushOptions flush_opts; + // We allow flush to stall write since we are trying to resume from error. + flush_opts.allow_write_stall = true; + if (immutable_db_options_.atomic_flush) { + autovector cfds; + SelectColumnFamiliesForAtomicFlush(&cfds); + mutex_.Unlock(); + s = AtomicFlushMemTables(cfds, flush_opts, FlushReason::kErrorRecovery); + mutex_.Lock(); + } else { + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (cfd->IsDropped()) { + continue; + } + cfd->Ref(); + mutex_.Unlock(); + s = FlushMemTable(cfd, flush_opts, FlushReason::kErrorRecovery); + mutex_.Lock(); + cfd->Unref(); + if (!s.ok()) { + break; + } + } + } + if (!s.ok()) { + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "DB resume requested but failed due to Flush failure [%s]", + s.ToString().c_str()); + } + } + + JobContext job_context(0); + FindObsoleteFiles(&job_context, true); + if (s.ok()) { + s = error_handler_.ClearBGError(); + } + mutex_.Unlock(); + + job_context.manifest_file_number = 1; + if (job_context.HaveSomethingToDelete()) { + PurgeObsoleteFiles(job_context); + } + job_context.Clean(); + + if (s.ok()) { + ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB"); + } + mutex_.Lock(); + // Check for shutdown again before scheduling further compactions, + // since we released and re-acquired the lock above + if (shutdown_initiated_) { + s = Status::ShutdownInProgress(); + } + if (s.ok()) { + for (auto cfd : *versions_->GetColumnFamilySet()) { + SchedulePendingCompaction(cfd); + } + MaybeScheduleFlushOrCompaction(); + } + + // Wake up any waiters - in this case, it could be the shutdown thread + bg_cv_.SignalAll(); + + // No need to check BGError again. If something happened, event listener would + // be notified and the operation causing it would have failed + return s; +} + +void DBImpl::WaitForBackgroundWork() { + // Wait for background work to finish + while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ || + bg_flush_scheduled_) { + bg_cv_.Wait(); + } } // Will lock the mutex_, will wait for completion if wait is true void DBImpl::CancelAllBackgroundWork(bool wait) { - InstrumentedMutexLock l(&mutex_); - ROCKS_LOG_INFO(immutable_db_options_.info_log, "Shutdown: canceling all background work"); + if (thread_dump_stats_ != nullptr) { + thread_dump_stats_->cancel(); + thread_dump_stats_.reset(); + } + if (thread_persist_stats_ != nullptr) { + thread_persist_stats_->cancel(); + thread_persist_stats_.reset(); + } + InstrumentedMutexLock l(&mutex_); if (!shutting_down_.load(std::memory_order_acquire) && has_unpersisted_data_.load(std::memory_order_relaxed) && !mutable_db_options_.avoid_flush_during_shutdown) { - for (auto cfd : *versions_->GetColumnFamilySet()) { - if (!cfd->IsDropped() && cfd->initialized() && !cfd->mem()->IsEmpty()) { - cfd->Ref(); - mutex_.Unlock(); - FlushMemTable(cfd, FlushOptions()); - mutex_.Lock(); - cfd->Unref(); + if (immutable_db_options_.atomic_flush) { + autovector cfds; + SelectColumnFamiliesForAtomicFlush(&cfds); + mutex_.Unlock(); + AtomicFlushMemTables(cfds, FlushOptions(), FlushReason::kShutDown); + mutex_.Lock(); + } else { + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (!cfd->IsDropped() && cfd->initialized() && !cfd->mem()->IsEmpty()) { + cfd->Ref(); + mutex_.Unlock(); + FlushMemTable(cfd, FlushOptions(), FlushReason::kShutDown); + mutex_.Lock(); + cfd->Unref(); + } } } versions_->GetColumnFamilySet()->FreeDeadColumnFamilies(); @@ -244,14 +426,20 @@ void DBImpl::CancelAllBackgroundWork(bool wait) { if (!wait) { return; } - // Wait for background work to finish - while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ || - bg_flush_scheduled_) { + WaitForBackgroundWork(); +} + +Status DBImpl::CloseHelper() { + // Guarantee that there is no background error recovery in progress before + // continuing with the shutdown + mutex_.Lock(); + shutdown_initiated_ = true; + error_handler_.CancelErrorRecovery(); + while (error_handler_.IsRecoveryInProgress()) { bg_cv_.Wait(); } -} + mutex_.Unlock(); -DBImpl::~DBImpl() { // CancelAllBackgroundWork called with false means we just set the shutdown // marker. After this we do a variant of the waiting and unschedule work // (to consider: moving all the waiting into CancelAllBackgroundWork(true)) @@ -260,6 +448,7 @@ DBImpl::~DBImpl() { env_->UnSchedule(this, Env::Priority::BOTTOM); int compactions_unscheduled = env_->UnSchedule(this, Env::Priority::LOW); int flushes_unscheduled = env_->UnSchedule(this, Env::Priority::HIGH); + Status ret; mutex_.Lock(); bg_bottom_compaction_scheduled_ -= bottom_compactions_unscheduled; bg_compaction_scheduled_ -= compactions_unscheduled; @@ -267,17 +456,24 @@ DBImpl::~DBImpl() { // Wait for background work to finish while (bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ || - bg_flush_scheduled_ || bg_purge_scheduled_) { + bg_flush_scheduled_ || bg_purge_scheduled_ || + pending_purge_obsolete_files_ || + error_handler_.IsRecoveryInProgress()) { TEST_SYNC_POINT("DBImpl::~DBImpl:WaitJob"); bg_cv_.Wait(); } + TEST_SYNC_POINT_CALLBACK("DBImpl::CloseHelper:PendingPurgeFinished", + &files_grabbed_for_purge_); EraseThreadStatusDbInfo(); flush_scheduler_.Clear(); while (!flush_queue_.empty()) { - auto cfd = PopFirstFromFlushQueue(); - if (cfd->Unref()) { - delete cfd; + const FlushRequest& flush_req = PopFirstFromFlushQueue(); + for (const auto& iter : flush_req) { + ColumnFamilyData* cfd = iter.first; + if (cfd->Unref()) { + delete cfd; + } } } while (!compaction_queue_.empty()) { @@ -321,7 +517,19 @@ DBImpl::~DBImpl() { delete l; } for (auto& log : logs_) { - log.ClearWriter(); + uint64_t log_number = log.writer->get_log_number(); + Status s = log.ClearWriter(); + if (!s.ok()) { + ROCKS_LOG_WARN( + immutable_db_options_.info_log, + "Unable to Sync WAL file %s with error -- %s", + LogFileName(immutable_db_options_.wal_dir, log_number).c_str(), + s.ToString().c_str()); + // Retain the first error + if (ret.ok()) { + ret = s; + } + } } logs_.clear(); @@ -354,6 +562,34 @@ DBImpl::~DBImpl() { ROCKS_LOG_INFO(immutable_db_options_.info_log, "Shutdown complete"); LogFlush(immutable_db_options_.info_log); + +#ifndef ROCKSDB_LITE + // If the sst_file_manager was allocated by us during DB::Open(), ccall + // Close() on it before closing the info_log. Otherwise, background thread + // in SstFileManagerImpl might try to log something + if (immutable_db_options_.sst_file_manager && own_sfm_) { + auto sfm = static_cast( + immutable_db_options_.sst_file_manager.get()); + sfm->Close(); + } +#endif // ROCKSDB_LITE + + if (immutable_db_options_.info_log && own_info_log_) { + Status s = immutable_db_options_.info_log->Close(); + if (ret.ok()) { + ret = s; + } + } + return ret; +} + +Status DBImpl::CloseImpl() { return CloseHelper(); } + +DBImpl::~DBImpl() { + if (!closed_) { + closed_ = true; + CloseHelper(); + } } void DBImpl::MaybeIgnoreError(Status* s) const { @@ -378,71 +614,180 @@ const Status DBImpl::CreateArchivalDirectory() { void DBImpl::PrintStatistics() { auto dbstats = immutable_db_options_.statistics.get(); if (dbstats) { - ROCKS_LOG_WARN(immutable_db_options_.info_log, "STATISTICS:\n %s", + ROCKS_LOG_INFO(immutable_db_options_.info_log, "STATISTICS:\n %s", dbstats->ToString().c_str()); } } -void DBImpl::MaybeDumpStats() { - mutex_.Lock(); - unsigned int stats_dump_period_sec = - mutable_db_options_.stats_dump_period_sec; - mutex_.Unlock(); - if (stats_dump_period_sec == 0) return; - - const uint64_t now_micros = env_->NowMicros(); +void DBImpl::StartTimedTasks() { + unsigned int stats_dump_period_sec = 0; + unsigned int stats_persist_period_sec = 0; + { + InstrumentedMutexLock l(&mutex_); + stats_dump_period_sec = mutable_db_options_.stats_dump_period_sec; + if (stats_dump_period_sec > 0) { + if (!thread_dump_stats_) { + thread_dump_stats_.reset(new rocksdb::RepeatableThread( + [this]() { DBImpl::DumpStats(); }, "dump_st", env_, + stats_dump_period_sec * 1000000)); + } + } + stats_persist_period_sec = mutable_db_options_.stats_persist_period_sec; + if (stats_persist_period_sec > 0) { + if (!thread_persist_stats_) { + thread_persist_stats_.reset(new rocksdb::RepeatableThread( + [this]() { DBImpl::PersistStats(); }, "pst_st", env_, + stats_persist_period_sec * 1000000)); + } + } + } +} - if (last_stats_dump_time_microsec_ + stats_dump_period_sec * 1000000 <= - now_micros) { - // Multiple threads could race in here simultaneously. - // However, the last one will update last_stats_dump_time_microsec_ - // atomically. We could see more than one dump during one dump - // period in rare cases. - last_stats_dump_time_microsec_ = now_micros; +// esitmate the total size of stats_history_ +size_t DBImpl::EstiamteStatsHistorySize() const { + size_t size_total = + sizeof(std::map>); + if (stats_history_.size() == 0) return size_total; + size_t size_per_slice = + sizeof(uint64_t) + sizeof(std::map); + // non-empty map, stats_history_.begin() guaranteed to exist + std::map sample_slice(stats_history_.begin()->second); + for (const auto& pairs : sample_slice) { + size_per_slice += + pairs.first.capacity() + sizeof(pairs.first) + sizeof(pairs.second); + } + size_total = size_per_slice * stats_history_.size(); + return size_total; +} +void DBImpl::PersistStats() { + TEST_SYNC_POINT("DBImpl::PersistStats:Entry"); #ifndef ROCKSDB_LITE - const DBPropertyInfo* cf_property_info = - GetPropertyInfo(DB::Properties::kCFStats); - assert(cf_property_info != nullptr); - const DBPropertyInfo* db_property_info = - GetPropertyInfo(DB::Properties::kDBStats); - assert(db_property_info != nullptr); - - std::string stats; - { - InstrumentedMutexLock l(&mutex_); - default_cf_internal_stats_->GetStringProperty( - *db_property_info, DB::Properties::kDBStats, &stats); - for (auto cfd : *versions_->GetColumnFamilySet()) { - if (cfd->initialized()) { - cfd->internal_stats()->GetStringProperty( - *cf_property_info, DB::Properties::kCFStatsNoFileHistogram, - &stats); + if (shutdown_initiated_) { + return; + } + uint64_t now_micros = env_->NowMicros(); + Statistics* statistics = immutable_db_options_.statistics.get(); + if (!statistics) { + return; + } + size_t stats_history_size_limit = 0; + { + InstrumentedMutexLock l(&mutex_); + stats_history_size_limit = mutable_db_options_.stats_history_buffer_size; + } + + // TODO(Zhongyi): also persist immutable_db_options_.statistics + { + std::map stats_map; + if (!statistics->getTickerMap(&stats_map)) { + return; + } + InstrumentedMutexLock l(&stats_history_mutex_); + // calculate the delta from last time + if (stats_slice_initialized_) { + std::map stats_delta; + for (const auto& stat : stats_map) { + if (stats_slice_.find(stat.first) != stats_slice_.end()) { + stats_delta[stat.first] = stat.second - stats_slice_[stat.first]; } } - for (auto cfd : *versions_->GetColumnFamilySet()) { - if (cfd->initialized()) { - cfd->internal_stats()->GetStringProperty( - *cf_property_info, DB::Properties::kCFFileHistogram, &stats); - } + stats_history_[now_micros] = stats_delta; + } + stats_slice_initialized_ = true; + std::swap(stats_slice_, stats_map); + TEST_SYNC_POINT("DBImpl::PersistStats:StatsCopied"); + + // delete older stats snapshots to control memory consumption + bool purge_needed = EstiamteStatsHistorySize() > stats_history_size_limit; + while (purge_needed && !stats_history_.empty()) { + stats_history_.erase(stats_history_.begin()); + purge_needed = EstiamteStatsHistorySize() > stats_history_size_limit; + } + } + // TODO: persist stats to disk +#endif // !ROCKSDB_LITE +} + +bool DBImpl::FindStatsByTime(uint64_t start_time, uint64_t end_time, + uint64_t* new_time, + std::map* stats_map) { + assert(new_time); + assert(stats_map); + if (!new_time || !stats_map) return false; + // lock when search for start_time + { + InstrumentedMutexLock l(&stats_history_mutex_); + auto it = stats_history_.lower_bound(start_time); + if (it != stats_history_.end() && it->first < end_time) { + // make a copy for timestamp and stats_map + *new_time = it->first; + *stats_map = it->second; + return true; + } else { + return false; + } + } +} + +Status DBImpl::GetStatsHistory( + uint64_t start_time, uint64_t end_time, + std::unique_ptr* stats_iterator) { + if (!stats_iterator) { + return Status::InvalidArgument("stats_iterator not preallocated."); + } + stats_iterator->reset( + new InMemoryStatsHistoryIterator(start_time, end_time, this)); + return (*stats_iterator)->status(); +} + +void DBImpl::DumpStats() { + TEST_SYNC_POINT("DBImpl::DumpStats:1"); +#ifndef ROCKSDB_LITE + const DBPropertyInfo* cf_property_info = + GetPropertyInfo(DB::Properties::kCFStats); + assert(cf_property_info != nullptr); + const DBPropertyInfo* db_property_info = + GetPropertyInfo(DB::Properties::kDBStats); + assert(db_property_info != nullptr); + + std::string stats; + if (shutdown_initiated_) { + return; + } + { + InstrumentedMutexLock l(&mutex_); + default_cf_internal_stats_->GetStringProperty( + *db_property_info, DB::Properties::kDBStats, &stats); + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (cfd->initialized()) { + cfd->internal_stats()->GetStringProperty( + *cf_property_info, DB::Properties::kCFStatsNoFileHistogram, &stats); } } - ROCKS_LOG_WARN(immutable_db_options_.info_log, - "------- DUMPING STATS -------"); - ROCKS_LOG_WARN(immutable_db_options_.info_log, "%s", stats.c_str()); - if (immutable_db_options_.dump_malloc_stats) { - stats.clear(); - DumpMallocStats(&stats); - if (!stats.empty()) { - ROCKS_LOG_WARN(immutable_db_options_.info_log, - "------- Malloc STATS -------"); - ROCKS_LOG_WARN(immutable_db_options_.info_log, "%s", stats.c_str()); + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (cfd->initialized()) { + cfd->internal_stats()->GetStringProperty( + *cf_property_info, DB::Properties::kCFFileHistogram, &stats); } } + } + TEST_SYNC_POINT("DBImpl::DumpStats:2"); + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "------- DUMPING STATS -------"); + ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s", stats.c_str()); + if (immutable_db_options_.dump_malloc_stats) { + stats.clear(); + DumpMallocStats(&stats); + if (!stats.empty()) { + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "------- Malloc STATS -------"); + ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s", stats.c_str()); + } + } #endif // !ROCKSDB_LITE - PrintStatistics(); - } + PrintStatistics(); } void DBImpl::ScheduleBgLogWriterClose(JobContext* job_context) { @@ -455,7 +800,16 @@ void DBImpl::ScheduleBgLogWriterClose(JobContext* job_context) { } } -Directory* DBImpl::Directories::GetDataDir(size_t path_id) { +Directory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const { + assert(cfd); + Directory* ret_dir = cfd->GetDataDir(path_id); + if (ret_dir == nullptr) { + return directories_.GetDataDir(path_id); + } + return ret_dir; +} + +Directory* DBImpl::Directories::GetDataDir(size_t path_id) const { assert(path_id < data_dirs_.size()); Directory* ret_dir = data_dirs_[path_id].get(); if (ret_dir == nullptr) { @@ -465,9 +819,12 @@ Directory* DBImpl::Directories::GetDataDir(size_t path_id) { return ret_dir; } -Status DBImpl::SetOptions(ColumnFamilyHandle* column_family, +Status DBImpl::SetOptions( + ColumnFamilyHandle* column_family, const std::unordered_map& options_map) { #ifdef ROCKSDB_LITE + (void)column_family; + (void)options_map; return Status::NotSupported("Not supported in ROCKSDB LITE"); #else auto* cfd = reinterpret_cast(column_family)->cfd(); @@ -481,7 +838,7 @@ Status DBImpl::SetOptions(ColumnFamilyHandle* column_family, MutableCFOptions new_options; Status s; Status persist_options_status; - WriteThread::Writer w; + SuperVersionContext sv_context(/* create_superversion */ true); { InstrumentedMutexLock l(&mutex_); s = cfd->SetOptions(options_map); @@ -494,18 +851,18 @@ Status DBImpl::SetOptions(ColumnFamilyHandle* column_family, // Trigger possible flush/compactions. This has to be before we persist // options to file, otherwise there will be a deadlock with writer // thread. - auto* old_sv = - InstallSuperVersionAndScheduleWork(cfd, nullptr, new_options); - delete old_sv; + InstallSuperVersionAndScheduleWork(cfd, &sv_context, new_options); persist_options_status = WriteOptionsFile( false /*need_mutex_lock*/, true /*need_enter_write_thread*/); + bg_cv_.SignalAll(); } } + sv_context.Clean(); - ROCKS_LOG_INFO(immutable_db_options_.info_log, - "SetOptions() on column family [%s], inputs:", - cfd->GetName().c_str()); + ROCKS_LOG_INFO( + immutable_db_options_.info_log, + "SetOptions() on column family [%s], inputs:", cfd->GetName().c_str()); for (const auto& o : options_map) { ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s: %s\n", o.first.c_str(), o.second.c_str()); @@ -529,6 +886,7 @@ Status DBImpl::SetOptions(ColumnFamilyHandle* column_family, Status DBImpl::SetDBOptions( const std::unordered_map& options_map) { #ifdef ROCKSDB_LITE + (void)options_map; return Status::NotSupported("Not supported in ROCKSDB LITE"); #else if (options_map.empty()) { @@ -540,7 +898,7 @@ Status DBImpl::SetDBOptions( MutableDBOptions new_options; Status s; Status persist_options_status; - WriteThread::Writer w; + bool wal_changed = false; WriteContext write_context; { InstrumentedMutexLock l(&mutex_); @@ -553,17 +911,60 @@ Status DBImpl::SetDBOptions( new_options.max_background_compactions, Env::Priority::LOW); MaybeScheduleFlushOrCompaction(); } - - write_controller_.set_max_delayed_write_rate(new_options.delayed_write_rate); + if (new_options.stats_dump_period_sec != + mutable_db_options_.stats_dump_period_sec) { + if (thread_dump_stats_) { + mutex_.Unlock(); + thread_dump_stats_->cancel(); + mutex_.Lock(); + } + if (new_options.stats_dump_period_sec > 0) { + thread_dump_stats_.reset(new rocksdb::RepeatableThread( + [this]() { DBImpl::DumpStats(); }, "dump_st", env_, + new_options.stats_dump_period_sec * 1000000)); + } else { + thread_dump_stats_.reset(); + } + } + if (new_options.stats_persist_period_sec != + mutable_db_options_.stats_persist_period_sec) { + if (thread_persist_stats_) { + mutex_.Unlock(); + thread_persist_stats_->cancel(); + mutex_.Lock(); + } + if (new_options.stats_persist_period_sec > 0) { + thread_persist_stats_.reset(new rocksdb::RepeatableThread( + [this]() { DBImpl::PersistStats(); }, "pst_st", env_, + new_options.stats_persist_period_sec * 1000000)); + } else { + thread_persist_stats_.reset(); + } + } + write_controller_.set_max_delayed_write_rate( + new_options.delayed_write_rate); table_cache_.get()->SetCapacity(new_options.max_open_files == -1 ? TableCache::kInfiniteCapacity : new_options.max_open_files - 10); - + wal_changed = mutable_db_options_.wal_bytes_per_sync != + new_options.wal_bytes_per_sync; + if (new_options.bytes_per_sync == 0) { + new_options.bytes_per_sync = 1024 * 1024; + } mutable_db_options_ = new_options; - + env_options_for_compaction_ = EnvOptions( + BuildDBOptions(immutable_db_options_, mutable_db_options_)); + env_options_for_compaction_ = env_->OptimizeForCompactionTableWrite( + env_options_for_compaction_, immutable_db_options_); + versions_->ChangeEnvOptions(mutable_db_options_); + env_options_for_compaction_ = env_->OptimizeForCompactionTableRead( + env_options_for_compaction_, immutable_db_options_); + env_options_for_compaction_.compaction_readahead_size = + mutable_db_options_.compaction_readahead_size; + WriteThread::Writer w; write_thread_.EnterUnbatched(&w, &mutex_); - if (total_log_size_ > GetMaxTotalWalSize()) { - Status purge_wal_status = HandleWALFull(&write_context); + if (total_log_size_ > GetMaxTotalWalSize() || wal_changed) { + Status purge_wal_status = SwitchWAL(&write_context); if (!purge_wal_status.ok()) { ROCKS_LOG_WARN(immutable_db_options_.info_log, "Unable to purge WAL files in SetDBOptions() -- %s", @@ -602,8 +1003,9 @@ Status DBImpl::SetDBOptions( } // return the same level if it cannot be moved -int DBImpl::FindMinimumEmptyLevelFitting(ColumnFamilyData* cfd, - const MutableCFOptions& mutable_cf_options, int level) { +int DBImpl::FindMinimumEmptyLevelFitting( + ColumnFamilyData* cfd, const MutableCFOptions& /*mutable_cf_options*/, + int level) { mutex_.AssertHeld(); const auto* vstorage = cfd->current()->storage_info(); int minimum_level = level; @@ -621,7 +1023,7 @@ int DBImpl::FindMinimumEmptyLevelFitting(ColumnFamilyData* cfd, } Status DBImpl::FlushWAL(bool sync) { - { + if (manual_wal_flush_) { // We need to lock log_write_mutex_ since logs_ might change concurrently InstrumentedMutexLock wl(&log_write_mutex_); log::Writer* cur_log_writer = logs_.back().writer; @@ -629,12 +1031,20 @@ Status DBImpl::FlushWAL(bool sync) { if (!s.ok()) { ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL flush error %s", s.ToString().c_str()); + // In case there is a fs error we should set it globally to prevent the + // future writes + WriteStatusCheck(s); + // whether sync or not, we should abort the rest of function upon error + return s; } if (!sync) { ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "FlushWAL sync=false"); return s; } } + if (!sync) { + return Status::OK(); + } // sync = true ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "FlushWAL sync=true"); return SyncWAL(); @@ -702,12 +1112,29 @@ Status DBImpl::SyncWAL() { return status; } -void DBImpl::MarkLogsSynced( - uint64_t up_to, bool synced_dir, const Status& status) { +Status DBImpl::LockWAL() { + log_write_mutex_.Lock(); + auto cur_log_writer = logs_.back().writer; + auto status = cur_log_writer->WriteBuffer(); + if (!status.ok()) { + ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL flush error %s", + status.ToString().c_str()); + // In case there is a fs error we should set it globally to prevent the + // future writes + WriteStatusCheck(status); + } + return status; +} + +Status DBImpl::UnlockWAL() { + log_write_mutex_.Unlock(); + return Status::OK(); +} + +void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir, + const Status& status) { mutex_.AssertHeld(); - if (synced_dir && - logfile_number_ == up_to && - status.ok()) { + if (synced_dir && logfile_number_ == up_to && status.ok()) { log_dir_synced_ = true; } for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;) { @@ -715,6 +1142,8 @@ void DBImpl::MarkLogsSynced( assert(log.getting_synced); if (status.ok() && logs_.size() > 1) { logs_to_free_.push_back(log.ReleaseWriter()); + // To modify logs_ both mutex_ and log_write_mutex_ must be held + InstrumentedMutexLock l(&log_write_mutex_); it = logs_.erase(it); } else { log.getting_synced = false; @@ -730,8 +1159,21 @@ SequenceNumber DBImpl::GetLatestSequenceNumber() const { return versions_->LastSequence(); } +void DBImpl::SetLastPublishedSequence(SequenceNumber seq) { + versions_->SetLastPublishedSequence(seq); +} + +bool DBImpl::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) { + if (seqnum > preserve_deletes_seqnum_.load()) { + preserve_deletes_seqnum_.store(seqnum); + return true; + } else { + return false; + } +} + InternalIterator* DBImpl::NewInternalIterator( - Arena* arena, RangeDelAggregator* range_del_agg, + Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence, ColumnFamilyHandle* column_family) { ColumnFamilyData* cfd; if (column_family == nullptr) { @@ -745,8 +1187,8 @@ InternalIterator* DBImpl::NewInternalIterator( SuperVersion* super_version = cfd->GetSuperVersion()->Ref(); mutex_.Unlock(); ReadOptions roptions; - return NewInternalIterator(roptions, cfd, super_version, arena, - range_del_agg); + return NewInternalIterator(roptions, cfd, super_version, arena, range_del_agg, + sequence); } void DBImpl::SchedulePurge() { @@ -768,16 +1210,14 @@ void DBImpl::BackgroundCallPurge() { if (!purge_queue_.empty()) { auto purge_file = purge_queue_.begin(); auto fname = purge_file->fname; + auto dir_to_sync = purge_file->dir_to_sync; auto type = purge_file->type; auto number = purge_file->number; - auto path_id = purge_file->path_id; auto job_id = purge_file->job_id; purge_queue_.pop_front(); mutex_.Unlock(); - Status file_deletion_status; - DeleteObsoleteFileImpl(file_deletion_status, job_id, fname, type, number, - path_id); + DeleteObsoleteFileImpl(job_id, fname, dir_to_sync, type, number); mutex_.Lock(); } else { assert(!logs_to_free_queue_.empty()); @@ -813,7 +1253,7 @@ struct IterState { bool background_purge; }; -static void CleanupIteratorState(void* arg1, void* arg2) { +static void CleanupIteratorState(void* arg1, void* /*arg2*/) { IterState* state = reinterpret_cast(arg1); if (state->super_version->Unref()) { @@ -850,10 +1290,12 @@ static void CleanupIteratorState(void* arg1, void* arg2) { } } // namespace -InternalIterator* DBImpl::NewInternalIterator( - const ReadOptions& read_options, ColumnFamilyData* cfd, - SuperVersion* super_version, Arena* arena, - RangeDelAggregator* range_del_agg) { +InternalIterator* DBImpl::NewInternalIterator(const ReadOptions& read_options, + ColumnFamilyData* cfd, + SuperVersion* super_version, + Arena* arena, + RangeDelAggregator* range_del_agg, + SequenceNumber sequence) { InternalIterator* internal_iter; assert(arena != nullptr); assert(range_del_agg != nullptr); @@ -861,16 +1303,16 @@ InternalIterator* DBImpl::NewInternalIterator( MergeIteratorBuilder merge_iter_builder( &cfd->internal_comparator(), arena, !read_options.total_order_seek && - cfd->ioptions()->prefix_extractor != nullptr); + super_version->mutable_cf_options.prefix_extractor != nullptr); // Collect iterator for mutable mem merge_iter_builder.AddIterator( super_version->mem->NewIterator(read_options, arena)); - std::unique_ptr range_del_iter; + std::unique_ptr range_del_iter; Status s; if (!read_options.ignore_range_deletions) { range_del_iter.reset( - super_version->mem->NewRangeTombstoneIterator(read_options)); - s = range_del_agg->AddTombstones(std::move(range_del_iter)); + super_version->mem->NewRangeTombstoneIterator(read_options, sequence)); + range_del_agg->AddTombstones(std::move(range_del_iter)); } // Collect all needed child iterators for immutable memtables if (s.ok()) { @@ -880,6 +1322,7 @@ InternalIterator* DBImpl::NewInternalIterator( range_del_agg); } } + TEST_SYNC_POINT_CALLBACK("DBImpl::NewInternalIterator:StatusCallback", &s); if (s.ok()) { // Collect iterators for files in L0 - Ln if (read_options.read_tier != kMemtableTier) { @@ -889,12 +1332,15 @@ InternalIterator* DBImpl::NewInternalIterator( internal_iter = merge_iter_builder.Finish(); IterState* cleanup = new IterState(this, &mutex_, super_version, - read_options.background_purge_on_iterator_cleanup); + read_options.background_purge_on_iterator_cleanup || + immutable_db_options_.avoid_unnecessary_blocking_io); internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr); return internal_iter; + } else { + CleanupSuperVersion(super_version); } - return NewErrorInternalIterator(s); + return NewErrorInternalIterator(s, arena); } ColumnFamilyHandle* DBImpl::DefaultColumnFamily() const { @@ -910,14 +1356,24 @@ Status DBImpl::Get(const ReadOptions& read_options, Status DBImpl::GetImpl(const ReadOptions& read_options, ColumnFamilyHandle* column_family, const Slice& key, PinnableSlice* pinnable_val, bool* value_found, - bool* is_blob_index) { + ReadCallback* callback, bool* is_blob_index) { assert(pinnable_val != nullptr); + PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_); StopWatch sw(env_, stats_, DB_GET); PERF_TIMER_GUARD(get_snapshot_time); auto cfh = reinterpret_cast(column_family); auto cfd = cfh->cfd(); + if (tracer_) { + // TODO: This mutex should be removed later, to improve performance when + // tracing is enabled. + InstrumentedMutexLock lock(&trace_mutex_); + if (tracer_) { + tracer_->Get(column_family, key); + } + } + // Acquire SuperVersion SuperVersion* sv = GetAndRefSuperVersion(cfd); @@ -926,8 +1382,19 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, SequenceNumber snapshot; if (read_options.snapshot != nullptr) { - snapshot = reinterpret_cast( - read_options.snapshot)->number_; + // Note: In WritePrepared txns this is not necessary but not harmful + // either. Because prep_seq > snapshot => commit_seq > snapshot so if + // a snapshot is specified we should be fine with skipping seq numbers + // that are greater than that. + // + // In WriteUnprepared, we cannot set snapshot in the lookup key because we + // may skip uncommitted data that should be visible to the transaction for + // reading own writes. + snapshot = + reinterpret_cast(read_options.snapshot)->number_; + if (callback) { + snapshot = std::max(snapshot, callback->max_visible_seq()); + } } else { // Since we get and reference the super version before getting // the snapshot number, without a mutex protection, it is possible @@ -935,18 +1402,20 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, // data for this snapshot is available. But it will contain all // the data available in the super version we have, which is also // a valid snapshot to read from. - // We shouldn't get snapshot before finding and referencing the - // super versipon because a flush happening in between may compact - // away data for the snapshot, but the snapshot is earlier than the - // data overwriting it, so users may see wrong results. - snapshot = versions_->LastSequence(); + // We shouldn't get snapshot before finding and referencing the super + // version because a flush happening in between may compact away data for + // the snapshot, but the snapshot is earlier than the data overwriting it, + // so users may see wrong results. + snapshot = last_seq_same_as_publish_seq_ + ? versions_->LastSequence() + : versions_->LastPublishedSequence(); } TEST_SYNC_POINT("DBImpl::GetImpl:3"); TEST_SYNC_POINT("DBImpl::GetImpl:4"); // Prepare to store a list of merge operations if merge occurs. MergeContext merge_context; - RangeDelAggregator range_del_agg(cfd->internal_comparator(), snapshot); + SequenceNumber max_covering_tombstone_seq = 0; Status s; // First look in the memtable, then in the immutable memtable (if any). @@ -960,26 +1429,29 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, bool done = false; if (!skip_memtable) { if (sv->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context, - &range_del_agg, read_options, is_blob_index)) { + &max_covering_tombstone_seq, read_options, callback, + is_blob_index)) { done = true; pinnable_val->PinSelf(); RecordTick(stats_, MEMTABLE_HIT); } else if ((s.ok() || s.IsMergeInProgress()) && sv->imm->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context, - &range_del_agg, read_options, is_blob_index)) { + &max_covering_tombstone_seq, read_options, callback, + is_blob_index)) { done = true; pinnable_val->PinSelf(); RecordTick(stats_, MEMTABLE_HIT); } if (!done && !s.ok() && !s.IsMergeInProgress()) { + ReturnAndCleanupSuperVersion(cfd, sv); return s; } } if (!done) { PERF_TIMER_GUARD(get_from_output_files_time); sv->current->Get(read_options, lkey, pinnable_val, &s, &merge_context, - &range_del_agg, value_found, nullptr, nullptr, - is_blob_index); + &max_covering_tombstone_seq, value_found, nullptr, nullptr, + callback, is_blob_index); RecordTick(stats_, MEMTABLE_MISS); } @@ -989,10 +1461,13 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, ReturnAndCleanupSuperVersion(cfd, sv); RecordTick(stats_, NUMBER_KEYS_READ); - size_t size = pinnable_val->size(); - RecordTick(stats_, BYTES_READ, size); - MeasureTime(stats_, BYTES_PER_READ, size); - PERF_COUNTER_ADD(get_read_bytes, size); + size_t size = 0; + if (s.ok()) { + size = pinnable_val->size(); + RecordTick(stats_, BYTES_READ, size); + PERF_COUNTER_ADD(get_read_bytes, size); + } + RecordInHistogram(stats_, BYTES_PER_READ, size); } return s; } @@ -1001,7 +1476,7 @@ std::vector DBImpl::MultiGet( const ReadOptions& read_options, const std::vector& column_family, const std::vector& keys, std::vector* values) { - + PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_); StopWatch sw(env_, stats_, DB_MULTIGET); PERF_TIMER_GUARD(get_snapshot_time); @@ -1010,31 +1485,96 @@ std::vector DBImpl::MultiGet( struct MultiGetColumnFamilyData { ColumnFamilyData* cfd; SuperVersion* super_version; + MultiGetColumnFamilyData(ColumnFamilyData* cf, SuperVersion* sv) + : cfd(cf), super_version(sv) {} }; - std::unordered_map multiget_cf_data; - // fill up and allocate outside of mutex + std::unordered_map multiget_cf_data( + column_family.size()); for (auto cf : column_family) { auto cfh = reinterpret_cast(cf); auto cfd = cfh->cfd(); if (multiget_cf_data.find(cfd->GetID()) == multiget_cf_data.end()) { - auto mgcfd = new MultiGetColumnFamilyData(); - mgcfd->cfd = cfd; - multiget_cf_data.insert({cfd->GetID(), mgcfd}); + multiget_cf_data.emplace(cfd->GetID(), + MultiGetColumnFamilyData(cfd, nullptr)); } } - mutex_.Lock(); - if (read_options.snapshot != nullptr) { - snapshot = reinterpret_cast( - read_options.snapshot)->number_; - } else { - snapshot = versions_->LastSequence(); - } - for (auto mgd_iter : multiget_cf_data) { - mgd_iter.second->super_version = - mgd_iter.second->cfd->GetSuperVersion()->Ref(); + bool last_try = false; + { + // If we end up with the same issue of memtable geting sealed during 2 + // consecutive retries, it means the write rate is very high. In that case + // its probably ok to take the mutex on the 3rd try so we can succeed for + // sure + static const int num_retries = 3; + for (auto i = 0; i < num_retries; ++i) { + last_try = (i == num_retries - 1); + bool retry = false; + + if (i > 0) { + for (auto mgd_iter = multiget_cf_data.begin(); + mgd_iter != multiget_cf_data.end(); ++mgd_iter) { + auto super_version = mgd_iter->second.super_version; + auto cfd = mgd_iter->second.cfd; + if (super_version != nullptr) { + ReturnAndCleanupSuperVersion(cfd, super_version); + } + mgd_iter->second.super_version = nullptr; + } + } + + if (read_options.snapshot == nullptr) { + if (last_try) { + TEST_SYNC_POINT("DBImpl::MultiGet::LastTry"); + // We're close to max number of retries. For the last retry, + // acquire the lock so we're sure to succeed + mutex_.Lock(); + } + snapshot = last_seq_same_as_publish_seq_ + ? versions_->LastSequence() + : versions_->LastPublishedSequence(); + } else { + snapshot = reinterpret_cast(read_options.snapshot) + ->number_; + } + + for (auto mgd_iter = multiget_cf_data.begin(); + mgd_iter != multiget_cf_data.end(); ++mgd_iter) { + if (!last_try) { + mgd_iter->second.super_version = + GetAndRefSuperVersion(mgd_iter->second.cfd); + } else { + mgd_iter->second.super_version = + mgd_iter->second.cfd->GetSuperVersion()->Ref(); + } + TEST_SYNC_POINT("DBImpl::MultiGet::AfterRefSV"); + if (read_options.snapshot != nullptr || last_try) { + // If user passed a snapshot, then we don't care if a memtable is + // sealed or compaction happens because the snapshot would ensure + // that older key versions are kept around. If this is the last + // retry, then we have the lock so nothing bad can happen + continue; + } + // We could get the earliest sequence number for the whole list of + // memtables, which will include immutable memtables as well, but that + // might be tricky to maintain in case we decide, in future, to do + // memtable compaction. + if (!last_try) { + auto seq = + mgd_iter->second.super_version->mem->GetEarliestSequenceNumber(); + if (seq > snapshot) { + retry = true; + break; + } + } + } + if (!retry) { + if (last_try) { + mutex_.Unlock(); + } + break; + } + } } - mutex_.Unlock(); // Contain a list of merge operations if merge occurs. MergeContext merge_context; @@ -1052,6 +1592,7 @@ std::vector DBImpl::MultiGet( // First look in the memtable, then in the immutable memtable (if any). // s is both in/out. When in, s could either be OK or MergeInProgress. // merge_operands will contain the sequence of merges in the latter case. + size_t num_found = 0; for (size_t i = 0; i < num_keys; ++i) { merge_context.Clear(); Status& s = stat_list[i]; @@ -1059,38 +1600,39 @@ std::vector DBImpl::MultiGet( LookupKey lkey(keys[i], snapshot); auto cfh = reinterpret_cast(column_family[i]); - RangeDelAggregator range_del_agg(cfh->cfd()->internal_comparator(), - snapshot); + SequenceNumber max_covering_tombstone_seq = 0; auto mgd_iter = multiget_cf_data.find(cfh->cfd()->GetID()); assert(mgd_iter != multiget_cf_data.end()); auto mgd = mgd_iter->second; - auto super_version = mgd->super_version; + auto super_version = mgd.super_version; bool skip_memtable = (read_options.read_tier == kPersistedTier && has_unpersisted_data_.load(std::memory_order_relaxed)); bool done = false; if (!skip_memtable) { if (super_version->mem->Get(lkey, value, &s, &merge_context, - &range_del_agg, read_options)) { + &max_covering_tombstone_seq, read_options)) { done = true; - // TODO(?): RecordTick(stats_, MEMTABLE_HIT)? + RecordTick(stats_, MEMTABLE_HIT); } else if (super_version->imm->Get(lkey, value, &s, &merge_context, - &range_del_agg, read_options)) { + &max_covering_tombstone_seq, + read_options)) { done = true; - // TODO(?): RecordTick(stats_, MEMTABLE_HIT)? + RecordTick(stats_, MEMTABLE_HIT); } } if (!done) { PinnableSlice pinnable_val; PERF_TIMER_GUARD(get_from_output_files_time); super_version->current->Get(read_options, lkey, &pinnable_val, &s, - &merge_context, &range_del_agg); + &merge_context, &max_covering_tombstone_seq); value->assign(pinnable_val.data(), pinnable_val.size()); - // TODO(?): RecordTick(stats_, MEMTABLE_MISS)? + RecordTick(stats_, MEMTABLE_MISS); } if (s.ok()) { bytes_read += value->size(); + num_found++; } } @@ -1098,28 +1640,19 @@ std::vector DBImpl::MultiGet( PERF_TIMER_GUARD(get_post_process_time); autovector superversions_to_delete; - // TODO(icanadi) do we need lock here or just around Cleanup()? - mutex_.Lock(); for (auto mgd_iter : multiget_cf_data) { auto mgd = mgd_iter.second; - if (mgd->super_version->Unref()) { - mgd->super_version->Cleanup(); - superversions_to_delete.push_back(mgd->super_version); + if (!last_try) { + ReturnAndCleanupSuperVersion(mgd.cfd, mgd.super_version); + } else { + mgd.cfd->GetSuperVersion()->Unref(); } } - mutex_.Unlock(); - - for (auto td : superversions_to_delete) { - delete td; - } - for (auto mgd : multiget_cf_data) { - delete mgd.second; - } - RecordTick(stats_, NUMBER_MULTIGET_CALLS); RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys); + RecordTick(stats_, NUMBER_MULTIGET_KEYS_FOUND, num_found); RecordTick(stats_, NUMBER_MULTIGET_BYTES_READ, bytes_read); - MeasureTime(stats_, BYTES_PER_MULTIGET, bytes_read); + RecordInHistogram(stats_, BYTES_PER_MULTIGET, bytes_read); PERF_COUNTER_ADD(multiget_read_bytes, bytes_read); PERF_TIMER_STOP(get_post_process_time); @@ -1205,10 +1738,22 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options, if (s.ok() && immutable_db_options_.allow_concurrent_memtable_write) { s = CheckConcurrentWritesSupported(cf_options); } + if (s.ok()) { + s = CheckCFPathsSupported(initial_db_options_, cf_options); + } + if (s.ok()) { + for (auto& cf_path : cf_options.cf_paths) { + s = env_->CreateDirIfMissing(cf_path.path); + if (!s.ok()) { + break; + } + } + } if (!s.ok()) { return s; } + SuperVersionContext sv_context(/* create_superversion */ true); { InstrumentedMutexLock l(&mutex_); @@ -1235,13 +1780,19 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options, &cf_options); write_thread_.ExitUnbatched(&w); } + if (s.ok()) { + auto* cfd = + versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name); + assert(cfd != nullptr); + s = cfd->AddDirectories(); + } if (s.ok()) { single_column_family_mode_ = false; auto* cfd = versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name); assert(cfd != nullptr); - delete InstallSuperVersionAndScheduleWork( - cfd, nullptr, *cfd->GetLatestMutableCFOptions()); + InstallSuperVersionAndScheduleWork(cfd, &sv_context, + *cfd->GetLatestMutableCFOptions()); if (!cfd->mem()->IsSnapshotSupported()) { is_snapshot_supported_ = false; @@ -1260,6 +1811,7 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options, } } // InstrumentedMutexLock l(&mutex_) + sv_context.Clean(); // this is outside the mutex if (s.ok()) { NewThreadStatusCfInfo( @@ -1322,8 +1874,8 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) { // we drop column family from a single write thread WriteThread::Writer w; write_thread_.EnterUnbatched(&w, &mutex_); - s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), - &edit, &mutex_); + s = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit, + &mutex_); write_thread_.ExitUnbatched(&w); } if (s.ok()) { @@ -1344,6 +1896,7 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) { } is_snapshot_supported_ = new_is_snapshot_supported; } + bg_cv_.SignalAll(); } if (s.ok()) { @@ -1372,7 +1925,7 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options, *value_found = true; } ReadOptions roptions = read_options; - roptions.read_tier = kBlockCacheTier; // read from block cache only + roptions.read_tier = kBlockCacheTier; // read from block cache only PinnableSlice pinnable_val; auto s = GetImpl(roptions, column_family, key, &pinnable_val, value_found); value->assign(pinnable_val.data(), pinnable_val.size()); @@ -1385,55 +1938,59 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options, Iterator* DBImpl::NewIterator(const ReadOptions& read_options, ColumnFamilyHandle* column_family) { + if (read_options.managed) { + return NewErrorIterator( + Status::NotSupported("Managed iterator is not supported anymore.")); + } + Iterator* result = nullptr; if (read_options.read_tier == kPersistedTier) { return NewErrorIterator(Status::NotSupported( "ReadTier::kPersistedData is not yet supported in iterators.")); } + // if iterator wants internal keys, we can only proceed if + // we can guarantee the deletes haven't been processed yet + if (immutable_db_options_.preserve_deletes && + read_options.iter_start_seqnum > 0 && + read_options.iter_start_seqnum < preserve_deletes_seqnum_.load()) { + return NewErrorIterator(Status::InvalidArgument( + "Iterator requested internal keys which are too old and are not" + " guaranteed to be preserved, try larger iter_start_seqnum opt.")); + } auto cfh = reinterpret_cast(column_family); auto cfd = cfh->cfd(); - if (read_options.managed) { -#ifdef ROCKSDB_LITE - // not supported in lite version - return NewErrorIterator(Status::InvalidArgument( - "Managed Iterators not supported in RocksDBLite.")); -#else - if ((read_options.tailing) || (read_options.snapshot != nullptr) || - (is_snapshot_supported_)) { - return new ManagedIterator(this, read_options, cfd); - } - // Managed iter not supported - return NewErrorIterator(Status::InvalidArgument( - "Managed Iterators not supported without snapshots.")); -#endif - } else if (read_options.tailing) { + ReadCallback* read_callback = nullptr; // No read callback provided. + if (read_options.tailing) { #ifdef ROCKSDB_LITE // not supported in lite version - return nullptr; + result = nullptr; + #else SuperVersion* sv = cfd->GetReferencedSuperVersion(&mutex_); auto iter = new ForwardIterator(this, read_options, cfd, sv); - return NewDBIterator( - env_, read_options, *cfd->ioptions(), cfd->user_comparator(), iter, - kMaxSequenceNumber, - sv->mutable_cf_options.max_sequential_skip_in_iterations); + result = NewDBIterator( + env_, read_options, *cfd->ioptions(), sv->mutable_cf_options, + cfd->user_comparator(), iter, kMaxSequenceNumber, + sv->mutable_cf_options.max_sequential_skip_in_iterations, read_callback, + this, cfd); #endif } else { - SequenceNumber latest_snapshot = versions_->LastSequence(); - auto snapshot = - read_options.snapshot != nullptr - ? reinterpret_cast(read_options.snapshot) - ->number_ - : latest_snapshot; - return NewIteratorImpl(read_options, cfd, snapshot); + // Note: no need to consider the special case of + // last_seq_same_as_publish_seq_==false since NewIterator is overridden in + // WritePreparedTxnDB + auto snapshot = read_options.snapshot != nullptr + ? read_options.snapshot->GetSequenceNumber() + : versions_->LastSequence(); + result = NewIteratorImpl(read_options, cfd, snapshot, read_callback); } - // To stop compiler from complaining - return nullptr; + return result; } ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options, ColumnFamilyData* cfd, SequenceNumber snapshot, - bool allow_blob) { + ReadCallback* read_callback, + bool allow_blob, + bool allow_refresh) { SuperVersion* sv = cfd->GetReferencedSuperVersion(&mutex_); // Try to generate a DB iterator tree in continuous memory area to be @@ -1479,14 +2036,14 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(const ReadOptions& read_options, // likely that any iterator pointer is close to the iterator it points to so // that they are likely to be in the same cache line and/or page. ArenaWrappedDBIter* db_iter = NewArenaWrappedDbIterator( - env_, read_options, *cfd->ioptions(), snapshot, + env_, read_options, *cfd->ioptions(), sv->mutable_cf_options, snapshot, sv->mutable_cf_options.max_sequential_skip_in_iterations, - sv->version_number, ((read_options.snapshot != nullptr) ? nullptr : this), - cfd, allow_blob); + sv->version_number, read_callback, this, cfd, allow_blob, + ((read_options.snapshot != nullptr) ? false : allow_refresh)); InternalIterator* internal_iter = NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(), - db_iter->GetRangeDelAggregator()); + db_iter->GetRangeDelAggregator(), snapshot); db_iter->SetIterUnderDBIter(internal_iter); return db_iter; @@ -1496,55 +2053,44 @@ Status DBImpl::NewIterators( const ReadOptions& read_options, const std::vector& column_families, std::vector* iterators) { + if (read_options.managed) { + return Status::NotSupported("Managed iterator is not supported anymore."); + } if (read_options.read_tier == kPersistedTier) { return Status::NotSupported( "ReadTier::kPersistedData is not yet supported in iterators."); } + ReadCallback* read_callback = nullptr; // No read callback provided. iterators->clear(); iterators->reserve(column_families.size()); - if (read_options.managed) { -#ifdef ROCKSDB_LITE - return Status::InvalidArgument( - "Managed interator not supported in RocksDB lite"); -#else - if ((!read_options.tailing) && (read_options.snapshot == nullptr) && - (!is_snapshot_supported_)) { - return Status::InvalidArgument( - "Managed interator not supported without snapshots"); - } - for (auto cfh : column_families) { - auto cfd = reinterpret_cast(cfh)->cfd(); - auto iter = new ManagedIterator(this, read_options, cfd); - iterators->push_back(iter); - } -#endif - } else if (read_options.tailing) { + if (read_options.tailing) { #ifdef ROCKSDB_LITE return Status::InvalidArgument( - "Tailing interator not supported in RocksDB lite"); + "Tailing iterator not supported in RocksDB lite"); #else for (auto cfh : column_families) { auto cfd = reinterpret_cast(cfh)->cfd(); SuperVersion* sv = cfd->GetReferencedSuperVersion(&mutex_); auto iter = new ForwardIterator(this, read_options, cfd, sv); iterators->push_back(NewDBIterator( - env_, read_options, *cfd->ioptions(), cfd->user_comparator(), iter, - kMaxSequenceNumber, - sv->mutable_cf_options.max_sequential_skip_in_iterations)); + env_, read_options, *cfd->ioptions(), sv->mutable_cf_options, + cfd->user_comparator(), iter, kMaxSequenceNumber, + sv->mutable_cf_options.max_sequential_skip_in_iterations, + read_callback, this, cfd)); } #endif } else { - SequenceNumber latest_snapshot = versions_->LastSequence(); - auto snapshot = - read_options.snapshot != nullptr - ? reinterpret_cast(read_options.snapshot) - ->number_ - : latest_snapshot; - + // Note: no need to consider the special case of + // last_seq_same_as_publish_seq_==false since NewIterators is overridden in + // WritePreparedTxnDB + auto snapshot = read_options.snapshot != nullptr + ? read_options.snapshot->GetSequenceNumber() + : versions_->LastSequence(); for (size_t i = 0; i < column_families.size(); ++i) { - auto* cfd = reinterpret_cast( - column_families[i])->cfd(); - iterators->push_back(NewIteratorImpl(read_options, cfd, snapshot)); + auto* cfd = + reinterpret_cast(column_families[i])->cfd(); + iterators->push_back( + NewIteratorImpl(read_options, cfd, snapshot, read_callback)); } } @@ -1559,36 +2105,93 @@ const Snapshot* DBImpl::GetSnapshotForWriteConflictBoundary() { } #endif // ROCKSDB_LITE -const Snapshot* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary) { +SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary, + bool lock) { int64_t unix_time = 0; env_->GetCurrentTime(&unix_time); // Ignore error SnapshotImpl* s = new SnapshotImpl; - InstrumentedMutexLock l(&mutex_); + if (lock) { + mutex_.Lock(); + } // returns null if the underlying memtable does not support snapshot. if (!is_snapshot_supported_) { + if (lock) { + mutex_.Unlock(); + } delete s; return nullptr; } - return snapshots_.New(s, versions_->LastSequence(), unix_time, - is_write_conflict_boundary); + auto snapshot_seq = last_seq_same_as_publish_seq_ + ? versions_->LastSequence() + : versions_->LastPublishedSequence(); + SnapshotImpl* snapshot = + snapshots_.New(s, snapshot_seq, unix_time, is_write_conflict_boundary); + if (lock) { + mutex_.Unlock(); + } + return snapshot; +} + +namespace { +typedef autovector CfdList; +bool CfdListContains(const CfdList& list, ColumnFamilyData* cfd) { + for (const ColumnFamilyData* t : list) { + if (t == cfd) { + return true; + } + } + return false; } +} // namespace void DBImpl::ReleaseSnapshot(const Snapshot* s) { const SnapshotImpl* casted_s = reinterpret_cast(s); { InstrumentedMutexLock l(&mutex_); snapshots_.Delete(casted_s); + uint64_t oldest_snapshot; + if (snapshots_.empty()) { + oldest_snapshot = last_seq_same_as_publish_seq_ + ? versions_->LastSequence() + : versions_->LastPublishedSequence(); + } else { + oldest_snapshot = snapshots_.oldest()->number_; + } + // Avoid to go through every column family by checking a global threshold + // first. + if (oldest_snapshot > bottommost_files_mark_threshold_) { + CfdList cf_scheduled; + for (auto* cfd : *versions_->GetColumnFamilySet()) { + cfd->current()->storage_info()->UpdateOldestSnapshot(oldest_snapshot); + if (!cfd->current() + ->storage_info() + ->BottommostFilesMarkedForCompaction() + .empty()) { + SchedulePendingCompaction(cfd); + MaybeScheduleFlushOrCompaction(); + cf_scheduled.push_back(cfd); + } + } + + // Calculate a new threshold, skipping those CFs where compactions are + // scheduled. We do not do the same pass as the previous loop because + // mutex might be unlocked during the loop, making the result inaccurate. + SequenceNumber new_bottommost_files_mark_threshold = kMaxSequenceNumber; + for (auto* cfd : *versions_->GetColumnFamilySet()) { + if (CfdListContains(cf_scheduled, cfd)) { + continue; + } + new_bottommost_files_mark_threshold = std::min( + new_bottommost_files_mark_threshold, + cfd->current()->storage_info()->bottommost_files_mark_threshold()); + } + bottommost_files_mark_threshold_ = new_bottommost_files_mark_threshold; + } } delete casted_s; } -bool DBImpl::HasActiveSnapshotInRange(SequenceNumber lower_bound, - SequenceNumber upper_bound) { - InstrumentedMutexLock l(&mutex_); - return snapshots_.HasSnapshotInRange(lower_bound, upper_bound); -} - #ifndef ROCKSDB_LITE Status DBImpl::GetPropertiesOfAllTables(ColumnFamilyHandle* column_family, TablePropertiesCollection* props) { @@ -1635,13 +2238,9 @@ Status DBImpl::GetPropertiesOfTablesInRange(ColumnFamilyHandle* column_family, #endif // ROCKSDB_LITE -const std::string& DBImpl::GetName() const { - return dbname_; -} +const std::string& DBImpl::GetName() const { return dbname_; } -Env* DBImpl::GetEnv() const { - return env_; -} +Env* DBImpl::GetEnv() const { return env_; } Options DBImpl::GetOptions(ColumnFamilyHandle* column_family) const { InstrumentedMutexLock l(&mutex_); @@ -1674,6 +2273,13 @@ bool DBImpl::GetProperty(ColumnFamilyHandle* column_family, InstrumentedMutexLock l(&mutex_); return cfd->internal_stats()->GetStringProperty(*property_info, property, value); + } else if (property_info->handle_string_dbimpl) { + std::string tmp_value; + bool ret_value = (this->*(property_info->handle_string_dbimpl))(&tmp_value); + if (ret_value) { + *value = tmp_value; + } + return ret_value; } // Shouldn't reach here since exactly one of handle_string and handle_int // should be non-nullptr. @@ -1683,7 +2289,7 @@ bool DBImpl::GetProperty(ColumnFamilyHandle* column_family, bool DBImpl::GetMapProperty(ColumnFamilyHandle* column_family, const Slice& property, - std::map* value) { + std::map* value) { const DBPropertyInfo* property_info = GetPropertyInfo(property); value->clear(); auto cfd = reinterpret_cast(column_family)->cfd(); @@ -1740,6 +2346,16 @@ bool DBImpl::GetIntPropertyInternal(ColumnFamilyData* cfd, } } +bool DBImpl::GetPropertyHandleOptionsStatistics(std::string* value) { + assert(value != nullptr); + Statistics* statistics = immutable_db_options_.statistics.get(); + if (!statistics) { + return false; + } + *value = statistics->ToString(); + return true; +} + #ifndef ROCKSDB_LITE Status DBImpl::ResetStats() { InstrumentedMutexLock l(&mutex_); @@ -1796,21 +2412,23 @@ SuperVersion* DBImpl::GetAndRefSuperVersion(uint32_t column_family_id) { return GetAndRefSuperVersion(cfd); } +void DBImpl::CleanupSuperVersion(SuperVersion* sv) { + // Release SuperVersion + if (sv->Unref()) { + { + InstrumentedMutexLock l(&mutex_); + sv->Cleanup(); + } + delete sv; + RecordTick(stats_, NUMBER_SUPERVERSION_CLEANUPS); + } + RecordTick(stats_, NUMBER_SUPERVERSION_RELEASES); +} + void DBImpl::ReturnAndCleanupSuperVersion(ColumnFamilyData* cfd, SuperVersion* sv) { - bool unref_sv = !cfd->ReturnThreadLocalSuperVersion(sv); - - if (unref_sv) { - // Release SuperVersion - if (sv->Unref()) { - { - InstrumentedMutexLock l(&mutex_); - sv->Cleanup(); - } - delete sv; - RecordTick(stats_, NUMBER_SUPERVERSION_CLEANUPS); - } - RecordTick(stats_, NUMBER_SUPERVERSION_RELEASES); + if (!cfd->ReturnThreadLocalSuperVersion(sv)) { + CleanupSuperVersion(sv); } } @@ -1839,17 +2457,18 @@ ColumnFamilyHandle* DBImpl::GetColumnFamilyHandle(uint32_t column_family_id) { } // REQUIRED: mutex is NOT held. -ColumnFamilyHandle* DBImpl::GetColumnFamilyHandleUnlocked( +std::unique_ptr DBImpl::GetColumnFamilyHandleUnlocked( uint32_t column_family_id) { - ColumnFamilyMemTables* cf_memtables = column_family_memtables_.get(); - InstrumentedMutexLock l(&mutex_); - if (!cf_memtables->Seek(column_family_id)) { + auto* cfd = + versions_->GetColumnFamilySet()->GetColumnFamily(column_family_id); + if (cfd == nullptr) { return nullptr; } - return cf_memtables->GetColumnFamilyHandle(); + return std::unique_ptr( + new ColumnFamilyHandleImpl(cfd, this, &mutex_)); } void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family, @@ -1920,9 +2539,8 @@ void DBImpl::ReleaseFileNumberFromPendingOutputs( #ifndef ROCKSDB_LITE Status DBImpl::GetUpdatesSince( - SequenceNumber seq, unique_ptr* iter, + SequenceNumber seq, std::unique_ptr* iter, const TransactionLogIterator::ReadOptions& read_options) { - RecordTick(stats_, GET_UPDATES_SINCE_CALLS); if (seq > versions_->LastSequence()) { return Status::NotFound("Requested sequence not yet written in the db"); @@ -1950,8 +2568,7 @@ Status DBImpl::DeleteFile(std::string name) { name.c_str()); return Status::NotSupported("Delete only supported for archived logs"); } - status = - env_->DeleteFile(immutable_db_options_.wal_dir + "/" + name.c_str()); + status = wal_manager_.DeleteFile(name, number); if (!status.ok()) { ROCKS_LOG_ERROR(immutable_db_options_.info_log, "DeleteFile %s failed -- %s.\n", name.c_str(), @@ -2013,8 +2630,9 @@ Status DBImpl::DeleteFile(std::string name) { status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit, &mutex_, directories_.GetDbDir()); if (status.ok()) { - InstallSuperVersionAndScheduleWorkWrapper( - cfd, &job_context, *cfd->GetLatestMutableCFOptions()); + InstallSuperVersionAndScheduleWork(cfd, + &job_context.superversion_contexts[0], + *cfd->GetLatestMutableCFOptions()); } FindObsoleteFiles(&job_context, false); } // lock released here @@ -2029,56 +2647,62 @@ Status DBImpl::DeleteFile(std::string name) { return status; } -Status DBImpl::DeleteFilesInRange(ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end) { +Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family, + const RangePtr* ranges, size_t n, + bool include_end) { Status status; auto cfh = reinterpret_cast(column_family); ColumnFamilyData* cfd = cfh->cfd(); VersionEdit edit; - std::vector deleted_files; + std::set deleted_files; JobContext job_context(next_job_id_.fetch_add(1), true); { InstrumentedMutexLock l(&mutex_); Version* input_version = cfd->current(); auto* vstorage = input_version->storage_info(); - for (int i = 1; i < cfd->NumberLevels(); i++) { - if (vstorage->LevelFiles(i).empty() || - !vstorage->OverlapInLevel(i, begin, end)) { - continue; - } - std::vector level_files; - InternalKey begin_storage, end_storage, *begin_key, *end_key; - if (begin == nullptr) { - begin_key = nullptr; - } else { - begin_storage.SetMaxPossibleForUserKey(*begin); - begin_key = &begin_storage; - } - if (end == nullptr) { - end_key = nullptr; - } else { - end_storage.SetMinPossibleForUserKey(*end); - end_key = &end_storage; - } + for (size_t r = 0; r < n; r++) { + auto begin = ranges[r].start, end = ranges[r].limit; + for (int i = 1; i < cfd->NumberLevels(); i++) { + if (vstorage->LevelFiles(i).empty() || + !vstorage->OverlapInLevel(i, begin, end)) { + continue; + } + std::vector level_files; + InternalKey begin_storage, end_storage, *begin_key, *end_key; + if (begin == nullptr) { + begin_key = nullptr; + } else { + begin_storage.SetMinPossibleForUserKey(*begin); + begin_key = &begin_storage; + } + if (end == nullptr) { + end_key = nullptr; + } else { + end_storage.SetMaxPossibleForUserKey(*end); + end_key = &end_storage; + } - vstorage->GetOverlappingInputs(i, begin_key, end_key, &level_files, -1, - nullptr, false); - FileMetaData* level_file; - for (uint32_t j = 0; j < level_files.size(); j++) { - level_file = level_files[j]; - if (((begin == nullptr) || - (cfd->internal_comparator().user_comparator()->Compare( - level_file->smallest.user_key(), *begin) >= 0)) && - ((end == nullptr) || - (cfd->internal_comparator().user_comparator()->Compare( - level_file->largest.user_key(), *end) <= 0))) { + vstorage->GetCleanInputsWithinInterval( + i, begin_key, end_key, &level_files, -1 /* hint_index */, + nullptr /* file_index */); + FileMetaData* level_file; + for (uint32_t j = 0; j < level_files.size(); j++) { + level_file = level_files[j]; if (level_file->being_compacted) { continue; } + if (deleted_files.find(level_file) != deleted_files.end()) { + continue; + } + if (!include_end && end != nullptr && + cfd->user_comparator()->Compare(level_file->largest.user_key(), + *end) == 0) { + continue; + } edit.SetColumnFamily(cfd->GetID()); edit.DeleteFile(i, level_file->fd.GetNumber()); - deleted_files.push_back(level_file); + deleted_files.insert(level_file); level_file->being_compacted = true; } } @@ -2091,8 +2715,9 @@ Status DBImpl::DeleteFilesInRange(ColumnFamilyHandle* column_family, status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit, &mutex_, directories_.GetDbDir()); if (status.ok()) { - InstallSuperVersionAndScheduleWorkWrapper( - cfd, &job_context, *cfd->GetLatestMutableCFOptions()); + InstallSuperVersionAndScheduleWork(cfd, + &job_context.superversion_contexts[0], + *cfd->GetLatestMutableCFOptions()); } for (auto* deleted_file : deleted_files) { deleted_file->being_compacted = false; @@ -2116,9 +2741,8 @@ void DBImpl::GetLiveFilesMetaData(std::vector* metadata) { versions_->GetLiveFilesMetaData(metadata); } -void DBImpl::GetColumnFamilyMetaData( - ColumnFamilyHandle* column_family, - ColumnFamilyMetaData* cf_meta) { +void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family, + ColumnFamilyMetaData* cf_meta) { assert(column_family); auto* cfd = reinterpret_cast(column_family)->cfd(); auto* sv = GetAndRefSuperVersion(cfd); @@ -2164,15 +2788,16 @@ Status DBImpl::CheckConsistency() { Status DBImpl::GetDbIdentity(std::string& identity) const { std::string idfilename = IdentityFileName(dbname_); const EnvOptions soptions; - unique_ptr id_file_reader; + std::unique_ptr id_file_reader; Status s; { - unique_ptr idfile; + std::unique_ptr idfile; s = env_->NewSequentialFile(idfilename, &idfile, soptions); if (!s.ok()) { return s; } - id_file_reader.reset(new SequentialFileReader(std::move(idfile))); + id_file_reader.reset( + new SequentialFileReader(std::move(idfile), idfilename)); } uint64_t file_size; @@ -2180,7 +2805,8 @@ Status DBImpl::GetDbIdentity(std::string& identity) const { if (!s.ok()) { return s; } - char* buffer = reinterpret_cast(alloca(file_size)); + char* buffer = + reinterpret_cast(alloca(static_cast(file_size))); Slice id; s = id_file_reader->Read(static_cast(file_size), &id, buffer); if (!s.ok()) { @@ -2195,31 +2821,31 @@ Status DBImpl::GetDbIdentity(std::string& identity) const { } // Default implementation -- returns not supported status -Status DB::CreateColumnFamily(const ColumnFamilyOptions& cf_options, - const std::string& column_family_name, - ColumnFamilyHandle** handle) { +Status DB::CreateColumnFamily(const ColumnFamilyOptions& /*cf_options*/, + const std::string& /*column_family_name*/, + ColumnFamilyHandle** /*handle*/) { return Status::NotSupported(""); } Status DB::CreateColumnFamilies( - const ColumnFamilyOptions& cf_options, - const std::vector& column_family_names, - std::vector* handles) { + const ColumnFamilyOptions& /*cf_options*/, + const std::vector& /*column_family_names*/, + std::vector* /*handles*/) { return Status::NotSupported(""); } Status DB::CreateColumnFamilies( - const std::vector& column_families, - std::vector* handles) { + const std::vector& /*column_families*/, + std::vector* /*handles*/) { return Status::NotSupported(""); } -Status DB::DropColumnFamily(ColumnFamilyHandle* column_family) { +Status DB::DropColumnFamily(ColumnFamilyHandle* /*column_family*/) { return Status::NotSupported(""); } Status DB::DropColumnFamilies( - const std::vector& column_families) { + const std::vector& /*column_families*/) { return Status::NotSupported(""); } @@ -2228,7 +2854,15 @@ Status DB::DestroyColumnFamilyHandle(ColumnFamilyHandle* column_family) { return Status::OK(); } -DB::~DB() { } +DB::~DB() {} + +Status DBImpl::Close() { + if (!closed_) { + closed_ = true; + return CloseImpl(); + } + return Status::OK(); +} Status DB::ListColumnFamilies(const DBOptions& db_options, const std::string& name, @@ -2236,14 +2870,17 @@ Status DB::ListColumnFamilies(const DBOptions& db_options, return VersionSet::ListColumnFamilies(column_families, name, db_options.env); } -Snapshot::~Snapshot() { -} +Snapshot::~Snapshot() {} -Status DestroyDB(const std::string& dbname, const Options& options) { - const ImmutableDBOptions soptions(SanitizeOptions(dbname, options)); +Status DestroyDB(const std::string& dbname, const Options& options, + const std::vector& column_families) { + ImmutableDBOptions soptions(SanitizeOptions(dbname, options)); Env* env = soptions.env; std::vector filenames; + // Reset the logger because it holds a handle to the + // log file and prevents cleanup and directory removal + soptions.info_log.reset(); // Ignore error in case directory does not exist env->GetChildren(dbname, &filenames); @@ -2254,15 +2891,15 @@ Status DestroyDB(const std::string& dbname, const Options& options) { uint64_t number; FileType type; InfoLogPrefix info_log_prefix(!soptions.db_log_dir.empty(), dbname); - for (size_t i = 0; i < filenames.size(); i++) { - if (ParseFileName(filenames[i], &number, info_log_prefix.prefix, &type) && + for (const auto& fname : filenames) { + if (ParseFileName(fname, &number, info_log_prefix.prefix, &type) && type != kDBLockFile) { // Lock file will be deleted at end Status del; - std::string path_to_delete = dbname + "/" + filenames[i]; + std::string path_to_delete = dbname + "/" + fname; if (type == kMetaDatabase) { del = DestroyDB(path_to_delete, options); - } else if (type == kTableFile) { - del = DeleteSSTFile(&soptions, path_to_delete, 0); + } else if (type == kTableFile || type == kLogFile) { + del = DeleteDBFile(&soptions, path_to_delete, dbname); } else { del = env->DeleteFile(path_to_delete); } @@ -2272,59 +2909,84 @@ Status DestroyDB(const std::string& dbname, const Options& options) { } } - for (size_t path_id = 0; path_id < options.db_paths.size(); path_id++) { - const auto& db_path = options.db_paths[path_id]; - env->GetChildren(db_path.path, &filenames); - for (size_t i = 0; i < filenames.size(); i++) { - if (ParseFileName(filenames[i], &number, &type) && - type == kTableFile) { // Lock file will be deleted at end - std::string table_path = db_path.path + "/" + filenames[i]; - Status del = DeleteSSTFile(&soptions, table_path, - static_cast(path_id)); - if (result.ok() && !del.ok()) { - result = del; + std::vector paths; + + for (const auto& path : options.db_paths) { + paths.emplace_back(path.path); + } + for (const auto& cf : column_families) { + for (const auto& path : cf.options.cf_paths) { + paths.emplace_back(path.path); + } + } + + // Remove duplicate paths. + // Note that we compare only the actual paths but not path ids. + // This reason is that same path can appear at different path_ids + // for different column families. + std::sort(paths.begin(), paths.end()); + paths.erase(std::unique(paths.begin(), paths.end()), paths.end()); + + for (const auto& path : paths) { + if (env->GetChildren(path, &filenames).ok()) { + for (const auto& fname : filenames) { + if (ParseFileName(fname, &number, &type) && + type == kTableFile) { // Lock file will be deleted at end + std::string table_path = path + "/" + fname; + Status del = DeleteDBFile(&soptions, table_path, dbname); + if (result.ok() && !del.ok()) { + result = del; + } } } + env->DeleteDir(path); } } std::vector walDirFiles; std::string archivedir = ArchivalDirectory(dbname); + bool wal_dir_exists = false; if (dbname != soptions.wal_dir) { - env->GetChildren(soptions.wal_dir, &walDirFiles); + wal_dir_exists = env->GetChildren(soptions.wal_dir, &walDirFiles).ok(); archivedir = ArchivalDirectory(soptions.wal_dir); } - // Delete log files in the WAL dir - for (const auto& file : walDirFiles) { - if (ParseFileName(file, &number, &type) && type == kLogFile) { - Status del = env->DeleteFile(LogFileName(soptions.wal_dir, number)); - if (result.ok() && !del.ok()) { - result = del; + // Archive dir may be inside wal dir or dbname and should be + // processed and removed before those otherwise we have issues + // removing them + std::vector archiveFiles; + if (env->GetChildren(archivedir, &archiveFiles).ok()) { + // Delete archival files. + for (const auto& file : archiveFiles) { + if (ParseFileName(file, &number, &type) && type == kLogFile) { + Status del = + DeleteDBFile(&soptions, archivedir + "/" + file, archivedir); + if (result.ok() && !del.ok()) { + result = del; + } } } + env->DeleteDir(archivedir); } - std::vector archiveFiles; - env->GetChildren(archivedir, &archiveFiles); - // Delete archival files. - for (size_t i = 0; i < archiveFiles.size(); ++i) { - if (ParseFileName(archiveFiles[i], &number, &type) && - type == kLogFile) { - Status del = env->DeleteFile(archivedir + "/" + archiveFiles[i]); - if (result.ok() && !del.ok()) { - result = del; + // Delete log files in the WAL dir + if (wal_dir_exists) { + for (const auto& file : walDirFiles) { + if (ParseFileName(file, &number, &type) && type == kLogFile) { + Status del = + DeleteDBFile(&soptions, LogFileName(soptions.wal_dir, number), + soptions.wal_dir); + if (result.ok() && !del.ok()) { + result = del; + } } } + env->DeleteDir(soptions.wal_dir); } - // ignore case where no archival directory is present - env->DeleteDir(archivedir); - env->UnlockFile(lock); // Ignore error since state is already gone env->DeleteFile(lockname); env->DeleteDir(dbname); // Ignore error in case dir contains other files - env->DeleteDir(soptions.wal_dir); } return result; } @@ -2386,6 +3048,9 @@ Status DBImpl::WriteOptionsFile(bool need_mutex_lock, s.ToString().c_str()); } } +#else + (void)need_mutex_lock; + (void)need_enter_write_thread; #endif // !ROCKSDB_LITE return Status::OK(); } @@ -2445,31 +3110,36 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) { #ifndef ROCKSDB_LITE Status s; - versions_->options_file_number_ = versions_->NewFileNumber(); + uint64_t options_file_number = versions_->NewFileNumber(); std::string options_file_name = - OptionsFileName(GetName(), versions_->options_file_number_); + OptionsFileName(GetName(), options_file_number); // Retry if the file name happen to conflict with an existing one. s = GetEnv()->RenameFile(file_name, options_file_name); + if (s.ok()) { + InstrumentedMutexLock l(&mutex_); + versions_->options_file_number_ = options_file_number; + } - DeleteObsoleteOptionsFiles(); + if (0 == disable_delete_obsolete_files_) { + DeleteObsoleteOptionsFiles(); + } return s; #else + (void)file_name; return Status::OK(); #endif // !ROCKSDB_LITE } #ifdef ROCKSDB_USING_THREAD_STATUS -void DBImpl::NewThreadStatusCfInfo( - ColumnFamilyData* cfd) const { +void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* cfd) const { if (immutable_db_options_.enable_thread_tracking) { ThreadStatusUtil::NewColumnFamilyInfo(this, cfd, cfd->GetName(), cfd->ioptions()->env); } } -void DBImpl::EraseThreadStatusCfInfo( - ColumnFamilyData* cfd) const { +void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* cfd) const { if (immutable_db_options_.enable_thread_tracking) { ThreadStatusUtil::EraseColumnFamilyInfo(cfd); } @@ -2482,21 +3152,16 @@ void DBImpl::EraseThreadStatusDbInfo() const { } #else -void DBImpl::NewThreadStatusCfInfo( - ColumnFamilyData* cfd) const { -} +void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {} -void DBImpl::EraseThreadStatusCfInfo( - ColumnFamilyData* cfd) const { -} +void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {} -void DBImpl::EraseThreadStatusDbInfo() const { -} +void DBImpl::EraseThreadStatusDbInfo() const {} #endif // ROCKSDB_USING_THREAD_STATUS // // A global method that can dump out the build version -void DumpRocksDBBuildVersion(Logger * log) { +void DumpRocksDBBuildVersion(Logger* log) { #if !defined(IOS_CROSS_COMPILE) // if we compile with Xcode, we don't run build_detect_version, so we don't // generate util/build_version.cc @@ -2504,6 +3169,8 @@ void DumpRocksDBBuildVersion(Logger * log) { ROCKSDB_MINOR, ROCKSDB_PATCH); ROCKS_LOG_HEADER(log, "Git sha %s", rocksdb_build_git_sha); ROCKS_LOG_HEADER(log, "Compile date %s", rocksdb_build_compile_date); +#else + (void)log; // ignore "-Wunused-parameter" #endif } @@ -2530,8 +3197,7 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key, bool* is_blob_index) { Status s; MergeContext merge_context; - RangeDelAggregator range_del_agg(sv->mem->GetInternalKeyComparator(), - kMaxSequenceNumber); + SequenceNumber max_covering_tombstone_seq = 0; ReadOptions read_options; SequenceNumber current_seq = versions_->LastSequence(); @@ -2541,8 +3207,8 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key, *found_record_for_key = false; // Check if there is a record for this key in the latest memtable - sv->mem->Get(lkey, nullptr, &s, &merge_context, &range_del_agg, seq, - read_options, is_blob_index); + sv->mem->Get(lkey, nullptr, &s, &merge_context, &max_covering_tombstone_seq, + seq, read_options, nullptr /*read_callback*/, is_blob_index); if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) { // unexpected error reading memtable. @@ -2560,8 +3226,8 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key, } // Check if there is a record for this key in the immutable memtables - sv->imm->Get(lkey, nullptr, &s, &merge_context, &range_del_agg, seq, - read_options, is_blob_index); + sv->imm->Get(lkey, nullptr, &s, &merge_context, &max_covering_tombstone_seq, + seq, read_options, nullptr /*read_callback*/, is_blob_index); if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) { // unexpected error reading memtable. @@ -2579,8 +3245,9 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key, } // Check if there is a record for this key in the immutable memtables - sv->imm->GetFromHistory(lkey, nullptr, &s, &merge_context, &range_del_agg, - seq, read_options, is_blob_index); + sv->imm->GetFromHistory(lkey, nullptr, &s, &merge_context, + &max_covering_tombstone_seq, seq, read_options, + is_blob_index); if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) { // unexpected error reading memtable. @@ -2603,63 +3270,143 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key, if (!cache_only) { // Check tables sv->current->Get(read_options, lkey, nullptr, &s, &merge_context, - &range_del_agg, nullptr /* value_found */, - found_record_for_key, seq, is_blob_index); + &max_covering_tombstone_seq, nullptr /* value_found */, + found_record_for_key, seq, nullptr /*read_callback*/, + is_blob_index); if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) { // unexpected error reading SST files ROCKS_LOG_ERROR(immutable_db_options_.info_log, "Unexpected status returned from Version::Get: %s\n", s.ToString().c_str()); - - return s; } } - return Status::OK(); + return s; } Status DBImpl::IngestExternalFile( ColumnFamilyHandle* column_family, const std::vector& external_files, const IngestExternalFileOptions& ingestion_options) { - Status status; - auto cfh = reinterpret_cast(column_family); - auto cfd = cfh->cfd(); + IngestExternalFileArg arg; + arg.column_family = column_family; + arg.external_files = external_files; + arg.options = ingestion_options; + return IngestExternalFiles({arg}); +} - // Ingest should immediately fail if ingest_behind is requested, - // but the DB doesn't support it. - if (ingestion_options.ingest_behind) { - if (!immutable_db_options_.allow_ingest_behind) { +Status DBImpl::IngestExternalFiles( + const std::vector& args) { + if (args.empty()) { + return Status::InvalidArgument("ingestion arg list is empty"); + } + { + std::unordered_set unique_cfhs; + for (const auto& arg : args) { + if (arg.column_family == nullptr) { + return Status::InvalidArgument("column family handle is null"); + } else if (unique_cfhs.count(arg.column_family) > 0) { + return Status::InvalidArgument( + "ingestion args have duplicate column families"); + } + unique_cfhs.insert(arg.column_family); + } + } + // Ingest multiple external SST files atomically. + size_t num_cfs = args.size(); + for (size_t i = 0; i != num_cfs; ++i) { + if (args[i].external_files.empty()) { + char err_msg[128] = {0}; + snprintf(err_msg, 128, "external_files[%zu] is empty", i); + return Status::InvalidArgument(err_msg); + } + } + for (const auto& arg : args) { + const IngestExternalFileOptions& ingest_opts = arg.options; + if (ingest_opts.ingest_behind && + !immutable_db_options_.allow_ingest_behind) { return Status::InvalidArgument( - "Can't ingest_behind file in DB with allow_ingest_behind=false"); + "can't ingest_behind file in DB with allow_ingest_behind=false"); } } - ExternalSstFileIngestionJob ingestion_job(env_, versions_.get(), cfd, - immutable_db_options_, env_options_, - &snapshots_, ingestion_options); - + // TODO (yanqin) maybe handle the case in which column_families have + // duplicates std::list::iterator pending_output_elem; - { + size_t total = 0; + for (const auto& arg : args) { + total += arg.external_files.size(); + } + uint64_t next_file_number = 0; + Status status = ReserveFileNumbersBeforeIngestion( + static_cast(args[0].column_family)->cfd(), total, + &pending_output_elem, &next_file_number); + if (!status.ok()) { InstrumentedMutexLock l(&mutex_); - if (!bg_error_.ok()) { - // Don't ingest files when there is a bg_error - return bg_error_; - } - - // Make sure that bg cleanup wont delete the files that we are ingesting - pending_output_elem = CaptureCurrentFileNumberInPendingOutputs(); + ReleaseFileNumberFromPendingOutputs(pending_output_elem); + return status; } - status = ingestion_job.Prepare(external_files); + std::vector ingestion_jobs; + for (const auto& arg : args) { + auto* cfd = static_cast(arg.column_family)->cfd(); + ingestion_jobs.emplace_back(env_, versions_.get(), cfd, + immutable_db_options_, env_options_, + &snapshots_, arg.options); + } + std::vector> exec_results; + for (size_t i = 0; i != num_cfs; ++i) { + exec_results.emplace_back(false, Status::OK()); + } + // TODO(yanqin) maybe make jobs run in parallel + for (size_t i = 1; i != num_cfs; ++i) { + uint64_t start_file_number = + next_file_number + args[i - 1].external_files.size(); + auto* cfd = + static_cast(args[i].column_family)->cfd(); + SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_); + exec_results[i].second = ingestion_jobs[i].Prepare( + args[i].external_files, start_file_number, super_version); + exec_results[i].first = true; + CleanupSuperVersion(super_version); + } + TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeLastJobPrepare:0"); + TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1"); + { + auto* cfd = + static_cast(args[0].column_family)->cfd(); + SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_); + exec_results[0].second = ingestion_jobs[0].Prepare( + args[0].external_files, next_file_number, super_version); + exec_results[0].first = true; + CleanupSuperVersion(super_version); + } + for (const auto& exec_result : exec_results) { + if (!exec_result.second.ok()) { + status = exec_result.second; + break; + } + } if (!status.ok()) { + for (size_t i = 0; i != num_cfs; ++i) { + if (exec_results[i].first) { + ingestion_jobs[i].Cleanup(status); + } + } + InstrumentedMutexLock l(&mutex_); + ReleaseFileNumberFromPendingOutputs(pending_output_elem); return status; } + std::vector sv_ctxs; + for (size_t i = 0; i != num_cfs; ++i) { + sv_ctxs.emplace_back(true /* create_superversion */); + } + TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeJobsRun:0"); + TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeJobsRun:1"); TEST_SYNC_POINT("DBImpl::AddFile:Start"); { - // Lock db mutex InstrumentedMutexLock l(&mutex_); TEST_SYNC_POINT("DBImpl::AddFile:MutexLock"); @@ -2667,84 +3414,180 @@ Status DBImpl::IngestExternalFile( WriteThread::Writer w; write_thread_.EnterUnbatched(&w, &mutex_); WriteThread::Writer nonmem_w; - if (concurrent_prepare_) { + if (two_write_queues_) { nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_); } - num_running_ingest_file_++; + num_running_ingest_file_ += static_cast(num_cfs); + TEST_SYNC_POINT("DBImpl::IngestExternalFile:AfterIncIngestFileCounter"); - // We cannot ingest a file into a dropped CF - if (cfd->IsDropped()) { - status = Status::InvalidArgument( - "Cannot ingest an external file into a dropped CF"); + bool at_least_one_cf_need_flush = false; + std::vector need_flush(num_cfs, false); + for (size_t i = 0; i != num_cfs; ++i) { + auto* cfd = + static_cast(args[i].column_family)->cfd(); + if (cfd->IsDropped()) { + // TODO (yanqin) investigate whether we should abort ingestion or + // proceed with other non-dropped column families. + status = Status::InvalidArgument( + "cannot ingest an external file into a dropped CF"); + break; + } + bool tmp = false; + status = ingestion_jobs[i].NeedsFlush(&tmp, cfd->GetSuperVersion()); + need_flush[i] = tmp; + at_least_one_cf_need_flush = (at_least_one_cf_need_flush || tmp); + if (!status.ok()) { + break; + } } + TEST_SYNC_POINT_CALLBACK("DBImpl::IngestExternalFile:NeedFlush", + &at_least_one_cf_need_flush); - // Figure out if we need to flush the memtable first - if (status.ok()) { - bool need_flush = false; - status = ingestion_job.NeedsFlush(&need_flush); - TEST_SYNC_POINT_CALLBACK("DBImpl::IngestExternalFile:NeedFlush", - &need_flush); - if (status.ok() && need_flush) { + if (status.ok() && at_least_one_cf_need_flush) { + FlushOptions flush_opts; + flush_opts.allow_write_stall = true; + if (immutable_db_options_.atomic_flush) { + autovector cfds_to_flush; + SelectColumnFamiliesForAtomicFlush(&cfds_to_flush); mutex_.Unlock(); - status = FlushMemTable(cfd, FlushOptions(), true /* writes_stopped */); + status = AtomicFlushMemTables(cfds_to_flush, flush_opts, + FlushReason::kExternalFileIngestion, + true /* writes_stopped */); mutex_.Lock(); + } else { + for (size_t i = 0; i != num_cfs; ++i) { + if (need_flush[i]) { + mutex_.Unlock(); + auto* cfd = + static_cast(args[i].column_family) + ->cfd(); + status = FlushMemTable(cfd, flush_opts, + FlushReason::kExternalFileIngestion, + true /* writes_stopped */); + mutex_.Lock(); + if (!status.ok()) { + break; + } + } + } } } - - // Run the ingestion job + // Run ingestion jobs. if (status.ok()) { - status = ingestion_job.Run(); + for (size_t i = 0; i != num_cfs; ++i) { + status = ingestion_jobs[i].Run(); + if (!status.ok()) { + break; + } + } } - - // Install job edit [Mutex will be unlocked here] - auto mutable_cf_options = cfd->GetLatestMutableCFOptions(); if (status.ok()) { + bool should_increment_last_seqno = + ingestion_jobs[0].ShouldIncrementLastSequence(); +#ifndef NDEBUG + for (size_t i = 1; i != num_cfs; ++i) { + assert(should_increment_last_seqno == + ingestion_jobs[i].ShouldIncrementLastSequence()); + } +#endif + if (should_increment_last_seqno) { + const SequenceNumber last_seqno = versions_->LastSequence(); + versions_->SetLastAllocatedSequence(last_seqno + 1); + versions_->SetLastPublishedSequence(last_seqno + 1); + versions_->SetLastSequence(last_seqno + 1); + } + autovector cfds_to_commit; + autovector mutable_cf_options_list; + autovector> edit_lists; + uint32_t num_entries = 0; + for (size_t i = 0; i != num_cfs; ++i) { + auto* cfd = + static_cast(args[i].column_family)->cfd(); + if (cfd->IsDropped()) { + continue; + } + cfds_to_commit.push_back(cfd); + mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions()); + autovector edit_list; + edit_list.push_back(ingestion_jobs[i].edit()); + edit_lists.push_back(edit_list); + ++num_entries; + } + // Mark the version edits as an atomic group if the number of version + // edits exceeds 1. + if (cfds_to_commit.size() > 1) { + for (auto& edits : edit_lists) { + assert(edits.size() == 1); + edits[0]->MarkAtomicGroup(--num_entries); + } + assert(0 == num_entries); + } status = - versions_->LogAndApply(cfd, *mutable_cf_options, ingestion_job.edit(), - &mutex_, directories_.GetDbDir()); + versions_->LogAndApply(cfds_to_commit, mutable_cf_options_list, + edit_lists, &mutex_, directories_.GetDbDir()); } + if (status.ok()) { - delete InstallSuperVersionAndScheduleWork(cfd, nullptr, - *mutable_cf_options); + for (size_t i = 0; i != num_cfs; ++i) { + auto* cfd = + static_cast(args[i].column_family)->cfd(); + if (!cfd->IsDropped()) { + InstallSuperVersionAndScheduleWork(cfd, &sv_ctxs[i], + *cfd->GetLatestMutableCFOptions()); +#ifndef NDEBUG + if (0 == i && num_cfs > 1) { + TEST_SYNC_POINT( + "DBImpl::IngestExternalFiles:InstallSVForFirstCF:0"); + TEST_SYNC_POINT( + "DBImpl::IngestExternalFiles:InstallSVForFirstCF:1"); + } +#endif // !NDEBUG + } + } } // Resume writes to the DB - if (concurrent_prepare_) { + if (two_write_queues_) { nonmem_write_thread_.ExitUnbatched(&nonmem_w); } write_thread_.ExitUnbatched(&w); - // Update stats if (status.ok()) { - ingestion_job.UpdateStats(); + for (auto& job : ingestion_jobs) { + job.UpdateStats(); + } } - ReleaseFileNumberFromPendingOutputs(pending_output_elem); - - num_running_ingest_file_--; - if (num_running_ingest_file_ == 0) { + num_running_ingest_file_ -= static_cast(num_cfs); + if (0 == num_running_ingest_file_) { bg_cv_.SignalAll(); } - TEST_SYNC_POINT("DBImpl::AddFile:MutexUnlock"); } // mutex_ is unlocked here // Cleanup - ingestion_job.Cleanup(status); - + for (size_t i = 0; i != num_cfs; ++i) { + sv_ctxs[i].Clean(); + // This may rollback jobs that have completed successfully. This is + // intended for atomicity. + ingestion_jobs[i].Cleanup(status); + } if (status.ok()) { - NotifyOnExternalFileIngested(cfd, ingestion_job); + for (size_t i = 0; i != num_cfs; ++i) { + auto* cfd = + static_cast(args[i].column_family)->cfd(); + if (!cfd->IsDropped()) { + NotifyOnExternalFileIngested(cfd, ingestion_jobs[i]); + } + } } - return status; } Status DBImpl::VerifyChecksum() { Status s; - Options options; - EnvOptions env_options; std::vector cfd_list; { InstrumentedMutexLock l(&mutex_); @@ -2761,13 +3604,20 @@ Status DBImpl::VerifyChecksum() { } for (auto& sv : sv_list) { VersionStorageInfo* vstorage = sv->current->storage_info(); + ColumnFamilyData* cfd = sv->current->cfd(); + Options opts; + { + InstrumentedMutexLock l(&mutex_); + opts = Options(BuildDBOptions(immutable_db_options_, mutable_db_options_), + cfd->GetLatestCFOptions()); + } for (int i = 0; i < vstorage->num_non_empty_levels() && s.ok(); i++) { for (size_t j = 0; j < vstorage->LevelFilesBrief(i).num_files && s.ok(); j++) { const auto& fd = vstorage->LevelFilesBrief(i).files[j].fd; - std::string fname = TableFileName(immutable_db_options_.db_paths, + std::string fname = TableFileName(cfd->ioptions()->cf_paths, fd.GetNumber(), fd.GetPathId()); - s = rocksdb::VerifySstFileChecksum(options, env_options, fname); + s = rocksdb::VerifySstFileChecksum(opts, env_options_, fname); } } if (!s.ok()) { @@ -2783,7 +3633,7 @@ Status DBImpl::VerifyChecksum() { } } for (auto cfd : cfd_list) { - cfd->Unref(); + cfd->Unref(); } } return s; @@ -2791,7 +3641,6 @@ Status DBImpl::VerifyChecksum() { void DBImpl::NotifyOnExternalFileIngested( ColumnFamilyData* cfd, const ExternalSstFileIngestionJob& ingestion_job) { -#ifndef ROCKSDB_LITE if (immutable_db_options_.listeners.empty()) { return; } @@ -2807,8 +3656,6 @@ void DBImpl::NotifyOnExternalFileIngested( listener->OnExternalFileIngested(this, info); } } - -#endif } void DBImpl::WaitForIngestFile() { @@ -2818,6 +3665,77 @@ void DBImpl::WaitForIngestFile() { } } +Status DBImpl::StartTrace(const TraceOptions& trace_options, + std::unique_ptr&& trace_writer) { + InstrumentedMutexLock lock(&trace_mutex_); + tracer_.reset(new Tracer(env_, trace_options, std::move(trace_writer))); + return Status::OK(); +} + +Status DBImpl::EndTrace() { + InstrumentedMutexLock lock(&trace_mutex_); + Status s; + if (tracer_ != nullptr) { + s = tracer_->Close(); + tracer_.reset(); + } else { + return Status::IOError("No trace file to close"); + } + return s; +} + +Status DBImpl::TraceIteratorSeek(const uint32_t& cf_id, const Slice& key) { + Status s; + if (tracer_) { + InstrumentedMutexLock lock(&trace_mutex_); + if (tracer_) { + s = tracer_->IteratorSeek(cf_id, key); + } + } + return s; +} + +Status DBImpl::TraceIteratorSeekForPrev(const uint32_t& cf_id, + const Slice& key) { + Status s; + if (tracer_) { + InstrumentedMutexLock lock(&trace_mutex_); + if (tracer_) { + s = tracer_->IteratorSeekForPrev(cf_id, key); + } + } + return s; +} + +Status DBImpl::ReserveFileNumbersBeforeIngestion( + ColumnFamilyData* cfd, uint64_t num, + std::list::iterator* pending_output_elem, + uint64_t* next_file_number) { + Status s; + SuperVersionContext dummy_sv_ctx(true /* create_superversion */); + assert(nullptr != pending_output_elem); + assert(nullptr != next_file_number); + InstrumentedMutexLock l(&mutex_); + if (error_handler_.IsDBStopped()) { + // Do not ingest files when there is a bg_error + return error_handler_.GetBGError(); + } + *pending_output_elem = CaptureCurrentFileNumberInPendingOutputs(); + *next_file_number = versions_->FetchAddFileNumber(static_cast(num)); + auto cf_options = cfd->GetLatestMutableCFOptions(); + VersionEdit dummy_edit; + // If crash happen after a hard link established, Recover function may + // reuse the file number that has already assigned to the internal file, + // and this will overwrite the external file. To protect the external + // file, we have to make sure the file number will never being reused. + s = versions_->LogAndApply(cfd, *cf_options, &dummy_edit, &mutex_, + directories_.GetDbDir()); + if (s.ok()) { + InstallSuperVersionAndScheduleWork(cfd, &dummy_sv_ctx, *cf_options); + } + dummy_sv_ctx.Clean(); + return s; +} #endif // ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_impl.h b/thirdparty/rocksdb/db/db_impl.h index f1730f9adb..e834e0fbec 100644 --- a/thirdparty/rocksdb/db/db_impl.h +++ b/thirdparty/rocksdb/db/db_impl.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -23,11 +22,18 @@ #include "db/column_family.h" #include "db/compaction_job.h" #include "db/dbformat.h" +#include "db/error_handler.h" +#include "db/event_helpers.h" #include "db/external_sst_file_ingestion_job.h" #include "db/flush_job.h" #include "db/flush_scheduler.h" #include "db/internal_stats.h" #include "db/log_writer.h" +#include "db/logs_with_prep_tracker.h" +#include "db/pre_release_callback.h" +#include "db/range_del_aggregator.h" +#include "db/read_callback.h" +#include "db/snapshot_checker.h" #include "db/snapshot_impl.h" #include "db/version_edit.h" #include "db/wal_manager.h" @@ -41,24 +47,29 @@ #include "rocksdb/env.h" #include "rocksdb/memtablerep.h" #include "rocksdb/status.h" +#include "rocksdb/trace_reader_writer.h" #include "rocksdb/transaction_log.h" #include "rocksdb/write_buffer_manager.h" #include "table/scoped_arena_iterator.h" #include "util/autovector.h" #include "util/event_logger.h" #include "util/hash.h" +#include "util/repeatable_thread.h" #include "util/stop_watch.h" #include "util/thread_local.h" +#include "util/trace_replay.h" namespace rocksdb { +class Arena; class ArenaWrappedDBIter; +class InMemoryStatsHistoryIterator; class MemTable; class TableCache; +class TaskLimiterToken; class Version; class VersionEdit; class VersionSet; -class Arena; class WriteCallback; struct JobContext; struct ExternalSstFileInfo; @@ -66,9 +77,13 @@ struct MemTableInfo; class DBImpl : public DB { public: - DBImpl(const DBOptions& options, const std::string& dbname); + DBImpl(const DBOptions& options, const std::string& dbname, + const bool seq_per_batch = false, const bool batch_per_txn = true); virtual ~DBImpl(); + using DB::Resume; + virtual Status Resume() override; + // Implementations of the DB interface using DB::Put; virtual Status Put(const WriteOptions& options, @@ -99,7 +114,8 @@ class DBImpl : public DB { // Note: 'value_found' from KeyMayExist propagates here Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family, const Slice& key, PinnableSlice* value, - bool* value_found = nullptr, bool* is_blob_index = nullptr); + bool* value_found = nullptr, ReadCallback* callback = nullptr, + bool* is_blob_index = nullptr); using DB::MultiGet; virtual std::vector MultiGet( @@ -142,7 +158,9 @@ class DBImpl : public DB { ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& options, ColumnFamilyData* cfd, SequenceNumber snapshot, - bool allow_blob = false); + ReadCallback* read_callback, + bool allow_blob = false, + bool allow_refresh = true); virtual const Snapshot* GetSnapshot() override; virtual void ReleaseSnapshot(const Snapshot* snapshot) override; @@ -150,9 +168,9 @@ class DBImpl : public DB { virtual bool GetProperty(ColumnFamilyHandle* column_family, const Slice& property, std::string* value) override; using DB::GetMapProperty; - virtual bool GetMapProperty(ColumnFamilyHandle* column_family, - const Slice& property, - std::map* value) override; + virtual bool GetMapProperty( + ColumnFamilyHandle* column_family, const Slice& property, + std::map* value) override; using DB::GetIntProperty; virtual bool GetIntProperty(ColumnFamilyHandle* column_family, const Slice& property, uint64_t* value) override; @@ -160,10 +178,9 @@ class DBImpl : public DB { virtual bool GetAggregatedIntProperty(const Slice& property, uint64_t* aggregated_value) override; using DB::GetApproximateSizes; - virtual void GetApproximateSizes(ColumnFamilyHandle* column_family, - const Range* range, int n, uint64_t* sizes, - uint8_t include_flags - = INCLUDE_FILES) override; + virtual void GetApproximateSizes( + ColumnFamilyHandle* column_family, const Range* range, int n, + uint64_t* sizes, uint8_t include_flags = INCLUDE_FILES) override; using DB::GetApproximateMemTableStats; virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family, const Range& range, @@ -175,11 +192,13 @@ class DBImpl : public DB { const Slice* begin, const Slice* end) override; using DB::CompactFiles; - virtual Status CompactFiles(const CompactionOptions& compact_options, - ColumnFamilyHandle* column_family, - const std::vector& input_file_names, - const int output_level, - const int output_path_id = -1) override; + virtual Status CompactFiles( + const CompactionOptions& compact_options, + ColumnFamilyHandle* column_family, + const std::vector& input_file_names, const int output_level, + const int output_path_id = -1, + std::vector* const output_file_names = nullptr, + CompactionJobInfo* compaction_job_info = nullptr) override; virtual Status PauseBackgroundWork() override; virtual Status ContinueBackgroundWork() override; @@ -211,14 +230,32 @@ class DBImpl : public DB { using DB::Flush; virtual Status Flush(const FlushOptions& options, ColumnFamilyHandle* column_family) override; + virtual Status Flush( + const FlushOptions& options, + const std::vector& column_families) override; virtual Status FlushWAL(bool sync) override; + bool TEST_WALBufferIsEmpty(bool lock = true); virtual Status SyncWAL() override; + virtual Status LockWAL() override; + virtual Status UnlockWAL() override; virtual SequenceNumber GetLatestSequenceNumber() const override; + virtual SequenceNumber GetLastPublishedSequence() const { + if (last_seq_same_as_publish_seq_) { + return versions_->LastSequence(); + } else { + return versions_->LastPublishedSequence(); + } + } + // REQUIRES: joined the main write queue if two_write_queues is disabled, and + // the second write queue otherwise. + virtual void SetLastPublishedSequence(SequenceNumber seq); + // Returns LastSequence in last_seq_same_as_publish_seq_ + // mode and LastAllocatedSequence otherwise. This is useful when visiblility + // depends also on data written to the WAL but not to the memtable. + SequenceNumber TEST_GetLastVisibleSequence() const; - // Whether there is an active snapshot in range [lower_bound, upper_bound). - bool HasActiveSnapshotInRange(SequenceNumber lower_bound, - SequenceNumber upper_bound); + virtual bool SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) override; #ifndef ROCKSDB_LITE using DB::ResetStats; @@ -233,12 +270,13 @@ class DBImpl : public DB { virtual Status GetSortedWalFiles(VectorLogPtr& files) override; virtual Status GetUpdatesSince( - SequenceNumber seq_number, unique_ptr* iter, - const TransactionLogIterator::ReadOptions& - read_options = TransactionLogIterator::ReadOptions()) override; + SequenceNumber seq_number, std::unique_ptr* iter, + const TransactionLogIterator::ReadOptions& read_options = + TransactionLogIterator::ReadOptions()) override; virtual Status DeleteFile(std::string name) override; - Status DeleteFilesInRange(ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end); + Status DeleteFilesInRanges(ColumnFamilyHandle* column_family, + const RangePtr* ranges, size_t n, + bool include_end = true); virtual void GetLiveFilesMetaData( std::vector* metadata) override; @@ -247,9 +285,8 @@ class DBImpl : public DB { // Status::NotFound() will be returned if the current DB does not have // any column family match the specified name. // TODO(yhchiang): output parameter is placed in the end in this codebase. - virtual void GetColumnFamilyMetaData( - ColumnFamilyHandle* column_family, - ColumnFamilyMetaData* metadata) override; + virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family, + ColumnFamilyMetaData* metadata) override; Status SuggestCompactRange(ColumnFamilyHandle* column_family, const Slice* begin, const Slice* end) override; @@ -310,8 +347,21 @@ class DBImpl : public DB { const std::vector& external_files, const IngestExternalFileOptions& ingestion_options) override; + using DB::IngestExternalFiles; + virtual Status IngestExternalFiles( + const std::vector& args) override; + virtual Status VerifyChecksum() override; + using DB::StartTrace; + virtual Status StartTrace( + const TraceOptions& options, + std::unique_ptr&& trace_writer) override; + + using DB::EndTrace; + virtual Status EndTrace() override; + Status TraceIteratorSeek(const uint32_t& cf_id, const Slice& key); + Status TraceIteratorSeekForPrev(const uint32_t& cf_id, const Slice& key); #endif // ROCKSDB_LITE // Similar to GetSnapshot(), but also lets the db know that this snapshot @@ -328,17 +378,21 @@ class DBImpl : public DB { Status RunManualCompaction(ColumnFamilyData* cfd, int input_level, int output_level, uint32_t output_path_id, - const Slice* begin, const Slice* end, - bool exclusive, + uint32_t max_subcompactions, const Slice* begin, + const Slice* end, bool exclusive, bool disallow_trivial_move = false); // Return an internal iterator over the current state of the database. // The keys of this iterator are internal keys (see format.h). // The returned iterator should be deleted when no longer needed. InternalIterator* NewInternalIterator( - Arena* arena, RangeDelAggregator* range_del_agg, + Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence, ColumnFamilyHandle* column_family = nullptr); + LogsWithPrepTracker* logs_with_prep_tracker() { + return &logs_with_prep_tracker_; + } + #ifndef NDEBUG // Extra methods (for testing) that are not in the public DB interface // Implemented in db_impl_debug.cc @@ -348,11 +402,9 @@ class DBImpl : public DB { ColumnFamilyHandle* column_family = nullptr, bool disallow_trivial_move = false); - void TEST_HandleWALFull(); + void TEST_SwitchWAL(); - bool TEST_UnableToFlushOldestLog() { - return unable_to_flush_oldest_log_; - } + bool TEST_UnableToReleaseOldestLog() { return unable_to_release_oldest_log_; } bool TEST_IsLogGettingFlushed() { return alive_log_files_.begin()->getting_flushed; @@ -361,23 +413,28 @@ class DBImpl : public DB { Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr); // Force current memtable contents to be flushed. - Status TEST_FlushMemTable(bool wait = true, + Status TEST_FlushMemTable(bool wait = true, bool allow_write_stall = false, ColumnFamilyHandle* cfh = nullptr); // Wait for memtable compaction Status TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family = nullptr); // Wait for any compaction - Status TEST_WaitForCompact(); + // We add a bool parameter to wait for unscheduledCompactions_ == 0, but this + // is only for the special test of CancelledCompactions + Status TEST_WaitForCompact(bool waitUnscheduled = false); // Return the maximum overlapping data (in bytes) at next level for any // file at a level >= 1. - int64_t TEST_MaxNextLevelOverlappingBytes(ColumnFamilyHandle* column_family = - nullptr); + int64_t TEST_MaxNextLevelOverlappingBytes( + ColumnFamilyHandle* column_family = nullptr); // Return the current manifest file no. uint64_t TEST_Current_Manifest_FileNo(); + // Returns the number that'll be assigned to the next file that's created. + uint64_t TEST_Current_Next_FileNo(); + // get total level0 file size. Only for testing. uint64_t TEST_GetLevel0TotalSize(); @@ -419,9 +476,16 @@ class DBImpl : public DB { uint64_t TEST_FindMinLogContainingOutstandingPrep(); uint64_t TEST_FindMinPrepLogReferencedByMemTable(); + size_t TEST_PreparedSectionCompletedSize(); + size_t TEST_LogsWithPrepSize(); int TEST_BGCompactionsAllowed() const; int TEST_BGFlushesAllowed() const; + size_t TEST_GetWalPreallocateBlockSize(uint64_t write_buffer_size) const; + void TEST_WaitForDumpStatsRun(std::function callback) const; + void TEST_WaitForPersistStatsRun(std::function callback) const; + bool TEST_IsPersistentStatsEnabled() const; + size_t TEST_EstiamteStatsHistorySize() const; #endif // NDEBUG @@ -443,6 +507,14 @@ class DBImpl : public DB { uint64_t MinLogNumberToKeep(); + // Returns the lower bound file number for SSTs that won't be deleted, even if + // they're obsolete. This lower bound is used internally to prevent newly + // created flush/compaction output files from being deleted before they're + // installed. This technique avoids the need for tracking the exact numbers of + // files pending creation, although it prevents more files than necessary from + // being deleted. + uint64_t MinObsoleteSstNumberToKeep(); + // Returns the list of live files in 'live' and the list // of all files in the filesystem in 'candidate_files'. // If force == false and the last call was less than @@ -452,10 +524,12 @@ class DBImpl : public DB { bool no_full_scan = false); // Diffs the files listed in filenames and those that do not - // belong to live files are posibly removed. Also, removes all the + // belong to live files are possibly removed. Also, removes all the // files in sst_delete_files and log_delete_files. // It is not necessary to hold the mutex when invoking this method. - void PurgeObsoleteFiles(const JobContext& background_contet, + // If FindObsoleteFiles() was run, we need to also run + // PurgeObsoleteFiles(), even if disable_delete_obsolete_files_ is true + void PurgeObsoleteFiles(JobContext& background_contet, bool schedule_only = false); void SchedulePurge(); @@ -481,6 +555,9 @@ class DBImpl : public DB { // mutex is held. SuperVersion* GetAndRefSuperVersion(uint32_t column_family_id); + // Un-reference the super version and clean it up if it is the last reference. + void CleanupSuperVersion(SuperVersion* sv); + // Un-reference the super version and return it to thread local cache if // needed. If it is the last reference of the super version. Clean it up // after un-referencing it. @@ -497,7 +574,8 @@ class DBImpl : public DB { ColumnFamilyHandle* GetColumnFamilyHandle(uint32_t column_family_id); // Same as above, should called without mutex held and not on write thread. - ColumnFamilyHandle* GetColumnFamilyHandleUnlocked(uint32_t column_family_id); + std::unique_ptr GetColumnFamilyHandleUnlocked( + uint32_t column_family_id); // Returns the number of currently running flushes. // REQUIREMENT: mutex_ must be held when calling this function. @@ -515,24 +593,58 @@ class DBImpl : public DB { const WriteController& write_controller() { return write_controller_; } - InternalIterator* NewInternalIterator(const ReadOptions&, - ColumnFamilyData* cfd, - SuperVersion* super_version, - Arena* arena, - RangeDelAggregator* range_del_agg); + InternalIterator* NewInternalIterator( + const ReadOptions&, ColumnFamilyData* cfd, SuperVersion* super_version, + Arena* arena, RangeDelAggregator* range_del_agg, SequenceNumber sequence); // hollow transactions shell used for recovery. // these will then be passed to TransactionDB so that // locks can be reacquired before writing can resume. struct RecoveredTransaction { - uint64_t log_number_; std::string name_; - WriteBatch* batch_; + bool unprepared_; + + struct BatchInfo { + uint64_t log_number_; + // TODO(lth): For unprepared, the memory usage here can be big for + // unprepared transactions. This is only useful for rollbacks, and we + // can in theory just keep keyset for that. + WriteBatch* batch_; + // Number of sub-batches. A new sub-batch is created if txn attempts to + // insert a duplicate key,seq to memtable. This is currently used in + // WritePreparedTxn/WriteUnpreparedTxn. + size_t batch_cnt_; + }; + + // This maps the seq of the first key in the batch to BatchInfo, which + // contains WriteBatch and other information relevant to the batch. + // + // For WriteUnprepared, batches_ can have size greater than 1, but for + // other write policies, it must be of size 1. + std::map batches_; + explicit RecoveredTransaction(const uint64_t log, const std::string& name, - WriteBatch* batch) - : log_number_(log), name_(name), batch_(batch) {} + WriteBatch* batch, SequenceNumber seq, + size_t batch_cnt, bool unprepared) + : name_(name), unprepared_(unprepared) { + batches_[seq] = {log, batch, batch_cnt}; + } - ~RecoveredTransaction() { delete batch_; } + ~RecoveredTransaction() { + for (auto& it : batches_) { + delete it.second.batch_; + } + } + + void AddBatch(SequenceNumber seq, uint64_t log_number, WriteBatch* batch, + size_t batch_cnt, bool unprepared) { + assert(batches_.count(seq) == 0); + batches_[seq] = {log_number, batch, batch_cnt}; + // Prior state must be unprepared, since the prepare batch must be the + // last batch. + assert(unprepared_); + unprepared_ = unprepared; + } }; bool allow_2pc() const { return immutable_db_options_.allow_2pc; } @@ -552,9 +664,21 @@ class DBImpl : public DB { } void InsertRecoveredTransaction(const uint64_t log, const std::string& name, - WriteBatch* batch) { - recovered_transactions_[name] = new RecoveredTransaction(log, name, batch); - MarkLogAsContainingPrepSection(log); + WriteBatch* batch, SequenceNumber seq, + size_t batch_cnt, bool unprepared_batch) { + // For WriteUnpreparedTxn, InsertRecoveredTransaction is called multiple + // times for every unprepared batch encountered during recovery. + // + // If the transaction is prepared, then the last call to + // InsertRecoveredTransaction will have unprepared_batch = false. + auto rtxn = recovered_transactions_.find(name); + if (rtxn == recovered_transactions_.end()) { + recovered_transactions_[name] = new RecoveredTransaction( + log, name, batch, seq, batch_cnt, unprepared_batch); + } else { + rtxn->second->AddBatch(seq, log, batch, batch_cnt, unprepared_batch); + } + logs_with_prep_tracker_.MarkLogAsContainingPrepSection(log); } void DeleteRecoveredTransaction(const std::string& name) { @@ -562,7 +686,10 @@ class DBImpl : public DB { assert(it != recovered_transactions_.end()); auto* trx = it->second; recovered_transactions_.erase(it); - MarkLogAsHavingPrepSectionFlushed(trx->log_number_); + for (const auto& info : trx->batches_) { + logs_with_prep_tracker_.MarkLogAsHavingPrepSectionFlushed( + info.second.log_number_); + } delete trx; } @@ -574,25 +701,84 @@ class DBImpl : public DB { recovered_transactions_.clear(); } - void MarkLogAsHavingPrepSectionFlushed(uint64_t log); - void MarkLogAsContainingPrepSection(uint64_t log); void AddToLogsToFreeQueue(log::Writer* log_writer) { logs_to_free_queue_.push_back(log_writer); } + + void SetSnapshotChecker(SnapshotChecker* snapshot_checker); + + // Fill JobContext with snapshot information needed by flush and compaction. + void GetSnapshotContext(JobContext* job_context, + std::vector* snapshot_seqs, + SequenceNumber* earliest_write_conflict_snapshot, + SnapshotChecker** snapshot_checker); + + // Not thread-safe. + void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback); + InstrumentedMutex* mutex() { return &mutex_; } Status NewDB(); + // This is to be used only by internal rocksdb classes. + static Status Open(const DBOptions& db_options, const std::string& name, + const std::vector& column_families, + std::vector* handles, DB** dbptr, + const bool seq_per_batch, const bool batch_per_txn); + + virtual Status Close() override; + + static Status CreateAndNewDirectory(Env* env, const std::string& dirname, + std::unique_ptr* directory); + + // Given a time window, return an iterator for accessing stats history + Status GetStatsHistory( + uint64_t start_time, uint64_t end_time, + std::unique_ptr* stats_iterator) override; + + // find stats map from stats_history_ with smallest timestamp in + // the range of [start_time, end_time) + bool FindStatsByTime(uint64_t start_time, uint64_t end_time, + uint64_t* new_time, + std::map* stats_map); + protected: Env* const env_; const std::string dbname_; - unique_ptr versions_; + std::unique_ptr versions_; + // Flag to check whether we allocated and own the info log file + bool own_info_log_; const DBOptions initial_db_options_; const ImmutableDBOptions immutable_db_options_; MutableDBOptions mutable_db_options_; Statistics* stats_; std::unordered_map recovered_transactions_; + std::unique_ptr tracer_; + InstrumentedMutex trace_mutex_; + + // State below is protected by mutex_ + // With two_write_queues enabled, some of the variables that accessed during + // WriteToWAL need different synchronization: log_empty_, alive_log_files_, + // logs_, logfile_number_. Refer to the definition of each variable below for + // more description. + mutable InstrumentedMutex mutex_; + + ColumnFamilyHandleImpl* default_cf_handle_; + InternalStats* default_cf_internal_stats_; + + // only used for dynamically adjusting max_total_wal_size. it is a sum of + // [write_buffer_size * max_write_buffer_number] over all column families + uint64_t max_total_in_memory_state_; + // If true, we have only one (default) column family. We use this to optimize + // some code-paths + bool single_column_family_mode_; + + // The options to access storage files + const EnvOptions env_options_; + + // Additonal options for compaction and flush + EnvOptions env_options_for_compaction_; // Except in DB::Open(), WriteOptionsFile can only be called when: // Persist options to options file. @@ -614,8 +800,12 @@ class DBImpl : public DB { const MutableCFOptions& mutable_cf_options, int job_id, TableProperties prop); - void NotifyOnCompactionCompleted(ColumnFamilyData* cfd, - Compaction *c, const Status &st, + void NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c, + const Status& st, + const CompactionJobStats& job_stats, int job_id); + + void NotifyOnCompactionCompleted(ColumnFamilyData* cfd, Compaction* c, + const Status& st, const CompactionJobStats& job_stats, int job_id); void NotifyOnMemTableSealed(ColumnFamilyData* cfd, @@ -632,10 +822,27 @@ class DBImpl : public DB { void EraseThreadStatusDbInfo() const; + // If disable_memtable is set the application logic must guarantee that the + // batch will still be skipped from memtable during the recovery. An excption + // to this is seq_per_batch_ mode, in which since each batch already takes one + // seq, it is ok for the batch to write to memtable during recovery as long as + // it only takes one sequence number: i.e., no duplicate keys. + // In WriteCommitted it is guarnateed since disable_memtable is used for + // prepare batch which will be written to memtable later during the commit, + // and in WritePrepared it is guaranteed since it will be used only for WAL + // markers which will never be written to memtable. If the commit marker is + // accompanied with CommitTimeWriteBatch that is not written to memtable as + // long as it has no duplicate keys, it does not violate the one-seq-per-batch + // policy. + // batch_cnt is expected to be non-zero in seq_per_batch mode and + // indicates the number of sub-patches. A sub-patch is a subset of the write + // batch that does not have duplicate keys. Status WriteImpl(const WriteOptions& options, WriteBatch* updates, WriteCallback* callback = nullptr, uint64_t* log_used = nullptr, uint64_t log_ref = 0, - bool disable_memtable = false, uint64_t* seq_used = nullptr); + bool disable_memtable = false, uint64_t* seq_used = nullptr, + size_t batch_cnt = 0, + PreReleaseCallback* pre_release_callback = nullptr); Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates, WriteCallback* callback = nullptr, @@ -643,40 +850,69 @@ class DBImpl : public DB { bool disable_memtable = false, uint64_t* seq_used = nullptr); + // batch_cnt is expected to be non-zero in seq_per_batch mode and indicates + // the number of sub-patches. A sub-patch is a subset of the write batch that + // does not have duplicate keys. Status WriteImplWALOnly(const WriteOptions& options, WriteBatch* updates, WriteCallback* callback = nullptr, uint64_t* log_used = nullptr, uint64_t log_ref = 0, - uint64_t* seq_used = nullptr); + uint64_t* seq_used = nullptr, size_t batch_cnt = 0, + PreReleaseCallback* pre_release_callback = nullptr); - uint64_t FindMinLogContainingOutstandingPrep(); - uint64_t FindMinPrepLogReferencedByMemTable(); + // write cached_recoverable_state_ to memtable if it is not empty + // The writer must be the leader in write_thread_ and holding mutex_ + Status WriteRecoverableState(); + + // Actual implementation of Close() + Status CloseImpl(); + + // Recover the descriptor from persistent storage. May do a significant + // amount of work to recover recently logged updates. Any changes to + // be made to the descriptor are added to *edit. + virtual Status Recover( + const std::vector& column_families, + bool read_only = false, bool error_if_log_file_exist = false, + bool error_if_data_exists_in_logs = false); private: friend class DB; + friend class ErrorHandler; friend class InternalStats; friend class PessimisticTransaction; + friend class TransactionBaseImpl; friend class WriteCommittedTxn; friend class WritePreparedTxn; + friend class WritePreparedTxnDB; + friend class WriteBatchWithIndex; + friend class WriteUnpreparedTxnDB; + friend class WriteUnpreparedTxn; + #ifndef ROCKSDB_LITE friend class ForwardIterator; #endif friend struct SuperVersion; friend class CompactedDBImpl; + friend class DBTest_ConcurrentFlushWAL_Test; + friend class DBTest_MixedSlowdownOptionsStop_Test; + friend class DBCompactionTest_CompactBottomLevelFilesWithDeletions_Test; #ifndef NDEBUG friend class DBTest2_ReadCallbackTest_Test; + friend class WriteCallbackTest_WriteWithCallbackTest_Test; friend class XFTransactionWriteHandler; friend class DBBlobIndexTest; + friend class WriteUnpreparedTransactionTest_RecoveryTest_Test; #endif struct CompactionState; struct WriteContext { - autovector superversions_to_free_; + SuperVersionContext superversion_context; autovector memtables_to_free_; + explicit WriteContext(bool create_superversion = false) + : superversion_context(create_superversion) {} + ~WriteContext() { - for (auto& sv : superversions_to_free_) { - delete sv; - } + superversion_context.Clean(); for (auto& m : memtables_to_free_) { delete m; } @@ -686,12 +922,7 @@ class DBImpl : public DB { struct PrepickedCompaction; struct PurgeFileInfo; - // Recover the descriptor from persistent storage. May do a significant - // amount of work to recover recently logged updates. Any changes to - // be made to the descriptor are added to *edit. - Status Recover(const std::vector& column_families, - bool read_only = false, bool error_if_log_file_exist = false, - bool error_if_data_exists_in_logs = false); + Status ResumeImpl(); void MaybeIgnoreError(Status* s) const; @@ -706,9 +937,9 @@ class DBImpl : public DB { // Delete any unneeded files and stale in-memory entries. void DeleteObsoleteFiles(); // Delete obsolete files and log status and information of file deletion - void DeleteObsoleteFileImpl(Status file_deletion_status, int job_id, - const std::string& fname, FileType type, - uint64_t number, uint32_t path_id); + void DeleteObsoleteFileImpl(int job_id, const std::string& fname, + const std::string& path_to_sync, FileType type, + uint64_t number); // Background process needs to call // auto x = CaptureCurrentFileNumberInPendingOutputs() @@ -732,11 +963,54 @@ class DBImpl : public DB { Status SyncClosedLogs(JobContext* job_context); // Flush the in-memory write buffer to storage. Switches to a new - // log-file/memtable and writes a new descriptor iff successful. - Status FlushMemTableToOutputFile(ColumnFamilyData* cfd, - const MutableCFOptions& mutable_cf_options, - bool* madeProgress, JobContext* job_context, - LogBuffer* log_buffer); + // log-file/memtable and writes a new descriptor iff successful. Then + // installs a new super version for the column family. + Status FlushMemTableToOutputFile( + ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options, + bool* madeProgress, JobContext* job_context, + SuperVersionContext* superversion_context, + std::vector& snapshot_seqs, + SequenceNumber earliest_write_conflict_snapshot, + SnapshotChecker* snapshot_checker, LogBuffer* log_buffer, + Env::Priority thread_pri); + + // Argument required by background flush thread. + struct BGFlushArg { + BGFlushArg() + : cfd_(nullptr), max_memtable_id_(0), superversion_context_(nullptr) {} + BGFlushArg(ColumnFamilyData* cfd, uint64_t max_memtable_id, + SuperVersionContext* superversion_context) + : cfd_(cfd), + max_memtable_id_(max_memtable_id), + superversion_context_(superversion_context) {} + + // Column family to flush. + ColumnFamilyData* cfd_; + // Maximum ID of memtable to flush. In this column family, memtables with + // IDs smaller than this value must be flushed before this flush completes. + uint64_t max_memtable_id_; + // Pointer to a SuperVersionContext object. After flush completes, RocksDB + // installs a new superversion for the column family. This operation + // requires a SuperVersionContext object (currently embedded in JobContext). + SuperVersionContext* superversion_context_; + }; + + // Argument passed to flush thread. + struct FlushThreadArg { + DBImpl* db_; + + Env::Priority thread_pri_; + }; + + // Flush the memtables of (multiple) column families to multiple files on + // persistent storage. + Status FlushMemTablesToOutputFiles( + const autovector& bg_flush_args, bool* made_progress, + JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri); + + Status AtomicFlushMemTablesToOutputFiles( + const autovector& bg_flush_args, bool* made_progress, + JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri); // REQUIRES: log_numbers are sorted in ascending order Status RecoverLogFiles(const std::vector& log_numbers, @@ -750,6 +1024,12 @@ class DBImpl : public DB { Status WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd, MemTable* mem, VersionEdit* edit); + // Restore alive_log_files_ and total_log_size_ after recovery. + // It needs to run only when there's no flush during recovery + // (e.g. avoid_flush_during_recovery=true). May also trigger flush + // in case total_log_size > max_total_wal_size. + Status RestoreAliveLogFiles(const std::vector& log_numbers); + // num_bytes: for slowdown case, delay time is calculated based on // `num_bytes` going through. Status DelayWrite(uint64_t num_bytes, const WriteOptions& write_options); @@ -761,15 +1041,44 @@ class DBImpl : public DB { Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context); + void SelectColumnFamiliesForAtomicFlush(autovector* cfds); + // Force current memtable contents to be flushed. Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options, - bool writes_stopped = false); + FlushReason flush_reason, bool writes_stopped = false); + + Status AtomicFlushMemTables( + const autovector& column_family_datas, + const FlushOptions& options, FlushReason flush_reason, + bool writes_stopped = false); + + // Wait until flushing this column family won't stall writes + Status WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd, + bool* flush_needed); + + // Wait for memtable flushed. + // If flush_memtable_id is non-null, wait until the memtable with the ID + // gets flush. Otherwise, wait until the column family don't have any + // memtable pending flush. + // resuming_from_bg_err indicates whether the caller is attempting to resume + // from background error. + Status WaitForFlushMemTable(ColumnFamilyData* cfd, + const uint64_t* flush_memtable_id = nullptr, + bool resuming_from_bg_err = false) { + return WaitForFlushMemTables({cfd}, {flush_memtable_id}, + resuming_from_bg_err); + } + // Wait for memtables to be flushed for multiple column families. + Status WaitForFlushMemTables( + const autovector& cfds, + const autovector& flush_memtable_ids, + bool resuming_from_bg_err); - // Wait for memtable flushed - Status WaitForFlushMemTable(ColumnFamilyData* cfd); + // REQUIRES: mutex locked and in write thread. + void AssignAtomicFlushSeq(const autovector& cfds); // REQUIRES: mutex locked - Status HandleWALFull(WriteContext* write_context); + Status SwitchWAL(WriteContext* write_context); // REQUIRES: mutex locked Status HandleWriteBufferFull(WriteContext* write_context); @@ -779,7 +1088,8 @@ class DBImpl : public DB { WriteContext* write_context); WriteBatch* MergeBatch(const WriteThread::WriteGroup& write_group, - WriteBatch* tmp_batch, size_t* write_with_wal); + WriteBatch* tmp_batch, size_t* write_with_wal, + WriteBatch** to_be_cached_state); Status WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer, uint64_t* log_used, uint64_t* log_size); @@ -791,10 +1101,10 @@ class DBImpl : public DB { Status ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group, uint64_t* log_used, SequenceNumber* last_sequence, - int total_count); + size_t seq_inc); // Used by WriteImpl to update bg_error_ if paranoid check is enabled. - void WriteCallbackStatusCheck(const Status& status); + void WriteStatusCheck(const Status& status); // Used by WriteImpl to update bg_error_ in case of memtable insert error. void MemTableInsertStatusCheck(const Status& memtable_insert_status); @@ -804,8 +1114,10 @@ class DBImpl : public DB { Status CompactFilesImpl(const CompactionOptions& compact_options, ColumnFamilyData* cfd, Version* version, const std::vector& input_file_names, + std::vector* const output_file_names, const int output_level, int output_path_id, - JobContext* job_context, LogBuffer* log_buffer); + JobContext* job_context, LogBuffer* log_buffer, + CompactionJobInfo* compaction_job_info); // Wait for current IngestExternalFile() calls to finish. // REQUIRES: mutex_ held @@ -820,36 +1132,71 @@ class DBImpl : public DB { ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name); void MaybeScheduleFlushOrCompaction(); - void SchedulePendingFlush(ColumnFamilyData* cfd); + + // A flush request specifies the column families to flush as well as the + // largest memtable id to persist for each column family. Once all the + // memtables whose IDs are smaller than or equal to this per-column-family + // specified value, this flush request is considered to have completed its + // work of flushing this column family. After completing the work for all + // column families in this request, this flush is considered complete. + typedef std::vector> FlushRequest; + + void GenerateFlushRequest(const autovector& cfds, + FlushRequest* req); + + void SchedulePendingFlush(const FlushRequest& req, FlushReason flush_reason); + void SchedulePendingCompaction(ColumnFamilyData* cfd); - void SchedulePendingPurge(std::string fname, FileType type, uint64_t number, - uint32_t path_id, int job_id); + void SchedulePendingPurge(std::string fname, std::string dir_to_sync, + FileType type, uint64_t number, int job_id); static void BGWorkCompaction(void* arg); // Runs a pre-chosen universal compaction involving bottom level in a // separate, bottom-pri thread pool. static void BGWorkBottomCompaction(void* arg); - static void BGWorkFlush(void* db); + static void BGWorkFlush(void* arg); static void BGWorkPurge(void* arg); - static void UnscheduleCallback(void* arg); + static void UnscheduleCompactionCallback(void* arg); + static void UnscheduleFlushCallback(void* arg); void BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction, - Env::Priority bg_thread_pri); - void BackgroundCallFlush(); + Env::Priority thread_pri); + void BackgroundCallFlush(Env::Priority thread_pri); void BackgroundCallPurge(); Status BackgroundCompaction(bool* madeProgress, JobContext* job_context, LogBuffer* log_buffer, - PrepickedCompaction* prepicked_compaction); + PrepickedCompaction* prepicked_compaction, + Env::Priority thread_pri); Status BackgroundFlush(bool* madeProgress, JobContext* job_context, - LogBuffer* log_buffer); + LogBuffer* log_buffer, FlushReason* reason, + Env::Priority thread_pri); + + bool EnoughRoomForCompaction(ColumnFamilyData* cfd, + const std::vector& inputs, + bool* sfm_bookkeeping, LogBuffer* log_buffer); + + // Request compaction tasks token from compaction thread limiter. + // It always succeeds if force = true or limiter is disable. + bool RequestCompactionToken(ColumnFamilyData* cfd, bool force, + std::unique_ptr* token, + LogBuffer* log_buffer); + + // Schedule background tasks + void StartTimedTasks(); void PrintStatistics(); + size_t EstiamteStatsHistorySize() const; + + // persist stats to column family "_persistent_stats" + void PersistStats(); + // dump rocksdb.stats to LOG - void MaybeDumpStats(); + void DumpStats(); // Return the minimum empty level that could hold the total data in the // input level. Return the input level, if such level could not be found. int FindMinimumEmptyLevelFitting(ColumnFamilyData* cfd, - const MutableCFOptions& mutable_cf_options, int level); + const MutableCFOptions& mutable_cf_options, + int level); // Move the files in the input level to the target level. // If target_level < 0, automatically calculate the minimum level that could @@ -859,33 +1206,39 @@ class DBImpl : public DB { // helper functions for adding and removing from flush & compaction queues void AddToCompactionQueue(ColumnFamilyData* cfd); ColumnFamilyData* PopFirstFromCompactionQueue(); - void AddToFlushQueue(ColumnFamilyData* cfd); - ColumnFamilyData* PopFirstFromFlushQueue(); + FlushRequest PopFirstFromFlushQueue(); + + // Pick the first unthrottled compaction with task token from queue. + ColumnFamilyData* PickCompactionFromQueue( + std::unique_ptr* token, LogBuffer* log_buffer); // helper function to call after some of the logs_ were synced void MarkLogsSynced(uint64_t up_to, bool synced_dir, const Status& status); - const Snapshot* GetSnapshotImpl(bool is_write_conflict_boundary); + SnapshotImpl* GetSnapshotImpl(bool is_write_conflict_boundary, + bool lock = true); uint64_t GetMaxTotalWalSize() const; + Directory* GetDataDir(ColumnFamilyData* cfd, size_t path_id) const; + + Status CloseHelper(); + + void WaitForBackgroundWork(); + // table_cache_ provides its own synchronization std::shared_ptr table_cache_; // Lock over the persistent DB state. Non-nullptr iff successfully acquired. FileLock* db_lock_; + // In addition to mutex_, log_write_mutex_ protected writes to stats_history_ + InstrumentedMutex stats_history_mutex_; // In addition to mutex_, log_write_mutex_ protected writes to logs_ and - // logfile_number_. With concurrent_prepare it also protects alive_log_files_, + // logfile_number_. With two_write_queues it also protects alive_log_files_, // and log_empty_. Refer to the definition of each variable below for more // details. InstrumentedMutex log_write_mutex_; - // State below is protected by mutex_ - // With concurrent_prepare enabled, some of the variables that accessed during - // WriteToWAL need different synchronization: log_empty_, alive_log_files_, - // logs_, logfile_number_. Refer to the definition of each variable below for - // more description. - mutable InstrumentedMutex mutex_; std::atomic shutting_down_; // This condition variable is signaled on these conditions: @@ -897,6 +1250,10 @@ class DBImpl : public DB { // (i.e. whenever a flush is done, even if it didn't make any progress) // * whenever there is an error in background purge, flush or compaction // * whenever num_running_ingest_file_ goes to 0. + // * whenever pending_purge_obsolete_files_ goes to 0. + // * whenever disable_delete_obsolete_files_ goes to 0. + // * whenever SetOptions successfully updates options. + // * whenever a column family is dropped. InstrumentedCondVar bg_cv_; // Writes are protected by locking both mutex_ and log_write_mutex_, and reads // must be under either mutex_ or log_write_mutex_. Since after ::Open, @@ -904,21 +1261,19 @@ class DBImpl : public DB { // from the same write_thread_ without any locks. uint64_t logfile_number_; std::deque - log_recycle_files; // a list of log files that we can recycle + log_recycle_files_; // a list of log files that we can recycle bool log_dir_synced_; - // Without concurrent_prepare, read and writes to log_empty_ are protected by + // Without two_write_queues, read and writes to log_empty_ are protected by // mutex_. Since it is currently updated/read only in write_thread_, it can be // accessed from the same write_thread_ without any locks. With - // concurrent_prepare writes, where it can be updated in different threads, + // two_write_queues writes, where it can be updated in different threads, // read and writes are protected by log_write_mutex_ instead. This is to avoid // expesnive mutex_ lock during WAL write, which update log_empty_. bool log_empty_; - ColumnFamilyHandleImpl* default_cf_handle_; - InternalStats* default_cf_internal_stats_; - unique_ptr column_family_memtables_; + + std::unique_ptr column_family_memtables_; struct LogFileNumberSize { - explicit LogFileNumberSize(uint64_t _number) - : number(_number) {} + explicit LogFileNumberSize(uint64_t _number) : number(_number) {} void AddSize(uint64_t new_size) { size += new_size; } uint64_t number; uint64_t size = 0; @@ -934,22 +1289,24 @@ class DBImpl : public DB { writer = nullptr; return w; } - void ClearWriter() { + Status ClearWriter() { + Status s = writer->WriteBuffer(); delete writer; writer = nullptr; + return s; } uint64_t number; // Visual Studio doesn't support deque's member to be noncopyable because - // of a unique_ptr as a member. + // of a std::unique_ptr as a member. log::Writer* writer; // own // true for some prefix of logs_ bool getting_synced = false; }; - // Without concurrent_prepare, read and writes to alive_log_files_ are + // Without two_write_queues, read and writes to alive_log_files_ are // protected by mutex_. However since back() is never popped, and push_back() // is done only from write_thread_, the same thread can access the item - // reffered by back() without mutex_. With concurrent_prepare_, writes + // reffered by back() without mutex_. With two_write_queues_, writes // are protected by locking both mutex_ and log_write_mutex_, and reads must // be under either mutex_ or log_write_mutex_. std::deque alive_log_files_; @@ -969,19 +1326,29 @@ class DBImpl : public DB { std::deque logs_; // Signaled when getting_synced becomes false for some of the logs_. InstrumentedCondVar log_sync_cv_; + // This is the app-level state that is written to the WAL but will be used + // only during recovery. Using this feature enables not writing the state to + // memtable on normal writes and hence improving the throughput. Each new + // write of the state will replace the previous state entirely even if the + // keys in the two consecuitive states do not overlap. + // It is protected by log_write_mutex_ when two_write_queues_ is enabled. + // Otherwise only the heaad of write_thread_ can access it. + WriteBatch cached_recoverable_state_; + std::atomic cached_recoverable_state_empty_ = {true}; std::atomic total_log_size_; - // only used for dynamically adjusting max_total_wal_size. it is a sum of - // [write_buffer_size * max_write_buffer_number] over all column families - uint64_t max_total_in_memory_state_; - // If true, we have only one (default) column family. We use this to optimize - // some code-paths - bool single_column_family_mode_; + // If this is non-empty, we need to delete these log files in background // threads. Protected by db mutex. autovector logs_to_free_; bool is_snapshot_supported_; + std::map> stats_history_; + + std::map stats_slice_; + + bool stats_slice_initialized_ = false; + // Class to maintain directories for all database paths other than main one. class Directories { public: @@ -989,7 +1356,7 @@ class DBImpl : public DB { const std::string& wal_dir, const std::vector& data_paths); - Directory* GetDataDir(size_t path_id); + Directory* GetDataDir(size_t path_id) const; Directory* GetWalDir() { if (wal_dir_) { @@ -1004,9 +1371,6 @@ class DBImpl : public DB { std::unique_ptr db_dir_; std::vector> data_dirs_; std::unique_ptr wal_dir_; - - Status CreateAndNewDirectory(Env* env, const std::string& dirname, - std::unique_ptr* directory) const; }; Directories directories_; @@ -1021,7 +1385,7 @@ class DBImpl : public DB { WriteController write_controller_; - unique_ptr low_pri_write_rate_limiter_; + std::unique_ptr low_pri_write_rate_limiter_; // Size of the last batch group. In slowdown mode, next write needs to // sleep if it uses up the quota. @@ -1048,13 +1412,13 @@ class DBImpl : public DB { // purge_queue_ struct PurgeFileInfo { std::string fname; + std::string dir_to_sync; FileType type; uint64_t number; - uint32_t path_id; int job_id; - PurgeFileInfo(std::string fn, FileType t, uint64_t num, uint32_t pid, + PurgeFileInfo(std::string fn, std::string d, FileType t, uint64_t num, int jid) - : fname(fn), type(t), number(num), path_id(pid), job_id(jid) {} + : fname(fn), dir_to_sync(d), type(t), number(num), job_id(jid) {} }; // flush_queue_ and compaction_queue_ hold column families that we need to @@ -1070,14 +1434,14 @@ class DBImpl : public DB { // compacted. Consumers of these queues are flush and compaction threads. When // column family is put on this queue, we increase unscheduled_flushes_ and // unscheduled_compactions_. When these variables are bigger than zero, that - // means we need to schedule background threads for compaction and thread. + // means we need to schedule background threads for flush and compaction. // Once the background threads are scheduled, we decrease unscheduled_flushes_ // and unscheduled_compactions_. That way we keep track of number of // compaction and flush threads we need to schedule. This scheduling is done // in MaybeScheduleFlushOrCompaction() // invariant(column family present in flush_queue_ <==> // ColumnFamilyData::pending_flush_ == true) - std::deque flush_queue_; + std::deque flush_queue_; // invariant(column family present in compaction_queue_ <==> // ColumnFamilyData::pending_compaction_ == true) std::deque compaction_queue_; @@ -1085,6 +1449,10 @@ class DBImpl : public DB { // A queue to store filenames of the files to be purged std::deque purge_queue_; + // A vector to store the file numbers that have been assigned to certain + // JobContext. Current implementation tracks ssts only. + std::vector files_grabbed_for_purge_; + // A queue to store log writers to close std::deque logs_to_free_queue_; int unscheduled_flushes_; @@ -1117,15 +1485,15 @@ class DBImpl : public DB { uint32_t output_path_id; Status status; bool done; - bool in_progress; // compaction request being processed? - bool incomplete; // only part of requested range compacted - bool exclusive; // current behavior of only one manual - bool disallow_trivial_move; // Force actual compaction to run - const InternalKey* begin; // nullptr means beginning of key range - const InternalKey* end; // nullptr means end of key range - InternalKey* manual_end; // how far we are compacting - InternalKey tmp_storage; // Used to keep track of compaction progress - InternalKey tmp_storage1; // Used to keep track of compaction progress + bool in_progress; // compaction request being processed? + bool incomplete; // only part of requested range compacted + bool exclusive; // current behavior of only one manual + bool disallow_trivial_move; // Force actual compaction to run + const InternalKey* begin; // nullptr means beginning of key range + const InternalKey* end; // nullptr means end of key range + InternalKey* manual_end; // how far we are compacting + InternalKey tmp_storage; // Used to keep track of compaction progress + InternalKey tmp_storage1; // Used to keep track of compaction progress }; struct PrepickedCompaction { // background compaction takes ownership of `compaction`. @@ -1133,6 +1501,8 @@ class DBImpl : public DB { // caller retains ownership of `manual_compaction_state` as it is reused // across background compactions. ManualCompactionState* manual_compaction_state; // nullptr if non-manual + // task limiter token is requested during compaction picking. + std::unique_ptr task_token; }; std::deque manual_compaction_dequeue_; @@ -1143,9 +1513,6 @@ class DBImpl : public DB { PrepickedCompaction* prepicked_compaction; }; - // Have we encountered a background error in paranoid mode? - Status bg_error_; - // shall we disable deletion of obsolete files // if 0 the deletion is enabled. // if non-zero, files will not be getting deleted @@ -1154,6 +1521,10 @@ class DBImpl : public DB { // without any synchronization int disable_delete_obsolete_files_; + // Number of times FindObsoleteFiles has found deletable files and the + // corresponding call to PurgeObsoleteFiles has not yet finished. + int pending_purge_obsolete_files_; + // last time when DeleteObsoleteFiles with full scan was executed. Originaly // initialized with startup time. uint64_t delete_obsolete_files_last_run_; @@ -1171,12 +1542,12 @@ class DBImpl : public DB { std::atomic has_unpersisted_data_; // if an attempt was made to flush all column families that - // the oldest log depends on but uncommited data in the oldest + // the oldest log depends on but uncommitted data in the oldest // log prevents the log from being released. // We must attempt to free the dependent memtables again // at a later time after the transaction in the oldest // log is fully commited. - bool unable_to_flush_oldest_log_; + bool unable_to_release_oldest_log_; static const int KEEP_LOG_FILE_NUM = 1000; // MSVC version 1800 still does not have constexpr for ::max() @@ -1184,9 +1555,6 @@ class DBImpl : public DB { std::string db_absolute_path_; - // The options to access storage files - const EnvOptions env_options_; - // Number of running IngestExternalFile() calls. // REQUIRES: mutex held int num_running_ingest_file_; @@ -1210,27 +1578,27 @@ class DBImpl : public DB { // Indicate DB was opened successfully bool opened_successfully_; - // minimum log number still containing prepared data. - // this is used by FindObsoleteFiles to determine which - // flushed logs we must keep around because they still - // contain prepared data which has not been flushed or rolled back - std::priority_queue, std::greater> - min_log_with_prep_; - - // to be used in conjunction with min_log_with_prep_. - // once a transaction with data in log L is committed or rolled back - // rather than removing the value from the heap we add that value - // to prepared_section_completed_ which maps LOG -> instance_count - // since a log could contain multiple prepared sections - // - // when trying to determine the minimum log still active we first - // consult min_log_with_prep_. while that root value maps to - // a value > 0 in prepared_section_completed_ we decrement the - // instance_count for that log and pop the root value in - // min_log_with_prep_. This will work the same as a min_heap - // where we are deleteing arbitrary elements and the up heaping. - std::unordered_map prepared_section_completed_; - std::mutex prep_heap_mutex_; + // The min threshold to triggere bottommost compaction for removing + // garbages, among all column families. + SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber; + + LogsWithPrepTracker logs_with_prep_tracker_; + + // Callback for compaction to check if a key is visible to a snapshot. + // REQUIRES: mutex held + std::unique_ptr snapshot_checker_; + + // Callback for when the cached_recoverable_state_ is written to memtable + // Only to be set during initialization + std::unique_ptr recoverable_state_pre_release_callback_; + + // handle for scheduling stats dumping at fixed intervals + // REQUIRES: mutex locked + std::unique_ptr thread_dump_stats_; + + // handle for scheduling stats snapshoting at fixed intervals + // REQUIRES: mutex locked + std::unique_ptr thread_persist_stats_; // No copying allowed DBImpl(const DBImpl&); @@ -1238,24 +1606,20 @@ class DBImpl : public DB { // Background threads call this function, which is just a wrapper around // the InstallSuperVersion() function. Background threads carry - // job_context which can have new_superversion already + // sv_context which can have new_superversion already // allocated. - void InstallSuperVersionAndScheduleWorkWrapper( - ColumnFamilyData* cfd, JobContext* job_context, - const MutableCFOptions& mutable_cf_options); - // All ColumnFamily state changes go through this function. Here we analyze // the new state and we schedule background work if we detect that the new // state needs flush or compaction. - SuperVersion* InstallSuperVersionAndScheduleWork( - ColumnFamilyData* cfd, SuperVersion* new_sv, + void InstallSuperVersionAndScheduleWork( + ColumnFamilyData* cfd, SuperVersionContext* sv_context, const MutableCFOptions& mutable_cf_options); #ifndef ROCKSDB_LITE using DB::GetPropertiesOfAllTables; - virtual Status GetPropertiesOfAllTables(ColumnFamilyHandle* column_family, - TablePropertiesCollection* props) - override; + virtual Status GetPropertiesOfAllTables( + ColumnFamilyHandle* column_family, + TablePropertiesCollection* props) override; virtual Status GetPropertiesOfTablesInRange( ColumnFamilyHandle* column_family, const Range* range, std::size_t n, TablePropertiesCollection* props) override; @@ -1265,6 +1629,7 @@ class DBImpl : public DB { bool GetIntPropertyInternal(ColumnFamilyData* cfd, const DBPropertyInfo& property_info, bool is_locked, uint64_t* value); + bool GetPropertyHandleOptionsStatistics(std::string* value); bool HasPendingManualCompaction(); bool HasExclusiveManualCompaction(); @@ -1273,17 +1638,88 @@ class DBImpl : public DB { bool ShouldntRunManualCompaction(ManualCompactionState* m); bool HaveManualCompaction(ColumnFamilyData* cfd); bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1); +#ifndef ROCKSDB_LITE + void BuildCompactionJobInfo(const ColumnFamilyData* cfd, Compaction* c, + const Status& st, + const CompactionJobStats& compaction_job_stats, + const int job_id, const Version* current, + CompactionJobInfo* compaction_job_info) const; + // Reserve the next 'num' file numbers for to-be-ingested external SST files, + // and return the current file_number in 'next_file_number'. + // Write a version edit to the MANIFEST. + Status ReserveFileNumbersBeforeIngestion( + ColumnFamilyData* cfd, uint64_t num, + std::list::iterator* pending_output_elem, + uint64_t* next_file_number); +#endif //! ROCKSDB_LITE + + bool ShouldPurge(uint64_t file_number) const; + void MarkAsGrabbedForPurge(uint64_t file_number); size_t GetWalPreallocateBlockSize(uint64_t write_buffer_size) const; + Env::WriteLifeTimeHint CalculateWALWriteHint() { return Env::WLTH_SHORT; } - // When set, we use a seprate queue for writes that dont write to memtable. In - // 2PC these are the writes at Prepare phase. - const bool concurrent_prepare_; + // When set, we use a separate queue for writes that dont write to memtable. + // In 2PC these are the writes at Prepare phase. + const bool two_write_queues_; const bool manual_wal_flush_; + // Increase the sequence number after writing each batch, whether memtable is + // disabled for that or not. Otherwise the sequence number is increased after + // writing each key into memtable. This implies that when disable_memtable is + // set, the seq is not increased at all. + // + // Default: false + const bool seq_per_batch_; + // This determines during recovery whether we expect one writebatch per + // recovered transaction, or potentially multiple writebatches per + // transaction. For WriteUnprepared, this is set to false, since multiple + // batches can exist per transaction. + // + // Default: true + const bool batch_per_txn_; + // LastSequence also indicates last published sequence visibile to the + // readers. Otherwise LastPublishedSequence should be used. + const bool last_seq_same_as_publish_seq_; + // It indicates that a customized gc algorithm must be used for + // flush/compaction and if it is not provided vis SnapshotChecker, we should + // disable gc to be safe. + const bool use_custom_gc_; + // Flag to indicate that the DB instance shutdown has been initiated. This + // different from shutting_down_ atomic in that it is set at the beginning + // of shutdown sequence, specifically in order to prevent any background + // error recovery from going on in parallel. The latter, shutting_down_, + // is set a little later during the shutdown after scheduling memtable + // flushes + std::atomic shutdown_initiated_; + // Flag to indicate whether sst_file_manager object was allocated in + // DB::Open() or passed to us + bool own_sfm_; + + // Clients must periodically call SetPreserveDeletesSequenceNumber() + // to advance this seqnum. Default value is 0 which means ALL deletes are + // preserved. Note that this has no effect if DBOptions.preserve_deletes + // is set to false. + std::atomic preserve_deletes_seqnum_; + const bool preserve_deletes_; + + // Flag to check whether Close() has been called on this DB + bool closed_; + + ErrorHandler error_handler_; + + // Conditional variable to coordinate installation of atomic flush results. + // With atomic flush, each bg thread installs the result of flushing multiple + // column families, and different threads can flush different column + // families. It's difficult to rely on one thread to perform batch + // installation for all threads. This is different from the non-atomic flush + // case. + // atomic_flush_install_cv_ makes sure that threads install atomic flush + // results sequentially. Flush results of memtables with lower IDs get + // installed to MANIFEST first. + InstrumentedCondVar atomic_flush_install_cv_; }; -extern Options SanitizeOptions(const std::string& db, - const Options& src); +extern Options SanitizeOptions(const std::string& db, const Options& src); extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src); @@ -1291,6 +1727,25 @@ extern CompressionType GetCompressionFlush( const ImmutableCFOptions& ioptions, const MutableCFOptions& mutable_cf_options); +// Return the earliest log file to keep after the memtable flush is +// finalized. +// `cfd_to_flush` is the column family whose memtable (specified in +// `memtables_to_flush`) will be flushed and thus will not depend on any WAL +// file. +// The function is only applicable to 2pc mode. +extern uint64_t PrecomputeMinLogNumberToKeep( + VersionSet* vset, const ColumnFamilyData& cfd_to_flush, + autovector edit_list, + const autovector& memtables_to_flush, + LogsWithPrepTracker* prep_tracker); + +// `cfd_to_flush` is the column family whose memtable will be flushed and thus +// will not depend on any WAL file. nullptr means no memtable is being flushed. +// The function is only applicable to 2pc mode. +extern uint64_t FindMinPrepLogReferencedByMemTable( + VersionSet* vset, const ColumnFamilyData* cfd_to_flush, + const autovector& memtables_to_flush); + // Fix user-supplied options to be reasonable template static void ClipToRange(T* ptr, V minvalue, V maxvalue) { diff --git a/thirdparty/rocksdb/db/db_impl_compaction_flush.cc b/thirdparty/rocksdb/db/db_impl_compaction_flush.cc index 3e686fe703..f208b873dd 100644 --- a/thirdparty/rocksdb/db/db_impl_compaction_flush.cc +++ b/thirdparty/rocksdb/db/db_impl_compaction_flush.cc @@ -14,15 +14,73 @@ #include #include "db/builder.h" +#include "db/error_handler.h" #include "db/event_helpers.h" #include "monitoring/iostats_context_imp.h" #include "monitoring/perf_context_imp.h" #include "monitoring/thread_status_updater.h" #include "monitoring/thread_status_util.h" +#include "util/concurrent_task_limiter_impl.h" #include "util/sst_file_manager_impl.h" #include "util/sync_point.h" namespace rocksdb { + +bool DBImpl::EnoughRoomForCompaction( + ColumnFamilyData* cfd, const std::vector& inputs, + bool* sfm_reserved_compact_space, LogBuffer* log_buffer) { + // Check if we have enough room to do the compaction + bool enough_room = true; +#ifndef ROCKSDB_LITE + auto sfm = static_cast( + immutable_db_options_.sst_file_manager.get()); + if (sfm) { + // Pass the current bg_error_ to SFM so it can decide what checks to + // perform. If this DB instance hasn't seen any error yet, the SFM can be + // optimistic and not do disk space checks + enough_room = + sfm->EnoughRoomForCompaction(cfd, inputs, error_handler_.GetBGError()); + if (enough_room) { + *sfm_reserved_compact_space = true; + } + } +#else + (void)cfd; + (void)inputs; + (void)sfm_reserved_compact_space; +#endif // ROCKSDB_LITE + if (!enough_room) { + // Just in case tests want to change the value of enough_room + TEST_SYNC_POINT_CALLBACK( + "DBImpl::BackgroundCompaction():CancelledCompaction", &enough_room); + ROCKS_LOG_BUFFER(log_buffer, + "Cancelled compaction because not enough room"); + RecordTick(stats_, COMPACTION_CANCELLED, 1); + } + return enough_room; +} + +bool DBImpl::RequestCompactionToken(ColumnFamilyData* cfd, bool force, + std::unique_ptr* token, + LogBuffer* log_buffer) { + assert(*token == nullptr); + auto limiter = static_cast( + cfd->ioptions()->compaction_thread_limiter.get()); + if (limiter == nullptr) { + return true; + } + *token = limiter->GetToken(force); + if (*token != nullptr) { + ROCKS_LOG_BUFFER(log_buffer, + "Thread limiter [%s] increase [%s] compaction task, " + "force: %s, tasks after: %d", + limiter->GetName().c_str(), cfd->GetName().c_str(), + force ? "true" : "false", limiter->GetOutstandingTask()); + return true; + } + return false; +} + Status DBImpl::SyncClosedLogs(JobContext* job_context) { TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start"); mutex_.AssertHeld(); @@ -49,6 +107,9 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) { "[JOB %d] Syncing log #%" PRIu64, job_context->job_id, log->get_log_number()); s = log->file()->Sync(immutable_db_options_.use_fsync); + if (!s.ok()) { + break; + } } if (s.ok()) { s = directories_.GetWalDir()->Fsync(); @@ -60,14 +121,7 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) { // "number < current_log_number". MarkLogsSynced(current_log_number - 1, true, s); if (!s.ok()) { - Status new_bg_error = s; - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners, - BackgroundErrorReason::kFlush, - &new_bg_error, &mutex_); - if (!new_bg_error.ok()) { - bg_error_ = new_bg_error; - } + error_handler_.SetBGError(s, BackgroundErrorReason::kFlush); TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Failed"); return s; } @@ -77,26 +131,31 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) { Status DBImpl::FlushMemTableToOutputFile( ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options, - bool* made_progress, JobContext* job_context, LogBuffer* log_buffer) { + bool* made_progress, JobContext* job_context, + SuperVersionContext* superversion_context, + std::vector& snapshot_seqs, + SequenceNumber earliest_write_conflict_snapshot, + SnapshotChecker* snapshot_checker, LogBuffer* log_buffer, + Env::Priority thread_pri) { mutex_.AssertHeld(); assert(cfd->imm()->NumNotFlushed() != 0); assert(cfd->imm()->IsFlushPending()); - SequenceNumber earliest_write_conflict_snapshot; - std::vector snapshot_seqs = - snapshots_.GetAll(&earliest_write_conflict_snapshot); - FlushJob flush_job( - dbname_, cfd, immutable_db_options_, mutable_cf_options, env_options_, - versions_.get(), &mutex_, &shutting_down_, snapshot_seqs, - earliest_write_conflict_snapshot, job_context, log_buffer, - directories_.GetDbDir(), directories_.GetDataDir(0U), + dbname_, cfd, immutable_db_options_, mutable_cf_options, + nullptr /* memtable_id */, env_options_for_compaction_, versions_.get(), + &mutex_, &shutting_down_, snapshot_seqs, earliest_write_conflict_snapshot, + snapshot_checker, job_context, log_buffer, directories_.GetDbDir(), + GetDataDir(cfd, 0U), GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_, - &event_logger_, mutable_cf_options.report_bg_io_stats); + &event_logger_, mutable_cf_options.report_bg_io_stats, + true /* sync_output_directory */, true /* write_manifest */, thread_pri); FileMetaData file_meta; + TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables"); flush_job.PickMemTable(); + TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:AfterPickMemtables"); #ifndef ROCKSDB_LITE // may temporarily unlock and lock the mutex. @@ -106,7 +165,7 @@ Status DBImpl::FlushMemTableToOutputFile( Status s; if (logfile_number_ > 0 && - versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 0) { + versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1) { // If there are more than one column families, we need to make sure that // all the log files except the most recent one are synced. Otherwise if // the host crashes after flushing and before WAL is persistent, the @@ -114,6 +173,8 @@ Status DBImpl::FlushMemTableToOutputFile( // other column families are missing. // SyncClosedLogs() may unlock and re-lock the db_mutex. s = SyncClosedLogs(job_context); + } else { + TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Skip"); } // Within flush_job.Run, rocksdb may call event listener to notify @@ -123,16 +184,16 @@ Status DBImpl::FlushMemTableToOutputFile( // and EventListener callback will be called when the db_mutex // is unlocked by the current thread. if (s.ok()) { - s = flush_job.Run(&file_meta); + s = flush_job.Run(&logs_with_prep_tracker_, &file_meta); } else { flush_job.Cancel(); } if (s.ok()) { - InstallSuperVersionAndScheduleWorkWrapper(cfd, job_context, - mutable_cf_options); + InstallSuperVersionAndScheduleWork(cfd, superversion_context, + mutable_cf_options); if (made_progress) { - *made_progress = 1; + *made_progress = true; } VersionStorageInfo::LevelSummaryStorage tmp; ROCKS_LOG_BUFFER(log_buffer, "[%s] Level summary: %s\n", @@ -140,18 +201,9 @@ Status DBImpl::FlushMemTableToOutputFile( cfd->current()->storage_info()->LevelSummary(&tmp)); } - if (!s.ok() && !s.IsShutdownInProgress() && - immutable_db_options_.paranoid_checks && bg_error_.ok()) { + if (!s.ok() && !s.IsShutdownInProgress()) { Status new_bg_error = s; - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners, - BackgroundErrorReason::kFlush, - &new_bg_error, &mutex_); - if (!new_bg_error.ok()) { - // if a bad error happened (not ShutdownInProgress), paranoid_checks is - // true, and the error isn't handled by callback, mark DB read-only - bg_error_ = new_bg_error; - } + error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush); } if (s.ok()) { #ifndef ROCKSDB_LITE @@ -163,24 +215,324 @@ Status DBImpl::FlushMemTableToOutputFile( if (sfm) { // Notify sst_file_manager that a new file was added std::string file_path = MakeTableFileName( - immutable_db_options_.db_paths[0].path, file_meta.fd.GetNumber()); + cfd->ioptions()->cf_paths[0].path, file_meta.fd.GetNumber()); sfm->OnAddFile(file_path); - if (sfm->IsMaxAllowedSpaceReached() && bg_error_.ok()) { - Status new_bg_error = Status::IOError("Max allowed space was reached"); + if (sfm->IsMaxAllowedSpaceReached()) { + Status new_bg_error = + Status::SpaceLimit("Max allowed space was reached"); TEST_SYNC_POINT_CALLBACK( "DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached", &new_bg_error); - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners, - BackgroundErrorReason::kFlush, - &new_bg_error, &mutex_); - if (!new_bg_error.ok()) { - bg_error_ = new_bg_error; + error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush); + } + } +#endif // ROCKSDB_LITE + } + return s; +} + +Status DBImpl::FlushMemTablesToOutputFiles( + const autovector& bg_flush_args, bool* made_progress, + JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) { + if (immutable_db_options_.atomic_flush) { + return AtomicFlushMemTablesToOutputFiles( + bg_flush_args, made_progress, job_context, log_buffer, thread_pri); + } + std::vector snapshot_seqs; + SequenceNumber earliest_write_conflict_snapshot; + SnapshotChecker* snapshot_checker; + GetSnapshotContext(job_context, &snapshot_seqs, + &earliest_write_conflict_snapshot, &snapshot_checker); + Status status; + for (auto& arg : bg_flush_args) { + ColumnFamilyData* cfd = arg.cfd_; + MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions(); + SuperVersionContext* superversion_context = arg.superversion_context_; + Status s = FlushMemTableToOutputFile( + cfd, mutable_cf_options, made_progress, job_context, + superversion_context, snapshot_seqs, earliest_write_conflict_snapshot, + snapshot_checker, log_buffer, thread_pri); + if (!s.ok()) { + status = s; + if (!s.IsShutdownInProgress()) { + // At this point, DB is not shutting down, nor is cfd dropped. + // Something is wrong, thus we break out of the loop. + break; + } + } + } + return status; +} + +/* + * Atomically flushes multiple column families. + * + * For each column family, all memtables with ID smaller than or equal to the + * ID specified in bg_flush_args will be flushed. Only after all column + * families finish flush will this function commit to MANIFEST. If any of the + * column families are not flushed successfully, this function does not have + * any side-effect on the state of the database. + */ +Status DBImpl::AtomicFlushMemTablesToOutputFiles( + const autovector& bg_flush_args, bool* made_progress, + JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) { + mutex_.AssertHeld(); + + autovector cfds; + for (const auto& arg : bg_flush_args) { + cfds.emplace_back(arg.cfd_); + } + +#ifndef NDEBUG + for (const auto cfd : cfds) { + assert(cfd->imm()->NumNotFlushed() != 0); + assert(cfd->imm()->IsFlushPending()); + } +#endif /* !NDEBUG */ + + std::vector snapshot_seqs; + SequenceNumber earliest_write_conflict_snapshot; + SnapshotChecker* snapshot_checker; + GetSnapshotContext(job_context, &snapshot_seqs, + &earliest_write_conflict_snapshot, &snapshot_checker); + + autovector distinct_output_dirs; + autovector distinct_output_dir_paths; + std::vector jobs; + std::vector all_mutable_cf_options; + int num_cfs = static_cast(cfds.size()); + all_mutable_cf_options.reserve(num_cfs); + for (int i = 0; i < num_cfs; ++i) { + auto cfd = cfds[i]; + Directory* data_dir = GetDataDir(cfd, 0U); + const std::string& curr_path = cfd->ioptions()->cf_paths[0].path; + + // Add to distinct output directories if eligible. Use linear search. Since + // the number of elements in the vector is not large, performance should be + // tolerable. + bool found = false; + for (const auto& path : distinct_output_dir_paths) { + if (path == curr_path) { + found = true; + break; + } + } + if (!found) { + distinct_output_dir_paths.emplace_back(curr_path); + distinct_output_dirs.emplace_back(data_dir); + } + + all_mutable_cf_options.emplace_back(*cfd->GetLatestMutableCFOptions()); + const MutableCFOptions& mutable_cf_options = all_mutable_cf_options.back(); + const uint64_t* max_memtable_id = &(bg_flush_args[i].max_memtable_id_); + jobs.emplace_back( + dbname_, cfd, immutable_db_options_, mutable_cf_options, + max_memtable_id, env_options_for_compaction_, versions_.get(), &mutex_, + &shutting_down_, snapshot_seqs, earliest_write_conflict_snapshot, + snapshot_checker, job_context, log_buffer, directories_.GetDbDir(), + data_dir, GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), + stats_, &event_logger_, mutable_cf_options.report_bg_io_stats, + false /* sync_output_directory */, false /* write_manifest */, + thread_pri); + jobs.back().PickMemTable(); + } + + std::vector file_meta(num_cfs); + Status s; + assert(num_cfs == static_cast(jobs.size())); + +#ifndef ROCKSDB_LITE + for (int i = 0; i != num_cfs; ++i) { + const MutableCFOptions& mutable_cf_options = all_mutable_cf_options.at(i); + // may temporarily unlock and lock the mutex. + NotifyOnFlushBegin(cfds[i], &file_meta[i], mutable_cf_options, + job_context->job_id, jobs[i].GetTableProperties()); + } +#endif /* !ROCKSDB_LITE */ + + if (logfile_number_ > 0) { + // TODO (yanqin) investigate whether we should sync the closed logs for + // single column family case. + s = SyncClosedLogs(job_context); + } + + // exec_status stores the execution status of flush_jobs as + // + autovector> exec_status; + for (int i = 0; i != num_cfs; ++i) { + // Initially all jobs are not executed, with status OK. + exec_status.emplace_back(false, Status::OK()); + } + + if (s.ok()) { + // TODO (yanqin): parallelize jobs with threads. + for (int i = 1; i != num_cfs; ++i) { + exec_status[i].second = + jobs[i].Run(&logs_with_prep_tracker_, &file_meta[i]); + exec_status[i].first = true; + } + if (num_cfs > 1) { + TEST_SYNC_POINT( + "DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:1"); + TEST_SYNC_POINT( + "DBImpl::AtomicFlushMemTablesToOutputFiles:SomeFlushJobsComplete:2"); + } + exec_status[0].second = + jobs[0].Run(&logs_with_prep_tracker_, &file_meta[0]); + exec_status[0].first = true; + + Status error_status; + for (const auto& e : exec_status) { + if (!e.second.ok()) { + s = e.second; + if (!e.second.IsShutdownInProgress()) { + // If a flush job did not return OK, and the CF is not dropped, and + // the DB is not shutting down, then we have to return this result to + // caller later. + error_status = e.second; + } + } + } + + s = error_status.ok() ? s : error_status; + } + + if (s.ok() || s.IsShutdownInProgress()) { + // Sync on all distinct output directories. + for (auto dir : distinct_output_dirs) { + if (dir != nullptr) { + s = dir->Fsync(); + if (!s.ok()) { + break; + } + } + } + } + + if (s.ok()) { + auto wait_to_install_func = [&]() { + bool ready = true; + for (size_t i = 0; i != cfds.size(); ++i) { + const auto& mems = jobs[i].GetMemTables(); + if (cfds[i]->IsDropped()) { + // If the column family is dropped, then do not wait. + continue; + } else if (!mems.empty() && + cfds[i]->imm()->GetEarliestMemTableID() < mems[0]->GetID()) { + // If a flush job needs to install the flush result for mems and + // mems[0] is not the earliest memtable, it means another thread must + // be installing flush results for the same column family, then the + // current thread needs to wait. + ready = false; + break; + } else if (mems.empty() && cfds[i]->imm()->GetEarliestMemTableID() <= + bg_flush_args[i].max_memtable_id_) { + // If a flush job does not need to install flush results, then it has + // to wait until all memtables up to max_memtable_id_ (inclusive) are + // installed. + ready = false; + break; + } + } + return ready; + }; + + bool resuming_from_bg_err = error_handler_.IsDBStopped(); + while ((!error_handler_.IsDBStopped() || + error_handler_.GetRecoveryError().ok()) && + !wait_to_install_func()) { + atomic_flush_install_cv_.Wait(); + } + + s = resuming_from_bg_err ? error_handler_.GetRecoveryError() + : error_handler_.GetBGError(); + } + + if (s.ok()) { + autovector tmp_cfds; + autovector*> mems_list; + autovector mutable_cf_options_list; + autovector tmp_file_meta; + for (int i = 0; i != num_cfs; ++i) { + const auto& mems = jobs[i].GetMemTables(); + if (!cfds[i]->IsDropped() && !mems.empty()) { + tmp_cfds.emplace_back(cfds[i]); + mems_list.emplace_back(&mems); + mutable_cf_options_list.emplace_back(&all_mutable_cf_options[i]); + tmp_file_meta.emplace_back(&file_meta[i]); + } + } + + s = InstallMemtableAtomicFlushResults( + nullptr /* imm_lists */, tmp_cfds, mutable_cf_options_list, mems_list, + versions_.get(), &mutex_, tmp_file_meta, + &job_context->memtables_to_free, directories_.GetDbDir(), log_buffer); + } + + if (s.ok() || s.IsShutdownInProgress()) { + assert(num_cfs == + static_cast(job_context->superversion_contexts.size())); + for (int i = 0; i != num_cfs; ++i) { + if (cfds[i]->IsDropped()) { + continue; + } + InstallSuperVersionAndScheduleWork(cfds[i], + &job_context->superversion_contexts[i], + all_mutable_cf_options[i]); + VersionStorageInfo::LevelSummaryStorage tmp; + ROCKS_LOG_BUFFER(log_buffer, "[%s] Level summary: %s\n", + cfds[i]->GetName().c_str(), + cfds[i]->current()->storage_info()->LevelSummary(&tmp)); + } + if (made_progress) { + *made_progress = true; + } +#ifndef ROCKSDB_LITE + auto sfm = static_cast( + immutable_db_options_.sst_file_manager.get()); + for (int i = 0; i != num_cfs; ++i) { + if (cfds[i]->IsDropped()) { + continue; + } + NotifyOnFlushCompleted(cfds[i], &file_meta[i], all_mutable_cf_options[i], + job_context->job_id, jobs[i].GetTableProperties()); + if (sfm) { + std::string file_path = MakeTableFileName( + cfds[i]->ioptions()->cf_paths[0].path, file_meta[i].fd.GetNumber()); + sfm->OnAddFile(file_path); + if (sfm->IsMaxAllowedSpaceReached() && + error_handler_.GetBGError().ok()) { + Status new_bg_error = + Status::SpaceLimit("Max allowed space was reached"); + error_handler_.SetBGError(new_bg_error, + BackgroundErrorReason::kFlush); } } } #endif // ROCKSDB_LITE } + + // Need to undo atomic flush if something went wrong, i.e. s is not OK and + // it is not because of CF drop. + if (!s.ok() && !s.IsShutdownInProgress()) { + // Have to cancel the flush jobs that have NOT executed because we need to + // unref the versions. + for (int i = 0; i != num_cfs; ++i) { + if (!exec_status[i].first) { + jobs[i].Cancel(); + } + } + for (int i = 0; i != num_cfs; ++i) { + if (exec_status[i].first && exec_status[i].second.ok()) { + auto& mems = jobs[i].GetMemTables(); + cfds[i]->imm()->RollbackMemtableFlush(mems, + file_meta[i].fd.GetNumber()); + } + } + Status new_bg_error = s; + error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush); + } + return s; } @@ -205,18 +557,20 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta, mutex_.Unlock(); { FlushJobInfo info; + info.cf_id = cfd->GetID(); info.cf_name = cfd->GetName(); // TODO(yhchiang): make db_paths dynamic in case flush does not // go to L0 in the future. - info.file_path = MakeTableFileName(immutable_db_options_.db_paths[0].path, + info.file_path = MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_meta->fd.GetNumber()); info.thread_id = env_->GetThreadID(); info.job_id = job_id; info.triggered_writes_slowdown = triggered_writes_slowdown; info.triggered_writes_stop = triggered_writes_stop; - info.smallest_seqno = file_meta->smallest_seqno; - info.largest_seqno = file_meta->largest_seqno; + info.smallest_seqno = file_meta->fd.smallest_seqno; + info.largest_seqno = file_meta->fd.largest_seqno; info.table_properties = prop; + info.flush_reason = cfd->GetFlushReason(); for (auto listener : immutable_db_options_.listeners) { listener->OnFlushBegin(this, info); } @@ -224,6 +578,12 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta, mutex_.Lock(); // no need to signal bg_cv_ as it will be signaled at the end of the // flush process. +#else + (void)cfd; + (void)file_meta; + (void)mutable_cf_options; + (void)job_id; + (void)prop; #endif // ROCKSDB_LITE } @@ -249,18 +609,20 @@ void DBImpl::NotifyOnFlushCompleted(ColumnFamilyData* cfd, mutex_.Unlock(); { FlushJobInfo info; + info.cf_id = cfd->GetID(); info.cf_name = cfd->GetName(); // TODO(yhchiang): make db_paths dynamic in case flush does not // go to L0 in the future. - info.file_path = MakeTableFileName(immutable_db_options_.db_paths[0].path, + info.file_path = MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_meta->fd.GetNumber()); info.thread_id = env_->GetThreadID(); info.job_id = job_id; info.triggered_writes_slowdown = triggered_writes_slowdown; info.triggered_writes_stop = triggered_writes_stop; - info.smallest_seqno = file_meta->smallest_seqno; - info.largest_seqno = file_meta->largest_seqno; + info.smallest_seqno = file_meta->fd.smallest_seqno; + info.largest_seqno = file_meta->fd.largest_seqno; info.table_properties = prop; + info.flush_reason = cfd->GetFlushReason(); for (auto listener : immutable_db_options_.listeners) { listener->OnFlushCompleted(this, info); } @@ -268,24 +630,57 @@ void DBImpl::NotifyOnFlushCompleted(ColumnFamilyData* cfd, mutex_.Lock(); // no need to signal bg_cv_ as it will be signaled at the end of the // flush process. +#else + (void)cfd; + (void)file_meta; + (void)mutable_cf_options; + (void)job_id; + (void)prop; #endif // ROCKSDB_LITE } Status DBImpl::CompactRange(const CompactRangeOptions& options, ColumnFamilyHandle* column_family, const Slice* begin, const Slice* end) { - if (options.target_path_id >= immutable_db_options_.db_paths.size()) { + auto cfh = reinterpret_cast(column_family); + auto cfd = cfh->cfd(); + + if (options.target_path_id >= cfd->ioptions()->cf_paths.size()) { return Status::InvalidArgument("Invalid target path ID"); } - auto cfh = reinterpret_cast(column_family); - auto cfd = cfh->cfd(); bool exclusive = options.exclusive_manual_compaction; - Status s = FlushMemTable(cfd, FlushOptions()); - if (!s.ok()) { - LogFlush(immutable_db_options_.info_log); - return s; + bool flush_needed = true; + if (begin != nullptr && end != nullptr) { + // TODO(ajkr): We could also optimize away the flush in certain cases where + // one/both sides of the interval are unbounded. But it requires more + // changes to RangesOverlapWithMemtables. + Range range(*begin, *end); + SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_); + cfd->RangesOverlapWithMemtables({range}, super_version, &flush_needed); + CleanupSuperVersion(super_version); + } + + Status s; + if (flush_needed) { + FlushOptions fo; + fo.allow_write_stall = options.allow_write_stall; + if (immutable_db_options_.atomic_flush) { + autovector cfds; + mutex_.Lock(); + SelectColumnFamiliesForAtomicFlush(&cfds); + mutex_.Unlock(); + s = AtomicFlushMemTables(cfds, fo, FlushReason::kManualCompaction, + false /* writes_stopped */); + } else { + s = FlushMemTable(cfd, fo, FlushReason::kManualCompaction, + false /* writes_stopped*/); + } + if (!s.ok()) { + LogFlush(immutable_db_options_.info_log); + return s; + } } int max_level_with_files = 0; @@ -311,7 +706,7 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options, } s = RunManualCompaction(cfd, ColumnFamilyData::kCompactAllLevels, final_output_level, options.target_path_id, - begin, end, exclusive); + options.max_subcompactions, begin, end, exclusive); } else { for (int level = 0; level <= max_level_with_files; level++) { int output_level; @@ -345,7 +740,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options, } } s = RunManualCompaction(cfd, level, output_level, options.target_path_id, - begin, end, exclusive); + options.max_subcompactions, begin, end, + exclusive); if (!s.ok()) { break; } @@ -384,13 +780,21 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options, return s; } -Status DBImpl::CompactFiles( - const CompactionOptions& compact_options, - ColumnFamilyHandle* column_family, - const std::vector& input_file_names, - const int output_level, const int output_path_id) { +Status DBImpl::CompactFiles(const CompactionOptions& compact_options, + ColumnFamilyHandle* column_family, + const std::vector& input_file_names, + const int output_level, const int output_path_id, + std::vector* const output_file_names, + CompactionJobInfo* compaction_job_info) { #ifdef ROCKSDB_LITE - // not supported in lite version + (void)compact_options; + (void)column_family; + (void)input_file_names; + (void)output_level; + (void)output_path_id; + (void)output_file_names; + (void)compaction_job_info; + // not supported in lite version return Status::NotSupported("Not supported in ROCKSDB LITE"); #else if (column_family == nullptr) { @@ -406,7 +810,7 @@ Status DBImpl::CompactFiles( immutable_db_options_.info_log.get()); // Perform CompactFiles - SuperVersion* sv = cfd->GetReferencedSuperVersion(&mutex_); + TEST_SYNC_POINT("TestCompactFiles::IngestExternalFile2"); { InstrumentedMutexLock l(&mutex_); @@ -414,15 +818,16 @@ Status DBImpl::CompactFiles( // IngestExternalFile() calls to finish. WaitForIngestFile(); - s = CompactFilesImpl(compact_options, cfd, sv->current, - input_file_names, output_level, - output_path_id, &job_context, &log_buffer); - } - if (sv->Unref()) { - mutex_.Lock(); - sv->Cleanup(); - mutex_.Unlock(); - delete sv; + // We need to get current after `WaitForIngestFile`, because + // `IngestExternalFile` may add files that overlap with `input_file_names` + auto* current = cfd->current(); + current->Ref(); + + s = CompactFilesImpl(compact_options, cfd, current, input_file_names, + output_file_names, output_level, output_path_id, + &job_context, &log_buffer, compaction_job_info); + + current->Unref(); } // Find and delete obsolete files @@ -436,7 +841,8 @@ Status DBImpl::CompactFiles( } // release the mutex // delete unnecessary files if any, this is done outside the mutex - if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) { + if (job_context.HaveSomethingToClean() || + job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) { // Have to flush the info logs before bg_compaction_scheduled_-- // because if bg_flush_scheduled_ becomes 0 and the lock is // released, the deconstructor of DB can kick in and destroy all the @@ -458,8 +864,9 @@ Status DBImpl::CompactFiles( Status DBImpl::CompactFilesImpl( const CompactionOptions& compact_options, ColumnFamilyData* cfd, Version* version, const std::vector& input_file_names, - const int output_level, int output_path_id, JobContext* job_context, - LogBuffer* log_buffer) { + std::vector* const output_file_names, const int output_level, + int output_path_id, JobContext* job_context, LogBuffer* log_buffer, + CompactionJobInfo* compaction_job_info) { mutex_.AssertHeld(); if (shutting_down_.load(std::memory_order_acquire)) { @@ -467,7 +874,7 @@ Status DBImpl::CompactFilesImpl( } std::unordered_set input_set; - for (auto file_name : input_file_names) { + for (const auto& file_name : input_file_names) { input_set.insert(TableFileNameToNumber(file_name)); } @@ -477,7 +884,7 @@ Status DBImpl::CompactFilesImpl( version->GetColumnFamilyMetaData(&cf_meta); if (output_path_id < 0) { - if (immutable_db_options_.db_paths.size() == 1U) { + if (cfd->ioptions()->cf_paths.size() == 1U) { output_path_id = 0; } else { return Status::NotSupported( @@ -499,57 +906,61 @@ Status DBImpl::CompactFilesImpl( return s; } - for (auto inputs : input_files) { + for (const auto& inputs : input_files) { if (cfd->compaction_picker()->AreFilesInCompaction(inputs.files)) { return Status::Aborted( "Some of the necessary compaction input " "files are already being compacted"); } } + bool sfm_reserved_compact_space = false; + // First check if we have enough room to do the compaction + bool enough_room = EnoughRoomForCompaction( + cfd, input_files, &sfm_reserved_compact_space, log_buffer); + + if (!enough_room) { + // m's vars will get set properly at the end of this function, + // as long as status == CompactionTooLarge + return Status::CompactionTooLarge(); + } // At this point, CompactFiles will be run. bg_compaction_scheduled_++; - unique_ptr c; + std::unique_ptr c; assert(cfd->compaction_picker()); c.reset(cfd->compaction_picker()->CompactFiles( compact_options, input_files, output_level, version->storage_info(), *cfd->GetLatestMutableCFOptions(), output_path_id)); - if (!c) { - return Status::Aborted("Another Level 0 compaction is running"); - } + // we already sanitized the set of input files and checked for conflicts + // without releasing the lock, so we're guaranteed a compaction can be formed. + assert(c != nullptr); + c->SetInputVersion(version); // deletion compaction currently not allowed in CompactFiles. assert(!c->deletion_compaction()); + std::vector snapshot_seqs; SequenceNumber earliest_write_conflict_snapshot; - std::vector snapshot_seqs = - snapshots_.GetAll(&earliest_write_conflict_snapshot); + SnapshotChecker* snapshot_checker; + GetSnapshotContext(job_context, &snapshot_seqs, + &earliest_write_conflict_snapshot, &snapshot_checker); auto pending_outputs_inserted_elem = CaptureCurrentFileNumberInPendingOutputs(); assert(is_snapshot_supported_ || snapshots_.empty()); + CompactionJobStats compaction_job_stats; CompactionJob compaction_job( - job_context->job_id, c.get(), immutable_db_options_, env_options_, - versions_.get(), &shutting_down_, log_buffer, directories_.GetDbDir(), - directories_.GetDataDir(c->output_path_id()), stats_, &mutex_, &bg_error_, - snapshot_seqs, earliest_write_conflict_snapshot, table_cache_, - &event_logger_, c->mutable_cf_options()->paranoid_file_checks, + job_context->job_id, c.get(), immutable_db_options_, + env_options_for_compaction_, versions_.get(), &shutting_down_, + preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(), + GetDataDir(c->column_family_data(), c->output_path_id()), stats_, &mutex_, + &error_handler_, snapshot_seqs, earliest_write_conflict_snapshot, + snapshot_checker, table_cache_, &event_logger_, + c->mutable_cf_options()->paranoid_file_checks, c->mutable_cf_options()->report_bg_io_stats, dbname_, - nullptr); // Here we pass a nullptr for CompactionJobStats because - // CompactFiles does not trigger OnCompactionCompleted(), - // which is the only place where CompactionJobStats is - // returned. The idea of not triggering OnCompationCompleted() - // is that CompactFiles runs in the caller thread, so the user - // should always know when it completes. As a result, it makes - // less sense to notify the users something they should already - // know. - // - // In the future, if we would like to add CompactionJobStats - // support for CompactFiles, we should have CompactFiles API - // pass a pointer of CompactionJobStats as the out-value - // instead of using EventListener. + &compaction_job_stats, Env::Priority::USER); // Creating a compaction influences the compaction score because the score // takes running compactions into account (by skipping files that are already @@ -570,13 +981,27 @@ Status DBImpl::CompactFilesImpl( Status status = compaction_job.Install(*c->mutable_cf_options()); if (status.ok()) { - InstallSuperVersionAndScheduleWorkWrapper( - c->column_family_data(), job_context, *c->mutable_cf_options()); + InstallSuperVersionAndScheduleWork(c->column_family_data(), + &job_context->superversion_contexts[0], + *c->mutable_cf_options()); } c->ReleaseCompactionFiles(s); +#ifndef ROCKSDB_LITE + // Need to make sure SstFileManager does its bookkeeping + auto sfm = static_cast( + immutable_db_options_.sst_file_manager.get()); + if (sfm && sfm_reserved_compact_space) { + sfm->OnCompactionCompletion(c.get()); + } +#endif // ROCKSDB_LITE ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem); + if (compaction_job_info != nullptr) { + BuildCompactionJobInfo(cfd, c.get(), s, compaction_job_stats, + job_context->job_id, version, compaction_job_info); + } + if (status.ok()) { // Done } else if (status.IsShutdownInProgress()) { @@ -586,15 +1011,15 @@ Status DBImpl::CompactFilesImpl( "[%s] [JOB %d] Compaction error: %s", c->column_family_data()->GetName().c_str(), job_context->job_id, status.ToString().c_str()); - if (immutable_db_options_.paranoid_checks && bg_error_.ok()) { - Status new_bg_error = status; - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners, - BackgroundErrorReason::kCompaction, - &new_bg_error, &mutex_); - if (!new_bg_error.ok()) { - bg_error_ = new_bg_error; - } + error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction); + } + + if (output_file_names != nullptr) { + for (const auto newf : c->edit()->GetNewFiles()) { + (*output_file_names) + .push_back(TableFileName(c->immutable_cf_options()->cf_paths, + newf.second.fd.GetNumber(), + newf.second.fd.GetPathId())); } } @@ -604,6 +1029,8 @@ Status DBImpl::CompactFilesImpl( if (bg_compaction_scheduled_ == 0) { bg_cv_.SignalAll(); } + MaybeScheduleFlushOrCompaction(); + TEST_SYNC_POINT("CompactFilesImpl:End"); return status; } @@ -637,21 +1064,23 @@ Status DBImpl::ContinueBackgroundWork() { return Status::OK(); } -void DBImpl::NotifyOnCompactionCompleted( - ColumnFamilyData* cfd, Compaction *c, const Status &st, - const CompactionJobStats& compaction_job_stats, - const int job_id) { +void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c, + const Status& st, + const CompactionJobStats& job_stats, + int job_id) { #ifndef ROCKSDB_LITE - if (immutable_db_options_.listeners.size() == 0U) { + if (immutable_db_options_.listeners.empty()) { return; } mutex_.AssertHeld(); if (shutting_down_.load(std::memory_order_acquire)) { return; } + Version* current = cfd->current(); + current->Ref(); // release lock while notifying events mutex_.Unlock(); - TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex"); + TEST_SYNC_POINT("DBImpl::NotifyOnCompactionBegin::UnlockMutex"); { CompactionJobInfo info; info.cf_name = cfd->GetName(); @@ -660,18 +1089,18 @@ void DBImpl::NotifyOnCompactionCompleted( info.job_id = job_id; info.base_input_level = c->start_level(); info.output_level = c->output_level(); - info.stats = compaction_job_stats; + info.stats = job_stats; info.table_properties = c->GetOutputTableProperties(); info.compaction_reason = c->compaction_reason(); info.compression = c->output_compression(); for (size_t i = 0; i < c->num_input_levels(); ++i) { for (const auto fmd : *c->inputs(i)) { - auto fn = TableFileName(immutable_db_options_.db_paths, + auto fn = TableFileName(c->immutable_cf_options()->cf_paths, fmd->fd.GetNumber(), fmd->fd.GetPathId()); info.input_files.push_back(fn); if (info.table_properties.count(fn) == 0) { std::shared_ptr tp; - auto s = cfd->current()->GetTableProperties(&tp, fmd, &fn); + auto s = current->GetTableProperties(&tp, fmd, &fn); if (s.ok()) { info.table_properties[fn] = tp; } @@ -679,17 +1108,59 @@ void DBImpl::NotifyOnCompactionCompleted( } } for (const auto newf : c->edit()->GetNewFiles()) { - info.output_files.push_back(TableFileName(immutable_db_options_.db_paths, - newf.second.fd.GetNumber(), - newf.second.fd.GetPathId())); + info.output_files.push_back(TableFileName( + c->immutable_cf_options()->cf_paths, newf.second.fd.GetNumber(), + newf.second.fd.GetPathId())); } + for (auto listener : immutable_db_options_.listeners) { + listener->OnCompactionBegin(this, info); + } + } + mutex_.Lock(); + current->Unref(); +#else + (void)cfd; + (void)c; + (void)st; + (void)job_stats; + (void)job_id; +#endif // ROCKSDB_LITE +} + +void DBImpl::NotifyOnCompactionCompleted( + ColumnFamilyData* cfd, Compaction* c, const Status& st, + const CompactionJobStats& compaction_job_stats, const int job_id) { +#ifndef ROCKSDB_LITE + if (immutable_db_options_.listeners.size() == 0U) { + return; + } + mutex_.AssertHeld(); + if (shutting_down_.load(std::memory_order_acquire)) { + return; + } + Version* current = cfd->current(); + current->Ref(); + // release lock while notifying events + mutex_.Unlock(); + TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex"); + { + CompactionJobInfo info; + BuildCompactionJobInfo(cfd, c, st, compaction_job_stats, job_id, current, + &info); for (auto listener : immutable_db_options_.listeners) { listener->OnCompactionCompleted(this, info); } } mutex_.Lock(); + current->Unref(); // no need to signal bg_cv_ as it will be signaled at the end of the // flush process. +#else + (void)cfd; + (void)c; + (void)st; + (void)compaction_job_stats; + (void)job_id; #endif // ROCKSDB_LITE } @@ -701,8 +1172,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) { return Status::InvalidArgument("Target level exceeds number of levels"); } - std::unique_ptr superversion_to_free; - std::unique_ptr new_superversion(new SuperVersion()); + SuperVersionContext sv_context(/* create_superversion */ true); Status status; @@ -748,7 +1218,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) { edit.DeleteFile(level, f->fd.GetNumber()); edit.AddFile(to_level, f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest, f->largest, - f->smallest_seqno, f->largest_seqno, + f->fd.smallest_seqno, f->fd.largest_seqno, f->marked_for_compaction); } ROCKS_LOG_DEBUG(immutable_db_options_.info_log, @@ -757,8 +1227,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) { status = versions_->LogAndApply(cfd, mutable_cf_options, &edit, &mutex_, directories_.GetDbDir()); - superversion_to_free.reset(InstallSuperVersionAndScheduleWork( - cfd, new_superversion.release(), mutable_cf_options)); + InstallSuperVersionAndScheduleWork(cfd, &sv_context, mutable_cf_options); ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] LogAndApply: %s\n", cfd->GetName().c_str(), status.ToString().data()); @@ -770,6 +1239,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) { } } + sv_context.Clean(); refitting_level_ = false; return status; @@ -780,25 +1250,83 @@ int DBImpl::NumberLevels(ColumnFamilyHandle* column_family) { return cfh->cfd()->NumberLevels(); } -int DBImpl::MaxMemCompactionLevel(ColumnFamilyHandle* column_family) { +int DBImpl::MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) { return 0; } int DBImpl::Level0StopWriteTrigger(ColumnFamilyHandle* column_family) { auto cfh = reinterpret_cast(column_family); InstrumentedMutexLock l(&mutex_); - return cfh->cfd()->GetSuperVersion()-> - mutable_cf_options.level0_stop_writes_trigger; + return cfh->cfd() + ->GetSuperVersion() + ->mutable_cf_options.level0_stop_writes_trigger; } Status DBImpl::Flush(const FlushOptions& flush_options, ColumnFamilyHandle* column_family) { auto cfh = reinterpret_cast(column_family); - return FlushMemTable(cfh->cfd(), flush_options); + ROCKS_LOG_INFO(immutable_db_options_.info_log, "[%s] Manual flush start.", + cfh->GetName().c_str()); + Status s; + if (immutable_db_options_.atomic_flush) { + s = AtomicFlushMemTables({cfh->cfd()}, flush_options, + FlushReason::kManualFlush); + } else { + s = FlushMemTable(cfh->cfd(), flush_options, FlushReason::kManualFlush); + } + + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "[%s] Manual flush finished, status: %s\n", + cfh->GetName().c_str(), s.ToString().c_str()); + return s; +} + +Status DBImpl::Flush(const FlushOptions& flush_options, + const std::vector& column_families) { + Status s; + if (!immutable_db_options_.atomic_flush) { + for (auto cfh : column_families) { + s = Flush(flush_options, cfh); + if (!s.ok()) { + break; + } + } + } else { + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "Manual atomic flush start.\n" + "=====Column families:====="); + for (auto cfh : column_families) { + auto cfhi = static_cast(cfh); + ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s", + cfhi->GetName().c_str()); + } + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "=====End of column families list====="); + autovector cfds; + std::for_each(column_families.begin(), column_families.end(), + [&cfds](ColumnFamilyHandle* elem) { + auto cfh = static_cast(elem); + cfds.emplace_back(cfh->cfd()); + }); + s = AtomicFlushMemTables(cfds, flush_options, FlushReason::kManualFlush); + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "Manual atomic flush finished, status: %s\n" + "=====Column families:=====", + s.ToString().c_str()); + for (auto cfh : column_families) { + auto cfhi = static_cast(cfh); + ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s", + cfhi->GetName().c_str()); + } + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "=====End of column families list====="); + } + return s; } Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level, int output_level, uint32_t output_path_id, + uint32_t max_subcompactions, const Slice* begin, const Slice* end, bool exclusive, bool disallow_trivial_move) { assert(input_level == ColumnFamilyData::kCompactAllLevels || @@ -826,7 +1354,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level, cfd->ioptions()->compaction_style == kCompactionStyleFIFO) { manual.begin = nullptr; } else { - begin_storage.SetMaxPossibleForUserKey(*begin); + begin_storage.SetMinPossibleForUserKey(*begin); manual.begin = &begin_storage; } if (end == nullptr || @@ -834,7 +1362,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level, cfd->ioptions()->compaction_style == kCompactionStyleFIFO) { manual.end = nullptr; } else { - end_storage.SetMinPossibleForUserKey(*end); + end_storage.SetMaxPossibleForUserKey(*end); manual.end = &end_storage; } @@ -874,21 +1402,24 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level, ROCKS_LOG_INFO(immutable_db_options_.info_log, "[%s] Manual compaction starting", cfd->GetName().c_str()); + LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, + immutable_db_options_.info_log.get()); // We don't check bg_error_ here, because if we get the error in compaction, // the compaction will set manual.status to bg_error_ and set manual.done to // true. while (!manual.done) { assert(HasPendingManualCompaction()); manual_conflict = false; - Compaction* compaction; + Compaction* compaction = nullptr; if (ShouldntRunManualCompaction(&manual) || (manual.in_progress == true) || scheduled || - ((manual.manual_end = &manual.tmp_storage1) && + (((manual.manual_end = &manual.tmp_storage1) != nullptr) && ((compaction = manual.cfd->CompactRange( *manual.cfd->GetLatestMutableCFOptions(), manual.input_level, - manual.output_level, manual.output_path_id, manual.begin, - manual.end, &manual.manual_end, &manual_conflict)) == nullptr) && - manual_conflict)) { + manual.output_level, manual.output_path_id, max_subcompactions, + manual.begin, manual.end, &manual.manual_end, + &manual_conflict)) == nullptr && + manual_conflict))) { // exclusive manual compactions should not see a conflict during // CompactRange assert(!exclusive || !manual_conflict); @@ -910,14 +1441,20 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level, ca->prepicked_compaction = new PrepickedCompaction; ca->prepicked_compaction->manual_compaction_state = &manual; ca->prepicked_compaction->compaction = compaction; + if (!RequestCompactionToken( + cfd, true, &ca->prepicked_compaction->task_token, &log_buffer)) { + // Don't throttle manual compaction, only count outstanding tasks. + assert(false); + } manual.incomplete = false; bg_compaction_scheduled_++; env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this, - &DBImpl::UnscheduleCallback); + &DBImpl::UnscheduleCompactionCallback); scheduled = true; } } + log_buffer.FlushBufferToLog(); assert(!manual.in_progress); assert(HasPendingManualCompaction()); RemoveManualCompaction(&manual); @@ -925,64 +1462,290 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level, return manual.status; } +void DBImpl::GenerateFlushRequest(const autovector& cfds, + FlushRequest* req) { + assert(req != nullptr); + req->reserve(cfds.size()); + for (const auto cfd : cfds) { + if (nullptr == cfd) { + // cfd may be null, see DBImpl::ScheduleFlushes + continue; + } + uint64_t max_memtable_id = cfd->imm()->GetLatestMemTableID(); + req->emplace_back(cfd, max_memtable_id); + } +} + Status DBImpl::FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& flush_options, - bool writes_stopped) { + FlushReason flush_reason, bool writes_stopped) { Status s; + uint64_t flush_memtable_id = 0; + if (!flush_options.allow_write_stall) { + bool flush_needed = true; + s = WaitUntilFlushWouldNotStallWrites(cfd, &flush_needed); + TEST_SYNC_POINT("DBImpl::FlushMemTable:StallWaitDone"); + if (!s.ok() || !flush_needed) { + return s; + } + } + FlushRequest flush_req; { WriteContext context; InstrumentedMutexLock guard_lock(&mutex_); - if (cfd->imm()->NumNotFlushed() == 0 && cfd->mem()->IsEmpty()) { - // Nothing to flush - return Status::OK(); - } - WriteThread::Writer w; if (!writes_stopped) { write_thread_.EnterUnbatched(&w, &mutex_); } - // SwitchMemtable() will release and reacquire mutex - // during execution - s = SwitchMemtable(cfd, &context); + if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load()) { + s = SwitchMemtable(cfd, &context); + } + + if (s.ok()) { + if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() || + !cached_recoverable_state_empty_.load()) { + flush_memtable_id = cfd->imm()->GetLatestMemTableID(); + flush_req.emplace_back(cfd, flush_memtable_id); + } + } + + if (s.ok() && !flush_req.empty()) { + for (auto& elem : flush_req) { + ColumnFamilyData* loop_cfd = elem.first; + loop_cfd->imm()->FlushRequested(); + } + SchedulePendingFlush(flush_req, flush_reason); + MaybeScheduleFlushOrCompaction(); + } if (!writes_stopped) { write_thread_.ExitUnbatched(&w); } + } - cfd->imm()->FlushRequested(); + if (s.ok() && flush_options.wait) { + autovector cfds; + autovector flush_memtable_ids; + for (auto& iter : flush_req) { + cfds.push_back(iter.first); + flush_memtable_ids.push_back(&(iter.second)); + } + s = WaitForFlushMemTables(cfds, flush_memtable_ids, + (flush_reason == FlushReason::kErrorRecovery)); + } + TEST_SYNC_POINT("FlushMemTableFinished"); + return s; +} - // schedule flush - SchedulePendingFlush(cfd); - MaybeScheduleFlushOrCompaction(); +// Flush all elments in 'column_family_datas' +// and atomically record the result to the MANIFEST. +Status DBImpl::AtomicFlushMemTables( + const autovector& column_family_datas, + const FlushOptions& flush_options, FlushReason flush_reason, + bool writes_stopped) { + Status s; + if (!flush_options.allow_write_stall) { + int num_cfs_to_flush = 0; + for (auto cfd : column_family_datas) { + bool flush_needed = true; + s = WaitUntilFlushWouldNotStallWrites(cfd, &flush_needed); + if (!s.ok()) { + return s; + } else if (flush_needed) { + ++num_cfs_to_flush; + } + } + if (0 == num_cfs_to_flush) { + return s; + } } + FlushRequest flush_req; + autovector cfds; + { + WriteContext context; + InstrumentedMutexLock guard_lock(&mutex_); + + WriteThread::Writer w; + if (!writes_stopped) { + write_thread_.EnterUnbatched(&w, &mutex_); + } + + for (auto cfd : column_family_datas) { + if (cfd->IsDropped()) { + continue; + } + if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() || + !cached_recoverable_state_empty_.load()) { + cfds.emplace_back(cfd); + } + } + for (auto cfd : cfds) { + if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load()) { + continue; + } + cfd->Ref(); + s = SwitchMemtable(cfd, &context); + cfd->Unref(); + if (!s.ok()) { + break; + } + } + if (s.ok()) { + AssignAtomicFlushSeq(cfds); + for (auto cfd : cfds) { + cfd->imm()->FlushRequested(); + } + GenerateFlushRequest(cfds, &flush_req); + SchedulePendingFlush(flush_req, flush_reason); + MaybeScheduleFlushOrCompaction(); + } + + if (!writes_stopped) { + write_thread_.ExitUnbatched(&w); + } + } + TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:AfterScheduleFlush"); if (s.ok() && flush_options.wait) { - // Wait until the compaction completes - s = WaitForFlushMemTable(cfd); + autovector flush_memtable_ids; + for (auto& iter : flush_req) { + flush_memtable_ids.push_back(&(iter.second)); + } + s = WaitForFlushMemTables(cfds, flush_memtable_ids, + (flush_reason == FlushReason::kErrorRecovery)); } return s; } -Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd) { - Status s; +// Calling FlushMemTable(), whether from DB::Flush() or from Backup Engine, can +// cause write stall, for example if one memtable is being flushed already. +// This method tries to avoid write stall (similar to CompactRange() behavior) +// it emulates how the SuperVersion / LSM would change if flush happens, checks +// it against various constrains and delays flush if it'd cause write stall. +// Called should check status and flush_needed to see if flush already happened. +Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd, + bool* flush_needed) { + { + *flush_needed = true; + InstrumentedMutexLock l(&mutex_); + uint64_t orig_active_memtable_id = cfd->mem()->GetID(); + WriteStallCondition write_stall_condition = WriteStallCondition::kNormal; + do { + if (write_stall_condition != WriteStallCondition::kNormal) { + // Same error handling as user writes: Don't wait if there's a + // background error, even if it's a soft error. We might wait here + // indefinitely as the pending flushes/compactions may never finish + // successfully, resulting in the stall condition lasting indefinitely + if (error_handler_.IsBGWorkStopped()) { + return error_handler_.GetBGError(); + } + + TEST_SYNC_POINT("DBImpl::WaitUntilFlushWouldNotStallWrites:StallWait"); + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "[%s] WaitUntilFlushWouldNotStallWrites" + " waiting on stall conditions to clear", + cfd->GetName().c_str()); + bg_cv_.Wait(); + } + if (cfd->IsDropped() || shutting_down_.load(std::memory_order_acquire)) { + return Status::ShutdownInProgress(); + } + + uint64_t earliest_memtable_id = + std::min(cfd->mem()->GetID(), cfd->imm()->GetEarliestMemTableID()); + if (earliest_memtable_id > orig_active_memtable_id) { + // We waited so long that the memtable we were originally waiting on was + // flushed. + *flush_needed = false; + return Status::OK(); + } + + const auto& mutable_cf_options = *cfd->GetLatestMutableCFOptions(); + const auto* vstorage = cfd->current()->storage_info(); + + // Skip stalling check if we're below auto-flush and auto-compaction + // triggers. If it stalled in these conditions, that'd mean the stall + // triggers are so low that stalling is needed for any background work. In + // that case we shouldn't wait since background work won't be scheduled. + if (cfd->imm()->NumNotFlushed() < + cfd->ioptions()->min_write_buffer_number_to_merge && + vstorage->l0_delay_trigger_count() < + mutable_cf_options.level0_file_num_compaction_trigger) { + break; + } + + // check whether one extra immutable memtable or an extra L0 file would + // cause write stalling mode to be entered. It could still enter stall + // mode due to pending compaction bytes, but that's less common + write_stall_condition = + ColumnFamilyData::GetWriteStallConditionAndCause( + cfd->imm()->NumNotFlushed() + 1, + vstorage->l0_delay_trigger_count() + 1, + vstorage->estimated_compaction_needed_bytes(), mutable_cf_options) + .first; + } while (write_stall_condition != WriteStallCondition::kNormal); + } + return Status::OK(); +} + +// Wait for memtables to be flushed for multiple column families. +// let N = cfds.size() +// for i in [0, N), +// 1) if flush_memtable_ids[i] is not null, then the memtables with lower IDs +// have to be flushed for THIS column family; +// 2) if flush_memtable_ids[i] is null, then all memtables in THIS column +// family have to be flushed. +// Finish waiting when ALL column families finish flushing memtables. +// resuming_from_bg_err indicates whether the caller is trying to resume from +// background error or in normal processing. +Status DBImpl::WaitForFlushMemTables( + const autovector& cfds, + const autovector& flush_memtable_ids, + bool resuming_from_bg_err) { + int num = static_cast(cfds.size()); // Wait until the compaction completes InstrumentedMutexLock l(&mutex_); - while (cfd->imm()->NumNotFlushed() > 0 && bg_error_.ok()) { + // If the caller is trying to resume from bg error, then + // error_handler_.IsDBStopped() is true. + while (resuming_from_bg_err || !error_handler_.IsDBStopped()) { if (shutting_down_.load(std::memory_order_acquire)) { return Status::ShutdownInProgress(); } - if (cfd->IsDropped()) { - // FlushJob cannot flush a dropped CF, if we did not break here - // we will loop forever since cfd->imm()->NumNotFlushed() will never - // drop to zero + // If an error has occurred during resumption, then no need to wait. + if (!error_handler_.GetRecoveryError().ok()) { + break; + } + // Number of column families that have been dropped. + int num_dropped = 0; + // Number of column families that have finished flush. + int num_finished = 0; + for (int i = 0; i < num; ++i) { + if (cfds[i]->IsDropped()) { + ++num_dropped; + } else if (cfds[i]->imm()->NumNotFlushed() == 0 || + (flush_memtable_ids[i] != nullptr && + cfds[i]->imm()->GetEarliestMemTableID() > + *flush_memtable_ids[i])) { + ++num_finished; + } + } + if (1 == num_dropped && 1 == num) { return Status::InvalidArgument("Cannot flush a dropped CF"); } + // Column families involved in this flush request have either been dropped + // or finished flush. Then it's time to finish waiting. + if (num_dropped + num_finished == num) { + break; + } bg_cv_.Wait(); } - if (!bg_error_.ok()) { - s = bg_error_; + Status s; + // If not resuming from bg error, and an error has caused the DB to stop, + // then report the bg error to caller. + if (!resuming_from_bg_err && error_handler_.IsDBStopped()) { + s = error_handler_.GetBGError(); } return s; } @@ -1010,18 +1773,27 @@ void DBImpl::MaybeScheduleFlushOrCompaction() { if (bg_work_paused_ > 0) { // we paused the background work return; + } else if (error_handler_.IsBGWorkStopped() && + !error_handler_.IsRecoveryInProgress()) { + // There has been a hard error and this call is not part of the recovery + // sequence. Bail out here so we don't get into an endless loop of + // scheduling BG work which will again call this function + return; } else if (shutting_down_.load(std::memory_order_acquire)) { // DB is being deleted; no more background compactions return; } auto bg_job_limits = GetBGJobLimits(); bool is_flush_pool_empty = - env_->GetBackgroundThreads(Env::Priority::HIGH) == 0; + env_->GetBackgroundThreads(Env::Priority::HIGH) == 0; while (!is_flush_pool_empty && unscheduled_flushes_ > 0 && bg_flush_scheduled_ < bg_job_limits.max_flushes) { - unscheduled_flushes_--; bg_flush_scheduled_++; - env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::HIGH, this); + FlushThreadArg* fta = new FlushThreadArg; + fta->db_ = this; + fta->thread_pri_ = Env::Priority::HIGH; + env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::HIGH, this, + &DBImpl::UnscheduleFlushCallback); } // special case -- if high-pri (flush) thread pool is empty, then schedule @@ -1030,20 +1802,30 @@ void DBImpl::MaybeScheduleFlushOrCompaction() { while (unscheduled_flushes_ > 0 && bg_flush_scheduled_ + bg_compaction_scheduled_ < bg_job_limits.max_flushes) { - unscheduled_flushes_--; bg_flush_scheduled_++; - env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::LOW, this); + FlushThreadArg* fta = new FlushThreadArg; + fta->db_ = this; + fta->thread_pri_ = Env::Priority::LOW; + env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::LOW, this, + &DBImpl::UnscheduleFlushCallback); } } if (bg_compaction_paused_ > 0) { // we paused the background compaction return; + } else if (error_handler_.IsBGWorkStopped()) { + // Compaction is not part of the recovery sequence from a hard error. We + // might get here because recovery might do a flush and install a new + // super version, which will try to schedule pending compactions. Bail + // out here and let the higher level recovery handle compactions + return; } if (HasExclusiveManualCompaction()) { // only manual compactions are allowed to run. don't schedule automatic // compactions + TEST_SYNC_POINT("DBImpl::MaybeScheduleFlushOrCompaction:Conflict"); return; } @@ -1055,7 +1837,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() { bg_compaction_scheduled_++; unscheduled_compactions_--; env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this, - &DBImpl::UnscheduleCallback); + &DBImpl::UnscheduleCompactionCallback); } } @@ -1091,63 +1873,92 @@ DBImpl::BGJobLimits DBImpl::GetBGJobLimits(int max_background_flushes, } void DBImpl::AddToCompactionQueue(ColumnFamilyData* cfd) { - assert(!cfd->pending_compaction()); + assert(!cfd->queued_for_compaction()); cfd->Ref(); compaction_queue_.push_back(cfd); - cfd->set_pending_compaction(true); + cfd->set_queued_for_compaction(true); } ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() { assert(!compaction_queue_.empty()); auto cfd = *compaction_queue_.begin(); compaction_queue_.pop_front(); - assert(cfd->pending_compaction()); - cfd->set_pending_compaction(false); + assert(cfd->queued_for_compaction()); + cfd->set_queued_for_compaction(false); return cfd; } -void DBImpl::AddToFlushQueue(ColumnFamilyData* cfd) { - assert(!cfd->pending_flush()); - cfd->Ref(); - flush_queue_.push_back(cfd); - cfd->set_pending_flush(true); -} - -ColumnFamilyData* DBImpl::PopFirstFromFlushQueue() { +DBImpl::FlushRequest DBImpl::PopFirstFromFlushQueue() { assert(!flush_queue_.empty()); - auto cfd = *flush_queue_.begin(); + FlushRequest flush_req = flush_queue_.front(); + assert(unscheduled_flushes_ >= static_cast(flush_req.size())); + unscheduled_flushes_ -= static_cast(flush_req.size()); flush_queue_.pop_front(); - assert(cfd->pending_flush()); - cfd->set_pending_flush(false); + // TODO: need to unset flush reason? + return flush_req; +} + +ColumnFamilyData* DBImpl::PickCompactionFromQueue( + std::unique_ptr* token, LogBuffer* log_buffer) { + assert(!compaction_queue_.empty()); + assert(*token == nullptr); + autovector throttled_candidates; + ColumnFamilyData* cfd = nullptr; + while (!compaction_queue_.empty()) { + auto first_cfd = *compaction_queue_.begin(); + compaction_queue_.pop_front(); + assert(first_cfd->queued_for_compaction()); + if (!RequestCompactionToken(first_cfd, false, token, log_buffer)) { + throttled_candidates.push_back(first_cfd); + continue; + } + cfd = first_cfd; + cfd->set_queued_for_compaction(false); + break; + } + // Add throttled compaction candidates back to queue in the original order. + for (auto iter = throttled_candidates.rbegin(); + iter != throttled_candidates.rend(); ++iter) { + compaction_queue_.push_front(*iter); + } return cfd; } -void DBImpl::SchedulePendingFlush(ColumnFamilyData* cfd) { - if (!cfd->pending_flush() && cfd->imm()->IsFlushPending()) { - AddToFlushQueue(cfd); - ++unscheduled_flushes_; +void DBImpl::SchedulePendingFlush(const FlushRequest& flush_req, + FlushReason flush_reason) { + if (flush_req.empty()) { + return; + } + for (auto& iter : flush_req) { + ColumnFamilyData* cfd = iter.first; + cfd->Ref(); + cfd->SetFlushReason(flush_reason); } + unscheduled_flushes_ += static_cast(flush_req.size()); + flush_queue_.push_back(flush_req); } void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) { - if (!cfd->pending_compaction() && cfd->NeedsCompaction()) { + if (!cfd->queued_for_compaction() && cfd->NeedsCompaction()) { AddToCompactionQueue(cfd); ++unscheduled_compactions_; } } -void DBImpl::SchedulePendingPurge(std::string fname, FileType type, - uint64_t number, uint32_t path_id, - int job_id) { +void DBImpl::SchedulePendingPurge(std::string fname, std::string dir_to_sync, + FileType type, uint64_t number, int job_id) { mutex_.AssertHeld(); - PurgeFileInfo file_info(fname, type, number, path_id, job_id); + PurgeFileInfo file_info(fname, dir_to_sync, type, number, job_id); purge_queue_.push_back(std::move(file_info)); } -void DBImpl::BGWorkFlush(void* db) { - IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH); +void DBImpl::BGWorkFlush(void* arg) { + FlushThreadArg fta = *(reinterpret_cast(arg)); + delete reinterpret_cast(arg); + + IOSTATS_SET_THREAD_POOL_ID(fta.thread_pri_); TEST_SYNC_POINT("DBImpl::BGWorkFlush"); - reinterpret_cast(db)->BackgroundCallFlush(); + reinterpret_cast(fta.db_)->BackgroundCallFlush(fta.thread_pri_); TEST_SYNC_POINT("DBImpl::BGWorkFlush:done"); } @@ -1182,7 +1993,7 @@ void DBImpl::BGWorkPurge(void* db) { TEST_SYNC_POINT("DBImpl::BGWorkPurge:end"); } -void DBImpl::UnscheduleCallback(void* arg) { +void DBImpl::UnscheduleCompactionCallback(void* arg) { CompactionArg ca = *(reinterpret_cast(arg)); delete reinterpret_cast(arg); if (ca.prepicked_compaction != nullptr) { @@ -1191,62 +2002,93 @@ void DBImpl::UnscheduleCallback(void* arg) { } delete ca.prepicked_compaction; } - TEST_SYNC_POINT("DBImpl::UnscheduleCallback"); + TEST_SYNC_POINT("DBImpl::UnscheduleCompactionCallback"); +} + +void DBImpl::UnscheduleFlushCallback(void* arg) { + delete reinterpret_cast(arg); + TEST_SYNC_POINT("DBImpl::UnscheduleFlushCallback"); } Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context, - LogBuffer* log_buffer) { + LogBuffer* log_buffer, FlushReason* reason, + Env::Priority thread_pri) { mutex_.AssertHeld(); - Status status = bg_error_; - if (status.ok() && shutting_down_.load(std::memory_order_acquire)) { - status = Status::ShutdownInProgress(); + Status status; + *reason = FlushReason::kOthers; + // If BG work is stopped due to an error, but a recovery is in progress, + // that means this flush is part of the recovery. So allow it to go through + if (!error_handler_.IsBGWorkStopped()) { + if (shutting_down_.load(std::memory_order_acquire)) { + status = Status::ShutdownInProgress(); + } + } else if (!error_handler_.IsRecoveryInProgress()) { + status = error_handler_.GetBGError(); } if (!status.ok()) { return status; } - ColumnFamilyData* cfd = nullptr; + autovector bg_flush_args; + std::vector& superversion_contexts = + job_context->superversion_contexts; while (!flush_queue_.empty()) { // This cfd is already referenced - auto first_cfd = PopFirstFromFlushQueue(); - - if (first_cfd->IsDropped() || !first_cfd->imm()->IsFlushPending()) { - // can't flush this CF, try next one - if (first_cfd->Unref()) { - delete first_cfd; + const FlushRequest& flush_req = PopFirstFromFlushQueue(); + superversion_contexts.clear(); + superversion_contexts.reserve(flush_req.size()); + + for (const auto& iter : flush_req) { + ColumnFamilyData* cfd = iter.first; + if (cfd->IsDropped() || !cfd->imm()->IsFlushPending()) { + // can't flush this CF, try next one + if (cfd->Unref()) { + delete cfd; + } + continue; } - continue; + superversion_contexts.emplace_back(SuperVersionContext(true)); + bg_flush_args.emplace_back(cfd, iter.second, + &(superversion_contexts.back())); + } + if (!bg_flush_args.empty()) { + break; } - - // found a flush! - cfd = first_cfd; - break; } - if (cfd != nullptr) { - const MutableCFOptions mutable_cf_options = - *cfd->GetLatestMutableCFOptions(); + if (!bg_flush_args.empty()) { auto bg_job_limits = GetBGJobLimits(); - ROCKS_LOG_BUFFER( - log_buffer, - "Calling FlushMemTableToOutputFile with column " - "family [%s], flush slots available %d, compaction slots available %d, " - "flush slots scheduled %d, compaction slots scheduled %d", - cfd->GetName().c_str(), bg_job_limits.max_flushes, - bg_job_limits.max_compactions, bg_flush_scheduled_, - bg_compaction_scheduled_); - status = FlushMemTableToOutputFile(cfd, mutable_cf_options, made_progress, - job_context, log_buffer); - if (cfd->Unref()) { - delete cfd; + for (const auto& arg : bg_flush_args) { + ColumnFamilyData* cfd = arg.cfd_; + ROCKS_LOG_BUFFER( + log_buffer, + "Calling FlushMemTableToOutputFile with column " + "family [%s], flush slots available %d, compaction slots available " + "%d, " + "flush slots scheduled %d, compaction slots scheduled %d", + cfd->GetName().c_str(), bg_job_limits.max_flushes, + bg_job_limits.max_compactions, bg_flush_scheduled_, + bg_compaction_scheduled_); + } + status = FlushMemTablesToOutputFiles(bg_flush_args, made_progress, + job_context, log_buffer, thread_pri); + // All the CFDs in the FlushReq must have the same flush reason, so just + // grab the first one + *reason = bg_flush_args[0].cfd_->GetFlushReason(); + for (auto& arg : bg_flush_args) { + ColumnFamilyData* cfd = arg.cfd_; + if (cfd->Unref()) { + delete cfd; + arg.cfd_ = nullptr; + } } } return status; } -void DBImpl::BackgroundCallFlush() { +void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) { bool made_progress = false; JobContext job_context(next_job_id_.fetch_add(1), true); @@ -1261,15 +2103,18 @@ void DBImpl::BackgroundCallFlush() { auto pending_outputs_inserted_elem = CaptureCurrentFileNumberInPendingOutputs(); + FlushReason reason; - Status s = BackgroundFlush(&made_progress, &job_context, &log_buffer); - if (!s.ok() && !s.IsShutdownInProgress()) { + Status s = BackgroundFlush(&made_progress, &job_context, &log_buffer, + &reason, thread_pri); + if (!s.ok() && !s.IsShutdownInProgress() && + reason != FlushReason::kErrorRecovery) { // Wait a little bit before retrying background flush in // case this is an environmental problem and we do not want to // chew up resources for failed flushes for the duration of // the problem. uint64_t error_cnt = - default_cf_internal_stats_->BumpAndGetBackgroundErrorCount(); + default_cf_internal_stats_->BumpAndGetBackgroundErrorCount(); bg_cv_.SignalAll(); // In case a waiter can proceed despite the error mutex_.Unlock(); ROCKS_LOG_ERROR(immutable_db_options_.info_log, @@ -1282,14 +2127,17 @@ void DBImpl::BackgroundCallFlush() { mutex_.Lock(); } + TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FlushFinish:0"); ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem); // If flush failed, we want to delete all temporary files that we might have // created. Thus, we force full scan in FindObsoleteFiles() FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress()); // delete unnecessary files if any, this is done outside the mutex - if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) { + if (job_context.HaveSomethingToClean() || + job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) { mutex_.Unlock(); + TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound"); // Have to flush the info logs before bg_flush_scheduled_-- // because if bg_flush_scheduled_ becomes 0 and the lock is // released, the deconstructor of DB can kick in and destroy all the @@ -1302,12 +2150,14 @@ void DBImpl::BackgroundCallFlush() { job_context.Clean(); mutex_.Lock(); } + TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:ContextCleanedUp"); assert(num_running_flushes_ > 0); num_running_flushes_--; bg_flush_scheduled_--; // See if there's more work to be done MaybeScheduleFlushOrCompaction(); + atomic_flush_install_cv_.SignalAll(); bg_cv_.SignalAll(); // IMPORTANT: there should be no code after calling SignalAll. This call may // signal the DB destructor that it's OK to proceed with destruction. In @@ -1321,7 +2171,6 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction, bool made_progress = false; JobContext job_context(next_job_id_.fetch_add(1), true); TEST_SYNC_POINT("BackgroundCallCompaction:0"); - MaybeDumpStats(); LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, immutable_db_options_.info_log.get()); { @@ -1340,9 +2189,14 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction, bg_bottom_compaction_scheduled_) || (bg_thread_pri == Env::Priority::LOW && bg_compaction_scheduled_)); Status s = BackgroundCompaction(&made_progress, &job_context, &log_buffer, - prepicked_compaction); + prepicked_compaction, bg_thread_pri); TEST_SYNC_POINT("BackgroundCallCompaction:1"); - if (!s.ok() && !s.IsShutdownInProgress()) { + if (s.IsBusy()) { + bg_cv_.SignalAll(); // In case a waiter can proceed despite the error + mutex_.Unlock(); + env_->SleepForMicroseconds(10000); // prevent hot loop + mutex_.Lock(); + } else if (!s.ok() && !s.IsShutdownInProgress()) { // Wait a little bit before retrying background compaction in // case this is an environmental problem and we do not want to // chew up resources for failed compactions for the duration of @@ -1367,9 +2221,11 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction, // have created (they might not be all recorded in job_context in case of a // failure). Thus, we force full scan in FindObsoleteFiles() FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress()); + TEST_SYNC_POINT("DBImpl::BackgroundCallCompaction:FoundObsoleteFiles"); // delete unnecessary files if any, this is done outside the mutex - if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) { + if (job_context.HaveSomethingToClean() || + job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) { mutex_.Unlock(); // Have to flush the info logs before bg_compaction_scheduled_-- // because if bg_flush_scheduled_ becomes 0 and the lock is @@ -1379,6 +2235,7 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction, log_buffer.FlushBufferToLog(); if (job_context.HaveSomethingToDelete()) { PurgeObsoleteFiles(job_context); + TEST_SYNC_POINT("DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles"); } job_context.Clean(); mutex_.Lock(); @@ -1400,7 +2257,7 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction, if (made_progress || (bg_compaction_scheduled_ == 0 && bg_bottom_compaction_scheduled_ == 0) || - HasPendingManualCompaction()) { + HasPendingManualCompaction() || unscheduled_compactions_ == 0) { // signal if // * made_progress -- need to wakeup DelayWrite // * bg_{bottom,}_compaction_scheduled_ == 0 -- need to wakeup ~DBImpl @@ -1419,7 +2276,8 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction, Status DBImpl::BackgroundCompaction(bool* made_progress, JobContext* job_context, LogBuffer* log_buffer, - PrepickedCompaction* prepicked_compaction) { + PrepickedCompaction* prepicked_compaction, + Env::Priority thread_pri) { ManualCompactionState* manual_compaction = prepicked_compaction == nullptr ? nullptr @@ -1429,7 +2287,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, TEST_SYNC_POINT("DBImpl::BackgroundCompaction:Start"); bool is_manual = (manual_compaction != nullptr); - unique_ptr c; + std::unique_ptr c; if (prepicked_compaction != nullptr && prepicked_compaction->compaction != nullptr) { c.reset(prepicked_compaction->compaction); @@ -1441,9 +2299,18 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, is_manual && manual_compaction->disallow_trivial_move; CompactionJobStats compaction_job_stats; - Status status = bg_error_; - if (status.ok() && shutting_down_.load(std::memory_order_acquire)) { - status = Status::ShutdownInProgress(); + Status status; + if (!error_handler_.IsBGWorkStopped()) { + if (shutting_down_.load(std::memory_order_acquire)) { + status = Status::ShutdownInProgress(); + } + } else { + status = error_handler_.GetBGError(); + // If we get here, it means a hard error happened after this compaction + // was scheduled by MaybeScheduleFlushOrCompaction(), but before it got + // a chance to execute. Since we didn't pop a cfd from the compaction + // queue, increment unscheduled_compactions_ + unscheduled_compactions_++; } if (!status.ok()) { @@ -1453,6 +2320,10 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, manual_compaction->in_progress = false; manual_compaction = nullptr; } + if (c) { + c->ReleaseCompactionFiles(status); + c.reset(); + } return status; } @@ -1461,8 +2332,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, manual_compaction->in_progress = true; } + std::unique_ptr task_token; + // InternalKey manual_end_storage; // InternalKey* manual_end = &manual_end_storage; + bool sfm_reserved_compact_space = false; if (is_manual) { ManualCompactionState* m = manual_compaction; assert(m->in_progress); @@ -1476,19 +2350,32 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, (m->begin ? m->begin->DebugString().c_str() : "(begin)"), (m->end ? m->end->DebugString().c_str() : "(end)")); } else { - ROCKS_LOG_BUFFER( - log_buffer, - "[%s] Manual compaction from level-%d to level-%d from %s .. " - "%s; will stop at %s\n", - m->cfd->GetName().c_str(), m->input_level, c->output_level(), - (m->begin ? m->begin->DebugString().c_str() : "(begin)"), - (m->end ? m->end->DebugString().c_str() : "(end)"), - ((m->done || m->manual_end == nullptr) - ? "(end)" - : m->manual_end->DebugString().c_str())); + // First check if we have enough room to do the compaction + bool enough_room = EnoughRoomForCompaction( + m->cfd, *(c->inputs()), &sfm_reserved_compact_space, log_buffer); + + if (!enough_room) { + // Then don't do the compaction + c->ReleaseCompactionFiles(status); + c.reset(); + // m's vars will get set properly at the end of this function, + // as long as status == CompactionTooLarge + status = Status::CompactionTooLarge(); + } else { + ROCKS_LOG_BUFFER( + log_buffer, + "[%s] Manual compaction from level-%d to level-%d from %s .. " + "%s; will stop at %s\n", + m->cfd->GetName().c_str(), m->input_level, c->output_level(), + (m->begin ? m->begin->DebugString().c_str() : "(begin)"), + (m->end ? m->end->DebugString().c_str() : "(end)"), + ((m->done || m->manual_end == nullptr) + ? "(end)" + : m->manual_end->DebugString().c_str())); + } } } else if (!is_prepicked && !compaction_queue_.empty()) { - if (HaveManualCompaction(compaction_queue_.front())) { + if (HasExclusiveManualCompaction()) { // Can't compact right now, but try again later TEST_SYNC_POINT("DBImpl::BackgroundCompaction()::Conflict"); @@ -1498,17 +2385,23 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, return Status::OK(); } - // cfd is referenced here - auto cfd = PopFirstFromCompactionQueue(); + auto cfd = PickCompactionFromQueue(&task_token, log_buffer); + if (cfd == nullptr) { + // Can't find any executable task from the compaction queue. + // All tasks have been throttled by compaction thread limiter. + ++unscheduled_compactions_; + return Status::Busy(); + } + // We unreference here because the following code will take a Ref() on // this cfd if it is going to use it (Compaction class holds a // reference). // This will all happen under a mutex so we don't have to be afraid of // somebody else deleting it. if (cfd->Unref()) { - delete cfd; // This was the last reference of the column family, so no need to // compact. + delete cfd; return Status::OK(); } @@ -1525,27 +2418,48 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, TEST_SYNC_POINT("DBImpl::BackgroundCompaction():BeforePickCompaction"); c.reset(cfd->PickCompaction(*mutable_cf_options, log_buffer)); TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction"); + if (c != nullptr) { - // update statistics - MeasureTime(stats_, NUM_FILES_IN_SINGLE_COMPACTION, - c->inputs(0)->size()); - // There are three things that can change compaction score: - // 1) When flush or compaction finish. This case is covered by - // InstallSuperVersionAndScheduleWork - // 2) When MutableCFOptions changes. This case is also covered by - // InstallSuperVersionAndScheduleWork, because this is when the new - // options take effect. - // 3) When we Pick a new compaction, we "remove" those files being - // compacted from the calculation, which then influences compaction - // score. Here we check if we need the new compaction even without the - // files that are currently being compacted. If we need another - // compaction, we might be able to execute it in parallel, so we add it - // to the queue and schedule a new thread. - if (cfd->NeedsCompaction()) { - // Yes, we need more compactions! + bool enough_room = EnoughRoomForCompaction( + cfd, *(c->inputs()), &sfm_reserved_compact_space, log_buffer); + + if (!enough_room) { + // Then don't do the compaction + c->ReleaseCompactionFiles(status); + c->column_family_data() + ->current() + ->storage_info() + ->ComputeCompactionScore(*(c->immutable_cf_options()), + *(c->mutable_cf_options())); AddToCompactionQueue(cfd); ++unscheduled_compactions_; - MaybeScheduleFlushOrCompaction(); + + c.reset(); + // Don't need to sleep here, because BackgroundCallCompaction + // will sleep if !s.ok() + status = Status::CompactionTooLarge(); + } else { + // update statistics + RecordInHistogram(stats_, NUM_FILES_IN_SINGLE_COMPACTION, + c->inputs(0)->size()); + // There are three things that can change compaction score: + // 1) When flush or compaction finish. This case is covered by + // InstallSuperVersionAndScheduleWork + // 2) When MutableCFOptions changes. This case is also covered by + // InstallSuperVersionAndScheduleWork, because this is when the new + // options take effect. + // 3) When we Pick a new compaction, we "remove" those files being + // compacted from the calculation, which then influences compaction + // score. Here we check if we need the new compaction even without the + // files that are currently being compacted. If we need another + // compaction, we might be able to execute it in parallel, so we add + // it to the queue and schedule a new thread. + if (cfd->NeedsCompaction()) { + // Yes, we need more compactions! + AddToCompactionQueue(cfd); + ++unscheduled_compactions_; + MaybeScheduleFlushOrCompaction(); + } } } } @@ -1557,6 +2471,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, } else if (c->deletion_compaction()) { // TODO(icanadi) Do we want to honor snapshots here? i.e. not delete old // file if there is alive snapshot pointing to it + TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction", + c->column_family_data()); assert(c->num_input_files(1) == 0); assert(c->level() == 0); assert(c->column_family_data()->ioptions()->compaction_style == @@ -1564,20 +2480,28 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, compaction_job_stats.num_input_files = c->num_input_files(0); + NotifyOnCompactionBegin(c->column_family_data(), c.get(), status, + compaction_job_stats, job_context->job_id); + for (const auto& f : *c->inputs(0)) { c->edit()->DeleteFile(c->level(), f->fd.GetNumber()); } status = versions_->LogAndApply(c->column_family_data(), *c->mutable_cf_options(), c->edit(), &mutex_, directories_.GetDbDir()); - InstallSuperVersionAndScheduleWorkWrapper( - c->column_family_data(), job_context, *c->mutable_cf_options()); + InstallSuperVersionAndScheduleWork(c->column_family_data(), + &job_context->superversion_contexts[0], + *c->mutable_cf_options()); ROCKS_LOG_BUFFER(log_buffer, "[%s] Deleted %d files\n", c->column_family_data()->GetName().c_str(), c->num_input_files(0)); *made_progress = true; + TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction", + c->column_family_data()); } else if (!trivial_move_disallowed && c->IsTrivialMove()) { TEST_SYNC_POINT("DBImpl::BackgroundCompaction:TrivialMove"); + TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction", + c->column_family_data()); // Instrument for event update // TODO(yhchiang): add op details for showing trivial-move. ThreadStatusUtil::SetColumnFamily( @@ -1587,6 +2511,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, compaction_job_stats.num_input_files = c->num_input_files(0); + NotifyOnCompactionBegin(c->column_family_data(), c.get(), status, + compaction_job_stats, job_context->job_id); + // Move files to next level int32_t moved_files = 0; int64_t moved_bytes = 0; @@ -1599,14 +2526,14 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, c->edit()->DeleteFile(c->level(l), f->fd.GetNumber()); c->edit()->AddFile(c->output_level(), f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest, - f->largest, f->smallest_seqno, f->largest_seqno, - f->marked_for_compaction); - - ROCKS_LOG_BUFFER(log_buffer, "[%s] Moving #%" PRIu64 - " to level-%d %" PRIu64 " bytes\n", - c->column_family_data()->GetName().c_str(), - f->fd.GetNumber(), c->output_level(), - f->fd.GetFileSize()); + f->largest, f->fd.smallest_seqno, + f->fd.largest_seqno, f->marked_for_compaction); + + ROCKS_LOG_BUFFER( + log_buffer, + "[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n", + c->column_family_data()->GetName().c_str(), f->fd.GetNumber(), + c->output_level(), f->fd.GetFileSize()); ++moved_files; moved_bytes += f->fd.GetFileSize(); } @@ -1616,8 +2543,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, *c->mutable_cf_options(), c->edit(), &mutex_, directories_.GetDbDir()); // Use latest MutableCFOptions - InstallSuperVersionAndScheduleWorkWrapper( - c->column_family_data(), job_context, *c->mutable_cf_options()); + InstallSuperVersionAndScheduleWork(c->column_family_data(), + &job_context->superversion_contexts[0], + *c->mutable_cf_options()); VersionStorageInfo::LevelSummaryStorage tmp; c->column_family_data()->internal_stats()->IncBytesMoved(c->output_level(), @@ -1639,9 +2567,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, // Clear Instrument ThreadStatusUtil::ResetThreadStatus(); - } else if (c->column_family_data()->ioptions()->compaction_style == - kCompactionStyleUniversal && - !is_prepicked && c->output_level() > 0 && + TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction", + c->column_family_data()); + } else if (!is_prepicked && c->output_level() > 0 && c->output_level() == c->column_family_data() ->current() @@ -1649,39 +2577,48 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, ->MaxOutputLevel( immutable_db_options_.allow_ingest_behind) && env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) { - // Forward universal compactions involving last level to the bottom pool - // if it exists, such that long-running compactions can't block short- - // lived ones, like L0->L0s. + // Forward compactions involving last level to the bottom pool if it exists, + // such that compactions unlikely to contribute to write stalls can be + // delayed or deprioritized. TEST_SYNC_POINT("DBImpl::BackgroundCompaction:ForwardToBottomPriPool"); CompactionArg* ca = new CompactionArg; ca->db = this; ca->prepicked_compaction = new PrepickedCompaction; ca->prepicked_compaction->compaction = c.release(); ca->prepicked_compaction->manual_compaction_state = nullptr; + // Transfer requested token, so it doesn't need to do it again. + ca->prepicked_compaction->task_token = std::move(task_token); ++bg_bottom_compaction_scheduled_; env_->Schedule(&DBImpl::BGWorkBottomCompaction, ca, Env::Priority::BOTTOM, - this, &DBImpl::UnscheduleCallback); + this, &DBImpl::UnscheduleCompactionCallback); } else { - int output_level __attribute__((unused)) = c->output_level(); + TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction", + c->column_family_data()); + int output_level __attribute__((__unused__)); + output_level = c->output_level(); TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial", &output_level); - + std::vector snapshot_seqs; SequenceNumber earliest_write_conflict_snapshot; - std::vector snapshot_seqs = - snapshots_.GetAll(&earliest_write_conflict_snapshot); - + SnapshotChecker* snapshot_checker; + GetSnapshotContext(job_context, &snapshot_seqs, + &earliest_write_conflict_snapshot, &snapshot_checker); assert(is_snapshot_supported_ || snapshots_.empty()); CompactionJob compaction_job( - job_context->job_id, c.get(), immutable_db_options_, env_options_, - versions_.get(), &shutting_down_, log_buffer, directories_.GetDbDir(), - directories_.GetDataDir(c->output_path_id()), stats_, &mutex_, - &bg_error_, snapshot_seqs, earliest_write_conflict_snapshot, - table_cache_, &event_logger_, - c->mutable_cf_options()->paranoid_file_checks, + job_context->job_id, c.get(), immutable_db_options_, + env_options_for_compaction_, versions_.get(), &shutting_down_, + preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(), + GetDataDir(c->column_family_data(), c->output_path_id()), stats_, + &mutex_, &error_handler_, snapshot_seqs, + earliest_write_conflict_snapshot, snapshot_checker, table_cache_, + &event_logger_, c->mutable_cf_options()->paranoid_file_checks, c->mutable_cf_options()->report_bg_io_stats, dbname_, - &compaction_job_stats); + &compaction_job_stats, thread_pri); compaction_job.Prepare(); + NotifyOnCompactionBegin(c->column_family_data(), c.get(), status, + compaction_job_stats, job_context->job_id); + mutex_.Unlock(); compaction_job.Run(); TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun"); @@ -1689,39 +2626,59 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, status = compaction_job.Install(*c->mutable_cf_options()); if (status.ok()) { - InstallSuperVersionAndScheduleWorkWrapper( - c->column_family_data(), job_context, *c->mutable_cf_options()); + InstallSuperVersionAndScheduleWork(c->column_family_data(), + &job_context->superversion_contexts[0], + *c->mutable_cf_options()); } *made_progress = true; + TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction", + c->column_family_data()); } if (c != nullptr) { c->ReleaseCompactionFiles(status); *made_progress = true; - NotifyOnCompactionCompleted( - c->column_family_data(), c.get(), status, - compaction_job_stats, job_context->job_id); + +#ifndef ROCKSDB_LITE + // Need to make sure SstFileManager does its bookkeeping + auto sfm = static_cast( + immutable_db_options_.sst_file_manager.get()); + if (sfm && sfm_reserved_compact_space) { + sfm->OnCompactionCompletion(c.get()); + } +#endif // ROCKSDB_LITE + + NotifyOnCompactionCompleted(c->column_family_data(), c.get(), status, + compaction_job_stats, job_context->job_id); } - // this will unref its input_version and column_family_data - c.reset(); - if (status.ok()) { + if (status.ok() || status.IsCompactionTooLarge()) { // Done } else if (status.IsShutdownInProgress()) { // Ignore compaction errors found during shutting down } else { ROCKS_LOG_WARN(immutable_db_options_.info_log, "Compaction error: %s", status.ToString().c_str()); - if (immutable_db_options_.paranoid_checks && bg_error_.ok()) { - Status new_bg_error = status; - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners, - BackgroundErrorReason::kCompaction, - &new_bg_error, &mutex_); - if (!new_bg_error.ok()) { - bg_error_ = new_bg_error; + error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction); + if (c != nullptr && !is_manual && !error_handler_.IsBGWorkStopped()) { + // Put this cfd back in the compaction queue so we can retry after some + // time + auto cfd = c->column_family_data(); + assert(cfd != nullptr); + // Since this compaction failed, we need to recompute the score so it + // takes the original input files into account + c->column_family_data() + ->current() + ->storage_info() + ->ComputeCompactionScore(*(c->immutable_cf_options()), + *(c->mutable_cf_options())); + if (!cfd->queued_for_compaction()) { + AddToCompactionQueue(cfd); + ++unscheduled_compactions_; } } } + // this will unref its input_version and column_family_data + c.reset(); if (is_manual) { ManualCompactionState* m = manual_compaction; @@ -1757,7 +2714,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress, m->begin = &m->tmp_storage; m->incomplete = true; } - m->in_progress = false; // not being processed anymore + m->in_progress = false; // not being processed anymore } TEST_SYNC_POINT("DBImpl::BackgroundCompaction:Finish"); return status; @@ -1856,30 +2813,59 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) { return true; } -// JobContext gets created and destructed outside of the lock -- -// we -// use this convinently to: +#ifndef ROCKSDB_LITE +void DBImpl::BuildCompactionJobInfo( + const ColumnFamilyData* cfd, Compaction* c, const Status& st, + const CompactionJobStats& compaction_job_stats, const int job_id, + const Version* current, CompactionJobInfo* compaction_job_info) const { + assert(compaction_job_info != nullptr); + compaction_job_info->cf_id = cfd->GetID(); + compaction_job_info->cf_name = cfd->GetName(); + compaction_job_info->status = st; + compaction_job_info->thread_id = env_->GetThreadID(); + compaction_job_info->job_id = job_id; + compaction_job_info->base_input_level = c->start_level(); + compaction_job_info->output_level = c->output_level(); + compaction_job_info->stats = compaction_job_stats; + compaction_job_info->table_properties = c->GetOutputTableProperties(); + compaction_job_info->compaction_reason = c->compaction_reason(); + compaction_job_info->compression = c->output_compression(); + for (size_t i = 0; i < c->num_input_levels(); ++i) { + for (const auto fmd : *c->inputs(i)) { + auto fn = TableFileName(c->immutable_cf_options()->cf_paths, + fmd->fd.GetNumber(), fmd->fd.GetPathId()); + compaction_job_info->input_files.push_back(fn); + if (compaction_job_info->table_properties.count(fn) == 0) { + std::shared_ptr tp; + auto s = current->GetTableProperties(&tp, fmd, &fn); + if (s.ok()) { + compaction_job_info->table_properties[fn] = tp; + } + } + } + } + for (const auto& newf : c->edit()->GetNewFiles()) { + compaction_job_info->output_files.push_back( + TableFileName(c->immutable_cf_options()->cf_paths, + newf.second.fd.GetNumber(), newf.second.fd.GetPathId())); + } +} +#endif + +// SuperVersionContext gets created and destructed outside of the lock -- +// we use this conveniently to: // * malloc one SuperVersion() outside of the lock -- new_superversion // * delete SuperVersion()s outside of the lock -- superversions_to_free // // However, if InstallSuperVersionAndScheduleWork() gets called twice with the -// same job_context, we can't reuse the SuperVersion() that got +// same sv_context, we can't reuse the SuperVersion() that got // malloced because // first call already used it. In that rare case, we take a hit and create a // new SuperVersion() inside of the mutex. We do similar thing // for superversion_to_free -void DBImpl::InstallSuperVersionAndScheduleWorkWrapper( - ColumnFamilyData* cfd, JobContext* job_context, - const MutableCFOptions& mutable_cf_options) { - mutex_.AssertHeld(); - SuperVersion* old_superversion = InstallSuperVersionAndScheduleWork( - cfd, job_context->new_superversion, mutable_cf_options); - job_context->new_superversion = nullptr; - job_context->superversions_to_free.push_back(old_superversion); -} -SuperVersion* DBImpl::InstallSuperVersionAndScheduleWork( - ColumnFamilyData* cfd, SuperVersion* new_sv, +void DBImpl::InstallSuperVersionAndScheduleWork( + ColumnFamilyData* cfd, SuperVersionContext* sv_context, const MutableCFOptions& mutable_cf_options) { mutex_.AssertHeld(); @@ -1891,20 +2877,98 @@ SuperVersion* DBImpl::InstallSuperVersionAndScheduleWork( old_sv->mutable_cf_options.max_write_buffer_number; } - auto* old = cfd->InstallSuperVersion( - new_sv ? new_sv : new SuperVersion(), &mutex_, mutable_cf_options); + // this branch is unlikely to step in + if (UNLIKELY(sv_context->new_superversion == nullptr)) { + sv_context->NewSuperVersion(); + } + cfd->InstallSuperVersion(sv_context, &mutex_, mutable_cf_options); + + // There may be a small data race here. The snapshot tricking bottommost + // compaction may already be released here. But assuming there will always be + // newer snapshot created and released frequently, the compaction will be + // triggered soon anyway. + bottommost_files_mark_threshold_ = kMaxSequenceNumber; + for (auto* my_cfd : *versions_->GetColumnFamilySet()) { + bottommost_files_mark_threshold_ = std::min( + bottommost_files_mark_threshold_, + my_cfd->current()->storage_info()->bottommost_files_mark_threshold()); + } // Whenever we install new SuperVersion, we might need to issue new flushes or // compactions. - SchedulePendingFlush(cfd); SchedulePendingCompaction(cfd); MaybeScheduleFlushOrCompaction(); // Update max_total_in_memory_state_ - max_total_in_memory_state_ = - max_total_in_memory_state_ - old_memtable_size + - mutable_cf_options.write_buffer_size * - mutable_cf_options.max_write_buffer_number; - return old; + max_total_in_memory_state_ = max_total_in_memory_state_ - old_memtable_size + + mutable_cf_options.write_buffer_size * + mutable_cf_options.max_write_buffer_number; +} + +// ShouldPurge is called by FindObsoleteFiles when doing a full scan, +// and db mutex (mutex_) should already be held. This function performs a +// linear scan of an vector (files_grabbed_for_purge_) in search of a +// certain element. We expect FindObsoleteFiles with full scan to occur once +// every 10 hours by default, and the size of the vector is small. +// Therefore, the cost is affordable even if the mutex is held. +// Actually, the current implementation of FindObsoleteFiles with +// full_scan=true can issue I/O requests to obtain list of files in +// directories, e.g. env_->getChildren while holding db mutex. +// In the future, if we want to reduce the cost of search, we may try to keep +// the vector sorted. +bool DBImpl::ShouldPurge(uint64_t file_number) const { + for (auto fn : files_grabbed_for_purge_) { + if (file_number == fn) { + return false; + } + } + for (const auto& purge_file_info : purge_queue_) { + if (purge_file_info.number == file_number) { + return false; + } + } + return true; +} + +// MarkAsGrabbedForPurge is called by FindObsoleteFiles, and db mutex +// (mutex_) should already be held. +void DBImpl::MarkAsGrabbedForPurge(uint64_t file_number) { + files_grabbed_for_purge_.emplace_back(file_number); +} + +void DBImpl::SetSnapshotChecker(SnapshotChecker* snapshot_checker) { + InstrumentedMutexLock l(&mutex_); + // snapshot_checker_ should only set once. If we need to set it multiple + // times, we need to make sure the old one is not deleted while it is still + // using by a compaction job. + assert(!snapshot_checker_); + snapshot_checker_.reset(snapshot_checker); +} + +void DBImpl::GetSnapshotContext( + JobContext* job_context, std::vector* snapshot_seqs, + SequenceNumber* earliest_write_conflict_snapshot, + SnapshotChecker** snapshot_checker_ptr) { + mutex_.AssertHeld(); + assert(job_context != nullptr); + assert(snapshot_seqs != nullptr); + assert(earliest_write_conflict_snapshot != nullptr); + assert(snapshot_checker_ptr != nullptr); + + *snapshot_checker_ptr = snapshot_checker_.get(); + if (use_custom_gc_ && *snapshot_checker_ptr == nullptr) { + *snapshot_checker_ptr = DisableGCSnapshotChecker::Instance(); + } + if (*snapshot_checker_ptr != nullptr) { + // If snapshot_checker is used, that means the flush/compaction may + // contain values not visible to snapshot taken after + // flush/compaction job starts. Take a snapshot and it will appear + // in snapshot_seqs and force compaction iterator to consider such + // snapshots. + const Snapshot* job_snapshot = + GetSnapshotImpl(false /*write_conflict_boundary*/, false /*lock*/); + job_context->job_snapshot.reset(new ManagedSnapshot(this, job_snapshot)); + } + *snapshot_seqs = snapshots_.GetAll(earliest_write_conflict_snapshot); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_impl_debug.cc b/thirdparty/rocksdb/db/db_impl_debug.cc index a4b378020a..982227149d 100644 --- a/thirdparty/rocksdb/db/db_impl_debug.cc +++ b/thirdparty/rocksdb/db/db_impl_debug.cc @@ -10,6 +10,7 @@ #ifndef NDEBUG #include "db/db_impl.h" +#include "db/error_handler.h" #include "monitoring/thread_status_updater.h" namespace rocksdb { @@ -19,10 +20,22 @@ uint64_t DBImpl::TEST_GetLevel0TotalSize() { return default_cf_handle_->cfd()->current()->storage_info()->NumLevelBytes(0); } -void DBImpl::TEST_HandleWALFull() { +void DBImpl::TEST_SwitchWAL() { WriteContext write_context; InstrumentedMutexLock l(&mutex_); - HandleWALFull(&write_context); + SwitchWAL(&write_context); +} + +bool DBImpl::TEST_WALBufferIsEmpty(bool lock) { + if (lock) { + log_write_mutex_.Lock(); + } + log::Writer* cur_log_writer = logs_.back().writer; + auto res = cur_log_writer->TEST_BufferIsEmpty(); + if (lock) { + log_write_mutex_.Unlock(); + } + return res; } int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes( @@ -60,6 +73,10 @@ uint64_t DBImpl::TEST_Current_Manifest_FileNo() { return versions_->manifest_file_number(); } +uint64_t DBImpl::TEST_Current_Next_FileNo() { + return versions_->current_next_file_number(); +} + Status DBImpl::TEST_CompactRange(int level, const Slice* begin, const Slice* end, ColumnFamilyHandle* column_family, @@ -76,7 +93,7 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin, cfd->ioptions()->compaction_style == kCompactionStyleFIFO) ? level : level + 1; - return RunManualCompaction(cfd, level, output_level, 0, begin, end, true, + return RunManualCompaction(cfd, level, output_level, 0, 0, begin, end, true, disallow_trivial_move); } @@ -89,9 +106,11 @@ Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) { return SwitchMemtable(cfd, &write_context); } -Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) { +Status DBImpl::TEST_FlushMemTable(bool wait, bool allow_write_stall, + ColumnFamilyHandle* cfh) { FlushOptions fo; fo.wait = wait; + fo.allow_write_stall = allow_write_stall; ColumnFamilyData* cfd; if (cfh == nullptr) { cfd = default_cf_handle_->cfd(); @@ -99,7 +118,7 @@ Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) { auto cfhi = reinterpret_cast(cfh); cfd = cfhi->cfd(); } - return FlushMemTable(cfd, fo); + return FlushMemTable(cfd, fo, FlushReason::kTest); } Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) { @@ -110,10 +129,10 @@ Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) { auto cfh = reinterpret_cast(column_family); cfd = cfh->cfd(); } - return WaitForFlushMemTable(cfd); + return WaitForFlushMemTable(cfd, nullptr, false); } -Status DBImpl::TEST_WaitForCompact() { +Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) { // Wait until the compaction completes // TODO: a bug here. This function actually does not necessarily @@ -122,20 +141,17 @@ Status DBImpl::TEST_WaitForCompact() { InstrumentedMutexLock l(&mutex_); while ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ || - bg_flush_scheduled_) && - bg_error_.ok()) { + bg_flush_scheduled_ || + (wait_unscheduled && unscheduled_compactions_)) && + (error_handler_.GetBGError() == Status::OK())) { bg_cv_.Wait(); } - return bg_error_; + return error_handler_.GetBGError(); } -void DBImpl::TEST_LockMutex() { - mutex_.Lock(); -} +void DBImpl::TEST_LockMutex() { mutex_.Lock(); } -void DBImpl::TEST_UnlockMutex() { - mutex_.Unlock(); -} +void DBImpl::TEST_UnlockMutex() { mutex_.Unlock(); } void* DBImpl::TEST_BeginWrite() { auto w = new WriteThread::Writer(); @@ -179,11 +195,21 @@ Status DBImpl::TEST_GetAllImmutableCFOptions( } uint64_t DBImpl::TEST_FindMinLogContainingOutstandingPrep() { - return FindMinLogContainingOutstandingPrep(); + return logs_with_prep_tracker_.FindMinLogContainingOutstandingPrep(); +} + +size_t DBImpl::TEST_PreparedSectionCompletedSize() { + return logs_with_prep_tracker_.TEST_PreparedSectionCompletedSize(); +} + +size_t DBImpl::TEST_LogsWithPrepSize() { + return logs_with_prep_tracker_.TEST_LogsWithPrepSize(); } uint64_t DBImpl::TEST_FindMinPrepLogReferencedByMemTable() { - return FindMinPrepLogReferencedByMemTable(); + autovector empty_list; + return FindMinPrepLogReferencedByMemTable(versions_.get(), nullptr, + empty_list); } Status DBImpl::TEST_GetLatestMutableCFOptions( @@ -205,5 +231,38 @@ int DBImpl::TEST_BGFlushesAllowed() const { return GetBGJobLimits().max_flushes; } +SequenceNumber DBImpl::TEST_GetLastVisibleSequence() const { + if (last_seq_same_as_publish_seq_) { + return versions_->LastSequence(); + } else { + return versions_->LastAllocatedSequence(); + } +} + +size_t DBImpl::TEST_GetWalPreallocateBlockSize( + uint64_t write_buffer_size) const { + InstrumentedMutexLock l(&mutex_); + return GetWalPreallocateBlockSize(write_buffer_size); +} + +void DBImpl::TEST_WaitForDumpStatsRun(std::function callback) const { + if (thread_dump_stats_ != nullptr) { + thread_dump_stats_->TEST_WaitForRun(callback); + } +} + +void DBImpl::TEST_WaitForPersistStatsRun(std::function callback) const { + if (thread_persist_stats_ != nullptr) { + thread_persist_stats_->TEST_WaitForRun(callback); + } +} + +bool DBImpl::TEST_IsPersistentStatsEnabled() const { + return thread_persist_stats_ && thread_persist_stats_->IsRunning(); +} + +size_t DBImpl::TEST_EstiamteStatsHistorySize() const { + return EstiamteStatsHistorySize(); +} } // namespace rocksdb #endif // NDEBUG diff --git a/thirdparty/rocksdb/db/db_impl_experimental.cc b/thirdparty/rocksdb/db/db_impl_experimental.cc index 0d010758e6..47a880199e 100644 --- a/thirdparty/rocksdb/db/db_impl_experimental.cc +++ b/thirdparty/rocksdb/db/db_impl_experimental.cc @@ -30,10 +30,10 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family, auto cfd = cfh->cfd(); InternalKey start_key, end_key; if (begin != nullptr) { - start_key.SetMaxPossibleForUserKey(*begin); + start_key.SetMinPossibleForUserKey(*begin); } if (end != nullptr) { - end_key.SetMinPossibleForUserKey(*end); + end_key.SetMaxPossibleForUserKey(*end); } { InstrumentedMutexLock l(&mutex_); @@ -131,15 +131,16 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) { edit.DeleteFile(0, f->fd.GetNumber()); edit.AddFile(target_level, f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest, f->largest, - f->smallest_seqno, f->largest_seqno, + f->fd.smallest_seqno, f->fd.largest_seqno, f->marked_for_compaction); } status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), &edit, &mutex_, directories_.GetDbDir()); if (status.ok()) { - InstallSuperVersionAndScheduleWorkWrapper( - cfd, &job_context, *cfd->GetLatestMutableCFOptions()); + InstallSuperVersionAndScheduleWork(cfd, + &job_context.superversion_contexts[0], + *cfd->GetLatestMutableCFOptions()); } } // lock released here LogFlush(immutable_db_options_.info_log); diff --git a/thirdparty/rocksdb/db/db_impl_files.cc b/thirdparty/rocksdb/db/db_impl_files.cc index e44e423189..90cc6a14ba 100644 --- a/thirdparty/rocksdb/db/db_impl_files.cc +++ b/thirdparty/rocksdb/db/db_impl_files.cc @@ -12,120 +12,29 @@ #define __STDC_FORMAT_MACROS #endif #include +#include +#include #include "db/event_helpers.h" +#include "db/memtable_list.h" #include "util/file_util.h" #include "util/sst_file_manager_impl.h" - namespace rocksdb { -uint64_t DBImpl::FindMinPrepLogReferencedByMemTable() { - if (!allow_2pc()) { - return 0; - } - - uint64_t min_log = 0; - - // we must look through the memtables for two phase transactions - // that have been committed but not yet flushed - for (auto loop_cfd : *versions_->GetColumnFamilySet()) { - if (loop_cfd->IsDropped()) { - continue; - } - - auto log = loop_cfd->imm()->GetMinLogContainingPrepSection(); - - if (log > 0 && (min_log == 0 || log < min_log)) { - min_log = log; - } - - log = loop_cfd->mem()->GetMinLogContainingPrepSection(); - - if (log > 0 && (min_log == 0 || log < min_log)) { - min_log = log; - } - } - - return min_log; -} - -void DBImpl::MarkLogAsHavingPrepSectionFlushed(uint64_t log) { - assert(log != 0); - std::lock_guard lock(prep_heap_mutex_); - auto it = prepared_section_completed_.find(log); - assert(it != prepared_section_completed_.end()); - it->second += 1; -} - -void DBImpl::MarkLogAsContainingPrepSection(uint64_t log) { - assert(log != 0); - std::lock_guard lock(prep_heap_mutex_); - min_log_with_prep_.push(log); - auto it = prepared_section_completed_.find(log); - if (it == prepared_section_completed_.end()) { - prepared_section_completed_[log] = 0; - } -} - -uint64_t DBImpl::FindMinLogContainingOutstandingPrep() { - - if (!allow_2pc()) { - return 0; - } - - std::lock_guard lock(prep_heap_mutex_); - uint64_t min_log = 0; - - // first we look in the prepared heap where we keep - // track of transactions that have been prepared (written to WAL) - // but not yet committed. - while (!min_log_with_prep_.empty()) { - min_log = min_log_with_prep_.top(); - - auto it = prepared_section_completed_.find(min_log); - - // value was marked as 'deleted' from heap - if (it != prepared_section_completed_.end() && it->second > 0) { - it->second -= 1; - min_log_with_prep_.pop(); - - // back to squere one... - min_log = 0; - continue; - } else { - // found a valid value - break; - } - } - - return min_log; -} uint64_t DBImpl::MinLogNumberToKeep() { - uint64_t log_number = versions_->MinLogNumber(); - if (allow_2pc()) { - // if are 2pc we must consider logs containing prepared - // sections of outstanding transactions. - // - // We must check min logs with outstanding prep before we check - // logs referneces by memtables because a log referenced by the - // first data structure could transition to the second under us. - // - // TODO(horuff): iterating over all column families under db mutex. - // should find more optimial solution - auto min_log_in_prep_heap = FindMinLogContainingOutstandingPrep(); - - if (min_log_in_prep_heap != 0 && min_log_in_prep_heap < log_number) { - log_number = min_log_in_prep_heap; - } - - auto min_log_refed_by_mem = FindMinPrepLogReferencedByMemTable(); + return versions_->min_log_number_to_keep_2pc(); + } else { + return versions_->MinLogNumberWithUnflushedData(); + } +} - if (min_log_refed_by_mem != 0 && min_log_refed_by_mem < log_number) { - log_number = min_log_refed_by_mem; - } +uint64_t DBImpl::MinObsoleteSstNumberToKeep() { + mutex_.AssertHeld(); + if (!pending_outputs_.empty()) { + return *pending_outputs_.begin(); } - return log_number; + return std::numeric_limits::max(); } // * Returns the list of live files in 'sst_live' @@ -148,7 +57,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, bool doing_the_full_scan = false; - // logic for figurint out if we're doing the full scan + // logic for figuring out if we're doing the full scan if (no_full_scan) { doing_the_full_scan = false; } else if (force || @@ -168,7 +77,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, // threads // Since job_context->min_pending_output is set, until file scan finishes, // mutex_ cannot be released. Otherwise, we might see no min_pending_output - // here but later find newer generated unfinalized files while scannint. + // here but later find newer generated unfinalized files while scanning. if (!pending_outputs_.empty()) { job_context->min_pending_output = *pending_outputs_.begin(); } else { @@ -182,27 +91,68 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, &job_context->manifest_delete_files, job_context->min_pending_output); + // Mark the elements in job_context->sst_delete_files as grabbedForPurge + // so that other threads calling FindObsoleteFiles with full_scan=true + // will not add these files to candidate list for purge. + for (const auto& sst_to_del : job_context->sst_delete_files) { + MarkAsGrabbedForPurge(sst_to_del.metadata->fd.GetNumber()); + } + // store the current filenum, lognum, etc job_context->manifest_file_number = versions_->manifest_file_number(); job_context->pending_manifest_file_number = versions_->pending_manifest_file_number(); job_context->log_number = MinLogNumberToKeep(); - job_context->prev_log_number = versions_->prev_log_number(); versions_->AddLiveFiles(&job_context->sst_live); if (doing_the_full_scan) { + InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(), + dbname_); + std::set paths; for (size_t path_id = 0; path_id < immutable_db_options_.db_paths.size(); path_id++) { + paths.insert(immutable_db_options_.db_paths[path_id].path); + } + + // Note that if cf_paths is not specified in the ColumnFamilyOptions + // of a particular column family, we use db_paths as the cf_paths + // setting. Hence, there can be multiple duplicates of files from db_paths + // in the following code. The duplicate are removed while identifying + // unique files in PurgeObsoleteFiles. + for (auto cfd : *versions_->GetColumnFamilySet()) { + for (size_t path_id = 0; path_id < cfd->ioptions()->cf_paths.size(); + path_id++) { + auto& path = cfd->ioptions()->cf_paths[path_id].path; + + if (paths.find(path) == paths.end()) { + paths.insert(path); + } + } + } + + for (auto& path : paths) { // set of all files in the directory. We'll exclude files that are still // alive in the subsequent processings. std::vector files; - env_->GetChildren(immutable_db_options_.db_paths[path_id].path, - &files); // Ignore errors - for (std::string file : files) { + env_->GetChildren(path, &files); // Ignore errors + for (const std::string& file : files) { + uint64_t number; + FileType type; + // 1. If we cannot parse the file name, we skip; + // 2. If the file with file_number equals number has already been + // grabbed for purge by another compaction job, or it has already been + // schedule for purge, we also skip it if we + // are doing full scan in order to avoid double deletion of the same + // file under race conditions. See + // https://github.com/facebook/rocksdb/issues/3573 + if (!ParseFileName(file, &number, info_log_prefix.prefix, &type) || + !ShouldPurge(number)) { + continue; + } + // TODO(icanadi) clean up this mess to avoid having one-off "/" prefixes - job_context->full_scan_candidate_files.emplace_back( - "/" + file, static_cast(path_id)); + job_context->full_scan_candidate_files.emplace_back("/" + file, path); } } @@ -211,8 +161,9 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, std::vector log_files; env_->GetChildren(immutable_db_options_.wal_dir, &log_files); // Ignore errors - for (std::string log_file : log_files) { - job_context->full_scan_candidate_files.emplace_back(log_file, 0); + for (const std::string& log_file : log_files) { + job_context->full_scan_candidate_files.emplace_back( + log_file, immutable_db_options_.wal_dir); } } // Add info log files in db_log_dir @@ -221,8 +172,9 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, std::vector info_log_files; // Ignore errors env_->GetChildren(immutable_db_options_.db_log_dir, &info_log_files); - for (std::string log_file : info_log_files) { - job_context->full_scan_candidate_files.emplace_back(log_file, 0); + for (std::string& log_file : info_log_files) { + job_context->full_scan_candidate_files.emplace_back( + log_file, immutable_db_options_.db_log_dir); } } } @@ -236,11 +188,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, while (alive_log_files_.begin()->number < min_log_number) { auto& earliest = *alive_log_files_.begin(); if (immutable_db_options_.recycle_log_file_num > - log_recycle_files.size()) { + log_recycle_files_.size()) { ROCKS_LOG_INFO(immutable_db_options_.info_log, "adding log %" PRIu64 " to recycle list\n", earliest.number); - log_recycle_files.push_back(earliest.number); + log_recycle_files_.push_back(earliest.number); } else { job_context->log_delete_files.push_back(earliest.number); } @@ -250,11 +202,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, } job_context->size_log_to_delete += earliest.size; total_log_size_ -= earliest.size; - if (concurrent_prepare_) { + if (two_write_queues_) { log_write_mutex_.Lock(); } alive_log_files_.pop_front(); - if (concurrent_prepare_) { + if (two_write_queues_) { log_write_mutex_.Unlock(); } // Current log should always stay alive since it can't have @@ -281,8 +233,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force, // We're just cleaning up for DB::Write(). assert(job_context->logs_to_free.empty()); job_context->logs_to_free = logs_to_free_; - job_context->log_recycle_files.assign(log_recycle_files.begin(), - log_recycle_files.end()); + job_context->log_recycle_files.assign(log_recycle_files_.begin(), + log_recycle_files_.end()); + if (job_context->HaveSomethingToDelete()) { + ++pending_purge_obsolete_files_; + } logs_to_free_.clear(); } @@ -294,21 +249,24 @@ bool CompareCandidateFile(const JobContext::CandidateFileInfo& first, } else if (first.file_name < second.file_name) { return false; } else { - return (first.path_id > second.path_id); + return (first.file_path > second.file_path); } } }; // namespace // Delete obsolete files and log status and information of file deletion -void DBImpl::DeleteObsoleteFileImpl(Status file_deletion_status, int job_id, - const std::string& fname, FileType type, - uint64_t number, uint32_t path_id) { - if (type == kTableFile) { +void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname, + const std::string& path_to_sync, + FileType type, uint64_t number) { + Status file_deletion_status; + if (type == kTableFile || type == kLogFile) { file_deletion_status = - DeleteSSTFile(&immutable_db_options_, fname, path_id); + DeleteDBFile(&immutable_db_options_, fname, path_to_sync); } else { file_deletion_status = env_->DeleteFile(fname); } + TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl:AfterDeletion", + &file_deletion_status); if (file_deletion_status.ok()) { ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[JOB %d] Delete %s type=%d #%" PRIu64 " -- %s\n", job_id, @@ -335,19 +293,16 @@ void DBImpl::DeleteObsoleteFileImpl(Status file_deletion_status, int job_id, } // Diffs the files listed in filenames and those that do not -// belong to live files are posibly removed. Also, removes all the +// belong to live files are possibly removed. Also, removes all the // files in sst_delete_files and log_delete_files. // It is not necessary to hold the mutex when invoking this method. -void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { +void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) { + TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:Begin"); // we'd better have sth to delete assert(state.HaveSomethingToDelete()); - // this checks if FindObsoleteFiles() was run before. If not, don't do - // PurgeObsoleteFiles(). If FindObsoleteFiles() was run, we need to also - // run PurgeObsoleteFiles(), even if disable_delete_obsolete_files_ is true - if (state.manifest_file_number == 0) { - return; - } + // FindObsoleteFiles() should've populated this so nonzero + assert(state.manifest_file_number != 0); // Now, convert live list to an unordered map, WITHOUT mutex held; // set is slow. @@ -364,23 +319,24 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { state.log_delete_files.size() + state.manifest_delete_files.size()); // We may ignore the dbname when generating the file names. const char* kDumbDbName = ""; - for (auto file : state.sst_delete_files) { + for (auto& file : state.sst_delete_files) { candidate_files.emplace_back( - MakeTableFileName(kDumbDbName, file->fd.GetNumber()), - file->fd.GetPathId()); - if (file->table_reader_handle) { - table_cache_->Release(file->table_reader_handle); + MakeTableFileName(kDumbDbName, file.metadata->fd.GetNumber()), + file.path); + if (file.metadata->table_reader_handle) { + table_cache_->Release(file.metadata->table_reader_handle); } - delete file; + file.DeleteMetadata(); } for (auto file_num : state.log_delete_files) { if (file_num > 0) { - candidate_files.emplace_back(LogFileName(kDumbDbName, file_num), 0); + candidate_files.emplace_back(LogFileName(kDumbDbName, file_num), + immutable_db_options_.wal_dir); } } for (const auto& filename : state.manifest_delete_files) { - candidate_files.emplace_back(filename, 0); + candidate_files.emplace_back(filename, dbname_); } // dedup state.candidate_files so we don't try to delete the same @@ -403,9 +359,32 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { std::vector old_info_log_files; InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(), dbname_); + + // File numbers of most recent two OPTIONS file in candidate_files (found in + // previos FindObsoleteFiles(full_scan=true)) + // At this point, there must not be any duplicate file numbers in + // candidate_files. + uint64_t optsfile_num1 = std::numeric_limits::min(); + uint64_t optsfile_num2 = std::numeric_limits::min(); for (const auto& candidate_file : candidate_files) { - std::string to_delete = candidate_file.file_name; - uint32_t path_id = candidate_file.path_id; + const std::string& fname = candidate_file.file_name; + uint64_t number; + FileType type; + if (!ParseFileName(fname, &number, info_log_prefix.prefix, &type) || + type != kOptionsFile) { + continue; + } + if (number > optsfile_num1) { + optsfile_num2 = optsfile_num1; + optsfile_num1 = number; + } else if (number > optsfile_num2) { + optsfile_num2 = number; + } + } + + std::unordered_set files_to_del; + for (const auto& candidate_file : candidate_files) { + const std::string& to_delete = candidate_file.file_name; uint64_t number; FileType type; // Ignore file if we cannot recognize it. @@ -431,6 +410,9 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { // DontDeletePendingOutputs fail keep = (sst_live_map.find(number) != sst_live_map.end()) || number >= state.min_pending_output; + if (!keep) { + files_to_del.insert(number); + } break; case kTempFile: // Any temp files that are currently being written to must @@ -451,11 +433,19 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { old_info_log_files.push_back(to_delete); } break; + case kOptionsFile: + keep = (number >= optsfile_num2); + TEST_SYNC_POINT_CALLBACK( + "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:1", + reinterpret_cast(&number)); + TEST_SYNC_POINT_CALLBACK( + "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:2", + reinterpret_cast(&keep)); + break; case kCurrentFile: case kDBLockFile: case kIdentityFile: case kMetaDatabase: - case kOptionsFile: case kBlobFile: keep = true; break; @@ -466,13 +456,21 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { } std::string fname; + std::string dir_to_sync; if (type == kTableFile) { // evict from cache TableCache::Evict(table_cache_.get(), number); - fname = TableFileName(immutable_db_options_.db_paths, number, path_id); + fname = MakeTableFileName(candidate_file.file_path, number); + dir_to_sync = candidate_file.file_path; } else { - fname = ((type == kLogFile) ? immutable_db_options_.wal_dir : dbname_) + - "/" + to_delete; + dir_to_sync = + (type == kLogFile) ? immutable_db_options_.wal_dir : dbname_; + fname = dir_to_sync + + ((!dir_to_sync.empty() && dir_to_sync.back() == '/') || + (!to_delete.empty() && to_delete.front() == '/') + ? "" + : "/") + + to_delete; } #ifndef ROCKSDB_LITE @@ -486,13 +484,25 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { Status file_deletion_status; if (schedule_only) { InstrumentedMutexLock guard_lock(&mutex_); - SchedulePendingPurge(fname, type, number, path_id, state.job_id); + SchedulePendingPurge(fname, dir_to_sync, type, number, state.job_id); } else { - DeleteObsoleteFileImpl(file_deletion_status, state.job_id, fname, type, - number, path_id); + DeleteObsoleteFileImpl(state.job_id, fname, dir_to_sync, type, number); } } + { + // After purging obsolete files, remove them from files_grabbed_for_purge_. + // Use a temporary vector to perform bulk deletion via swap. + InstrumentedMutexLock guard_lock(&mutex_); + std::vector tmp; + for (auto fn : files_grabbed_for_purge_) { + if (files_to_del.count(fn) == 0) { + tmp.emplace_back(fn); + } + } + files_grabbed_for_purge_.swap(tmp); + } + // Delete old info log files. size_t old_info_log_file_count = old_info_log_files.size(); if (old_info_log_file_count != 0 && @@ -531,6 +541,13 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) { wal_manager_.PurgeObsoleteWALFiles(); #endif // ROCKSDB_LITE LogFlush(immutable_db_options_.info_log); + InstrumentedMutexLock l(&mutex_); + --pending_purge_obsolete_files_; + assert(pending_purge_obsolete_files_ >= 0); + if (pending_purge_obsolete_files_ == 0) { + bg_cv_.SignalAll(); + } + TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:End"); } void DBImpl::DeleteObsoleteFiles() { @@ -545,4 +562,95 @@ void DBImpl::DeleteObsoleteFiles() { job_context.Clean(); mutex_.Lock(); } + +uint64_t FindMinPrepLogReferencedByMemTable( + VersionSet* vset, const ColumnFamilyData* cfd_to_flush, + const autovector& memtables_to_flush) { + uint64_t min_log = 0; + + // we must look through the memtables for two phase transactions + // that have been committed but not yet flushed + for (auto loop_cfd : *vset->GetColumnFamilySet()) { + if (loop_cfd->IsDropped() || loop_cfd == cfd_to_flush) { + continue; + } + + auto log = loop_cfd->imm()->PrecomputeMinLogContainingPrepSection( + memtables_to_flush); + + if (log > 0 && (min_log == 0 || log < min_log)) { + min_log = log; + } + + log = loop_cfd->mem()->GetMinLogContainingPrepSection(); + + if (log > 0 && (min_log == 0 || log < min_log)) { + min_log = log; + } + } + + return min_log; +} + +uint64_t PrecomputeMinLogNumberToKeep( + VersionSet* vset, const ColumnFamilyData& cfd_to_flush, + autovector edit_list, + const autovector& memtables_to_flush, + LogsWithPrepTracker* prep_tracker) { + assert(vset != nullptr); + assert(prep_tracker != nullptr); + // Calculate updated min_log_number_to_keep + // Since the function should only be called in 2pc mode, log number in + // the version edit should be sufficient. + + // Precompute the min log number containing unflushed data for the column + // family being flushed (`cfd_to_flush`). + uint64_t cf_min_log_number_to_keep = 0; + for (auto& e : edit_list) { + if (e->has_log_number()) { + cf_min_log_number_to_keep = + std::max(cf_min_log_number_to_keep, e->log_number()); + } + } + if (cf_min_log_number_to_keep == 0) { + // No version edit contains information on log number. The log number + // for this column family should stay the same as it is. + cf_min_log_number_to_keep = cfd_to_flush.GetLogNumber(); + } + + // Get min log number containing unflushed data for other column families. + uint64_t min_log_number_to_keep = + vset->PreComputeMinLogNumberWithUnflushedData(&cfd_to_flush); + if (cf_min_log_number_to_keep != 0) { + min_log_number_to_keep = + std::min(cf_min_log_number_to_keep, min_log_number_to_keep); + } + + // if are 2pc we must consider logs containing prepared + // sections of outstanding transactions. + // + // We must check min logs with outstanding prep before we check + // logs references by memtables because a log referenced by the + // first data structure could transition to the second under us. + // + // TODO: iterating over all column families under db mutex. + // should find more optimal solution + auto min_log_in_prep_heap = + prep_tracker->FindMinLogContainingOutstandingPrep(); + + if (min_log_in_prep_heap != 0 && + min_log_in_prep_heap < min_log_number_to_keep) { + min_log_number_to_keep = min_log_in_prep_heap; + } + + uint64_t min_log_refed_by_mem = FindMinPrepLogReferencedByMemTable( + vset, &cfd_to_flush, memtables_to_flush); + + if (min_log_refed_by_mem != 0 && + min_log_refed_by_mem < min_log_number_to_keep) { + min_log_number_to_keep = min_log_refed_by_mem; + } + return min_log_number_to_keep; +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_impl_open.cc b/thirdparty/rocksdb/db/db_impl_open.cc index bc94b6095f..f5008857bc 100644 --- a/thirdparty/rocksdb/db/db_impl_open.cc +++ b/thirdparty/rocksdb/db/db_impl_open.cc @@ -14,6 +14,7 @@ #include #include "db/builder.h" +#include "db/error_handler.h" #include "options/options_helper.h" #include "rocksdb/wal_filter.h" #include "table/block_based_table_factory.h" @@ -22,8 +23,7 @@ #include "util/sync_point.h" namespace rocksdb { -Options SanitizeOptions(const std::string& dbname, - const Options& src) { +Options SanitizeOptions(const std::string& dbname, const Options& src) { auto db_options = SanitizeOptions(dbname, DBOptions(src)); ImmutableDBOptions immutable_db_options(db_options); auto cf_options = @@ -41,6 +41,8 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) { max_max_open_files = 0x400000; } ClipToRange(&result.max_open_files, 20, max_max_open_files); + TEST_SYNC_POINT_CALLBACK("SanitizeOptions::AfterChangeMaxOpenFiles", + &result.max_open_files); } if (result.info_log == nullptr) { @@ -55,10 +57,9 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) { result.write_buffer_manager.reset( new WriteBufferManager(result.db_write_buffer_size)); } - auto bg_job_limits = DBImpl::GetBGJobLimits(result.max_background_flushes, - result.max_background_compactions, - result.max_background_jobs, - true /* parallelize_compactions */); + auto bg_job_limits = DBImpl::GetBGJobLimits( + result.max_background_flushes, result.max_background_compactions, + result.max_background_jobs, true /* parallelize_compactions */); result.env->IncBackgroundThreadsIfNeeded(bg_job_limits.max_compactions, Env::Priority::LOW); result.env->IncBackgroundThreadsIfNeeded(bg_job_limits.max_flushes, @@ -106,14 +107,12 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) { result.db_paths.emplace_back(dbname, std::numeric_limits::max()); } - if (result.use_direct_io_for_flush_and_compaction && - result.compaction_readahead_size == 0) { + if (result.use_direct_reads && result.compaction_readahead_size == 0) { TEST_SYNC_POINT_CALLBACK("SanitizeOptions:direct_io", nullptr); result.compaction_readahead_size = 1024 * 1024 * 2; } - if (result.compaction_readahead_size > 0 || - result.use_direct_io_for_flush_and_compaction) { + if (result.compaction_readahead_size > 0 || result.use_direct_reads) { result.new_table_reader_for_compaction_inputs = true; } @@ -124,6 +123,24 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) { result.avoid_flush_during_recovery = false; } +#ifndef ROCKSDB_LITE + // When the DB is stopped, it's possible that there are some .trash files that + // were not deleted yet, when we open the DB we will find these .trash files + // and schedule them to be deleted (or delete immediately if SstFileManager + // was not used) + auto sfm = static_cast(result.sst_file_manager.get()); + for (size_t i = 0; i < result.db_paths.size(); i++) { + DeleteScheduler::CleanupDirectory(result.env, sfm, result.db_paths[i].path); + } + + // Create a default SstFileManager for purposes of tracking compaction size + // and facilitating recovery from out of space errors. + if (result.sst_file_manager.get() == nullptr) { + std::shared_ptr sst_file_manager( + NewSstFileManager(result.env, result.info_log)); + result.sst_file_manager = sst_file_manager; + } +#endif return result; } @@ -152,28 +169,23 @@ static Status ValidateOptions( if (s.ok() && db_options.allow_concurrent_memtable_write) { s = CheckConcurrentWritesSupported(cfd.options); } + if (s.ok()) { + s = CheckCFPathsSupported(db_options, cfd.options); + } if (!s.ok()) { return s; } - if (db_options.db_paths.size() > 1) { - if ((cfd.options.compaction_style != kCompactionStyleUniversal) && - (cfd.options.compaction_style != kCompactionStyleLevel)) { - return Status::NotSupported( - "More than one DB paths are only supported in " - "universal and level compaction styles. "); - } - } - if (cfd.options.compaction_options_fifo.ttl > 0) { + + if (cfd.options.ttl > 0) { if (db_options.max_open_files != -1) { return Status::NotSupported( - "FIFO Compaction with TTL is only supported when files are always " + "TTL is only supported when files are always " "kept open (set max_open_files = -1). "); } if (cfd.options.table_factory->Name() != BlockBasedTableFactory().Name()) { return Status::NotSupported( - "FIFO Compaction with TTL is only supported in " - "Block-Based Table format. "); + "TTL is only supported in Block-Based Table format. "); } } } @@ -204,7 +216,7 @@ static Status ValidateOptions( return Status::OK(); } -} // namespace +} // namespace Status DBImpl::NewDB() { VersionEdit new_db; new_db.SetLogNumber(0); @@ -216,7 +228,7 @@ Status DBImpl::NewDB() { ROCKS_LOG_INFO(immutable_db_options_.info_log, "Creating manifest 1 \n"); const std::string manifest = DescriptorFileName(dbname_, 1); { - unique_ptr file; + std::unique_ptr file; EnvOptions env_options = env_->OptimizeForManifestWrite(env_options_); s = NewWritableFile(env_, manifest, &file, env_options); if (!s.ok()) { @@ -224,8 +236,9 @@ Status DBImpl::NewDB() { } file->SetPreallocationBlockSize( immutable_db_options_.manifest_preallocation_size); - unique_ptr file_writer( - new WritableFileWriter(std::move(file), env_options)); + std::unique_ptr file_writer(new WritableFileWriter( + std::move(file), manifest, env_options, env_, nullptr /* stats */, + immutable_db_options_.listeners)); log::Writer log(std::move(file_writer), 0, false); std::string record; new_db.EncodeTo(&record); @@ -243,9 +256,8 @@ Status DBImpl::NewDB() { return s; } -Status DBImpl::Directories::CreateAndNewDirectory( - Env* env, const std::string& dirname, - std::unique_ptr* directory) const { +Status DBImpl::CreateAndNewDirectory(Env* env, const std::string& dirname, + std::unique_ptr* directory) { // We call CreateDirIfMissing() as the directory may already exist (if we // are reopening a DB), when this happens we don't want creating the // directory to cause an error. However, we need to check if creating the @@ -263,12 +275,12 @@ Status DBImpl::Directories::CreateAndNewDirectory( Status DBImpl::Directories::SetDirectories( Env* env, const std::string& dbname, const std::string& wal_dir, const std::vector& data_paths) { - Status s = CreateAndNewDirectory(env, dbname, &db_dir_); + Status s = DBImpl::CreateAndNewDirectory(env, dbname, &db_dir_); if (!s.ok()) { return s; } if (!wal_dir.empty() && dbname != wal_dir) { - s = CreateAndNewDirectory(env, wal_dir, &wal_dir_); + s = DBImpl::CreateAndNewDirectory(env, wal_dir, &wal_dir_); if (!s.ok()) { return s; } @@ -281,7 +293,7 @@ Status DBImpl::Directories::SetDirectories( data_dirs_.emplace_back(nullptr); } else { std::unique_ptr path_directory; - s = CreateAndNewDirectory(env, db_path, &path_directory); + s = DBImpl::CreateAndNewDirectory(env, db_path, &path_directory); if (!s.ok()) { return s; } @@ -326,8 +338,8 @@ Status DBImpl::Recover( } } else if (s.ok()) { if (immutable_db_options_.error_if_exists) { - return Status::InvalidArgument( - dbname_, "exists (error_if_exists is true)"); + return Status::InvalidArgument(dbname_, + "exists (error_if_exists is true)"); } } else { // Unexpected error reading file @@ -345,12 +357,53 @@ Status DBImpl::Recover( assert(s.IsIOError()); return s; } + // Verify compatibility of env_options_ and filesystem + { + std::unique_ptr idfile; + EnvOptions customized_env(env_options_); + customized_env.use_direct_reads |= + immutable_db_options_.use_direct_io_for_flush_and_compaction; + s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile, + customized_env); + if (!s.ok()) { + std::string error_str = s.ToString(); + // Check if unsupported Direct I/O is the root cause + customized_env.use_direct_reads = false; + s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile, + customized_env); + if (s.ok()) { + return Status::InvalidArgument( + "Direct I/O is not supported by the specified DB."); + } else { + return Status::InvalidArgument( + "Found options incompatible with filesystem", error_str.c_str()); + } + } + } } Status s = versions_->Recover(column_families, read_only); if (immutable_db_options_.paranoid_checks && s.ok()) { s = CheckConsistency(); } + if (s.ok() && !read_only) { + for (auto cfd : *versions_->GetColumnFamilySet()) { + s = cfd->AddDirectories(); + if (!s.ok()) { + return s; + } + } + } + + // Initial max_total_in_memory_state_ before recovery logs. Log recovery + // may check this value to decide whether to flush. + max_total_in_memory_state_ = 0; + for (auto cfd : *versions_->GetColumnFamilySet()) { + auto* mutable_cf_options = cfd->GetLatestMutableCFOptions(); + max_total_in_memory_state_ += mutable_cf_options->write_buffer_size * + mutable_cf_options->max_write_buffer_number; + } + if (s.ok()) { SequenceNumber next_sequence(kMaxSequenceNumber); default_cf_handle_ = new ColumnFamilyHandleImpl( @@ -368,7 +421,10 @@ Status DBImpl::Recover( // produced by an older version of rocksdb. std::vector filenames; s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames); - if (!s.ok()) { + if (s.IsNotFound()) { + return Status::InvalidArgument("wal_dir not found", + immutable_db_options_.wal_dir); + } else if (!s.ok()) { return s; } @@ -423,12 +479,26 @@ Status DBImpl::Recover( } } - // Initial value - max_total_in_memory_state_ = 0; - for (auto cfd : *versions_->GetColumnFamilySet()) { - auto* mutable_cf_options = cfd->GetLatestMutableCFOptions(); - max_total_in_memory_state_ += mutable_cf_options->write_buffer_size * - mutable_cf_options->max_write_buffer_number; + if (read_only) { + // If we are opening as read-only, we need to update options_file_number_ + // to reflect the most recent OPTIONS file. It does not matter for regular + // read-write db instance because options_file_number_ will later be + // updated to versions_->NewFileNumber() in RenameTempFileToOptionsFile. + std::vector file_names; + if (s.ok()) { + s = env_->GetChildren(GetName(), &file_names); + } + if (s.ok()) { + uint64_t number = 0; + uint64_t options_file_number = 0; + FileType type; + for (const auto& fname : file_names) { + if (ParseFileName(fname, &number, &type) && type == kOptionsFile) { + options_file_number = std::max(number, options_file_number); + } + } + versions_->options_file_number_ = options_file_number; + } } return s; @@ -442,7 +512,7 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, Logger* info_log; const char* fname; Status* status; // nullptr if immutable_db_options_.paranoid_checks==false - virtual void Corruption(size_t bytes, const Status& s) override { + void Corruption(size_t bytes, const Status& s) override { ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s", (this->status == nullptr ? "(ignoring error) " : ""), fname, static_cast(bytes), s.ToString().c_str()); @@ -479,10 +549,9 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, std::map cf_name_id_map; std::map cf_lognumber_map; for (auto cfd : *versions_->GetColumnFamilySet()) { - cf_name_id_map.insert( - std::make_pair(cfd->GetName(), cfd->GetID())); + cf_name_id_map.insert(std::make_pair(cfd->GetName(), cfd->GetID())); cf_lognumber_map.insert( - std::make_pair(cfd->GetID(), cfd->GetLogNumber())); + std::make_pair(cfd->GetID(), cfd->GetLogNumber())); } immutable_db_options_.wal_filter->ColumnFamilyLogNumberMap(cf_lognumber_map, @@ -493,17 +562,25 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, bool stop_replay_by_wal_filter = false; bool stop_replay_for_corruption = false; bool flushed = false; + uint64_t corrupted_log_number = kMaxSequenceNumber; for (auto log_number : log_numbers) { + if (log_number < versions_->min_log_number_to_keep_2pc()) { + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "Skipping log #%" PRIu64 + " since it is older than min log to keep #%" PRIu64, + log_number, versions_->min_log_number_to_keep_2pc()); + continue; + } // The previous incarnation may not have written any MANIFEST // records after allocating this log number. So we manually // update the file number allocation counter in VersionSet. - versions_->MarkFileNumberUsedDuringRecovery(log_number); + versions_->MarkFileNumberUsed(log_number); // Open the log file std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number); ROCKS_LOG_INFO(immutable_db_options_.info_log, "Recovering log #%" PRIu64 " mode %d", log_number, - immutable_db_options_.wal_recovery_mode); + static_cast(immutable_db_options_.wal_recovery_mode)); auto logFileDropped = [this, &fname]() { uint64_t bytes; if (env_->GetFileSize(fname, &bytes).ok()) { @@ -517,9 +594,9 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, continue; } - unique_ptr file_reader; + std::unique_ptr file_reader; { - unique_ptr file; + std::unique_ptr file; status = env_->NewSequentialFile(fname, &file, env_->OptimizeForLogRead(env_options_)); if (!status.ok()) { @@ -532,7 +609,7 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, continue; } } - file_reader.reset(new SequentialFileReader(std::move(file))); + file_reader.reset(new SequentialFileReader(std::move(file), fname)); } // Create the log reader. @@ -552,8 +629,7 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, // to be skipped instead of propagating bad information (like overly // large sequence numbers). log::Reader reader(immutable_db_options_.info_log, std::move(file_reader), - &reporter, true /*checksum*/, 0 /*initial_offset*/, - log_number); + &reporter, true /*checksum*/, log_number); // Determine if we should tolerate incomplete records at the tail end of the // Read all the records and add to a memtable @@ -647,7 +723,8 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, " mode %d log filter %s returned " "more records (%d) than original (%d) which is not allowed. " "Aborting recovery.", - log_number, immutable_db_options_.wal_recovery_mode, + log_number, + static_cast(immutable_db_options_.wal_recovery_mode), immutable_db_options_.wal_filter->Name(), new_count, original_count); status = Status::NotSupported( @@ -674,7 +751,7 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, status = WriteBatchInternal::InsertInto( &batch, column_family_memtables_.get(), &flush_scheduler_, true, log_number, this, false /* concurrent_memtable_writes */, - next_sequence, &has_valid_writes); + next_sequence, &has_valid_writes, seq_per_batch_, batch_per_txn_); MaybeIgnoreError(&status); if (!status.ok()) { // We are treating this as a failure while reading since we read valid @@ -711,6 +788,12 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, } if (!status.ok()) { + if (status.IsNotSupported()) { + // We should not treat NotSupported as corruption. It is rather a clear + // sign that we are processing a WAL that is produced by an incompatible + // version of the code. + return status; + } if (immutable_db_options_.wal_recovery_mode == WALRecoveryMode::kSkipAnyCorruptedRecords) { // We should ignore all errors unconditionally @@ -720,6 +803,7 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, // We should ignore the error but not continue replaying status = Status::OK(); stop_replay_for_corruption = true; + corrupted_log_number = log_number; ROCKS_LOG_INFO(immutable_db_options_.info_log, "Point in time recovered to log #%" PRIu64 " seq #%" PRIu64, @@ -737,10 +821,30 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, auto last_sequence = *next_sequence - 1; if ((*next_sequence != kMaxSequenceNumber) && (versions_->LastSequence() <= last_sequence)) { - versions_->SetLastToBeWrittenSequence(last_sequence); + versions_->SetLastAllocatedSequence(last_sequence); + versions_->SetLastPublishedSequence(last_sequence); versions_->SetLastSequence(last_sequence); } } + // Compare the corrupted log number to all columnfamily's current log number. + // Abort Open() if any column family's log number is greater than + // the corrupted log number, which means CF contains data beyond the point of + // corruption. This could during PIT recovery when the WAL is corrupted and + // some (but not all) CFs are flushed + if (stop_replay_for_corruption == true && + (immutable_db_options_.wal_recovery_mode == + WALRecoveryMode::kPointInTimeRecovery || + immutable_db_options_.wal_recovery_mode == + WALRecoveryMode::kTolerateCorruptedTailRecords)) { + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (cfd->GetLogNumber() > corrupted_log_number) { + ROCKS_LOG_ERROR(immutable_db_options_.info_log, + "Column family inconsistency: SST file contains data" + " beyond the point of corruption."); + return Status::Corruption("SST file is ahead of WALs"); + } + } + } // True if there's any data in the WALs; if not, we can skip re-processing // them later @@ -796,9 +900,9 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, // not actually used. that is because VersionSet assumes // VersionSet::next_file_number_ always to be strictly greater than any // log number - versions_->MarkFileNumberUsedDuringRecovery(max_log_number + 1); - status = versions_->LogAndApply( - cfd, *cfd->GetLatestMutableCFOptions(), edit, &mutex_); + versions_->MarkFileNumberUsed(max_log_number + 1); + status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(), + edit, &mutex_); if (!status.ok()) { // Recovery failed break; @@ -806,18 +910,8 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, } } - if (data_seen && !flushed) { - // Mark these as alive so they'll be considered for deletion later by - // FindObsoleteFiles() - if (concurrent_prepare_) { - log_write_mutex_.Lock(); - } - for (auto log_number : log_numbers) { - alive_log_files_.push_back(LogFileNumberSize(log_number)); - } - if (concurrent_prepare_) { - log_write_mutex_.Unlock(); - } + if (status.ok() && data_seen && !flushed) { + status = RestoreAliveLogFiles(log_numbers); } event_logger_.Log() << "job" << job_id << "event" @@ -826,6 +920,60 @@ Status DBImpl::RecoverLogFiles(const std::vector& log_numbers, return status; } +Status DBImpl::RestoreAliveLogFiles(const std::vector& log_numbers) { + if (log_numbers.empty()) { + return Status::OK(); + } + Status s; + mutex_.AssertHeld(); + assert(immutable_db_options_.avoid_flush_during_recovery); + if (two_write_queues_) { + log_write_mutex_.Lock(); + } + // Mark these as alive so they'll be considered for deletion later by + // FindObsoleteFiles() + total_log_size_ = 0; + log_empty_ = false; + for (auto log_number : log_numbers) { + LogFileNumberSize log(log_number); + std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number); + // This gets the appear size of the logs, not including preallocated space. + s = env_->GetFileSize(fname, &log.size); + if (!s.ok()) { + break; + } + total_log_size_ += log.size; + alive_log_files_.push_back(log); + // We preallocate space for logs, but then after a crash and restart, those + // preallocated space are not needed anymore. It is likely only the last + // log has such preallocated space, so we only truncate for the last log. + if (log_number == log_numbers.back()) { + std::unique_ptr last_log; + Status truncate_status = env_->ReopenWritableFile( + fname, &last_log, + env_->OptimizeForLogWrite( + env_options_, + BuildDBOptions(immutable_db_options_, mutable_db_options_))); + if (truncate_status.ok()) { + truncate_status = last_log->Truncate(log.size); + } + if (truncate_status.ok()) { + truncate_status = last_log->Close(); + } + // Not a critical error if fail to truncate. + if (!truncate_status.ok()) { + ROCKS_LOG_WARN(immutable_db_options_.info_log, + "Failed to truncate log #%" PRIu64 ": %s", log_number, + truncate_status.ToString().c_str()); + } + } + } + if (two_write_queues_) { + log_write_mutex_.Unlock(); + } + return s; +} + Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd, MemTable* mem, VersionEdit* edit) { mutex_.AssertHeld(); @@ -857,26 +1005,35 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd, const uint64_t current_time = static_cast(_current_time); { + auto write_hint = cfd->CalculateSSTWriteHint(0); mutex_.Unlock(); SequenceNumber earliest_write_conflict_snapshot; std::vector snapshot_seqs = snapshots_.GetAll(&earliest_write_conflict_snapshot); - - EnvOptions optimized_env_options = - env_->OptimizeForCompactionTableWrite(env_options_, immutable_db_options_); + auto snapshot_checker = snapshot_checker_.get(); + if (use_custom_gc_ && snapshot_checker == nullptr) { + snapshot_checker = DisableGCSnapshotChecker::Instance(); + } + std::vector> + range_del_iters; + auto range_del_iter = + mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber); + if (range_del_iter != nullptr) { + range_del_iters.emplace_back(range_del_iter); + } s = BuildTable( dbname_, env_, *cfd->ioptions(), mutable_cf_options, - optimized_env_options, cfd->table_cache(), iter.get(), - std::unique_ptr(mem->NewRangeTombstoneIterator(ro)), - &meta, cfd->internal_comparator(), + env_options_for_compaction_, cfd->table_cache(), iter.get(), + std::move(range_del_iters), &meta, cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(), - snapshot_seqs, earliest_write_conflict_snapshot, + snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker, GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), + mutable_cf_options.sample_for_compression, cfd->ioptions()->compression_opts, paranoid_file_checks, cfd->internal_stats(), TableFileCreationReason::kRecovery, &event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */, - -1 /* level */, current_time); + -1 /* level */, current_time, write_hint); LogFlush(immutable_db_options_.info_log); ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] [WriteLevel0TableForRecovery]" @@ -894,17 +1051,17 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd, if (s.ok() && meta.fd.GetFileSize() > 0) { edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetPathId(), meta.fd.GetFileSize(), meta.smallest, meta.largest, - meta.smallest_seqno, meta.largest_seqno, + meta.fd.smallest_seqno, meta.fd.largest_seqno, meta.marked_for_compaction); } - InternalStats::CompactionStats stats(1); + InternalStats::CompactionStats stats(CompactionReason::kFlush, 1); stats.micros = env_->NowMicros() - start_micros; stats.bytes_written = meta.fd.GetFileSize(); stats.num_output_files = 1; - cfd->internal_stats()->AddCompactionStats(level, stats); - cfd->internal_stats()->AddCFStats( - InternalStats::BYTES_FLUSHED, meta.fd.GetFileSize()); + cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats); + cfd->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED, + meta.fd.GetFileSize()); RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize()); return s; } @@ -929,6 +1086,16 @@ Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) { Status DB::Open(const DBOptions& db_options, const std::string& dbname, const std::vector& column_families, std::vector* handles, DB** dbptr) { + const bool kSeqPerBatch = true; + const bool kBatchPerTxn = true; + return DBImpl::Open(db_options, dbname, column_families, handles, dbptr, + !kSeqPerBatch, kBatchPerTxn); +} + +Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname, + const std::vector& column_families, + std::vector* handles, DB** dbptr, + const bool seq_per_batch, const bool batch_per_txn) { Status s = SanitizeOptionsByTable(db_options, column_families); if (!s.ok()) { return s; @@ -948,15 +1115,30 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname, std::max(max_write_buffer_size, cf.options.write_buffer_size); } - DBImpl* impl = new DBImpl(db_options, dbname); + DBImpl* impl = new DBImpl(db_options, dbname, seq_per_batch, batch_per_txn); s = impl->env_->CreateDirIfMissing(impl->immutable_db_options_.wal_dir); if (s.ok()) { - for (auto db_path : impl->immutable_db_options_.db_paths) { - s = impl->env_->CreateDirIfMissing(db_path.path); + std::vector paths; + for (auto& db_path : impl->immutable_db_options_.db_paths) { + paths.emplace_back(db_path.path); + } + for (auto& cf : column_families) { + for (auto& cf_path : cf.options.cf_paths) { + paths.emplace_back(cf_path.path); + } + } + for (auto& path : paths) { + s = impl->env_->CreateDirIfMissing(path); if (!s.ok()) { break; } } + + // For recovery from NoSpace() error, we can only handle + // the case where the database is stored in a single path + if (paths.size() <= 1) { + impl->error_handler_.EnableAutoRecovery(); + } } if (!s.ok()) { @@ -970,33 +1152,38 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname, return s; } impl->mutex_.Lock(); + auto write_hint = impl->CalculateWALWriteHint(); // Handles create_if_missing, error_if_exists s = impl->Recover(column_families); if (s.ok()) { uint64_t new_log_number = impl->versions_->NewFileNumber(); - unique_ptr lfile; + std::unique_ptr lfile; EnvOptions soptions(db_options); EnvOptions opt_env_options = impl->immutable_db_options_.env->OptimizeForLogWrite( soptions, BuildDBOptions(impl->immutable_db_options_, impl->mutable_db_options_)); - s = NewWritableFile( - impl->immutable_db_options_.env, - LogFileName(impl->immutable_db_options_.wal_dir, new_log_number), - &lfile, opt_env_options); + std::string log_fname = + LogFileName(impl->immutable_db_options_.wal_dir, new_log_number); + s = NewWritableFile(impl->immutable_db_options_.env, log_fname, &lfile, + opt_env_options); if (s.ok()) { + lfile->SetWriteLifeTimeHint(write_hint); lfile->SetPreallocationBlockSize( impl->GetWalPreallocateBlockSize(max_write_buffer_size)); { InstrumentedMutexLock wl(&impl->log_write_mutex_); impl->logfile_number_ = new_log_number; - unique_ptr file_writer( - new WritableFileWriter(std::move(lfile), opt_env_options)); + const auto& listeners = impl->immutable_db_options_.listeners; + std::unique_ptr file_writer( + new WritableFileWriter(std::move(lfile), log_fname, opt_env_options, + impl->env_, nullptr /* stats */, listeners)); impl->logs_.emplace_back( new_log_number, new log::Writer( std::move(file_writer), new_log_number, - impl->immutable_db_options_.recycle_log_file_num > 0)); + impl->immutable_db_options_.recycle_log_file_num > 0, + impl->immutable_db_options_.manual_wal_flush)); } // set column family handles @@ -1027,16 +1214,18 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname, } } if (s.ok()) { + SuperVersionContext sv_context(/* create_superversion */ true); for (auto cfd : *impl->versions_->GetColumnFamilySet()) { - delete impl->InstallSuperVersionAndScheduleWork( - cfd, nullptr, *cfd->GetLatestMutableCFOptions()); + impl->InstallSuperVersionAndScheduleWork( + cfd, &sv_context, *cfd->GetLatestMutableCFOptions()); } - if (impl->concurrent_prepare_) { + sv_context.Clean(); + if (impl->two_write_queues_) { impl->log_write_mutex_.Lock(); } impl->alive_log_files_.push_back( DBImpl::LogFileNumberSize(impl->logfile_number_)); - if (impl->concurrent_prepare_) { + if (impl->two_write_queues_) { impl->log_write_mutex_.Unlock(); } impl->DeleteObsoleteFiles(); @@ -1065,7 +1254,8 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname, !cfd->mem()->IsMergeOperatorSupported()) { s = Status::InvalidArgument( "The memtable of column family %s does not support merge operator " - "its options.merge_operator is non-null", cfd->GetName().c_str()); + "its options.merge_operator is non-null", + cfd->GetName().c_str()); } if (!s.ok()) { break; @@ -1091,31 +1281,57 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname, impl->immutable_db_options_.sst_file_manager.get()); if (s.ok() && sfm) { // Notify SstFileManager about all sst files that already exist in - // db_paths[0] when the DB is opened. - auto& db_path = impl->immutable_db_options_.db_paths[0]; - std::vector existing_files; - impl->immutable_db_options_.env->GetChildren(db_path.path, &existing_files); - for (auto& file_name : existing_files) { - uint64_t file_number; - FileType file_type; - std::string file_path = db_path.path + "/" + file_name; - if (ParseFileName(file_name, &file_number, &file_type) && - file_type == kTableFile) { - sfm->OnAddFile(file_path); + // db_paths[0] and cf_paths[0] when the DB is opened. + std::vector paths; + paths.emplace_back(impl->immutable_db_options_.db_paths[0].path); + for (auto& cf : column_families) { + if (!cf.options.cf_paths.empty()) { + paths.emplace_back(cf.options.cf_paths[0].path); } } + // Remove duplicate paths. + std::sort(paths.begin(), paths.end()); + paths.erase(std::unique(paths.begin(), paths.end()), paths.end()); + for (auto& path : paths) { + std::vector existing_files; + impl->immutable_db_options_.env->GetChildren(path, &existing_files); + for (auto& file_name : existing_files) { + uint64_t file_number; + FileType file_type; + std::string file_path = path + "/" + file_name; + if (ParseFileName(file_name, &file_number, &file_type) && + file_type == kTableFile) { + sfm->OnAddFile(file_path); + } + } + } + + // Reserve some disk buffer space. This is a heuristic - when we run out + // of disk space, this ensures that there is atleast write_buffer_size + // amount of free space before we resume DB writes. In low disk space + // conditions, we want to avoid a lot of small L0 files due to frequent + // WAL write failures and resultant forced flushes + sfm->ReserveDiskBuffer(max_write_buffer_size, + impl->immutable_db_options_.db_paths[0].path); } #endif // !ROCKSDB_LITE if (s.ok()) { - ROCKS_LOG_INFO(impl->immutable_db_options_.info_log, "DB pointer %p", impl); + ROCKS_LOG_HEADER(impl->immutable_db_options_.info_log, "DB pointer %p", + impl); LogFlush(impl->immutable_db_options_.info_log); + assert(impl->TEST_WALBufferIsEmpty()); + // If the assert above fails then we need to FlushWAL before returning + // control back to the user. if (!persist_options_status.ok()) { s = Status::IOError( "DB::Open() failed --- Unable to persist Options file", persist_options_status.ToString()); } } + if (s.ok()) { + impl->StartTimedTasks(); + } if (!s.ok()) { for (auto* h : *handles) { delete h; diff --git a/thirdparty/rocksdb/db/db_impl_readonly.cc b/thirdparty/rocksdb/db/db_impl_readonly.cc index d69eecb988..5d7515c28e 100644 --- a/thirdparty/rocksdb/db/db_impl_readonly.cc +++ b/thirdparty/rocksdb/db/db_impl_readonly.cc @@ -9,7 +9,6 @@ #include "db/db_impl.h" #include "db/db_iter.h" #include "db/merge_context.h" -#include "db/range_del_aggregator.h" #include "monitoring/perf_context_imp.h" namespace rocksdb { @@ -24,30 +23,45 @@ DBImplReadOnly::DBImplReadOnly(const DBOptions& db_options, LogFlush(immutable_db_options_.info_log); } -DBImplReadOnly::~DBImplReadOnly() { -} +DBImplReadOnly::~DBImplReadOnly() {} // Implementations of the DB interface Status DBImplReadOnly::Get(const ReadOptions& read_options, ColumnFamilyHandle* column_family, const Slice& key, PinnableSlice* pinnable_val) { assert(pinnable_val != nullptr); + // TODO: stopwatch DB_GET needed?, perf timer needed? + PERF_TIMER_GUARD(get_snapshot_time); Status s; SequenceNumber snapshot = versions_->LastSequence(); auto cfh = reinterpret_cast(column_family); auto cfd = cfh->cfd(); + if (tracer_) { + InstrumentedMutexLock lock(&trace_mutex_); + if (tracer_) { + tracer_->Get(column_family, key); + } + } SuperVersion* super_version = cfd->GetSuperVersion(); MergeContext merge_context; - RangeDelAggregator range_del_agg(cfd->internal_comparator(), snapshot); + SequenceNumber max_covering_tombstone_seq = 0; LookupKey lkey(key, snapshot); + PERF_TIMER_STOP(get_snapshot_time); if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context, - &range_del_agg, read_options)) { + &max_covering_tombstone_seq, read_options)) { pinnable_val->PinSelf(); + RecordTick(stats_, MEMTABLE_HIT); } else { PERF_TIMER_GUARD(get_from_output_files_time); super_version->current->Get(read_options, lkey, pinnable_val, &s, - &merge_context, &range_del_agg); + &merge_context, &max_covering_tombstone_seq); + RecordTick(stats_, MEMTABLE_MISS); } + RecordTick(stats_, NUMBER_KEYS_READ); + size_t size = pinnable_val->size(); + RecordTick(stats_, BYTES_READ, size); + RecordInHistogram(stats_, BYTES_PER_READ, size); + PERF_COUNTER_ADD(get_read_bytes, size); return s; } @@ -57,17 +71,20 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options, auto cfd = cfh->cfd(); SuperVersion* super_version = cfd->GetSuperVersion()->Ref(); SequenceNumber latest_snapshot = versions_->LastSequence(); + SequenceNumber read_seq = + read_options.snapshot != nullptr + ? reinterpret_cast(read_options.snapshot) + ->number_ + : latest_snapshot; + ReadCallback* read_callback = nullptr; // No read callback provided. auto db_iter = NewArenaWrappedDbIterator( - env_, read_options, *cfd->ioptions(), - (read_options.snapshot != nullptr - ? reinterpret_cast(read_options.snapshot) - ->number_ - : latest_snapshot), + env_, read_options, *cfd->ioptions(), super_version->mutable_cf_options, + read_seq, super_version->mutable_cf_options.max_sequential_skip_in_iterations, - super_version->version_number); + super_version->version_number, read_callback); auto internal_iter = NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(), - db_iter->GetRangeDelAggregator()); + db_iter->GetRangeDelAggregator(), read_seq); db_iter->SetIterUnderDBIter(internal_iter); return db_iter; } @@ -76,27 +93,29 @@ Status DBImplReadOnly::NewIterators( const ReadOptions& read_options, const std::vector& column_families, std::vector* iterators) { + ReadCallback* read_callback = nullptr; // No read callback provided. if (iterators == nullptr) { return Status::InvalidArgument("iterators not allowed to be nullptr"); } iterators->clear(); iterators->reserve(column_families.size()); SequenceNumber latest_snapshot = versions_->LastSequence(); + SequenceNumber read_seq = + read_options.snapshot != nullptr + ? reinterpret_cast(read_options.snapshot) + ->number_ + : latest_snapshot; for (auto cfh : column_families) { auto* cfd = reinterpret_cast(cfh)->cfd(); auto* sv = cfd->GetSuperVersion()->Ref(); auto* db_iter = NewArenaWrappedDbIterator( - env_, read_options, *cfd->ioptions(), - (read_options.snapshot != nullptr - ? reinterpret_cast(read_options.snapshot) - ->number_ - : latest_snapshot), + env_, read_options, *cfd->ioptions(), sv->mutable_cf_options, read_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations, - sv->version_number); + sv->version_number, read_callback); auto* internal_iter = NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(), - db_iter->GetRangeDelAggregator()); + db_iter->GetRangeDelAggregator(), read_seq); db_iter->SetIterUnderDBIter(internal_iter); iterators->push_back(db_iter); } @@ -105,7 +124,7 @@ Status DBImplReadOnly::NewIterators( } Status DB::OpenForReadOnly(const Options& options, const std::string& dbname, - DB** dbptr, bool error_if_log_file_exist) { + DB** dbptr, bool /*error_if_log_file_exist*/) { *dbptr = nullptr; // Try to first open DB as fully compacted DB @@ -140,6 +159,7 @@ Status DB::OpenForReadOnly( *dbptr = nullptr; handles->clear(); + SuperVersionContext sv_context(/* create_superversion */ true); DBImplReadOnly* impl = new DBImplReadOnly(db_options, dbname); impl->mutex_.Lock(); Status s = impl->Recover(column_families, true /* read only */, @@ -158,10 +178,12 @@ Status DB::OpenForReadOnly( } if (s.ok()) { for (auto cfd : *impl->versions_->GetColumnFamilySet()) { - delete cfd->InstallSuperVersion(new SuperVersion(), &impl->mutex_); + sv_context.NewSuperVersion(); + cfd->InstallSuperVersion(&sv_context, &impl->mutex_); } } impl->mutex_.Unlock(); + sv_context.Clean(); if (s.ok()) { *dbptr = impl; for (auto* h : *handles) { @@ -178,20 +200,21 @@ Status DB::OpenForReadOnly( return s; } -#else // !ROCKSDB_LITE +#else // !ROCKSDB_LITE -Status DB::OpenForReadOnly(const Options& options, const std::string& dbname, - DB** dbptr, bool error_if_log_file_exist) { +Status DB::OpenForReadOnly(const Options& /*options*/, + const std::string& /*dbname*/, DB** /*dbptr*/, + bool /*error_if_log_file_exist*/) { return Status::NotSupported("Not supported in ROCKSDB_LITE."); } Status DB::OpenForReadOnly( - const DBOptions& db_options, const std::string& dbname, - const std::vector& column_families, - std::vector* handles, DB** dbptr, - bool error_if_log_file_exist) { + const DBOptions& /*db_options*/, const std::string& /*dbname*/, + const std::vector& /*column_families*/, + std::vector* /*handles*/, DB** /*dbptr*/, + bool /*error_if_log_file_exist*/) { return Status::NotSupported("Not supported in ROCKSDB_LITE."); } #endif // !ROCKSDB_LITE -} // namespace rocksdb +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_impl_readonly.h b/thirdparty/rocksdb/db/db_impl_readonly.h index 9bdc95cc87..23816210dc 100644 --- a/thirdparty/rocksdb/db/db_impl_readonly.h +++ b/thirdparty/rocksdb/db/db_impl_readonly.h @@ -7,9 +7,9 @@ #ifndef ROCKSDB_LITE -#include "db/db_impl.h" -#include #include +#include +#include "db/db_impl.h" namespace rocksdb { @@ -36,46 +36,49 @@ class DBImplReadOnly : public DBImpl { std::vector* iterators) override; using DBImpl::Put; - virtual Status Put(const WriteOptions& options, - ColumnFamilyHandle* column_family, const Slice& key, - const Slice& value) override { + virtual Status Put(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/, const Slice& /*value*/) override { return Status::NotSupported("Not supported operation in read only mode."); } using DBImpl::Merge; - virtual Status Merge(const WriteOptions& options, - ColumnFamilyHandle* column_family, const Slice& key, - const Slice& value) override { + virtual Status Merge(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/, const Slice& /*value*/) override { return Status::NotSupported("Not supported operation in read only mode."); } using DBImpl::Delete; - virtual Status Delete(const WriteOptions& options, - ColumnFamilyHandle* column_family, - const Slice& key) override { + virtual Status Delete(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/) override { return Status::NotSupported("Not supported operation in read only mode."); } using DBImpl::SingleDelete; - virtual Status SingleDelete(const WriteOptions& options, - ColumnFamilyHandle* column_family, - const Slice& key) override { + virtual Status SingleDelete(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/) override { return Status::NotSupported("Not supported operation in read only mode."); } - virtual Status Write(const WriteOptions& options, - WriteBatch* updates) override { + virtual Status Write(const WriteOptions& /*options*/, + WriteBatch* /*updates*/) override { return Status::NotSupported("Not supported operation in read only mode."); } using DBImpl::CompactRange; - virtual Status CompactRange(const CompactRangeOptions& options, - ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end) override { + virtual Status CompactRange(const CompactRangeOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice* /*begin*/, + const Slice* /*end*/) override { return Status::NotSupported("Not supported operation in read only mode."); } using DBImpl::CompactFiles; virtual Status CompactFiles( - const CompactionOptions& compact_options, - ColumnFamilyHandle* column_family, - const std::vector& input_file_names, - const int output_level, const int output_path_id = -1) override { + const CompactionOptions& /*compact_options*/, + ColumnFamilyHandle* /*column_family*/, + const std::vector& /*input_file_names*/, + const int /*output_level*/, const int /*output_path_id*/ = -1, + std::vector* const /*output_file_names*/ = nullptr, + CompactionJobInfo* /*compaction_job_info*/ = nullptr) override { return Status::NotSupported("Not supported operation in read only mode."); } @@ -83,18 +86,19 @@ class DBImplReadOnly : public DBImpl { return Status::NotSupported("Not supported operation in read only mode."); } - virtual Status EnableFileDeletions(bool force) override { + virtual Status EnableFileDeletions(bool /*force*/) override { return Status::NotSupported("Not supported operation in read only mode."); } - virtual Status GetLiveFiles(std::vector&, + virtual Status GetLiveFiles(std::vector& ret, uint64_t* manifest_file_size, - bool flush_memtable = true) override { - return Status::NotSupported("Not supported operation in read only mode."); + bool /*flush_memtable*/) override { + return DBImpl::GetLiveFiles(ret, manifest_file_size, + false /* flush_memtable */); } using DBImpl::Flush; - virtual Status Flush(const FlushOptions& options, - ColumnFamilyHandle* column_family) override { + virtual Status Flush(const FlushOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/) override { return Status::NotSupported("Not supported operation in read only mode."); } @@ -105,9 +109,9 @@ class DBImplReadOnly : public DBImpl { using DB::IngestExternalFile; virtual Status IngestExternalFile( - ColumnFamilyHandle* column_family, - const std::vector& external_files, - const IngestExternalFileOptions& ingestion_options) override { + ColumnFamilyHandle* /*column_family*/, + const std::vector& /*external_files*/, + const IngestExternalFileOptions& /*ingestion_options*/) override { return Status::NotSupported("Not supported operation in read only mode."); } @@ -118,6 +122,6 @@ class DBImplReadOnly : public DBImpl { DBImplReadOnly(const DBImplReadOnly&); void operator=(const DBImplReadOnly&); }; -} +} // namespace rocksdb #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/db_impl_secondary.cc b/thirdparty/rocksdb/db/db_impl_secondary.cc new file mode 100644 index 0000000000..acc952524b --- /dev/null +++ b/thirdparty/rocksdb/db/db_impl_secondary.cc @@ -0,0 +1,356 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#include "db/db_impl_secondary.h" +#include "db/db_iter.h" +#include "db/merge_context.h" +#include "monitoring/perf_context_imp.h" +#include "util/auto_roll_logger.h" + +namespace rocksdb { + +#ifndef ROCKSDB_LITE + +DBImplSecondary::DBImplSecondary(const DBOptions& db_options, + const std::string& dbname) + : DBImpl(db_options, dbname) { + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "Opening the db in secondary mode"); + LogFlush(immutable_db_options_.info_log); +} + +DBImplSecondary::~DBImplSecondary() {} + +Status DBImplSecondary::Recover( + const std::vector& column_families, + bool /*readonly*/, bool /*error_if_log_file_exist*/, + bool /*error_if_data_exists_in_logs*/) { + mutex_.AssertHeld(); + + Status s; + s = static_cast(versions_.get()) + ->Recover(column_families, &manifest_reader_, &manifest_reporter_, + &manifest_reader_status_); + if (!s.ok()) { + return s; + } + if (immutable_db_options_.paranoid_checks && s.ok()) { + s = CheckConsistency(); + } + // Initial max_total_in_memory_state_ before recovery logs. + max_total_in_memory_state_ = 0; + for (auto cfd : *versions_->GetColumnFamilySet()) { + auto* mutable_cf_options = cfd->GetLatestMutableCFOptions(); + max_total_in_memory_state_ += mutable_cf_options->write_buffer_size * + mutable_cf_options->max_write_buffer_number; + } + if (s.ok()) { + default_cf_handle_ = new ColumnFamilyHandleImpl( + versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_); + default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats(); + single_column_family_mode_ = + versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1; + } + + // TODO: attempt to recover from WAL files. + return s; +} + +// Implementation of the DB interface +Status DBImplSecondary::Get(const ReadOptions& read_options, + ColumnFamilyHandle* column_family, const Slice& key, + PinnableSlice* value) { + return GetImpl(read_options, column_family, key, value); +} + +Status DBImplSecondary::GetImpl(const ReadOptions& read_options, + ColumnFamilyHandle* column_family, + const Slice& key, PinnableSlice* pinnable_val) { + assert(pinnable_val != nullptr); + PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_); + StopWatch sw(env_, stats_, DB_GET); + PERF_TIMER_GUARD(get_snapshot_time); + + auto cfh = static_cast(column_family); + ColumnFamilyData* cfd = cfh->cfd(); + if (tracer_) { + InstrumentedMutexLock lock(&trace_mutex_); + if (tracer_) { + tracer_->Get(column_family, key); + } + } + // Acquire SuperVersion + SuperVersion* super_version = GetAndRefSuperVersion(cfd); + SequenceNumber snapshot = versions_->LastSequence(); + MergeContext merge_context; + SequenceNumber max_covering_tombstone_seq = 0; + Status s; + LookupKey lkey(key, snapshot); + PERF_TIMER_STOP(get_snapshot_time); + + bool done = false; + if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context, + &max_covering_tombstone_seq, read_options)) { + done = true; + pinnable_val->PinSelf(); + RecordTick(stats_, MEMTABLE_HIT); + } else if ((s.ok() || s.IsMergeInProgress()) && + super_version->imm->Get( + lkey, pinnable_val->GetSelf(), &s, &merge_context, + &max_covering_tombstone_seq, read_options)) { + done = true; + pinnable_val->PinSelf(); + RecordTick(stats_, MEMTABLE_HIT); + } + if (!done && !s.ok() && !s.IsMergeInProgress()) { + ReturnAndCleanupSuperVersion(cfd, super_version); + return s; + } + if (!done) { + PERF_TIMER_GUARD(get_from_output_files_time); + super_version->current->Get(read_options, lkey, pinnable_val, &s, + &merge_context, &max_covering_tombstone_seq); + RecordTick(stats_, MEMTABLE_MISS); + } + { + PERF_TIMER_GUARD(get_post_process_time); + ReturnAndCleanupSuperVersion(cfd, super_version); + RecordTick(stats_, NUMBER_KEYS_READ); + size_t size = pinnable_val->size(); + RecordTick(stats_, BYTES_READ, size); + RecordTimeToHistogram(stats_, BYTES_PER_READ, size); + PERF_COUNTER_ADD(get_read_bytes, size); + } + return s; +} + +Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options, + ColumnFamilyHandle* column_family) { + if (read_options.managed) { + return NewErrorIterator( + Status::NotSupported("Managed iterator is not supported anymore.")); + } + if (read_options.read_tier == kPersistedTier) { + return NewErrorIterator(Status::NotSupported( + "ReadTier::kPersistedData is not yet supported in iterators.")); + } + Iterator* result = nullptr; + auto cfh = reinterpret_cast(column_family); + auto cfd = cfh->cfd(); + ReadCallback* read_callback = nullptr; // No read callback provided. + if (read_options.tailing) { + return NewErrorIterator(Status::NotSupported( + "tailing iterator not supported in secondary mode")); + } else if (read_options.snapshot != nullptr) { + // TODO (yanqin) support snapshot. + return NewErrorIterator( + Status::NotSupported("snapshot not supported in secondary mode")); + } else { + auto snapshot = versions_->LastSequence(); + result = NewIteratorImpl(read_options, cfd, snapshot, read_callback); + } + return result; +} + +ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl( + const ReadOptions& read_options, ColumnFamilyData* cfd, + SequenceNumber snapshot, ReadCallback* read_callback) { + assert(nullptr != cfd); + SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_); + auto db_iter = NewArenaWrappedDbIterator( + env_, read_options, *cfd->ioptions(), super_version->mutable_cf_options, + snapshot, + super_version->mutable_cf_options.max_sequential_skip_in_iterations, + super_version->version_number, read_callback); + auto internal_iter = + NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(), + db_iter->GetRangeDelAggregator(), snapshot); + db_iter->SetIterUnderDBIter(internal_iter); + return db_iter; +} + +Status DBImplSecondary::NewIterators( + const ReadOptions& read_options, + const std::vector& column_families, + std::vector* iterators) { + if (read_options.managed) { + return Status::NotSupported("Managed iterator is not supported anymore."); + } + if (read_options.read_tier == kPersistedTier) { + return Status::NotSupported( + "ReadTier::kPersistedData is not yet supported in iterators."); + } + ReadCallback* read_callback = nullptr; // No read callback provided. + if (iterators == nullptr) { + return Status::InvalidArgument("iterators not allowed to be nullptr"); + } + iterators->clear(); + iterators->reserve(column_families.size()); + if (read_options.tailing) { + return Status::NotSupported( + "tailing iterator not supported in secondary mode"); + } else if (read_options.snapshot != nullptr) { + // TODO (yanqin) support snapshot. + return Status::NotSupported("snapshot not supported in secondary mode"); + } else { + SequenceNumber read_seq = versions_->LastSequence(); + for (auto cfh : column_families) { + ColumnFamilyData* cfd = static_cast(cfh)->cfd(); + iterators->push_back( + NewIteratorImpl(read_options, cfd, read_seq, read_callback)); + } + } + return Status::OK(); +} + +Status DBImplSecondary::TryCatchUpWithPrimary() { + assert(versions_.get() != nullptr); + assert(manifest_reader_.get() != nullptr); + Status s; + std::unordered_set cfds_changed; + InstrumentedMutexLock lock_guard(&mutex_); + s = static_cast(versions_.get()) + ->ReadAndApply(&mutex_, &manifest_reader_, &cfds_changed); + if (s.ok()) { + SuperVersionContext sv_context(true /* create_superversion */); + for (auto cfd : cfds_changed) { + sv_context.NewSuperVersion(); + cfd->InstallSuperVersion(&sv_context, &mutex_); + } + sv_context.Clean(); + } + return s; +} + +Status DB::OpenAsSecondary(const Options& options, const std::string& dbname, + const std::string& secondary_path, DB** dbptr) { + *dbptr = nullptr; + + DBOptions db_options(options); + ColumnFamilyOptions cf_options(options); + std::vector column_families; + column_families.emplace_back(kDefaultColumnFamilyName, cf_options); + std::vector handles; + + Status s = DB::OpenAsSecondary(db_options, dbname, secondary_path, + column_families, &handles, dbptr); + if (s.ok()) { + assert(handles.size() == 1); + delete handles[0]; + } + return s; +} + +Status DB::OpenAsSecondary( + const DBOptions& db_options, const std::string& dbname, + const std::string& secondary_path, + const std::vector& column_families, + std::vector* handles, DB** dbptr) { + *dbptr = nullptr; + if (db_options.max_open_files != -1) { + // TODO (yanqin) maybe support max_open_files != -1 by creating hard links + // on SST files so that db secondary can still have access to old SSTs + // while primary instance may delete original. + return Status::InvalidArgument("require max_open_files to be -1"); + } + + DBOptions tmp_opts(db_options); + if (nullptr == tmp_opts.info_log) { + Env* env = tmp_opts.env; + assert(env != nullptr); + std::string secondary_abs_path; + env->GetAbsolutePath(secondary_path, &secondary_abs_path); + std::string fname = InfoLogFileName(secondary_path, secondary_abs_path, + tmp_opts.db_log_dir); + + env->CreateDirIfMissing(secondary_path); + if (tmp_opts.log_file_time_to_roll > 0 || tmp_opts.max_log_file_size > 0) { + AutoRollLogger* result = new AutoRollLogger( + env, secondary_path, tmp_opts.db_log_dir, tmp_opts.max_log_file_size, + tmp_opts.log_file_time_to_roll, tmp_opts.info_log_level); + Status s = result->GetStatus(); + if (!s.ok()) { + delete result; + } else { + tmp_opts.info_log.reset(result); + } + } + if (nullptr == tmp_opts.info_log) { + env->RenameFile( + fname, OldInfoLogFileName(secondary_path, env->NowMicros(), + secondary_abs_path, tmp_opts.db_log_dir)); + Status s = env->NewLogger(fname, &(tmp_opts.info_log)); + if (tmp_opts.info_log != nullptr) { + tmp_opts.info_log->SetInfoLogLevel(tmp_opts.info_log_level); + } + } + } + + assert(tmp_opts.info_log != nullptr); + + handles->clear(); + DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname); + impl->versions_.reset(new ReactiveVersionSet( + dbname, &impl->immutable_db_options_, impl->env_options_, + impl->table_cache_.get(), impl->write_buffer_manager_, + &impl->write_controller_)); + impl->column_family_memtables_.reset( + new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet())); + impl->mutex_.Lock(); + Status s = impl->Recover(column_families, true, false, false); + if (s.ok()) { + for (auto cf : column_families) { + auto cfd = + impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name); + if (nullptr == cfd) { + s = Status::InvalidArgument("Column family not found: ", cf.name); + break; + } + handles->push_back(new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_)); + } + } + SuperVersionContext sv_context(true /* create_superversion */); + if (s.ok()) { + for (auto cfd : *impl->versions_->GetColumnFamilySet()) { + sv_context.NewSuperVersion(); + cfd->InstallSuperVersion(&sv_context, &impl->mutex_); + } + } + impl->mutex_.Unlock(); + sv_context.Clean(); + if (s.ok()) { + *dbptr = impl; + for (auto h : *handles) { + impl->NewThreadStatusCfInfo( + reinterpret_cast(h)->cfd()); + } + } else { + for (auto h : *handles) { + delete h; + } + handles->clear(); + delete impl; + } + return s; +} +#else // !ROCKSDB_LITE + +Status DB::OpenAsSecondary(const Options& /*options*/, + const std::string& /*name*/, + const std::string& /*secondary_path*/, + DB** /*dbptr*/) { + return Status::NotSupported("Not supported in ROCKSDB_LITE."); +} + +Status DB::OpenAsSecondary( + const DBOptions& /*db_options*/, const std::string& /*dbname*/, + const std::string& /*secondary_path*/, + const std::vector& /*column_families*/, + std::vector* /*handles*/, DB** /*dbptr*/) { + return Status::NotSupported("Not supported in ROCKSDB_LITE."); +} +#endif // !ROCKSDB_LITE + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_impl_secondary.h b/thirdparty/rocksdb/db/db_impl_secondary.h new file mode 100644 index 0000000000..1b6746f7e4 --- /dev/null +++ b/thirdparty/rocksdb/db/db_impl_secondary.h @@ -0,0 +1,151 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#ifndef ROCKSDB_LITE + +#include +#include +#include "db/db_impl.h" + +namespace rocksdb { + +class DBImplSecondary : public DBImpl { + public: + DBImplSecondary(const DBOptions& options, const std::string& dbname); + ~DBImplSecondary() override; + + Status Recover(const std::vector& column_families, + bool read_only, bool error_if_log_file_exist, + bool error_if_data_exists_in_logs) override; + + // Implementations of the DB interface + using DB::Get; + Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family, + const Slice& key, PinnableSlice* value) override; + + Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family, + const Slice& key, PinnableSlice* value); + + using DBImpl::NewIterator; + Iterator* NewIterator(const ReadOptions&, + ColumnFamilyHandle* column_family) override; + + ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& read_options, + ColumnFamilyData* cfd, + SequenceNumber snapshot, + ReadCallback* read_callback); + + Status NewIterators(const ReadOptions& options, + const std::vector& column_families, + std::vector* iterators) override; + + using DBImpl::Put; + Status Put(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/, + const Slice& /*value*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DBImpl::Merge; + Status Merge(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/, + const Slice& /*value*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DBImpl::Delete; + Status Delete(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DBImpl::SingleDelete; + Status SingleDelete(const WriteOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice& /*key*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + Status Write(const WriteOptions& /*options*/, + WriteBatch* /*updates*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DBImpl::CompactRange; + Status CompactRange(const CompactRangeOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice* /*begin*/, const Slice* /*end*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DBImpl::CompactFiles; + Status CompactFiles( + const CompactionOptions& /*compact_options*/, + ColumnFamilyHandle* /*column_family*/, + const std::vector& /*input_file_names*/, + const int /*output_level*/, const int /*output_path_id*/ = -1, + std::vector* const /*output_file_names*/ = nullptr, + CompactionJobInfo* /*compaction_job_info*/ = nullptr) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + Status DisableFileDeletions() override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + Status EnableFileDeletions(bool /*force*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + Status GetLiveFiles(std::vector&, + uint64_t* /*manifest_file_size*/, + bool /*flush_memtable*/ = true) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DBImpl::Flush; + Status Flush(const FlushOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DBImpl::SyncWAL; + Status SyncWAL() override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + using DB::IngestExternalFile; + Status IngestExternalFile( + ColumnFamilyHandle* /*column_family*/, + const std::vector& /*external_files*/, + const IngestExternalFileOptions& /*ingestion_options*/) override { + return Status::NotSupported("Not supported operation in read only mode."); + } + + // Try to catch up with the primary by reading as much as possible from the + // log files until there is nothing more to read or encounters an error. If + // the amount of information in the log files to process is huge, this + // method can take long time due to all the I/O and CPU costs. + Status TryCatchUpWithPrimary() override; + + private: + friend class DB; + + // No copying allowed + DBImplSecondary(const DBImplSecondary&); + void operator=(const DBImplSecondary&); + + using DBImpl::Recover; + + std::unique_ptr manifest_reader_; + std::unique_ptr manifest_reporter_; + std::unique_ptr manifest_reader_status_; +}; +} // namespace rocksdb + +#endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/db_impl_write.cc b/thirdparty/rocksdb/db/db_impl_write.cc index 8a11948f7e..21a9378d21 100644 --- a/thirdparty/rocksdb/db/db_impl_write.cc +++ b/thirdparty/rocksdb/db/db_impl_write.cc @@ -12,6 +12,7 @@ #define __STDC_FORMAT_MACROS #endif #include +#include "db/error_handler.h" #include "db/event_helpers.h" #include "monitoring/perf_context_imp.h" #include "options/options_helper.h" @@ -45,6 +46,11 @@ Status DBImpl::SingleDelete(const WriteOptions& write_options, return DB::SingleDelete(write_options, column_family, key); } +void DBImpl::SetRecoverableStatePreReleaseCallback( + PreReleaseCallback* callback) { + recoverable_state_pre_release_callback_.reset(callback); +} + Status DBImpl::Write(const WriteOptions& write_options, WriteBatch* my_batch) { return WriteImpl(write_options, my_batch, nullptr, nullptr); } @@ -57,17 +63,40 @@ Status DBImpl::WriteWithCallback(const WriteOptions& write_options, } #endif // ROCKSDB_LITE +// The main write queue. This is the only write queue that updates LastSequence. +// When using one write queue, the same sequence also indicates the last +// published sequence. Status DBImpl::WriteImpl(const WriteOptions& write_options, WriteBatch* my_batch, WriteCallback* callback, uint64_t* log_used, uint64_t log_ref, - bool disable_memtable, uint64_t* seq_used) { + bool disable_memtable, uint64_t* seq_used, + size_t batch_cnt, + PreReleaseCallback* pre_release_callback) { + assert(!seq_per_batch_ || batch_cnt != 0); if (my_batch == nullptr) { return Status::Corruption("Batch is nullptr!"); } - if (concurrent_prepare_ && immutable_db_options_.enable_pipelined_write) { + if (tracer_) { + InstrumentedMutexLock lock(&trace_mutex_); + if (tracer_) { + tracer_->Write(my_batch); + } + } + if (write_options.sync && write_options.disableWAL) { + return Status::InvalidArgument("Sync writes has to enable WAL."); + } + if (two_write_queues_ && immutable_db_options_.enable_pipelined_write) { return Status::NotSupported( "pipelined_writes is not compatible with concurrent prepares"); } + if (seq_per_batch_ && immutable_db_options_.enable_pipelined_write) { + // TODO(yiwu): update pipeline write with seq_per_batch and batch_cnt + return Status::NotSupported( + "pipelined_writes is not compatible with seq_per_batch"); + } + // Otherwise IsLatestPersistentState optimization does not make sense + assert(!WriteBatchInternal::IsLatestPersistentState(my_batch) || + disable_memtable); Status status; if (write_options.low_pri) { @@ -77,9 +106,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, } } - if (concurrent_prepare_ && disable_memtable) { + if (two_write_queues_ && disable_memtable) { return WriteImplWALOnly(write_options, my_batch, callback, log_used, - log_ref, seq_used); + log_ref, seq_used, batch_cnt, pre_release_callback); } if (immutable_db_options_.enable_pipelined_write) { @@ -89,7 +118,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, PERF_TIMER_GUARD(write_pre_and_post_process_time); WriteThread::Writer w(write_options, my_batch, callback, log_ref, - disable_memtable); + disable_memtable, batch_cnt, pre_release_callback); if (!write_options.disableWAL) { RecordTick(stats_, WRITE_WITH_WAL); @@ -100,19 +129,24 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, write_thread_.JoinBatchGroup(&w); if (w.state == WriteThread::STATE_PARALLEL_MEMTABLE_WRITER) { // we are a non-leader in a parallel group - PERF_TIMER_GUARD(write_memtable_time); if (w.ShouldWriteToMemtable()) { + PERF_TIMER_STOP(write_pre_and_post_process_time); + PERF_TIMER_GUARD(write_memtable_time); + ColumnFamilyMemTablesImpl column_family_memtables( versions_->GetColumnFamilySet()); w.status = WriteBatchInternal::InsertInto( &w, w.sequence, &column_family_memtables, &flush_scheduler_, write_options.ignore_missing_column_families, 0 /*log_number*/, this, - true /*concurrent_memtable_writes*/); + true /*concurrent_memtable_writes*/, seq_per_batch_, w.batch_cnt); + + PERF_TIMER_START(write_pre_and_post_process_time); } if (write_thread_.CompleteParallelMemTableWriter(&w)) { // we're responsible for exit batch group + // TODO(myabandeh): propagate status to write_group auto last_sequence = w.write_group->last_sequence; versions_->SetLastSequence(last_sequence); MemTableInsertStatusCheck(w.status); @@ -144,19 +178,25 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, WriteThread::WriteGroup write_group; bool in_parallel_group = false; uint64_t last_sequence = kMaxSequenceNumber; - if (!concurrent_prepare_) { + if (!two_write_queues_) { last_sequence = versions_->LastSequence(); } mutex_.Lock(); - bool need_log_sync = !write_options.disableWAL && write_options.sync; + bool need_log_sync = write_options.sync; bool need_log_dir_sync = need_log_sync && !log_dir_synced_; - if (!concurrent_prepare_ || !disable_memtable) { + if (!two_write_queues_ || !disable_memtable) { // With concurrent writes we do preprocess only in the write thread that // also does write to memtable to avoid sync issue on shared data structure // with the other thread + + // PreprocessWrite does its own perf timing. + PERF_TIMER_STOP(write_pre_and_post_process_time); + status = PreprocessWrite(write_options, &need_log_sync, &write_context); + + PERF_TIMER_START(write_pre_and_post_process_time); } log::Writer* log_writer = logs_.back().writer; @@ -167,6 +207,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, // and protects against concurrent loggers and concurrent writes // into memtables + TEST_SYNC_POINT("DBImpl::WriteImpl:BeforeLeaderEnters"); last_batch_group_size_ = write_thread_.EnterAsBatchGroupLeader(&w, &write_group); @@ -184,10 +225,12 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, // more than once to a particular key. bool parallel = immutable_db_options_.allow_concurrent_memtable_write && write_group.size > 1; - int total_count = 0; - uint64_t total_byte_size = 0; + size_t total_count = 0; + size_t valid_batches = 0; + size_t total_byte_size = 0; for (auto* writer : write_group) { if (writer->CheckCallback(this)) { + valid_batches += writer->batch_cnt; if (writer->ShouldWriteToMemtable()) { total_count += WriteBatchInternal::Count(writer->batch); parallel = parallel && !writer->batch->HasMerge(); @@ -197,8 +240,15 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, total_byte_size, WriteBatchInternal::ByteSize(writer->batch)); } } - - const bool concurrent_update = concurrent_prepare_; + // Note about seq_per_batch_: either disableWAL is set for the entire write + // group or not. In either case we inc seq for each write batch with no + // failed callback. This means that there could be a batch with + // disalbe_memtable in between; although we do not write this batch to + // memtable it still consumes a seq. Otherwise, if !seq_per_batch_, we inc + // the seq per valid written key to mem. + size_t seq_inc = seq_per_batch_ ? valid_batches : total_count; + + const bool concurrent_update = two_write_queues_; // Update stats while we are an exclusive group leader, so we know // that nobody else can be writing to these particular stats. // We're optimistic, updating the stats before we successfully @@ -218,7 +268,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, concurrent_update); RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other); } - MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size); + RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size); if (write_options.disableWAL) { has_unpersisted_data_.store(true, std::memory_order_relaxed); @@ -226,7 +276,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, PERF_TIMER_STOP(write_pre_and_post_process_time); - if (!concurrent_prepare_) { + if (!two_write_queues_) { if (status.ok() && !write_options.disableWAL) { PERF_TIMER_GUARD(write_wal_time); status = WriteToWAL(write_group, log_writer, log_used, need_log_sync, @@ -235,38 +285,60 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, } else { if (status.ok() && !write_options.disableWAL) { PERF_TIMER_GUARD(write_wal_time); - // LastToBeWrittenSequence is increased inside WriteToWAL under + // LastAllocatedSequence is increased inside WriteToWAL under // wal_write_mutex_ to ensure ordered events in WAL status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence, - total_count); + seq_inc); } else { // Otherwise we inc seq number for memtable writes - last_sequence = versions_->FetchAddLastToBeWrittenSequence(total_count); + last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc); } } assert(last_sequence != kMaxSequenceNumber); const SequenceNumber current_sequence = last_sequence + 1; - last_sequence += total_count; + last_sequence += seq_inc; + + // PreReleaseCallback is called after WAL write and before memtable write + if (status.ok()) { + SequenceNumber next_sequence = current_sequence; + // Note: the logic for advancing seq here must be consistent with the + // logic in WriteBatchInternal::InsertInto(write_group...) as well as + // with WriteBatchInternal::InsertInto(write_batch...) that is called on + // the merged batch during recovery from the WAL. + for (auto* writer : write_group) { + if (writer->CallbackFailed()) { + continue; + } + writer->sequence = next_sequence; + if (writer->pre_release_callback) { + Status ws = writer->pre_release_callback->Callback( + writer->sequence, disable_memtable, writer->log_used); + if (!ws.ok()) { + status = ws; + break; + } + } + if (seq_per_batch_) { + assert(writer->batch_cnt); + next_sequence += writer->batch_cnt; + } else if (writer->ShouldWriteToMemtable()) { + next_sequence += WriteBatchInternal::Count(writer->batch); + } + } + } if (status.ok()) { PERF_TIMER_GUARD(write_memtable_time); if (!parallel) { + // w.sequence will be set inside InsertInto w.status = WriteBatchInternal::InsertInto( write_group, current_sequence, column_family_memtables_.get(), &flush_scheduler_, write_options.ignore_missing_column_families, - 0 /*recovery_log_number*/, this); + 0 /*recovery_log_number*/, this, parallel, seq_per_batch_, + batch_per_txn_); } else { - SequenceNumber next_sequence = current_sequence; - for (auto* writer : write_group) { - if (writer->ShouldWriteToMemtable()) { - writer->sequence = next_sequence; - next_sequence += WriteBatchInternal::Count(writer->batch); - } - } write_group.last_sequence = last_sequence; - write_group.running.store(static_cast(write_group.size), - std::memory_order_relaxed); write_thread_.LaunchParallelMemTableWriters(&write_group); in_parallel_group = true; @@ -279,27 +351,28 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, w.status = WriteBatchInternal::InsertInto( &w, w.sequence, &column_family_memtables, &flush_scheduler_, write_options.ignore_missing_column_families, 0 /*log_number*/, - this, true /*concurrent_memtable_writes*/); - } - if (seq_used != nullptr) { - *seq_used = w.sequence; + this, true /*concurrent_memtable_writes*/, seq_per_batch_, + w.batch_cnt, batch_per_txn_); } } + if (seq_used != nullptr) { + *seq_used = w.sequence; + } } } PERF_TIMER_START(write_pre_and_post_process_time); if (!w.CallbackFailed()) { - WriteCallbackStatusCheck(status); + WriteStatusCheck(status); } if (need_log_sync) { mutex_.Lock(); MarkLogsSynced(logfile_number_, need_log_dir_sync, status); mutex_.Unlock(); - // Requesting sync with concurrent_prepare_ is expected to be very rare. We - // hance provide a simple implementation that is not necessarily efficient. - if (concurrent_prepare_) { + // Requesting sync with two_write_queues_ is expected to be very rare. We + // hence provide a simple implementation that is not necessarily efficient. + if (two_write_queues_) { if (manual_wal_flush_) { status = FlushWAL(true); } else { @@ -316,10 +389,12 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options, } if (should_exit_batch_group) { if (status.ok()) { + // Note: if we are to resume after non-OK statuses we need to revisit how + // we reacts to non-OK statuses here. versions_->SetLastSequence(last_sequence); } MemTableInsertStatusCheck(w.status); - write_thread_.ExitAsBatchGroupLeader(write_group, w.status); + write_thread_.ExitAsBatchGroupLeader(write_group, status); } if (status.ok()) { @@ -348,7 +423,10 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options, mutex_.Lock(); bool need_log_sync = !write_options.disableWAL && write_options.sync; bool need_log_dir_sync = need_log_sync && !log_dir_synced_; + // PreprocessWrite does its own perf timing. + PERF_TIMER_STOP(write_pre_and_post_process_time); w.status = PreprocessWrite(write_options, &need_log_sync, &write_context); + PERF_TIMER_START(write_pre_and_post_process_time); log::Writer* log_writer = logs_.back().writer; mutex_.Unlock(); @@ -385,10 +463,11 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options, RecordTick(stats_, NUMBER_KEYS_WRITTEN, total_count); stats->AddDBStats(InternalStats::BYTES_WRITTEN, total_byte_size); RecordTick(stats_, BYTES_WRITTEN, total_byte_size); + RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size); PERF_TIMER_STOP(write_pre_and_post_process_time); - if (w.ShouldWriteToWAL()) { + if (w.status.ok() && !write_options.disableWAL) { PERF_TIMER_GUARD(write_wal_time); stats->AddDBStats(InternalStats::WRITE_DONE_BY_SELF, 1); RecordTick(stats_, WRITE_DONE_BY_SELF, 1); @@ -402,7 +481,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options, } if (!w.CallbackFailed()) { - WriteCallbackStatusCheck(w.status); + WriteStatusCheck(w.status); } if (need_log_sync) { @@ -417,7 +496,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options, WriteThread::WriteGroup memtable_write_group; if (w.state == WriteThread::STATE_MEMTABLE_WRITER_LEADER) { PERF_TIMER_GUARD(write_memtable_time); - assert(w.status.ok()); + assert(w.ShouldWriteToMemtable()); write_thread_.EnterAsMemTableWriter(&w, &memtable_write_group); if (memtable_write_group.size > 1 && immutable_db_options_.allow_concurrent_memtable_write) { @@ -426,7 +505,8 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options, memtable_write_group.status = WriteBatchInternal::InsertInto( memtable_write_group, w.sequence, column_family_memtables_.get(), &flush_scheduler_, write_options.ignore_missing_column_families, - 0 /*log_number*/, this); + 0 /*log_number*/, this, false /*concurrent_memtable_writes*/, + seq_per_batch_, batch_per_txn_); versions_->SetLastSequence(memtable_write_group.last_sequence); write_thread_.ExitAsMemTableWriter(&w, memtable_write_group); } @@ -454,17 +534,19 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options, return w.FinalStatus(); } +// The 2nd write queue. If enabled it will be used only for WAL-only writes. +// This is the only queue that updates LastPublishedSequence which is only +// applicable in a two-queue setting. Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options, WriteBatch* my_batch, WriteCallback* callback, uint64_t* log_used, uint64_t log_ref, - uint64_t* seq_used) { + uint64_t* seq_used, size_t batch_cnt, + PreReleaseCallback* pre_release_callback) { Status status; PERF_TIMER_GUARD(write_pre_and_post_process_time); WriteThread::Writer w(write_options, my_batch, callback, log_ref, - true /* disable_memtable */); - if (write_options.disableWAL) { - return status; - } + true /* disable_memtable */, batch_cnt, + pre_release_callback); RecordTick(stats_, WRITE_WITH_WAL); StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE); @@ -481,14 +563,13 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options, } // else we are the leader of the write batch group assert(w.state == WriteThread::STATE_GROUP_LEADER); - WriteContext write_context; WriteThread::WriteGroup write_group; uint64_t last_sequence; nonmem_write_thread_.EnterAsBatchGroupLeader(&w, &write_group); // Note: no need to update last_batch_group_size_ here since the batch writes // to WAL only - uint64_t total_byte_size = 0; + size_t total_byte_size = 0; for (auto* writer : write_group) { if (writer->CheckCallback(this)) { total_byte_size = WriteBatchInternal::AppendedByteSize( @@ -513,24 +594,44 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options, concurrent_update); RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other); } - MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size); + RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size); PERF_TIMER_STOP(write_pre_and_post_process_time); PERF_TIMER_GUARD(write_wal_time); - // LastToBeWrittenSequence is increased inside WriteToWAL under + // LastAllocatedSequence is increased inside WriteToWAL under // wal_write_mutex_ to ensure ordered events in WAL - status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence, - 0 /*total_count*/); + size_t seq_inc = 0 /* total_count */; + if (seq_per_batch_) { + size_t total_batch_cnt = 0; + for (auto* writer : write_group) { + assert(writer->batch_cnt); + total_batch_cnt += writer->batch_cnt; + } + seq_inc = total_batch_cnt; + } + if (!write_options.disableWAL) { + status = + ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc); + } else { + // Otherwise we inc seq number to do solely the seq allocation + last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc); + } auto curr_seq = last_sequence + 1; for (auto* writer : write_group) { - if (writer->CheckCallback(this)) { - writer->sequence = curr_seq; - curr_seq += WriteBatchInternal::Count(writer->batch); + if (writer->CallbackFailed()) { + continue; } + writer->sequence = curr_seq; + if (seq_per_batch_) { + assert(writer->batch_cnt); + curr_seq += writer->batch_cnt; + } + // else seq advances only by memtable writes } if (status.ok() && write_options.sync) { - // Requesting sync with concurrent_prepare_ is expected to be very rare. We + assert(!write_options.disableWAL); + // Requesting sync with two_write_queues_ is expected to be very rare. We // hance provide a simple implementation that is not necessarily efficient. if (manual_wal_flush_) { status = FlushWAL(true); @@ -541,9 +642,23 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options, PERF_TIMER_START(write_pre_and_post_process_time); if (!w.CallbackFailed()) { - WriteCallbackStatusCheck(status); + WriteStatusCheck(status); } - nonmem_write_thread_.ExitAsBatchGroupLeader(write_group, w.status); + if (status.ok()) { + for (auto* writer : write_group) { + if (!writer->CallbackFailed() && writer->pre_release_callback) { + assert(writer->sequence != kMaxSequenceNumber); + const bool DISABLE_MEMTABLE = true; + Status ws = writer->pre_release_callback->Callback( + writer->sequence, DISABLE_MEMTABLE, writer->log_used); + if (!ws.ok()) { + status = ws; + break; + } + } + } + } + nonmem_write_thread_.ExitAsBatchGroupLeader(write_group, status); if (status.ok()) { status = w.FinalStatus(); } @@ -553,22 +668,13 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options, return status; } -void DBImpl::WriteCallbackStatusCheck(const Status& status) { +void DBImpl::WriteStatusCheck(const Status& status) { // Is setting bg_error_ enough here? This will at least stop // compaction and fail any further writes. if (immutable_db_options_.paranoid_checks && !status.ok() && !status.IsBusy() && !status.IsIncomplete()) { mutex_.Lock(); - if (bg_error_.ok()) { - Status new_bg_error = status; - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners, - BackgroundErrorReason::kWriteCallback, - &new_bg_error, &mutex_); - if (!new_bg_error.ok()) { - bg_error_ = new_bg_error; // stop compaction & fail any further writes - } - } + error_handler_.SetBGError(status, BackgroundErrorReason::kWriteCallback); mutex_.Unlock(); } } @@ -581,15 +687,8 @@ void DBImpl::MemTableInsertStatusCheck(const Status& status) { // ignore_missing_column_families. if (!status.ok()) { mutex_.Lock(); - assert(bg_error_.ok()); - Status new_bg_error = status; - // may temporarily unlock and lock the mutex. - EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners, - BackgroundErrorReason::kMemTable, - &new_bg_error, &mutex_); - if (!new_bg_error.ok()) { - bg_error_ = new_bg_error; // stop compaction & fail any further writes - } + assert(!error_handler_.IsBGWorkStopped()); + error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable); mutex_.Unlock(); } } @@ -601,11 +700,17 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options, assert(write_context != nullptr && need_log_sync != nullptr); Status status; + if (error_handler_.IsDBStopped()) { + status = error_handler_.GetBGError(); + } + + PERF_TIMER_GUARD(write_scheduling_flushes_compactions_time); + assert(!single_column_family_mode_ || versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1); if (UNLIKELY(status.ok() && !single_column_family_mode_ && total_log_size_ > GetMaxTotalWalSize())) { - status = HandleWALFull(write_context); + status = SwitchWAL(write_context); } if (UNLIKELY(status.ok() && write_buffer_manager_->ShouldFlush())) { @@ -617,22 +722,23 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options, status = HandleWriteBufferFull(write_context); } - if (UNLIKELY(status.ok() && !bg_error_.ok())) { - return bg_error_; - } - if (UNLIKELY(status.ok() && !flush_scheduler_.Empty())) { status = ScheduleFlushes(write_context); } + PERF_TIMER_STOP(write_scheduling_flushes_compactions_time); + PERF_TIMER_GUARD(write_pre_and_post_process_time); + if (UNLIKELY(status.ok() && (write_controller_.IsStopped() || write_controller_.NeedsDelay()))) { + PERF_TIMER_STOP(write_pre_and_post_process_time); PERF_TIMER_GUARD(write_delay_time); // We don't know size of curent batch so that we always use the size // for previous one. It might create a fairness issue that expiration // might happen for smaller writes but larger writes can go through. // Can optimize it if it is an issue. status = DelayWrite(last_batch_group_size_, write_options); + PERF_TIMER_START(write_pre_and_post_process_time); } if (status.ok() && *need_log_sync) { @@ -663,18 +769,24 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options, } WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group, - WriteBatch* tmp_batch, size_t* write_with_wal) { + WriteBatch* tmp_batch, size_t* write_with_wal, + WriteBatch** to_be_cached_state) { assert(write_with_wal != nullptr); assert(tmp_batch != nullptr); + assert(*to_be_cached_state == nullptr); WriteBatch* merged_batch = nullptr; *write_with_wal = 0; auto* leader = write_group.leader; - if (write_group.size == 1 && leader->ShouldWriteToWAL() && + assert(!leader->disable_wal); // Same holds for all in the batch group + if (write_group.size == 1 && !leader->CallbackFailed() && leader->batch->GetWalTerminationPoint().is_cleared()) { // we simply write the first WriteBatch to WAL if the group only // contains one batch, that batch should be written to the WAL, // and the batch is not wanting to be truncated merged_batch = leader->batch; + if (WriteBatchInternal::IsLatestPersistentState(merged_batch)) { + *to_be_cached_state = merged_batch; + } *write_with_wal = 1; } else { // WAL needs all of the batches flattened into a single batch. @@ -682,9 +794,13 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group, // interface merged_batch = tmp_batch; for (auto writer : write_group) { - if (writer->ShouldWriteToWAL()) { + if (!writer->CallbackFailed()) { WriteBatchInternal::Append(merged_batch, writer->batch, /*WAL_only*/ true); + if (WriteBatchInternal::IsLatestPersistentState(writer->batch)) { + // We only need to cache the last of such write batch + *to_be_cached_state = writer->batch; + } (*write_with_wal)++; } } @@ -692,7 +808,7 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group, return merged_batch; } -// When concurrent_prepare_ is disabled, this function is called from the only +// When two_write_queues_ is disabled, this function is called from the only // write thread. Otherwise this must be called holding log_write_mutex_. Status DBImpl::WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer, uint64_t* log_used, @@ -700,7 +816,21 @@ Status DBImpl::WriteToWAL(const WriteBatch& merged_batch, assert(log_size != nullptr); Slice log_entry = WriteBatchInternal::Contents(&merged_batch); *log_size = log_entry.size(); + // When two_write_queues_ WriteToWAL has to be protected from concurretn calls + // from the two queues anyway and log_write_mutex_ is already held. Otherwise + // if manual_wal_flush_ is enabled we need to protect log_writer->AddRecord + // from possible concurrent calls via the FlushWAL by the application. + const bool needs_locking = manual_wal_flush_ && !two_write_queues_; + // Due to performance cocerns of missed branch prediction penalize the new + // manual_wal_flush_ feature (by UNLIKELY) instead of the more common case + // when we do not need any locking. + if (UNLIKELY(needs_locking)) { + log_write_mutex_.Lock(); + } Status status = log_writer->AddRecord(log_entry); + if (UNLIKELY(needs_locking)) { + log_write_mutex_.Unlock(); + } if (log_used != nullptr) { *log_used = logfile_number_; } @@ -718,9 +848,12 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group, SequenceNumber sequence) { Status status; + assert(!write_group.leader->disable_wal); + // Same holds for all in the batch group size_t write_with_wal = 0; - WriteBatch* merged_batch = - MergeBatch(write_group, &tmp_batch_, &write_with_wal); + WriteBatch* to_be_cached_state = nullptr; + WriteBatch* merged_batch = MergeBatch(write_group, &tmp_batch_, + &write_with_wal, &to_be_cached_state); if (merged_batch == write_group.leader->batch) { write_group.leader->log_used = logfile_number_; } else if (write_with_wal > 1) { @@ -733,6 +866,10 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group, uint64_t log_size; status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size); + if (to_be_cached_state) { + cached_recoverable_state_ = *to_be_cached_state; + cached_recoverable_state_empty_ = false; + } if (status.ok() && need_log_sync) { StopWatch sw(env_, stats_, WAL_FILE_SYNC_MICROS); @@ -777,13 +914,16 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group, Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group, uint64_t* log_used, SequenceNumber* last_sequence, - int total_count) { + size_t seq_inc) { Status status; + assert(!write_group.leader->disable_wal); + // Same holds for all in the batch group WriteBatch tmp_batch; size_t write_with_wal = 0; + WriteBatch* to_be_cached_state = nullptr; WriteBatch* merged_batch = - MergeBatch(write_group, &tmp_batch, &write_with_wal); + MergeBatch(write_group, &tmp_batch, &write_with_wal, &to_be_cached_state); // We need to lock log_write_mutex_ since logs_ and alive_log_files might be // pushed back concurrently @@ -795,13 +935,17 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group, writer->log_used = logfile_number_; } } - *last_sequence = versions_->FetchAddLastToBeWrittenSequence(total_count); + *last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc); auto sequence = *last_sequence + 1; WriteBatchInternal::SetSequence(merged_batch, sequence); log::Writer* log_writer = logs_.back().writer; uint64_t log_size; status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size); + if (to_be_cached_state) { + cached_recoverable_state_ = *to_be_cached_state; + cached_recoverable_state_empty_ = false; + } log_write_mutex_.Unlock(); if (status.ok()) { @@ -816,7 +960,76 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group, return status; } -Status DBImpl::HandleWALFull(WriteContext* write_context) { +Status DBImpl::WriteRecoverableState() { + mutex_.AssertHeld(); + if (!cached_recoverable_state_empty_) { + bool dont_care_bool; + SequenceNumber next_seq; + if (two_write_queues_) { + log_write_mutex_.Lock(); + } + SequenceNumber seq; + if (two_write_queues_) { + seq = versions_->FetchAddLastAllocatedSequence(0); + } else { + seq = versions_->LastSequence(); + } + WriteBatchInternal::SetSequence(&cached_recoverable_state_, seq + 1); + auto status = WriteBatchInternal::InsertInto( + &cached_recoverable_state_, column_family_memtables_.get(), + &flush_scheduler_, true, 0 /*recovery_log_number*/, this, + false /* concurrent_memtable_writes */, &next_seq, &dont_care_bool, + seq_per_batch_); + auto last_seq = next_seq - 1; + if (two_write_queues_) { + versions_->FetchAddLastAllocatedSequence(last_seq - seq); + versions_->SetLastPublishedSequence(last_seq); + } + versions_->SetLastSequence(last_seq); + if (two_write_queues_) { + log_write_mutex_.Unlock(); + } + if (status.ok() && recoverable_state_pre_release_callback_) { + const bool DISABLE_MEMTABLE = true; + for (uint64_t sub_batch_seq = seq + 1; + sub_batch_seq < next_seq && status.ok(); sub_batch_seq++) { + uint64_t const no_log_num = 0; + status = recoverable_state_pre_release_callback_->Callback( + sub_batch_seq, !DISABLE_MEMTABLE, no_log_num); + } + } + if (status.ok()) { + cached_recoverable_state_.Clear(); + cached_recoverable_state_empty_ = true; + } + return status; + } + return Status::OK(); +} + +void DBImpl::SelectColumnFamiliesForAtomicFlush( + autovector* cfds) { + for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) { + if (cfd->IsDropped()) { + continue; + } + if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() || + !cached_recoverable_state_empty_.load()) { + cfds->push_back(cfd); + } + } +} + +// Assign sequence number for atomic flush. +void DBImpl::AssignAtomicFlushSeq(const autovector& cfds) { + assert(immutable_db_options_.atomic_flush); + auto seq = versions_->LastSequence(); + for (auto cfd : cfds) { + cfd->imm()->AssignAtomicFlushSeq(seq); + } +} + +Status DBImpl::SwitchWAL(WriteContext* write_context) { mutex_.AssertHeld(); assert(write_context != nullptr); Status status; @@ -826,52 +1039,78 @@ Status DBImpl::HandleWALFull(WriteContext* write_context) { } auto oldest_alive_log = alive_log_files_.begin()->number; - auto oldest_log_with_uncommited_prep = FindMinLogContainingOutstandingPrep(); - - if (allow_2pc() && - oldest_log_with_uncommited_prep > 0 && - oldest_log_with_uncommited_prep <= oldest_alive_log) { - if (unable_to_flush_oldest_log_) { + bool flush_wont_release_oldest_log = false; + if (allow_2pc()) { + auto oldest_log_with_uncommitted_prep = + logs_with_prep_tracker_.FindMinLogContainingOutstandingPrep(); + + assert(oldest_log_with_uncommitted_prep == 0 || + oldest_log_with_uncommitted_prep >= oldest_alive_log); + if (oldest_log_with_uncommitted_prep > 0 && + oldest_log_with_uncommitted_prep == oldest_alive_log) { + if (unable_to_release_oldest_log_) { // we already attempted to flush all column families dependent on - // the oldest alive log but the log still contained uncommited transactions. - // the oldest alive log STILL contains uncommited transaction so there - // is still nothing that we can do. + // the oldest alive log but the log still contained uncommitted + // transactions so there is still nothing that we can do. return status; - } else { - ROCKS_LOG_WARN( - immutable_db_options_.info_log, - "Unable to release oldest log due to uncommited transaction"); - unable_to_flush_oldest_log_ = true; + } else { + ROCKS_LOG_WARN( + immutable_db_options_.info_log, + "Unable to release oldest log due to uncommitted transaction"); + unable_to_release_oldest_log_ = true; + flush_wont_release_oldest_log = true; + } } - } else { + } + if (!flush_wont_release_oldest_log) { // we only mark this log as getting flushed if we have successfully // flushed all data in this log. If this log contains outstanding prepared - // transactions then we cannot flush this log until those transactions are commited. - unable_to_flush_oldest_log_ = false; + // transactions then we cannot flush this log until those transactions are + // commited. + unable_to_release_oldest_log_ = false; alive_log_files_.begin()->getting_flushed = true; } - ROCKS_LOG_INFO(immutable_db_options_.info_log, - "Flushing all column families with data in WAL number %" PRIu64 - ". Total log size is %" PRIu64 - " while max_total_wal_size is %" PRIu64, - oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize()); + ROCKS_LOG_INFO( + immutable_db_options_.info_log, + "Flushing all column families with data in WAL number %" PRIu64 + ". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64, + oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize()); // no need to refcount because drop is happening in write thread, so can't // happen while we're in the write thread - for (auto cfd : *versions_->GetColumnFamilySet()) { - if (cfd->IsDropped()) { - continue; - } - if (cfd->OldestLogToKeep() <= oldest_alive_log) { - status = SwitchMemtable(cfd, write_context); - if (!status.ok()) { - break; + autovector cfds; + if (immutable_db_options_.atomic_flush) { + SelectColumnFamiliesForAtomicFlush(&cfds); + } else { + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (cfd->IsDropped()) { + continue; + } + if (cfd->OldestLogToKeep() <= oldest_alive_log) { + cfds.push_back(cfd); } + } + } + for (const auto cfd : cfds) { + cfd->Ref(); + status = SwitchMemtable(cfd, write_context); + cfd->Unref(); + if (!status.ok()) { + break; + } + } + if (status.ok()) { + if (immutable_db_options_.atomic_flush) { + AssignAtomicFlushSeq(cfds); + } + for (auto cfd : cfds) { cfd->imm()->FlushRequested(); - SchedulePendingFlush(cfd); } + FlushRequest flush_req; + GenerateFlushRequest(cfds, &flush_req); + SchedulePendingFlush(flush_req, FlushReason::kWriteBufferManager); + MaybeScheduleFlushOrCompaction(); } - MaybeScheduleFlushOrCompaction(); return status; } @@ -888,35 +1127,59 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) { ROCKS_LOG_INFO( immutable_db_options_.info_log, "Flushing column family with largest mem table size. Write buffer is " - "using %" PRIu64 " bytes out of a total of %" PRIu64 ".", + "using %" ROCKSDB_PRIszt " bytes out of a total of %" ROCKSDB_PRIszt ".", write_buffer_manager_->memory_usage(), write_buffer_manager_->buffer_size()); // no need to refcount because drop is happening in write thread, so can't // happen while we're in the write thread - ColumnFamilyData* cfd_picked = nullptr; - SequenceNumber seq_num_for_cf_picked = kMaxSequenceNumber; + autovector cfds; + if (immutable_db_options_.atomic_flush) { + SelectColumnFamiliesForAtomicFlush(&cfds); + } else { + ColumnFamilyData* cfd_picked = nullptr; + SequenceNumber seq_num_for_cf_picked = kMaxSequenceNumber; - for (auto cfd : *versions_->GetColumnFamilySet()) { - if (cfd->IsDropped()) { + for (auto cfd : *versions_->GetColumnFamilySet()) { + if (cfd->IsDropped()) { + continue; + } + if (!cfd->mem()->IsEmpty()) { + // We only consider active mem table, hoping immutable memtable is + // already in the process of flushing. + uint64_t seq = cfd->mem()->GetCreationSeq(); + if (cfd_picked == nullptr || seq < seq_num_for_cf_picked) { + cfd_picked = cfd; + seq_num_for_cf_picked = seq; + } + } + } + if (cfd_picked != nullptr) { + cfds.push_back(cfd_picked); + } + } + + for (const auto cfd : cfds) { + if (cfd->mem()->IsEmpty()) { continue; } - if (!cfd->mem()->IsEmpty()) { - // We only consider active mem table, hoping immutable memtable is - // already in the process of flushing. - uint64_t seq = cfd->mem()->GetCreationSeq(); - if (cfd_picked == nullptr || seq < seq_num_for_cf_picked) { - cfd_picked = cfd; - seq_num_for_cf_picked = seq; - } + cfd->Ref(); + status = SwitchMemtable(cfd, write_context); + cfd->Unref(); + if (!status.ok()) { + break; } } - if (cfd_picked != nullptr) { - status = SwitchMemtable(cfd_picked, write_context); - if (status.ok()) { - cfd_picked->imm()->FlushRequested(); - SchedulePendingFlush(cfd_picked); - MaybeScheduleFlushOrCompaction(); + if (status.ok()) { + if (immutable_db_options_.atomic_flush) { + AssignAtomicFlushSeq(cfds); } + for (const auto cfd : cfds) { + cfd->imm()->FlushRequested(); + } + FlushRequest flush_req; + GenerateFlushRequest(cfds, &flush_req); + SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull); + MaybeScheduleFlushOrCompaction(); } return status; } @@ -939,10 +1202,14 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, uint64_t delay = write_controller_.GetDelay(env_, num_bytes); if (delay > 0) { if (write_options.no_slowdown) { - return Status::Incomplete(); + return Status::Incomplete("Write stall"); } TEST_SYNC_POINT("DBImpl::DelayWrite:Sleep"); + // Notify write_thread_ about the stall so it can setup a barrier and + // fail any pending writers with no_slowdown + write_thread_.BeginWriteStall(); + TEST_SYNC_POINT("DBImpl::DelayWrite:BeginWriteStallDone"); mutex_.Unlock(); // We will delay the write until we have slept for delay ms or // we don't need a delay anymore @@ -959,15 +1226,25 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, env_->SleepForMicroseconds(kDelayInterval); } mutex_.Lock(); + write_thread_.EndWriteStall(); } - while (bg_error_.ok() && write_controller_.IsStopped()) { + // Don't wait if there's a background error, even if its a soft error. We + // might wait here indefinitely as the background compaction may never + // finish successfully, resulting in the stall condition lasting + // indefinitely + while (error_handler_.GetBGError().ok() && write_controller_.IsStopped()) { if (write_options.no_slowdown) { - return Status::Incomplete(); + return Status::Incomplete("Write stall"); } delayed = true; + + // Notify write_thread_ about the stall so it can setup a barrier and + // fail any pending writers with no_slowdown + write_thread_.BeginWriteStall(); TEST_SYNC_POINT("DBImpl::DelayWrite:Wait"); bg_cv_.Wait(); + write_thread_.EndWriteStall(); } } assert(!delayed || !write_options.no_slowdown); @@ -977,7 +1254,19 @@ Status DBImpl::DelayWrite(uint64_t num_bytes, RecordTick(stats_, STALL_MICROS, time_delayed); } - return bg_error_; + // If DB is not in read-only mode and write_controller is not stopping + // writes, we can ignore any background errors and allow the write to + // proceed + Status s; + if (write_controller_.IsStopped()) { + // If writes are still stopped, it means we bailed due to a background + // error + s = Status::Incomplete(error_handler_.GetBGError().ToString()); + } + if (error_handler_.IsDBStopped()) { + s = error_handler_.GetBGError(); + } + return s; } Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options, @@ -1001,6 +1290,7 @@ Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options, // is that in case the write is heavy, low pri writes may never have // a chance to run. Now we guarantee we are still slowly making // progress. + PERF_TIMER_GUARD(write_delay_time); write_controller_.low_pri_rate_limiter()->Request( my_batch->GetDataSize(), Env::IO_HIGH, nullptr /* stats */, RateLimiter::OpType::kWrite); @@ -1010,21 +1300,46 @@ Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options, } Status DBImpl::ScheduleFlushes(WriteContext* context) { - ColumnFamilyData* cfd; - while ((cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) { - auto status = SwitchMemtable(cfd, context); + autovector cfds; + if (immutable_db_options_.atomic_flush) { + SelectColumnFamiliesForAtomicFlush(&cfds); + for (auto cfd : cfds) { + cfd->Ref(); + } + flush_scheduler_.Clear(); + } else { + ColumnFamilyData* tmp_cfd; + while ((tmp_cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) { + cfds.push_back(tmp_cfd); + } + } + Status status; + for (auto& cfd : cfds) { + if (!cfd->mem()->IsEmpty()) { + status = SwitchMemtable(cfd, context); + } if (cfd->Unref()) { delete cfd; + cfd = nullptr; } if (!status.ok()) { - return status; + break; } } - return Status::OK(); + if (status.ok()) { + if (immutable_db_options_.atomic_flush) { + AssignAtomicFlushSeq(cfds); + } + FlushRequest flush_req; + GenerateFlushRequest(cfds, &flush_req); + SchedulePendingFlush(flush_req, FlushReason::kWriteBufferFull); + MaybeScheduleFlushOrCompaction(); + } + return status; } #ifndef ROCKSDB_LITE -void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* cfd, +void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/, const MemTableInfo& mem_table_info) { if (immutable_db_options_.listeners.size() == 0U) { return; @@ -1044,41 +1359,51 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* cfd, Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) { mutex_.AssertHeld(); WriteThread::Writer nonmem_w; - if (concurrent_prepare_) { + if (two_write_queues_) { // SwitchMemtable is a rare event. To simply the reasoning, we make sure // that there is no concurrent thread writing to WAL. nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_); } - unique_ptr lfile; + std::unique_ptr lfile; log::Writer* new_log = nullptr; MemTable* new_mem = nullptr; + // Recoverable state is persisted in WAL. After memtable switch, WAL might + // be deleted, so we write the state to memtable to be persisted as well. + Status s = WriteRecoverableState(); + if (!s.ok()) { + return s; + } + // In case of pipelined write is enabled, wait for all pending memtable // writers. if (immutable_db_options_.enable_pipelined_write) { + // Memtable writers may call DB::Get in case max_successive_merges > 0, + // which may lock mutex. Unlocking mutex here to avoid deadlock. + mutex_.Unlock(); write_thread_.WaitForMemTableWriters(); + mutex_.Lock(); } // Attempt to switch to a new memtable and trigger flush of old. // Do this without holding the dbmutex lock. assert(versions_->prev_log_number() == 0); - if (concurrent_prepare_) { + if (two_write_queues_) { log_write_mutex_.Lock(); } bool creating_new_log = !log_empty_; - if (concurrent_prepare_) { + if (two_write_queues_) { log_write_mutex_.Unlock(); } uint64_t recycle_log_number = 0; if (creating_new_log && immutable_db_options_.recycle_log_file_num && - !log_recycle_files.empty()) { - recycle_log_number = log_recycle_files.front(); - log_recycle_files.pop_front(); + !log_recycle_files_.empty()) { + recycle_log_number = log_recycle_files_.front(); + log_recycle_files_.pop_front(); } uint64_t new_log_number = creating_new_log ? versions_->NewFileNumber() : logfile_number_; - SuperVersion* new_superversion = nullptr; const MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions(); // Set memtable_info for memtable sealed callback @@ -1096,10 +1421,12 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) { DBOptions db_options = BuildDBOptions(immutable_db_options_, mutable_db_options_); const auto preallocate_block_size = - GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size); + GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size); + auto write_hint = CalculateWALWriteHint(); mutex_.Unlock(); - Status s; { + std::string log_fname = + LogFileName(immutable_db_options_.wal_dir, new_log_number); if (creating_new_log) { EnvOptions opt_env_opt = env_->OptimizeForLogWrite(env_options_, db_options); @@ -1107,14 +1434,12 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) { ROCKS_LOG_INFO(immutable_db_options_.info_log, "reusing log %" PRIu64 " from recycle list\n", recycle_log_number); - s = env_->ReuseWritableFile( - LogFileName(immutable_db_options_.wal_dir, new_log_number), - LogFileName(immutable_db_options_.wal_dir, recycle_log_number), - &lfile, opt_env_opt); + std::string old_log_fname = + LogFileName(immutable_db_options_.wal_dir, recycle_log_number); + s = env_->ReuseWritableFile(log_fname, old_log_fname, &lfile, + opt_env_opt); } else { - s = NewWritableFile( - env_, LogFileName(immutable_db_options_.wal_dir, new_log_number), - &lfile, opt_env_opt); + s = NewWritableFile(env_, log_fname, &lfile, opt_env_opt); } if (s.ok()) { // Our final size should be less than write_buffer_size @@ -1123,8 +1448,10 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) { // use preallocate_block_size instead // of calling GetWalPreallocateBlockSize() lfile->SetPreallocationBlockSize(preallocate_block_size); - unique_ptr file_writer( - new WritableFileWriter(std::move(lfile), opt_env_opt)); + lfile->SetWriteLifeTimeHint(write_hint); + std::unique_ptr file_writer(new WritableFileWriter( + std::move(lfile), log_fname, opt_env_opt, env_, nullptr /* stats */, + immutable_db_options_.listeners)); new_log = new log::Writer( std::move(file_writer), new_log_number, immutable_db_options_.recycle_log_file_num > 0, manual_wal_flush_); @@ -1134,46 +1461,65 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) { if (s.ok()) { SequenceNumber seq = versions_->LastSequence(); new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq); - new_superversion = new SuperVersion(); + context->superversion_context.NewSuperVersion(); } - -#ifndef ROCKSDB_LITE - // PLEASE NOTE: We assume that there are no failable operations - // after lock is acquired below since we are already notifying - // client about mem table becoming immutable. - NotifyOnMemTableSealed(cfd, memtable_info); -#endif //ROCKSDB_LITE } ROCKS_LOG_INFO(immutable_db_options_.info_log, "[%s] New memtable created with log file: #%" PRIu64 ". Immutable memtables: %d.\n", cfd->GetName().c_str(), new_log_number, num_imm_unflushed); mutex_.Lock(); - if (!s.ok()) { - // how do we fail if we're not creating new log? - assert(creating_new_log); - assert(!new_mem); - assert(!new_log); - if (concurrent_prepare_) { - nonmem_write_thread_.ExitUnbatched(&nonmem_w); - } - return s; - } - if (creating_new_log) { + if (s.ok() && creating_new_log) { log_write_mutex_.Lock(); - logfile_number_ = new_log_number; assert(new_log != nullptr); - log_empty_ = true; - log_dir_synced_ = false; if (!logs_.empty()) { // Alway flush the buffer of the last log before switching to a new one log::Writer* cur_log_writer = logs_.back().writer; - cur_log_writer->WriteBuffer(); + s = cur_log_writer->WriteBuffer(); + if (!s.ok()) { + ROCKS_LOG_WARN(immutable_db_options_.info_log, + "[%s] Failed to switch from #%" PRIu64 " to #%" PRIu64 + " WAL file\n", + cfd->GetName().c_str(), cur_log_writer->get_log_number(), + new_log_number); + } + } + if (s.ok()) { + logfile_number_ = new_log_number; + log_empty_ = true; + log_dir_synced_ = false; + logs_.emplace_back(logfile_number_, new_log); + alive_log_files_.push_back(LogFileNumberSize(logfile_number_)); } - logs_.emplace_back(logfile_number_, new_log); - alive_log_files_.push_back(LogFileNumberSize(logfile_number_)); log_write_mutex_.Unlock(); } + + if (!s.ok()) { + // how do we fail if we're not creating new log? + assert(creating_new_log); + if (new_mem) { + delete new_mem; + } + if (new_log) { + delete new_log; + } + SuperVersion* new_superversion = + context->superversion_context.new_superversion.release(); + if (new_superversion != nullptr) { + delete new_superversion; + } + // We may have lost data from the WritableFileBuffer in-memory buffer for + // the current log, so treat it as a fatal error and set bg_error + error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable); + // Read back bg_error in order to get the right severity + s = error_handler_.GetBGError(); + + if (two_write_queues_) { + nonmem_write_thread_.ExitUnbatched(&nonmem_w); + } + return s; + } + for (auto loop_cfd : *versions_->GetColumnFamilySet()) { // all this is just optimization to delete logs that // are no longer needed -- if CF is empty, that means it @@ -1192,9 +1538,16 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) { cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_); new_mem->Ref(); cfd->SetMemtable(new_mem); - context->superversions_to_free_.push_back(InstallSuperVersionAndScheduleWork( - cfd, new_superversion, mutable_cf_options)); - if (concurrent_prepare_) { + InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context, + mutable_cf_options); +#ifndef ROCKSDB_LITE + mutex_.Unlock(); + // Notify client that memtable is sealed, now that we have successfully + // installed a new memtable + NotifyOnMemTableSealed(cfd, memtable_info); + mutex_.Lock(); +#endif // ROCKSDB_LITE + if (two_write_queues_) { nonmem_write_thread_.ExitUnbatched(&nonmem_w); } return s; @@ -1202,11 +1555,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) { size_t DBImpl::GetWalPreallocateBlockSize(uint64_t write_buffer_size) const { mutex_.AssertHeld(); - size_t bsize = write_buffer_size / 10 + write_buffer_size; + size_t bsize = + static_cast(write_buffer_size / 10 + write_buffer_size); // Some users might set very high write_buffer_size and rely on // max_total_wal_size or other parameters to control the WAL size. if (mutable_db_options_.max_total_wal_size > 0) { - bsize = std::min(bsize, mutable_db_options_.max_total_wal_size); + bsize = std::min( + bsize, static_cast(mutable_db_options_.max_total_wal_size)); } if (immutable_db_options_.db_write_buffer_size > 0) { bsize = std::min(bsize, immutable_db_options_.db_write_buffer_size); @@ -1228,7 +1583,10 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family, // 8 bytes are taken by header, 4 bytes for count, 1 byte for type, // and we allocate 11 extra bytes for key length, as well as value length. WriteBatch batch(key.size() + value.size() + 24); - batch.Put(column_family, key, value); + Status s = batch.Put(column_family, key, value); + if (!s.ok()) { + return s; + } return Write(opt, &batch); } @@ -1257,7 +1615,10 @@ Status DB::DeleteRange(const WriteOptions& opt, Status DB::Merge(const WriteOptions& opt, ColumnFamilyHandle* column_family, const Slice& key, const Slice& value) { WriteBatch batch; - batch.Merge(column_family, key, value); + Status s = batch.Merge(column_family, key, value); + if (!s.ok()) { + return s; + } return Write(opt, &batch); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_info_dumper.cc b/thirdparty/rocksdb/db/db_info_dumper.cc index 1668a1638f..31050d20a2 100644 --- a/thirdparty/rocksdb/db/db_info_dumper.cc +++ b/thirdparty/rocksdb/db/db_info_dumper.cc @@ -42,7 +42,7 @@ void DumpDBFileSummary(const ImmutableDBOptions& options, "Error when reading %s dir\n", dbname.c_str()); } std::sort(files.begin(), files.end()); - for (std::string file : files) { + for (const std::string& file : files) { if (!ParseFileName(file, &number, &type)) { continue; } @@ -85,7 +85,7 @@ void DumpDBFileSummary(const ImmutableDBOptions& options, continue; } std::sort(files.begin(), files.end()); - for (std::string file : files) { + for (const std::string& file : files) { if (ParseFileName(file, &number, &type)) { if (type == kTableFile && ++file_num < 10) { file_info.append(file).append(" "); @@ -109,7 +109,7 @@ void DumpDBFileSummary(const ImmutableDBOptions& options, return; } wal_info.clear(); - for (std::string file : files) { + for (const std::string& file : files) { if (ParseFileName(file, &number, &type)) { if (type == kLogFile) { env->GetFileSize(options.wal_dir + "/" + file, &file_size); diff --git a/thirdparty/rocksdb/db/db_io_failure_test.cc b/thirdparty/rocksdb/db/db_io_failure_test.cc index 9f4dcc5d05..ba8f197596 100644 --- a/thirdparty/rocksdb/db/db_io_failure_test.cc +++ b/thirdparty/rocksdb/db/db_io_failure_test.cc @@ -88,7 +88,6 @@ TEST_F(DBIOFailureTest, DropWritesFlush) { env_->drop_writes_.store(false, std::memory_order_release); } while (ChangeCompactOptions()); } -#endif // ROCKSDB_LITE // Check that CompactRange() returns failure if there is not enough space left // on device @@ -116,6 +115,7 @@ TEST_F(DBIOFailureTest, NoSpaceCompactRange) { env_->no_space_.store(false, std::memory_order_release); } while (ChangeCompactOptions()); } +#endif // ROCKSDB_LITE TEST_F(DBIOFailureTest, NonWritableFileSystem) { do { diff --git a/thirdparty/rocksdb/db/db_iter.cc b/thirdparty/rocksdb/db/db_iter.cc index e4a6c92a7d..541a5fbed9 100644 --- a/thirdparty/rocksdb/db/db_iter.cc +++ b/thirdparty/rocksdb/db/db_iter.cc @@ -9,6 +9,7 @@ #include "db/db_iter.h" #include +#include #include #include "db/dbformat.h" @@ -26,6 +27,8 @@ #include "util/logging.h" #include "util/mutexlock.h" #include "util/string_util.h" +#include "util/trace_replay.h" +#include "util/user_comparator_wrapper.h" namespace rocksdb { @@ -47,12 +50,17 @@ static void DumpInternalIter(Iterator* iter) { // combines multiple entries for the same userkey found in the DB // representation into a single entry while accounting for sequence // numbers, deletion markers, overwrites, etc. -class DBIter: public Iterator { +class DBIter final: public Iterator { public: // The following is grossly complicated. TODO: clean it up // Which direction is the iterator currently moving? - // (1) When moving forward, the internal iterator is positioned at - // the exact entry that yields this->key(), this->value() + // (1) When moving forward: + // (1a) if current_entry_is_merged_ = false, the internal iterator is + // positioned at the exact entry that yields this->key(), this->value() + // (1b) if current_entry_is_merged_ = true, the internal iterator is + // positioned immediately after the last entry that contributed to the + // current this->value(). That entry may or may not have key equal to + // this->key(). // (2) When moving backwards, the internal iterator is positioned // just before all entries whose user key == this->key(). enum Direction { @@ -75,6 +83,7 @@ class DBIter: public Iterator { prev_count_ = 0; prev_found_count_ = 0; bytes_read_ = 0; + skip_count_ = 0; } void BumpGlobalStatistics(Statistics* global_statistics) { @@ -83,6 +92,7 @@ class DBIter: public Iterator { RecordTick(global_statistics, NUMBER_DB_PREV, prev_count_); RecordTick(global_statistics, NUMBER_DB_PREV_FOUND, prev_found_count_); RecordTick(global_statistics, ITER_BYTES_READ, bytes_read_); + RecordTick(global_statistics, NUMBER_ITER_SKIP, skip_count_); PERF_COUNTER_ADD(iter_read_bytes, bytes_read_); ResetCounters(); } @@ -97,32 +107,43 @@ class DBIter: public Iterator { uint64_t prev_found_count_; // Map to Tickers::ITER_BYTES_READ uint64_t bytes_read_; + // Map to Tickers::NUMBER_ITER_SKIP + uint64_t skip_count_; }; DBIter(Env* _env, const ReadOptions& read_options, - const ImmutableCFOptions& cf_options, const Comparator* cmp, + const ImmutableCFOptions& cf_options, + const MutableCFOptions& mutable_cf_options, const Comparator* cmp, InternalIterator* iter, SequenceNumber s, bool arena_mode, - uint64_t max_sequential_skip_in_iterations, bool allow_blob) - : arena_mode_(arena_mode), - env_(_env), + uint64_t max_sequential_skip_in_iterations, + ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd, + bool allow_blob) + : env_(_env), logger_(cf_options.info_log), user_comparator_(cmp), merge_operator_(cf_options.merge_operator), iter_(iter), + read_callback_(read_callback), sequence_(s), + statistics_(cf_options.statistics), + num_internal_keys_skipped_(0), + iterate_lower_bound_(read_options.iterate_lower_bound), + iterate_upper_bound_(read_options.iterate_upper_bound), direction_(kForward), valid_(false), current_entry_is_merged_(false), - statistics_(cf_options.statistics), - iterate_upper_bound_(read_options.iterate_upper_bound), prefix_same_as_start_(read_options.prefix_same_as_start), pin_thru_lifetime_(read_options.pin_data), total_order_seek_(read_options.total_order_seek), - range_del_agg_(cf_options.internal_comparator, s, - true /* collapse_deletions */), - allow_blob_(allow_blob) { - RecordTick(statistics_, NO_ITERATORS); - prefix_extractor_ = cf_options.prefix_extractor; + allow_blob_(allow_blob), + is_blob_(false), + arena_mode_(arena_mode), + range_del_agg_(&cf_options.internal_comparator, s), + db_impl_(db_impl), + cfd_(cfd), + start_seqnum_(read_options.iter_start_seqnum) { + RecordTick(statistics_, NO_ITERATOR_CREATED); + prefix_extractor_ = mutable_cf_options.prefix_extractor.get(); max_skip_ = max_sequential_skip_in_iterations; max_skippable_internal_keys_ = read_options.max_skippable_internal_keys; if (pin_thru_lifetime_) { @@ -132,12 +153,13 @@ class DBIter: public Iterator { iter_->SetPinnedItersMgr(&pinned_iters_mgr_); } } - virtual ~DBIter() { + ~DBIter() override { // Release pinned data if any if (pinned_iters_mgr_.PinningEnabled()) { pinned_iters_mgr_.ReleasePinnedData(); } - RecordTick(statistics_, NO_ITERATORS, -1); + RecordTick(statistics_, NO_ITERATOR_DELETED); + ResetInternalKeysSkippedCounter(); local_stats_.BumpGlobalStatistics(statistics_); if (!arena_mode_) { delete iter_; @@ -150,16 +172,20 @@ class DBIter: public Iterator { iter_ = iter; iter_->SetPinnedItersMgr(&pinned_iters_mgr_); } - virtual RangeDelAggregator* GetRangeDelAggregator() { + virtual ReadRangeDelAggregator* GetRangeDelAggregator() { return &range_del_agg_; } - virtual bool Valid() const override { return valid_; } - virtual Slice key() const override { + bool Valid() const override { return valid_; } + Slice key() const override { assert(valid_); - return saved_key_.GetUserKey(); + if(start_seqnum_ > 0) { + return saved_key_.GetInternalKey(); + } else { + return saved_key_.GetUserKey(); + } } - virtual Slice value() const override { + Slice value() const override { assert(valid_); if (current_entry_is_merged_) { // If pinned_value_ is set then the result of merge operator is one of @@ -171,10 +197,11 @@ class DBIter: public Iterator { return iter_->value(); } } - virtual Status status() const override { + Status status() const override { if (status_.ok()) { return iter_->status(); } else { + assert(!valid_); return status_; } } @@ -183,8 +210,7 @@ class DBIter: public Iterator { return is_blob_; } - virtual Status GetProperty(std::string prop_name, - std::string* prop) override { + Status GetProperty(std::string prop_name, std::string* prop) override { if (prop == nullptr) { return Status::InvalidArgument("prop is nullptr"); } @@ -198,34 +224,52 @@ class DBIter: public Iterator { *prop = "Iterator is not valid."; } return Status::OK(); + } else if (prop_name == "rocksdb.iterator.internal-key") { + *prop = saved_key_.GetUserKey().ToString(); + return Status::OK(); } - return Status::InvalidArgument("Undentified property."); + return Status::InvalidArgument("Unidentified property."); } - virtual void Next() override; - virtual void Prev() override; - virtual void Seek(const Slice& target) override; - virtual void SeekForPrev(const Slice& target) override; - virtual void SeekToFirst() override; - virtual void SeekToLast() override; + void Next() override; + void Prev() override; + void Seek(const Slice& target) override; + void SeekForPrev(const Slice& target) override; + void SeekToFirst() override; + void SeekToLast() override; Env* env() { return env_; } - void set_sequence(uint64_t s) { sequence_ = s; } + void set_sequence(uint64_t s) { + sequence_ = s; + if (read_callback_) { + read_callback_->Refresh(s); + } + } void set_valid(bool v) { valid_ = v; } private: - void ReverseToForward(); - void ReverseToBackward(); - void PrevInternal(); - void FindParseableKey(ParsedInternalKey* ikey, Direction direction); + // For all methods in this block: + // PRE: iter_->Valid() && status_.ok() + // Return false if there was an error, and status() is non-ok, valid_ = false; + // in this case callers would usually stop what they were doing and return. + bool ReverseToForward(); + bool ReverseToBackward(); bool FindValueForCurrentKey(); bool FindValueForCurrentKeyUsingSeek(); - void FindPrevUserKey(); - void FindNextUserKey(); - inline void FindNextUserEntry(bool skipping, bool prefix_check); - void FindNextUserEntryInternal(bool skipping, bool prefix_check); + bool FindUserKeyBeforeSavedKey(); + inline bool FindNextUserEntry(bool skipping, bool prefix_check); + bool FindNextUserEntryInternal(bool skipping, bool prefix_check); bool ParseKey(ParsedInternalKey* key); - void MergeValuesNewToOld(); + bool MergeValuesNewToOld(); + + void PrevInternal(); bool TooManyInternalKeysSkipped(bool increment = true); + inline bool IsVisible(SequenceNumber sequence); + + // CanReseekToSkip() returns whether the iterator can use the optimization + // where it reseek by sequence number to get the next key when there are too + // many versions. This is disabled for write unprepared because seeking to + // sequence number does not guarantee that it is visible. + inline bool CanReseekToSkip(); // Temporarily pin the blocks that we encounter until ReleaseTempPinnedData() // is called @@ -252,45 +296,64 @@ class DBIter: public Iterator { } inline void ResetInternalKeysSkippedCounter() { + local_stats_.skip_count_ += num_internal_keys_skipped_; + if (valid_) { + local_stats_.skip_count_--; + } num_internal_keys_skipped_ = 0; } const SliceTransform* prefix_extractor_; - bool arena_mode_; Env* const env_; Logger* logger_; - const Comparator* const user_comparator_; + UserComparatorWrapper user_comparator_; const MergeOperator* const merge_operator_; InternalIterator* iter_; + ReadCallback* read_callback_; + // Max visible sequence number. It is normally the snapshot seq unless we have + // uncommitted data in db as in WriteUnCommitted. SequenceNumber sequence_; - Status status_; IterKey saved_key_; + // Reusable internal key data structure. This is only used inside one function + // and should not be used across functions. Reusing this object can reduce + // overhead of calling construction of the function if creating it each time. + ParsedInternalKey ikey_; std::string saved_value_; Slice pinned_value_; - Direction direction_; - bool valid_; - bool current_entry_is_merged_; // for prefix seek mode to support prev() Statistics* statistics_; uint64_t max_skip_; uint64_t max_skippable_internal_keys_; uint64_t num_internal_keys_skipped_; + const Slice* iterate_lower_bound_; const Slice* iterate_upper_bound_; + IterKey prefix_start_buf_; + + Status status_; Slice prefix_start_key_; + Direction direction_; + bool valid_; + bool current_entry_is_merged_; const bool prefix_same_as_start_; // Means that we will pin all data blocks we read as long the Iterator // is not deleted, will be true if ReadOptions::pin_data is true const bool pin_thru_lifetime_; const bool total_order_seek_; + bool allow_blob_; + bool is_blob_; + bool arena_mode_; // List of operands for merge operator. MergeContext merge_context_; - RangeDelAggregator range_del_agg_; + ReadRangeDelAggregator range_del_agg_; LocalStatistics local_stats_; PinnedIteratorsManager pinned_iters_mgr_; - bool allow_blob_; - bool is_blob_; + DBImpl* db_impl_; + ColumnFamilyData* cfd_; + // for diff snapshots we want the lower bound on the seqnum; + // if this value > 0 iterator will return internal keys + SequenceNumber start_seqnum_; // No copying allowed DBIter(const DBIter&); @@ -300,6 +363,7 @@ class DBIter: public Iterator { inline bool DBIter::ParseKey(ParsedInternalKey* ikey) { if (!ParseInternalKey(iter_->key(), ikey)) { status_ = Status::Corruption("corrupted internal key in DBIter"); + valid_ = false; ROCKS_LOG_ERROR(logger_, "corrupted internal key in DBIter: %s", iter_->key().ToString(true).c_str()); return false; @@ -310,12 +374,17 @@ inline bool DBIter::ParseKey(ParsedInternalKey* ikey) { void DBIter::Next() { assert(valid_); + assert(status_.ok()); + PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, env_); // Release temporarily pinned blocks from last operation ReleaseTempPinnedData(); ResetInternalKeysSkippedCounter(); + bool ok = true; if (direction_ == kReverse) { - ReverseToForward(); + if (!ReverseToForward()) { + ok = false; + } } else if (iter_->Valid() && !current_entry_is_merged_) { // If the current value is not a merge, the iter position is the // current key, which is already returned. We can safely issue a @@ -329,13 +398,12 @@ void DBIter::Next() { if (statistics_ != nullptr) { local_stats_.next_count_++; } - // Now we point to the next internal position, for both of merge and - // not merge cases. - if (!iter_->Valid()) { + if (ok && iter_->Valid()) { + FindNextUserEntry(true /* skipping the current user key */, + prefix_same_as_start_); + } else { valid_ = false; - return; } - FindNextUserEntry(true /* skipping the current user key */, prefix_same_as_start_); if (statistics_ != nullptr && valid_) { local_stats_.next_found_count_++; local_stats_.bytes_read_ += (key().size() + value().size()); @@ -356,15 +424,16 @@ void DBIter::Next() { // keys against the prefix of the seeked key. Set to false when // performing a seek without a key (e.g. SeekToFirst). Set to // prefix_same_as_start_ for other iterations. -inline void DBIter::FindNextUserEntry(bool skipping, bool prefix_check) { +inline bool DBIter::FindNextUserEntry(bool skipping, bool prefix_check) { PERF_TIMER_GUARD(find_next_user_entry_time); - FindNextUserEntryInternal(skipping, prefix_check); + return FindNextUserEntryInternal(skipping, prefix_check); } // Actual implementation of DBIter::FindNextUserEntry() -void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { +bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { // Loop until we hit an acceptable entry to yield assert(iter_->Valid()); + assert(status_.ok()); assert(direction_ == kForward); current_entry_is_merged_ = false; @@ -383,85 +452,109 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { is_blob_ = false; do { - ParsedInternalKey ikey; - - if (!ParseKey(&ikey)) { - // Skip corrupted keys. - iter_->Next(); - continue; + if (!ParseKey(&ikey_)) { + return false; } if (iterate_upper_bound_ != nullptr && - user_comparator_->Compare(ikey.user_key, *iterate_upper_bound_) >= 0) { + user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) { break; } if (prefix_extractor_ && prefix_check && - prefix_extractor_->Transform(ikey.user_key) - .compare(prefix_start_key_) != 0) { + prefix_extractor_->Transform(ikey_.user_key) + .compare(prefix_start_key_) != 0) { break; } if (TooManyInternalKeysSkipped()) { - return; + return false; } - if (ikey.sequence <= sequence_) { - if (skipping && - user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) <= - 0) { + if (IsVisible(ikey_.sequence)) { + if (skipping && user_comparator_.Compare(ikey_.user_key, + saved_key_.GetUserKey()) <= 0) { num_skipped++; // skip this entry PERF_COUNTER_ADD(internal_key_skipped_count, 1); } else { num_skipped = 0; - switch (ikey.type) { + switch (ikey_.type) { case kTypeDeletion: case kTypeSingleDeletion: // Arrange to skip all upcoming entries for this key since // they are hidden by this deletion. - saved_key_.SetUserKey( - ikey.user_key, - !iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */); - skipping = true; - PERF_COUNTER_ADD(internal_delete_skipped_count, 1); + // if iterartor specified start_seqnum we + // 1) return internal key, including the type + // 2) return ikey only if ikey.seqnum >= start_seqnum_ + // note that if deletion seqnum is < start_seqnum_ we + // just skip it like in normal iterator. + if (start_seqnum_ > 0 && ikey_.sequence >= start_seqnum_) { + saved_key_.SetInternalKey(ikey_); + valid_ = true; + return true; + } else { + saved_key_.SetUserKey( + ikey_.user_key, + !pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */); + skipping = true; + PERF_COUNTER_ADD(internal_delete_skipped_count, 1); + } break; case kTypeValue: case kTypeBlobIndex: - saved_key_.SetUserKey( - ikey.user_key, - !iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */); - if (range_del_agg_.ShouldDelete( - ikey, RangeDelAggregator::RangePositioningMode:: - kForwardTraversal)) { - // Arrange to skip all upcoming entries for this key since - // they are hidden by this deletion. - skipping = true; - num_skipped = 0; - PERF_COUNTER_ADD(internal_delete_skipped_count, 1); - } else if (ikey.type == kTypeBlobIndex) { - if (!allow_blob_) { - ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index."); - status_ = Status::NotSupported( - "Encounter unexpected blob index. Please open DB with " - "rocksdb::blob_db::BlobDB instead."); - valid_ = false; + if (start_seqnum_ > 0) { + // we are taking incremental snapshot here + // incremental snapshots aren't supported on DB with range deletes + assert(!( + (ikey_.type == kTypeBlobIndex) && (start_seqnum_ > 0) + )); + if (ikey_.sequence >= start_seqnum_) { + saved_key_.SetInternalKey(ikey_); + valid_ = true; + return true; } else { + // this key and all previous versions shouldn't be included, + // skipping + saved_key_.SetUserKey(ikey_.user_key, + !pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */); + skipping = true; + } + } else { + saved_key_.SetUserKey( + ikey_.user_key, + !pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */); + if (range_del_agg_.ShouldDelete( + ikey_, RangeDelPositioningMode::kForwardTraversal)) { + // Arrange to skip all upcoming entries for this key since + // they are hidden by this deletion. + skipping = true; + num_skipped = 0; + PERF_COUNTER_ADD(internal_delete_skipped_count, 1); + } else if (ikey_.type == kTypeBlobIndex) { + if (!allow_blob_) { + ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index."); + status_ = Status::NotSupported( + "Encounter unexpected blob index. Please open DB with " + "rocksdb::blob_db::BlobDB instead."); + valid_ = false; + return false; + } + is_blob_ = true; valid_ = true; + return true; + } else { + valid_ = true; + return true; } - return; - } else { - valid_ = true; - return; } break; case kTypeMerge: saved_key_.SetUserKey( - ikey.user_key, - !iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */); + ikey_.user_key, + !pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */); if (range_del_agg_.ShouldDelete( - ikey, RangeDelAggregator::RangePositioningMode:: - kForwardTraversal)) { + ikey_, RangeDelPositioningMode::kForwardTraversal)) { // Arrange to skip all upcoming entries for this key since // they are hidden by this deletion. skipping = true; @@ -472,8 +565,7 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { // value current_entry_is_merged_ = true; valid_ = true; - MergeValuesNewToOld(); // Go to a different state machine - return; + return MergeValuesNewToOld(); // Go to a different state machine } break; default: @@ -482,17 +574,18 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { } } } else { - // This key was inserted after our snapshot was taken. PERF_COUNTER_ADD(internal_recent_skipped_count, 1); - // Here saved_key_ may contain some old key, or the default empty key, or - // key assigned by some random other method. We don't care. - if (user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) <= - 0) { + // This key was inserted after our snapshot was taken. + // If this happens too many times in a row for the same user key, we want + // to seek to the target sequence number. + int cmp = + user_comparator_.Compare(ikey_.user_key, saved_key_.GetUserKey()); + if (cmp == 0 || (skipping && cmp <= 0)) { num_skipped++; } else { saved_key_.SetUserKey( - ikey.user_key, + ikey_.user_key, !iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */); skipping = false; num_skipped = 0; @@ -501,7 +594,7 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { // If we have sequentially iterated via numerous equal keys, then it's // better to seek so that we can avoid too many key comparisons. - if (num_skipped > max_skip_) { + if (num_skipped > max_skip_ && CanReseekToSkip()) { num_skipped = 0; std::string last_key; if (skipping) { @@ -528,7 +621,9 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { iter_->Next(); } } while (iter_->Valid()); + valid_ = false; + return iter_->status().ok(); } // Merge values of the same user key starting from the current iter_ position @@ -537,12 +632,12 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) { // saved_key_ stores the user key // POST: saved_value_ has the merged value for the user key // iter_ points to the next entry (or invalid) -void DBIter::MergeValuesNewToOld() { +bool DBIter::MergeValuesNewToOld() { if (!merge_operator_) { ROCKS_LOG_ERROR(logger_, "Options::merge_operator is null."); status_ = Status::InvalidArgument("merge_operator_ must be set."); valid_ = false; - return; + return false; } // Temporarily pin the blocks that hold merge operands @@ -551,22 +646,22 @@ void DBIter::MergeValuesNewToOld() { // Start the merge process by pushing the first operand merge_context_.PushOperand(iter_->value(), iter_->IsValuePinned() /* operand_pinned */); + TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:PushedFirstOperand"); ParsedInternalKey ikey; Status s; for (iter_->Next(); iter_->Valid(); iter_->Next()) { + TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:SteppedToNextOperand"); if (!ParseKey(&ikey)) { - // skip corrupted key - continue; + return false; } - if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) { + if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) { // hit the next user key, stop right here break; } else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type || range_del_agg_.ShouldDelete( - ikey, RangeDelAggregator::RangePositioningMode:: - kForwardTraversal)) { + ikey, RangeDelPositioningMode::kForwardTraversal)) { // hit a delete with the same user key, stop right here // iter_ is positioned after delete iter_->Next(); @@ -574,17 +669,22 @@ void DBIter::MergeValuesNewToOld() { } else if (kTypeValue == ikey.type) { // hit a put, merge the put value with operands and store the // final result in saved_value_. We are done! - // ignore corruption if there is any. const Slice val = iter_->value(); s = MergeHelper::TimedFullMerge( merge_operator_, ikey.user_key, &val, merge_context_.GetOperands(), &saved_value_, logger_, statistics_, env_, &pinned_value_, true); if (!s.ok()) { + valid_ = false; status_ = s; + return false; } // iter_ is positioned after put iter_->Next(); - return; + if (!iter_->status().ok()) { + valid_ = false; + return false; + } + return true; } else if (kTypeMerge == ikey.type) { // hit a merge, add the value as an operand and run associative merge. // when complete, add result to operands and continue. @@ -602,12 +702,17 @@ void DBIter::MergeValuesNewToOld() { Status::NotSupported("Blob DB does not support merge operator."); } valid_ = false; - return; + return false; } else { assert(false); } } + if (!iter_->status().ok()) { + valid_ = false; + return false; + } + // we either exhausted all internal keys under this user key, or hit // a deletion marker. // feed null as the existing value to the merge operator, such that @@ -617,18 +722,31 @@ void DBIter::MergeValuesNewToOld() { &saved_value_, logger_, statistics_, env_, &pinned_value_, true); if (!s.ok()) { + valid_ = false; status_ = s; + return false; } + + assert(status_.ok()); + return true; } void DBIter::Prev() { assert(valid_); + assert(status_.ok()); + + PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, env_); ReleaseTempPinnedData(); ResetInternalKeysSkippedCounter(); + bool ok = true; if (direction_ == kForward) { - ReverseToBackward(); + if (!ReverseToBackward()) { + ok = false; + } + } + if (ok) { + PrevInternal(); } - PrevInternal(); if (statistics_ != nullptr) { local_stats_.prev_count_++; if (valid_) { @@ -638,113 +756,131 @@ void DBIter::Prev() { } } -void DBIter::ReverseToForward() { - if (prefix_extractor_ != nullptr && !total_order_seek_) { +bool DBIter::ReverseToForward() { + assert(iter_->status().ok()); + + // When moving backwards, iter_ is positioned on _previous_ key, which may + // not exist or may have different prefix than the current key(). + // If that's the case, seek iter_ to current key. + if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_->Valid()) { IterKey last_key; last_key.SetInternalKey(ParsedInternalKey( saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek)); iter_->Seek(last_key.GetInternalKey()); } - FindNextUserKey(); + direction_ = kForward; - if (!iter_->Valid()) { - iter_->SeekToFirst(); - range_del_agg_.InvalidateTombstoneMapPositions(); + // Skip keys less than the current key() (a.k.a. saved_key_). + while (iter_->Valid()) { + ParsedInternalKey ikey; + if (!ParseKey(&ikey)) { + return false; + } + if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) >= 0) { + return true; + } + iter_->Next(); + } + + if (!iter_->status().ok()) { + valid_ = false; + return false; } + + return true; } -void DBIter::ReverseToBackward() { - if (prefix_extractor_ != nullptr && !total_order_seek_) { +// Move iter_ to the key before saved_key_. +bool DBIter::ReverseToBackward() { + assert(iter_->status().ok()); + + // When current_entry_is_merged_ is true, iter_ may be positioned on the next + // key, which may not exist or may have prefix different from current. + // If that's the case, seek to saved_key_. + if (current_entry_is_merged_ && + ((prefix_extractor_ != nullptr && !total_order_seek_) || + !iter_->Valid())) { IterKey last_key; - last_key.SetInternalKey(ParsedInternalKey(saved_key_.GetUserKey(), 0, - kValueTypeForSeekForPrev)); - iter_->SeekForPrev(last_key.GetInternalKey()); - } - if (current_entry_is_merged_) { - // Not placed in the same key. Need to call Prev() until finding the - // previous key. - if (!iter_->Valid()) { - iter_->SeekToLast(); - range_del_agg_.InvalidateTombstoneMapPositions(); - } - ParsedInternalKey ikey; - FindParseableKey(&ikey, kReverse); - while (iter_->Valid() && - user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) > - 0) { - assert(ikey.sequence != kMaxSequenceNumber); - if (ikey.sequence > sequence_) { - PERF_COUNTER_ADD(internal_recent_skipped_count, 1); - } else { - PERF_COUNTER_ADD(internal_key_skipped_count, 1); + // Using kMaxSequenceNumber and kValueTypeForSeek + // (not kValueTypeForSeekForPrev) to seek to a key strictly smaller + // than saved_key_. + last_key.SetInternalKey(ParsedInternalKey( + saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek)); + if (prefix_extractor_ != nullptr && !total_order_seek_) { + iter_->SeekForPrev(last_key.GetInternalKey()); + } else { + // Some iterators may not support SeekForPrev(), so we avoid using it + // when prefix seek mode is disabled. This is somewhat expensive + // (an extra Prev(), as well as an extra change of direction of iter_), + // so we may need to reconsider it later. + iter_->Seek(last_key.GetInternalKey()); + if (!iter_->Valid() && iter_->status().ok()) { + iter_->SeekToLast(); } - iter_->Prev(); - FindParseableKey(&ikey, kReverse); } } -#ifndef NDEBUG - if (iter_->Valid()) { - ParsedInternalKey ikey; - assert(ParseKey(&ikey)); - assert(user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) <= - 0); - } -#endif - FindPrevUserKey(); direction_ = kReverse; + return FindUserKeyBeforeSavedKey(); } void DBIter::PrevInternal() { - if (!iter_->Valid()) { - valid_ = false; - return; - } - - ParsedInternalKey ikey; - while (iter_->Valid()) { saved_key_.SetUserKey( ExtractUserKey(iter_->key()), !iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */); - if (FindValueForCurrentKey()) { - if (!iter_->Valid()) { - return; - } - FindParseableKey(&ikey, kReverse); - if (user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) { - FindPrevUserKey(); - } - if (valid_ && prefix_extractor_ && prefix_same_as_start_ && - prefix_extractor_->Transform(saved_key_.GetUserKey()) - .compare(prefix_start_key_) != 0) { - valid_ = false; - } + if (prefix_extractor_ && prefix_same_as_start_ && + prefix_extractor_->Transform(saved_key_.GetUserKey()) + .compare(prefix_start_key_) != 0) { + // Current key does not have the same prefix as start + valid_ = false; return; } - if (TooManyInternalKeysSkipped(false)) { + if (iterate_lower_bound_ != nullptr && + user_comparator_.Compare(saved_key_.GetUserKey(), + *iterate_lower_bound_) < 0) { + // We've iterated earlier than the user-specified lower bound. + valid_ = false; return; } - if (!iter_->Valid()) { - break; + if (!FindValueForCurrentKey()) { // assigns valid_ + return; } - FindParseableKey(&ikey, kReverse); - if (user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) { - FindPrevUserKey(); + + // Whether or not we found a value for current key, we need iter_ to end up + // on a smaller key. + if (!FindUserKeyBeforeSavedKey()) { + return; + } + + if (valid_) { + // Found the value. + return; + } + + if (TooManyInternalKeysSkipped(false)) { + return; } } + // We haven't found any key - iterator is not valid - // Or the prefix is different than start prefix - assert(!iter_->Valid()); valid_ = false; } -// This function checks, if the entry with biggest sequence_number <= sequence_ -// is non kTypeDeletion or kTypeSingleDeletion. If it's not, we save value in -// saved_value_ +// Used for backwards iteration. +// Looks at the entries with user key saved_key_ and finds the most up-to-date +// value for it, or executes a merge, or determines that the value was deleted. +// Sets valid_ to true if the value is found and is ready to be presented to +// the user through value(). +// Sets valid_ to false if the value was deleted, and we should try another key. +// Returns false if an error occurred, and !status().ok() and !valid_. +// +// PRE: iter_ is positioned on the last entry with user key equal to saved_key_. +// POST: iter_ is positioned on one of the entries equal to saved_key_, or on +// the entry just before them, or on the entry just after them. bool DBIter::FindValueForCurrentKey() { assert(iter_->Valid()); merge_context_.Clear(); @@ -754,21 +890,28 @@ bool DBIter::FindValueForCurrentKey() { ValueType last_not_merge_type = kTypeDeletion; ValueType last_key_entry_type = kTypeDeletion; - ParsedInternalKey ikey; - FindParseableKey(&ikey, kReverse); - // Temporarily pin blocks that hold (merge operands / the value) ReleaseTempPinnedData(); TempPinData(); size_t num_skipped = 0; - while (iter_->Valid() && ikey.sequence <= sequence_ && - user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) { + while (iter_->Valid()) { + ParsedInternalKey ikey; + if (!ParseKey(&ikey)) { + return false; + } + + if (!IsVisible(ikey.sequence) || + !user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) { + break; + } if (TooManyInternalKeysSkipped()) { return false; } - // We iterate too much: let's use Seek() to avoid too much key comparisons - if (num_skipped >= max_skip_) { + // This user key has lots of entries. + // We're going from old to new, and it's taking too long. Let's do a Seek() + // and go from new to old. This helps when a key was overwritten many times. + if (num_skipped >= max_skip_ && CanReseekToSkip()) { return FindValueForCurrentKeyUsingSeek(); } @@ -777,8 +920,7 @@ bool DBIter::FindValueForCurrentKey() { case kTypeValue: case kTypeBlobIndex: if (range_del_agg_.ShouldDelete( - ikey, - RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) { + ikey, RangeDelPositioningMode::kBackwardTraversal)) { last_key_entry_type = kTypeRangeDeletion; PERF_COUNTER_ADD(internal_delete_skipped_count, 1); } else { @@ -796,8 +938,7 @@ bool DBIter::FindValueForCurrentKey() { break; case kTypeMerge: if (range_del_agg_.ShouldDelete( - ikey, - RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) { + ikey, RangeDelPositioningMode::kBackwardTraversal)) { merge_context_.Clear(); last_key_entry_type = kTypeRangeDeletion; last_not_merge_type = last_key_entry_type; @@ -814,10 +955,13 @@ bool DBIter::FindValueForCurrentKey() { } PERF_COUNTER_ADD(internal_key_skipped_count, 1); - assert(user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())); iter_->Prev(); ++num_skipped; - FindParseableKey(&ikey, kReverse); + } + + if (!iter_->status().ok()) { + valid_ = false; + return false; } Status s; @@ -827,7 +971,7 @@ bool DBIter::FindValueForCurrentKey() { case kTypeSingleDeletion: case kTypeRangeDeletion: valid_ = false; - return false; + return true; case kTypeMerge: current_entry_is_merged_ = true; if (last_not_merge_type == kTypeDeletion || @@ -848,7 +992,7 @@ bool DBIter::FindValueForCurrentKey() { Status::NotSupported("Blob DB does not support merge operator."); } valid_ = false; - return true; + return false; } else { assert(last_not_merge_type == kTypeValue); s = MergeHelper::TimedFullMerge( @@ -858,7 +1002,7 @@ bool DBIter::FindValueForCurrentKey() { } break; case kTypeValue: - // do nothing - we've already has value in saved_value_ + // do nothing - we've already has value in pinned_value_ break; case kTypeBlobIndex: if (!allow_blob_) { @@ -867,7 +1011,7 @@ bool DBIter::FindValueForCurrentKey() { "Encounter unexpected blob index. Please open DB with " "rocksdb::blob_db::BlobDB instead."); valid_ = false; - return true; + return false; } is_blob_ = true; break; @@ -875,15 +1019,19 @@ bool DBIter::FindValueForCurrentKey() { assert(false); break; } - valid_ = true; if (!s.ok()) { + valid_ = false; status_ = s; + return false; } + valid_ = true; return true; } // This function is used in FindValueForCurrentKey. // We use Seek() function instead of Prev() to find necessary value +// TODO: This is very similar to FindNextUserEntry() and MergeValuesNewToOld(). +// Would be nice to reuse some code. bool DBIter::FindValueForCurrentKeyUsingSeek() { // FindValueForCurrentKey will enable pinning before calling // FindValueForCurrentKeyUsingSeek() @@ -894,15 +1042,38 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() { iter_->Seek(last_key); RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION); - // assume there is at least one parseable key for this user key + // In case read_callback presents, the value we seek to may not be visible. + // Find the next value that's visible. ParsedInternalKey ikey; - FindParseableKey(&ikey, kForward); + while (true) { + if (!iter_->Valid()) { + valid_ = false; + return iter_->status().ok(); + } + + if (!ParseKey(&ikey)) { + return false; + } + if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) { + // No visible values for this key, even though FindValueForCurrentKey() + // has seen some. This is possible if we're using a tailing iterator, and + // the entries were discarded in a compaction. + valid_ = false; + return true; + } + + if (IsVisible(ikey.sequence)) { + break; + } + + iter_->Next(); + } if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion || range_del_agg_.ShouldDelete( - ikey, RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) { + ikey, RangeDelPositioningMode::kBackwardTraversal)) { valid_ = false; - return false; + return true; } if (ikey.type == kTypeBlobIndex && !allow_blob_) { ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index."); @@ -910,7 +1081,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() { "Encounter unexpected blob index. Please open DB with " "rocksdb::blob_db::BlobDB instead."); valid_ = false; - return true; + return false; } if (ikey.type == kTypeValue || ikey.type == kTypeBlobIndex) { assert(iter_->IsValuePinned()); @@ -921,111 +1092,146 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() { // kTypeMerge. We need to collect all kTypeMerge values and save them // in operands + assert(ikey.type == kTypeMerge); current_entry_is_merged_ = true; merge_context_.Clear(); - while ( - iter_->Valid() && - user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey()) && - ikey.type == kTypeMerge && - !range_del_agg_.ShouldDelete( - ikey, RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) { - merge_context_.PushOperand(iter_->value(), - iter_->IsValuePinned() /* operand_pinned */); - PERF_COUNTER_ADD(internal_merge_count, 1); + merge_context_.PushOperand(iter_->value(), + iter_->IsValuePinned() /* operand_pinned */); + while (true) { iter_->Next(); - FindParseableKey(&ikey, kForward); - } - Status s; - if (!iter_->Valid() || - !user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey()) || - ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion || - range_del_agg_.ShouldDelete( - ikey, RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) { - s = MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetUserKey(), - nullptr, merge_context_.GetOperands(), - &saved_value_, logger_, statistics_, env_, - &pinned_value_, true); - // Make iter_ valid and point to saved_key_ - if (!iter_->Valid() || - !user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) { - iter_->Seek(last_key); - RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION); + if (!iter_->Valid()) { + if (!iter_->status().ok()) { + valid_ = false; + return false; + } + break; } - valid_ = true; - if (!s.ok()) { - status_ = s; + if (!ParseKey(&ikey)) { + return false; + } + if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) { + break; + } + + if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion || + range_del_agg_.ShouldDelete( + ikey, RangeDelPositioningMode::kForwardTraversal)) { + break; + } else if (ikey.type == kTypeValue) { + const Slice val = iter_->value(); + Status s = MergeHelper::TimedFullMerge( + merge_operator_, saved_key_.GetUserKey(), &val, + merge_context_.GetOperands(), &saved_value_, logger_, statistics_, + env_, &pinned_value_, true); + if (!s.ok()) { + valid_ = false; + status_ = s; + return false; + } + valid_ = true; + return true; + } else if (ikey.type == kTypeMerge) { + merge_context_.PushOperand(iter_->value(), + iter_->IsValuePinned() /* operand_pinned */); + PERF_COUNTER_ADD(internal_merge_count, 1); + } else if (ikey.type == kTypeBlobIndex) { + if (!allow_blob_) { + ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index."); + status_ = Status::NotSupported( + "Encounter unexpected blob index. Please open DB with " + "rocksdb::blob_db::BlobDB instead."); + } else { + status_ = + Status::NotSupported("Blob DB does not support merge operator."); + } + valid_ = false; + return false; + } else { + assert(false); } - return true; } - const Slice& val = iter_->value(); - s = MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetUserKey(), - &val, merge_context_.GetOperands(), - &saved_value_, logger_, statistics_, env_, - &pinned_value_, true); - valid_ = true; + Status s = MergeHelper::TimedFullMerge( + merge_operator_, saved_key_.GetUserKey(), nullptr, + merge_context_.GetOperands(), &saved_value_, logger_, statistics_, env_, + &pinned_value_, true); if (!s.ok()) { + valid_ = false; status_ = s; + return false; } - return true; -} -// Used in Next to change directions -// Go to next user key -// Don't use Seek(), -// because next user key will be very close -void DBIter::FindNextUserKey() { - if (!iter_->Valid()) { - return; - } - ParsedInternalKey ikey; - FindParseableKey(&ikey, kForward); - while (iter_->Valid() && - !user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) { - iter_->Next(); - FindParseableKey(&ikey, kForward); + // Make sure we leave iter_ in a good state. If it's valid and we don't care + // about prefixes, that's already good enough. Otherwise it needs to be + // seeked to the current key. + if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_->Valid()) { + if (prefix_extractor_ != nullptr && !total_order_seek_) { + iter_->SeekForPrev(last_key); + } else { + iter_->Seek(last_key); + if (!iter_->Valid() && iter_->status().ok()) { + iter_->SeekToLast(); + } + } + RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION); } + + valid_ = true; + return true; } -// Go to previous user_key -void DBIter::FindPrevUserKey() { - if (!iter_->Valid()) { - return; - } +// Move backwards until the key smaller than saved_key_. +// Changes valid_ only if return value is false. +bool DBIter::FindUserKeyBeforeSavedKey() { + assert(status_.ok()); size_t num_skipped = 0; - ParsedInternalKey ikey; - FindParseableKey(&ikey, kReverse); - int cmp; - while (iter_->Valid() && - ((cmp = user_comparator_->Compare(ikey.user_key, - saved_key_.GetUserKey())) == 0 || - (cmp > 0 && ikey.sequence > sequence_))) { - if (TooManyInternalKeysSkipped()) { - return; + while (iter_->Valid()) { + ParsedInternalKey ikey; + if (!ParseKey(&ikey)) { + return false; } - if (cmp == 0) { - if (num_skipped >= max_skip_) { - num_skipped = 0; - IterKey last_key; - last_key.SetInternalKey(ParsedInternalKey( - saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek)); - iter_->Seek(last_key.GetInternalKey()); - RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION); - } else { - ++num_skipped; - } + if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) < 0) { + return true; } + + if (TooManyInternalKeysSkipped()) { + return false; + } + assert(ikey.sequence != kMaxSequenceNumber); - if (ikey.sequence > sequence_) { + if (!IsVisible(ikey.sequence)) { PERF_COUNTER_ADD(internal_recent_skipped_count, 1); } else { PERF_COUNTER_ADD(internal_key_skipped_count, 1); } + + if (num_skipped >= max_skip_ && CanReseekToSkip()) { + num_skipped = 0; + IterKey last_key; + last_key.SetInternalKey(ParsedInternalKey( + saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek)); + // It would be more efficient to use SeekForPrev() here, but some + // iterators may not support it. + iter_->Seek(last_key.GetInternalKey()); + RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION); + if (!iter_->Valid()) { + break; + } + } else { + ++num_skipped; + } + iter_->Prev(); - FindParseableKey(&ikey, kReverse); } + + if (!iter_->status().ok()) { + valid_ = false; + return false; + } + + return true; } bool DBIter::TooManyInternalKeysSkipped(bool increment) { @@ -1040,28 +1246,46 @@ bool DBIter::TooManyInternalKeysSkipped(bool increment) { return false; } -// Skip all unparseable keys -void DBIter::FindParseableKey(ParsedInternalKey* ikey, Direction direction) { - while (iter_->Valid() && !ParseKey(ikey)) { - if (direction == kReverse) { - iter_->Prev(); - } else { - iter_->Next(); - } +bool DBIter::IsVisible(SequenceNumber sequence) { + if (read_callback_ == nullptr) { + return sequence <= sequence_; + } else { + return read_callback_->IsVisible(sequence); } } +bool DBIter::CanReseekToSkip() { + return read_callback_ == nullptr || read_callback_->CanReseekToSkip(); +} + void DBIter::Seek(const Slice& target) { + PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_); StopWatch sw(env_, statistics_, DB_SEEK); + status_ = Status::OK(); ReleaseTempPinnedData(); ResetInternalKeysSkippedCounter(); + + SequenceNumber seq = sequence_; saved_key_.Clear(); - saved_key_.SetInternalKey(target, sequence_); + saved_key_.SetInternalKey(target, seq); + +#ifndef ROCKSDB_LITE + if (db_impl_ != nullptr && cfd_ != nullptr) { + db_impl_->TraceIteratorSeek(cfd_->GetID(), target); + } +#endif // ROCKSDB_LITE + + if (iterate_lower_bound_ != nullptr && + user_comparator_.Compare(saved_key_.GetUserKey(), *iterate_lower_bound_) < + 0) { + saved_key_.Clear(); + saved_key_.SetInternalKey(*iterate_lower_bound_, seq); + } { PERF_TIMER_GUARD(seek_internal_seek_time); iter_->Seek(saved_key_.GetInternalKey()); - range_del_agg_.InvalidateTombstoneMapPositions(); + range_del_agg_.InvalidateRangeDelMapPositions(); } RecordTick(statistics_, NUMBER_DB_SEEK); if (iter_->Valid()) { @@ -1076,6 +1300,7 @@ void DBIter::Seek(const Slice& target) { } if (statistics_ != nullptr) { if (valid_) { + // Decrement since we don't want to count this key as skipped RecordTick(statistics_, NUMBER_DB_SEEK_FOUND); RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size()); PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size()); @@ -1092,7 +1317,9 @@ void DBIter::Seek(const Slice& target) { } void DBIter::SeekForPrev(const Slice& target) { + PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_); StopWatch sw(env_, statistics_, DB_SEEK); + status_ = Status::OK(); ReleaseTempPinnedData(); ResetInternalKeysSkippedCounter(); saved_key_.Clear(); @@ -1100,12 +1327,25 @@ void DBIter::SeekForPrev(const Slice& target) { saved_key_.SetInternalKey(target, 0 /* sequence_number */, kValueTypeForSeekForPrev); + if (iterate_upper_bound_ != nullptr && + user_comparator_.Compare(saved_key_.GetUserKey(), + *iterate_upper_bound_) >= 0) { + saved_key_.Clear(); + saved_key_.SetInternalKey(*iterate_upper_bound_, kMaxSequenceNumber); + } + { PERF_TIMER_GUARD(seek_internal_seek_time); iter_->SeekForPrev(saved_key_.GetInternalKey()); - range_del_agg_.InvalidateTombstoneMapPositions(); + range_del_agg_.InvalidateRangeDelMapPositions(); } +#ifndef ROCKSDB_LITE + if (db_impl_ != nullptr && cfd_ != nullptr) { + db_impl_->TraceIteratorSeekForPrev(cfd_->GetID(), target); + } +#endif // ROCKSDB_LITE + RecordTick(statistics_, NUMBER_DB_SEEK); if (iter_->Valid()) { if (prefix_extractor_ && prefix_same_as_start_) { @@ -1134,11 +1374,17 @@ void DBIter::SeekForPrev(const Slice& target) { } void DBIter::SeekToFirst() { + if (iterate_lower_bound_ != nullptr) { + Seek(*iterate_lower_bound_); + return; + } + PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_); // Don't use iter_::Seek() if we set a prefix extractor // because prefix seek will be used. - if (prefix_extractor_ != nullptr) { + if (prefix_extractor_ != nullptr && !total_order_seek_) { max_skip_ = std::numeric_limits::max(); } + status_ = Status::OK(); direction_ = kForward; ReleaseTempPinnedData(); ResetInternalKeysSkippedCounter(); @@ -1147,7 +1393,7 @@ void DBIter::SeekToFirst() { { PERF_TIMER_GUARD(seek_internal_seek_time); iter_->SeekToFirst(); - range_del_agg_.InvalidateTombstoneMapPositions(); + range_del_agg_.InvalidateRangeDelMapPositions(); } RecordTick(statistics_, NUMBER_DB_SEEK); @@ -1174,11 +1420,23 @@ void DBIter::SeekToFirst() { } void DBIter::SeekToLast() { + if (iterate_upper_bound_ != nullptr) { + // Seek to last key strictly less than ReadOptions.iterate_upper_bound. + SeekForPrev(*iterate_upper_bound_); + if (Valid() && user_comparator_.Equal(*iterate_upper_bound_, key())) { + ReleaseTempPinnedData(); + PrevInternal(); + } + return; + } + + PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_); // Don't use iter_::Seek() if we set a prefix extractor // because prefix seek will be used. - if (prefix_extractor_ != nullptr) { + if (prefix_extractor_ != nullptr && !total_order_seek_) { max_skip_ = std::numeric_limits::max(); } + status_ = Status::OK(); direction_ = kReverse; ReleaseTempPinnedData(); ResetInternalKeysSkippedCounter(); @@ -1187,22 +1445,9 @@ void DBIter::SeekToLast() { { PERF_TIMER_GUARD(seek_internal_seek_time); iter_->SeekToLast(); - range_del_agg_.InvalidateTombstoneMapPositions(); - } - // When the iterate_upper_bound is set to a value, - // it will seek to the last key before the - // ReadOptions.iterate_upper_bound - if (iter_->Valid() && iterate_upper_bound_ != nullptr) { - SeekForPrev(*iterate_upper_bound_); - range_del_agg_.InvalidateTombstoneMapPositions(); - if (!Valid()) { - return; - } else if (user_comparator_->Equal(*iterate_upper_bound_, key())) { - Prev(); - } - } else { - PrevInternal(); + range_del_agg_.InvalidateRangeDelMapPositions(); } + PrevInternal(); if (statistics_ != nullptr) { RecordTick(statistics_, NUMBER_DB_SEEK); if (valid_) { @@ -1220,20 +1465,23 @@ void DBIter::SeekToLast() { Iterator* NewDBIterator(Env* env, const ReadOptions& read_options, const ImmutableCFOptions& cf_options, + const MutableCFOptions& mutable_cf_options, const Comparator* user_key_comparator, InternalIterator* internal_iter, const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations, - bool allow_blob) { + ReadCallback* read_callback, DBImpl* db_impl, + ColumnFamilyData* cfd, bool allow_blob) { DBIter* db_iter = new DBIter( - env, read_options, cf_options, user_key_comparator, internal_iter, - sequence, false, max_sequential_skip_in_iterations, allow_blob); + env, read_options, cf_options, mutable_cf_options, user_key_comparator, + internal_iter, sequence, false, max_sequential_skip_in_iterations, + read_callback, db_impl, cfd, allow_blob); return db_iter; } ArenaWrappedDBIter::~ArenaWrappedDBIter() { db_iter_->~DBIter(); } -RangeDelAggregator* ArenaWrappedDBIter::GetRangeDelAggregator() { +ReadRangeDelAggregator* ArenaWrappedDBIter::GetRangeDelAggregator() { return db_iter_->GetRangeDelAggregator(); } @@ -1270,21 +1518,30 @@ inline Status ArenaWrappedDBIter::GetProperty(std::string prop_name, void ArenaWrappedDBIter::Init(Env* env, const ReadOptions& read_options, const ImmutableCFOptions& cf_options, + const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iteration, - uint64_t version_number, bool allow_blob) { + uint64_t version_number, + ReadCallback* read_callback, DBImpl* db_impl, + ColumnFamilyData* cfd, bool allow_blob, + bool allow_refresh) { auto mem = arena_.AllocateAligned(sizeof(DBIter)); - db_iter_ = new (mem) - DBIter(env, read_options, cf_options, cf_options.user_comparator, nullptr, - sequence, true, max_sequential_skip_in_iteration, allow_blob); + db_iter_ = new (mem) DBIter(env, read_options, cf_options, mutable_cf_options, + cf_options.user_comparator, nullptr, sequence, + true, max_sequential_skip_in_iteration, + read_callback, db_impl, cfd, allow_blob); sv_number_ = version_number; + allow_refresh_ = allow_refresh; } Status ArenaWrappedDBIter::Refresh() { - if (cfd_ == nullptr || db_impl_ == nullptr) { + if (cfd_ == nullptr || db_impl_ == nullptr || !allow_refresh_) { return Status::NotSupported("Creating renew iterator is not allowed."); } assert(db_iter_ != nullptr); + // TODO(yiwu): For last_seq_same_as_publish_seq_==false, this is not the + // correct behavior. Will be corrected automatically when we take a snapshot + // here for the case of WritePreparedTxnDB. SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber(); uint64_t cur_sv_number = cfd_->GetSuperVersionNumber(); if (sv_number_ != cur_sv_number) { @@ -1294,12 +1551,17 @@ Status ArenaWrappedDBIter::Refresh() { new (&arena_) Arena(); SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_->mutex()); - Init(env, read_options_, *(cfd_->ioptions()), latest_seq, - sv->mutable_cf_options.max_sequential_skip_in_iterations, - cur_sv_number, allow_blob_); + if (read_callback_) { + read_callback_->Refresh(latest_seq); + } + Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options, + latest_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations, + cur_sv_number, read_callback_, db_impl_, cfd_, allow_blob_, + allow_refresh_); InternalIterator* internal_iter = db_impl_->NewInternalIterator( - read_options_, cfd_, sv, &arena_, db_iter_->GetRangeDelAggregator()); + read_options_, cfd_, sv, &arena_, db_iter_->GetRangeDelAggregator(), + latest_seq); SetIterUnderDBIter(internal_iter); } else { db_iter_->set_sequence(latest_seq); @@ -1310,14 +1572,18 @@ Status ArenaWrappedDBIter::Refresh() { ArenaWrappedDBIter* NewArenaWrappedDbIterator( Env* env, const ReadOptions& read_options, - const ImmutableCFOptions& cf_options, const SequenceNumber& sequence, + const ImmutableCFOptions& cf_options, + const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations, uint64_t version_number, - DBImpl* db_impl, ColumnFamilyData* cfd, bool allow_blob) { + ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd, + bool allow_blob, bool allow_refresh) { ArenaWrappedDBIter* iter = new ArenaWrappedDBIter(); - iter->Init(env, read_options, cf_options, sequence, - max_sequential_skip_in_iterations, version_number, allow_blob); - if (db_impl != nullptr && cfd != nullptr) { - iter->StoreRefreshInfo(read_options, db_impl, cfd, allow_blob); + iter->Init(env, read_options, cf_options, mutable_cf_options, sequence, + max_sequential_skip_in_iterations, version_number, read_callback, + db_impl, cfd, allow_blob, allow_refresh); + if (db_impl != nullptr && cfd != nullptr && allow_refresh) { + iter->StoreRefreshInfo(read_options, db_impl, cfd, read_callback, + allow_blob); } return iter; diff --git a/thirdparty/rocksdb/db/db_iter.h b/thirdparty/rocksdb/db/db_iter.h index 26fcd44cbd..a640f0296e 100644 --- a/thirdparty/rocksdb/db/db_iter.h +++ b/thirdparty/rocksdb/db/db_iter.h @@ -23,18 +23,18 @@ namespace rocksdb { class Arena; class DBIter; -class InternalIterator; // Return a new iterator that converts internal keys (yielded by // "*internal_iter") that were live at the specified "sequence" number // into appropriate user keys. -extern Iterator* NewDBIterator(Env* env, const ReadOptions& read_options, - const ImmutableCFOptions& cf_options, - const Comparator* user_key_comparator, - InternalIterator* internal_iter, - const SequenceNumber& sequence, - uint64_t max_sequential_skip_in_iterations, - bool allow_blob = false); +extern Iterator* NewDBIterator( + Env* env, const ReadOptions& read_options, + const ImmutableCFOptions& cf_options, + const MutableCFOptions& mutable_cf_options, + const Comparator* user_key_comparator, InternalIterator* internal_iter, + const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations, + ReadCallback* read_callback, DBImpl* db_impl = nullptr, + ColumnFamilyData* cfd = nullptr, bool allow_blob = false); // A wrapper iterator which wraps DB Iterator and the arena, with which the DB // iterator is supposed be allocated. This class is used as an entry point of @@ -48,7 +48,7 @@ class ArenaWrappedDBIter : public Iterator { // Get the arena to be used to allocate memory for DBIter to be wrapped, // as well as child iterators in it. virtual Arena* GetArena() { return &arena_; } - virtual RangeDelAggregator* GetRangeDelAggregator(); + virtual ReadRangeDelAggregator* GetRangeDelAggregator(); // Set the internal iterator wrapped inside the DB Iterator. Usually it is // a merging iterator. @@ -70,15 +70,19 @@ class ArenaWrappedDBIter : public Iterator { void Init(Env* env, const ReadOptions& read_options, const ImmutableCFOptions& cf_options, + const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations, uint64_t version_number, - bool allow_blob); + ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd, + bool allow_blob, bool allow_refresh); void StoreRefreshInfo(const ReadOptions& read_options, DBImpl* db_impl, - ColumnFamilyData* cfd, bool allow_blob) { + ColumnFamilyData* cfd, ReadCallback* read_callback, + bool allow_blob) { read_options_ = read_options; db_impl_ = db_impl; cfd_ = cfd; + read_callback_ = read_callback; allow_blob_ = allow_blob; } @@ -89,7 +93,9 @@ class ArenaWrappedDBIter : public Iterator { ColumnFamilyData* cfd_ = nullptr; DBImpl* db_impl_ = nullptr; ReadOptions read_options_; + ReadCallback* read_callback_; bool allow_blob_ = false; + bool allow_refresh_ = true; }; // Generate the arena wrapped iterator class. @@ -97,9 +103,10 @@ class ArenaWrappedDBIter : public Iterator { // be supported. extern ArenaWrappedDBIter* NewArenaWrappedDbIterator( Env* env, const ReadOptions& read_options, - const ImmutableCFOptions& cf_options, const SequenceNumber& sequence, + const ImmutableCFOptions& cf_options, + const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations, uint64_t version_number, - DBImpl* db_impl = nullptr, ColumnFamilyData* cfd = nullptr, - bool allow_blob = false); - + ReadCallback* read_callback, DBImpl* db_impl = nullptr, + ColumnFamilyData* cfd = nullptr, bool allow_blob = false, + bool allow_refresh = true); } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_iter_stress_test.cc b/thirdparty/rocksdb/db/db_iter_stress_test.cc new file mode 100644 index 0000000000..a0f1dfeab4 --- /dev/null +++ b/thirdparty/rocksdb/db/db_iter_stress_test.cc @@ -0,0 +1,654 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#include "db/db_iter.h" +#include "db/dbformat.h" +#include "rocksdb/comparator.h" +#include "rocksdb/options.h" +#include "rocksdb/slice.h" +#include "util/random.h" +#include "util/string_util.h" +#include "util/testharness.h" +#include "utilities/merge_operators.h" + +#ifdef GFLAGS + +#include "util/gflags_compat.h" + +using GFLAGS_NAMESPACE::ParseCommandLineFlags; + +DEFINE_bool(verbose, false, + "Print huge, detailed trace. Intended for debugging failures."); + +#else + +void ParseCommandLineFlags(int*, char***, bool) {} +bool FLAGS_verbose = false; + +#endif + +namespace rocksdb { + +class DBIteratorStressTest : public testing::Test { + public: + Env* env_; + + DBIteratorStressTest() : env_(Env::Default()) {} +}; + +namespace { + +struct Entry { + std::string key; + ValueType type; // kTypeValue, kTypeDeletion, kTypeMerge + uint64_t sequence; + std::string ikey; // internal key, made from `key`, `sequence` and `type` + std::string value; + // If false, we'll pretend that this entry doesn't exist. + bool visible = true; + + bool operator<(const Entry& e) const { + if (key != e.key) return key < e.key; + return std::tie(sequence, type) > std::tie(e.sequence, e.type); + } +}; + +struct Data { + std::vector entries; + + // Indices in `entries` with `visible` = false. + std::vector hidden; + // Keys of entries whose `visible` changed since the last seek of iterators. + std::set recently_touched_keys; +}; + +struct StressTestIterator : public InternalIterator { + Data* data; + Random64* rnd; + InternalKeyComparator cmp; + + // Each operation will return error with this probability... + double error_probability = 0; + // ... and add/remove entries with this probability. + double mutation_probability = 0; + // The probability of adding vs removing entries will be chosen so that the + // amount of removed entries stays somewhat close to this number. + double target_hidden_fraction = 0; + // If true, print all mutations to stdout for debugging. + bool trace = false; + + int iter = -1; + Status status_; + + StressTestIterator(Data* _data, Random64* _rnd, const Comparator* _cmp) + : data(_data), rnd(_rnd), cmp(_cmp) {} + + bool Valid() const override { + if (iter >= 0 && iter < (int)data->entries.size()) { + assert(status_.ok()); + return true; + } + return false; + } + + Status status() const override { return status_; } + + bool MaybeFail() { + if (rnd->Next() >= + std::numeric_limits::max() * error_probability) { + return false; + } + if (rnd->Next() % 2) { + status_ = Status::Incomplete("test"); + } else { + status_ = Status::IOError("test"); + } + if (trace) { + std::cout << "injecting " << status_.ToString() << std::endl; + } + iter = -1; + return true; + } + + void MaybeMutate() { + if (rnd->Next() >= + std::numeric_limits::max() * mutation_probability) { + return; + } + do { + // If too many entries are hidden, hide less, otherwise hide more. + double hide_probability = + data->hidden.size() > data->entries.size() * target_hidden_fraction + ? 1. / 3 + : 2. / 3; + if (data->hidden.empty()) { + hide_probability = 1; + } + bool do_hide = + rnd->Next() < std::numeric_limits::max() * hide_probability; + if (do_hide) { + // Hide a random entry. + size_t idx = rnd->Next() % data->entries.size(); + Entry& e = data->entries[idx]; + if (e.visible) { + if (trace) { + std::cout << "hiding idx " << idx << std::endl; + } + e.visible = false; + data->hidden.push_back(idx); + data->recently_touched_keys.insert(e.key); + } else { + // Already hidden. Let's go unhide something instead, just because + // it's easy and it doesn't really matter what we do. + do_hide = false; + } + } + if (!do_hide) { + // Unhide a random entry. + size_t hi = rnd->Next() % data->hidden.size(); + size_t idx = data->hidden[hi]; + if (trace) { + std::cout << "unhiding idx " << idx << std::endl; + } + Entry& e = data->entries[idx]; + assert(!e.visible); + e.visible = true; + data->hidden[hi] = data->hidden.back(); + data->hidden.pop_back(); + data->recently_touched_keys.insert(e.key); + } + } while (rnd->Next() % 3 != 0); // do 3 mutations on average + } + + void SkipForward() { + while (iter < (int)data->entries.size() && !data->entries[iter].visible) { + ++iter; + } + } + void SkipBackward() { + while (iter >= 0 && !data->entries[iter].visible) { + --iter; + } + } + + void SeekToFirst() override { + if (MaybeFail()) return; + MaybeMutate(); + + status_ = Status::OK(); + iter = 0; + SkipForward(); + } + void SeekToLast() override { + if (MaybeFail()) return; + MaybeMutate(); + + status_ = Status::OK(); + iter = (int)data->entries.size() - 1; + SkipBackward(); + } + + void Seek(const Slice& target) override { + if (MaybeFail()) return; + MaybeMutate(); + + status_ = Status::OK(); + // Binary search. + auto it = std::partition_point( + data->entries.begin(), data->entries.end(), + [&](const Entry& e) { return cmp.Compare(e.ikey, target) < 0; }); + iter = (int)(it - data->entries.begin()); + SkipForward(); + } + void SeekForPrev(const Slice& target) override { + if (MaybeFail()) return; + MaybeMutate(); + + status_ = Status::OK(); + // Binary search. + auto it = std::partition_point( + data->entries.begin(), data->entries.end(), + [&](const Entry& e) { return cmp.Compare(e.ikey, target) <= 0; }); + iter = (int)(it - data->entries.begin()); + --iter; + SkipBackward(); + } + + void Next() override { + assert(Valid()); + if (MaybeFail()) return; + MaybeMutate(); + ++iter; + SkipForward(); + } + void Prev() override { + assert(Valid()); + if (MaybeFail()) return; + MaybeMutate(); + --iter; + SkipBackward(); + } + + Slice key() const override { + assert(Valid()); + return data->entries[iter].ikey; + } + Slice value() const override { + assert(Valid()); + return data->entries[iter].value; + } + + bool IsKeyPinned() const override { return true; } + bool IsValuePinned() const override { return true; } +}; + +// A small reimplementation of DBIter, supporting only some of the features, +// and doing everything in O(log n). +// Skips all keys that are in recently_touched_keys. +struct ReferenceIterator { + Data* data; + uint64_t sequence; // ignore entries with sequence number below this + + bool valid = false; + std::string key; + std::string value; + + ReferenceIterator(Data* _data, uint64_t _sequence) + : data(_data), sequence(_sequence) {} + + bool Valid() const { return valid; } + + // Finds the first entry with key + // greater/less/greater-or-equal/less-or-equal than `key`, depending on + // arguments: if `skip`, inequality is strict; if `forward`, it's + // greater/greater-or-equal, otherwise less/less-or-equal. + // Sets `key` to the result. + // If no such key exists, returns false. Doesn't check `visible`. + bool FindNextKey(bool skip, bool forward) { + valid = false; + auto it = std::partition_point(data->entries.begin(), data->entries.end(), + [&](const Entry& e) { + if (forward != skip) { + return e.key < key; + } else { + return e.key <= key; + } + }); + if (forward) { + if (it != data->entries.end()) { + key = it->key; + return true; + } + } else { + if (it != data->entries.begin()) { + --it; + key = it->key; + return true; + } + } + return false; + } + + bool FindValueForCurrentKey() { + if (data->recently_touched_keys.count(key)) { + return false; + } + + // Find the first entry for the key. The caller promises that it exists. + auto it = std::partition_point(data->entries.begin(), data->entries.end(), + [&](const Entry& e) { + if (e.key != key) { + return e.key < key; + } + return e.sequence > sequence; + }); + + // Find the first visible entry. + for (;; ++it) { + if (it == data->entries.end()) { + return false; + } + Entry& e = *it; + if (e.key != key) { + return false; + } + assert(e.sequence <= sequence); + if (!e.visible) continue; + if (e.type == kTypeDeletion) { + return false; + } + if (e.type == kTypeValue) { + value = e.value; + valid = true; + return true; + } + assert(e.type == kTypeMerge); + break; + } + + // Collect merge operands. + std::vector operands; + for (; it != data->entries.end(); ++it) { + Entry& e = *it; + if (e.key != key) { + break; + } + assert(e.sequence <= sequence); + if (!e.visible) continue; + if (e.type == kTypeDeletion) { + break; + } + operands.push_back(e.value); + if (e.type == kTypeValue) { + break; + } + } + + // Do a merge. + value = operands.back().ToString(); + for (int i = (int)operands.size() - 2; i >= 0; --i) { + value.append(","); + value.append(operands[i].data(), operands[i].size()); + } + + valid = true; + return true; + } + + // Start at `key` and move until we encounter a valid value. + // `forward` defines the direction of movement. + // If `skip` is true, we're looking for key not equal to `key`. + void DoTheThing(bool skip, bool forward) { + while (FindNextKey(skip, forward) && !FindValueForCurrentKey()) { + skip = true; + } + } + + void Seek(const Slice& target) { + key = target.ToString(); + DoTheThing(false, true); + } + void SeekForPrev(const Slice& target) { + key = target.ToString(); + DoTheThing(false, false); + } + void SeekToFirst() { Seek(""); } + void SeekToLast() { + key = data->entries.back().key; + DoTheThing(false, false); + } + void Next() { + assert(Valid()); + DoTheThing(true, true); + } + void Prev() { + assert(Valid()); + DoTheThing(true, false); + } +}; + +} // namespace + +// Use an internal iterator that sometimes returns errors and sometimes +// adds/removes entries on the fly. Do random operations on a DBIter and +// check results. +// TODO: can be improved for more coverage: +// * Override IsKeyPinned() and IsValuePinned() to actually use +// PinnedIteratorManager and check that there's no use-after free. +// * Try different combinations of prefix_extractor, total_order_seek, +// prefix_same_as_start, iterate_lower_bound, iterate_upper_bound. +TEST_F(DBIteratorStressTest, StressTest) { + // We use a deterministic RNG, and everything happens in a single thread. + Random64 rnd(826909345792864532ll); + + auto gen_key = [&](int max_key) { + assert(max_key > 0); + int len = 0; + int a = max_key; + while (a) { + a /= 10; + ++len; + } + std::string s = ToString(rnd.Next() % static_cast(max_key)); + s.insert(0, len - (int)s.size(), '0'); + return s; + }; + + Options options; + options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); + ReadOptions ropt; + + size_t num_matching = 0; + size_t num_at_end = 0; + size_t num_not_ok = 0; + size_t num_recently_removed = 0; + + // Number of iterations for each combination of parameters + // (there are ~250 of those). + // Tweak this to change the test run time. + // As of the time of writing, the test takes ~4 seconds for value of 5000. + const int num_iterations = 5000; + // Enable this to print all the operations for debugging. + bool trace = FLAGS_verbose; + + for (int num_entries : {5, 10, 100}) { + for (double key_space : {0.1, 1.0, 3.0}) { + for (ValueType prevalent_entry_type : + {kTypeValue, kTypeDeletion, kTypeMerge}) { + for (double error_probability : {0.01, 0.1}) { + for (double mutation_probability : {0.01, 0.5}) { + for (double target_hidden_fraction : {0.1, 0.5}) { + std::string trace_str = + "entries: " + ToString(num_entries) + + ", key_space: " + ToString(key_space) + + ", error_probability: " + ToString(error_probability) + + ", mutation_probability: " + ToString(mutation_probability) + + ", target_hidden_fraction: " + + ToString(target_hidden_fraction); + SCOPED_TRACE(trace_str); + if (trace) { + std::cout << trace_str << std::endl; + } + + // Generate data. + Data data; + int max_key = (int)(num_entries * key_space) + 1; + for (int i = 0; i < num_entries; ++i) { + Entry e; + e.key = gen_key(max_key); + if (rnd.Next() % 10 != 0) { + e.type = prevalent_entry_type; + } else { + const ValueType types[] = {kTypeValue, kTypeDeletion, + kTypeMerge}; + e.type = + types[rnd.Next() % (sizeof(types) / sizeof(types[0]))]; + } + e.sequence = i; + e.value = "v" + ToString(i); + ParsedInternalKey internal_key(e.key, e.sequence, e.type); + AppendInternalKey(&e.ikey, internal_key); + + data.entries.push_back(e); + } + std::sort(data.entries.begin(), data.entries.end()); + if (trace) { + std::cout << "entries:"; + for (size_t i = 0; i < data.entries.size(); ++i) { + Entry& e = data.entries[i]; + std::cout + << "\n idx " << i << ": \"" << e.key << "\": \"" + << e.value << "\" seq: " << e.sequence << " type: " + << (e.type == kTypeValue + ? "val" + : e.type == kTypeDeletion ? "del" : "merge"); + } + std::cout << std::endl; + } + + std::unique_ptr db_iter; + std::unique_ptr ref_iter; + for (int iteration = 0; iteration < num_iterations; ++iteration) { + SCOPED_TRACE(iteration); + // Create a new iterator every ~30 operations. + if (db_iter == nullptr || rnd.Next() % 30 == 0) { + uint64_t sequence = rnd.Next() % (data.entries.size() + 2); + ref_iter.reset(new ReferenceIterator(&data, sequence)); + if (trace) { + std::cout << "new iterator, seq: " << sequence << std::endl; + } + + auto internal_iter = + new StressTestIterator(&data, &rnd, BytewiseComparator()); + internal_iter->error_probability = error_probability; + internal_iter->mutation_probability = mutation_probability; + internal_iter->target_hidden_fraction = + target_hidden_fraction; + internal_iter->trace = trace; + db_iter.reset(NewDBIterator( + env_, ropt, ImmutableCFOptions(options), + MutableCFOptions(options), BytewiseComparator(), + internal_iter, sequence, + options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); + } + + // Do a random operation. It's important to do it on ref_it + // later than on db_iter to make sure ref_it sees the correct + // recently_touched_keys. + std::string old_key; + bool forward = rnd.Next() % 2 > 0; + // Do Next()/Prev() ~90% of the time. + bool seek = !ref_iter->Valid() || rnd.Next() % 10 == 0; + if (trace) { + std::cout << iteration << ": "; + } + + if (!seek) { + assert(db_iter->Valid()); + old_key = ref_iter->key; + if (trace) { + std::cout << (forward ? "Next" : "Prev") << std::endl; + } + + if (forward) { + db_iter->Next(); + ref_iter->Next(); + } else { + db_iter->Prev(); + ref_iter->Prev(); + } + } else { + data.recently_touched_keys.clear(); + // Do SeekToFirst less often than Seek. + if (rnd.Next() % 4 == 0) { + if (trace) { + std::cout << (forward ? "SeekToFirst" : "SeekToLast") + << std::endl; + } + + if (forward) { + old_key = ""; + db_iter->SeekToFirst(); + ref_iter->SeekToFirst(); + } else { + old_key = data.entries.back().key; + db_iter->SeekToLast(); + ref_iter->SeekToLast(); + } + } else { + old_key = gen_key(max_key); + if (trace) { + std::cout << (forward ? "Seek" : "SeekForPrev") << " \"" + << old_key << '"' << std::endl; + } + if (forward) { + db_iter->Seek(old_key); + ref_iter->Seek(old_key); + } else { + db_iter->SeekForPrev(old_key); + ref_iter->SeekForPrev(old_key); + } + } + } + + // Check the result. + if (db_iter->Valid()) { + ASSERT_TRUE(db_iter->status().ok()); + if (data.recently_touched_keys.count( + db_iter->key().ToString())) { + // Ended on a key that may have been mutated during the + // operation. Reference iterator skips such keys, so we + // can't check the exact result. + + // Check that the key moved in the right direction. + if (forward) { + if (seek) + ASSERT_GE(db_iter->key().ToString(), old_key); + else + ASSERT_GT(db_iter->key().ToString(), old_key); + } else { + if (seek) + ASSERT_LE(db_iter->key().ToString(), old_key); + else + ASSERT_LT(db_iter->key().ToString(), old_key); + } + + if (ref_iter->Valid()) { + // Check that DBIter didn't miss any non-mutated key. + if (forward) { + ASSERT_LT(db_iter->key().ToString(), ref_iter->key); + } else { + ASSERT_GT(db_iter->key().ToString(), ref_iter->key); + } + } + // Tell the next iteration of the loop to reseek the + // iterators. + ref_iter->valid = false; + + ++num_recently_removed; + } else { + ASSERT_TRUE(ref_iter->Valid()); + ASSERT_EQ(ref_iter->key, db_iter->key().ToString()); + ASSERT_EQ(ref_iter->value, db_iter->value()); + ++num_matching; + } + } else if (db_iter->status().ok()) { + ASSERT_FALSE(ref_iter->Valid()); + ++num_at_end; + } else { + // Non-ok status. Nothing to check here. + // Tell the next iteration of the loop to reseek the + // iterators. + ref_iter->valid = false; + ++num_not_ok; + } + } + } + } + } + } + } + } + + // Check that all cases were hit many times. + EXPECT_GT(num_matching, 10000); + EXPECT_GT(num_at_end, 10000); + EXPECT_GT(num_not_ok, 10000); + EXPECT_GT(num_recently_removed, 10000); + + std::cout << "stats:\n exact matches: " << num_matching + << "\n end reached: " << num_at_end + << "\n non-ok status: " << num_not_ok + << "\n mutated on the fly: " << num_recently_removed << std::endl; +} + +} // namespace rocksdb + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ParseCommandLineFlags(&argc, &argv, true); + return RUN_ALL_TESTS(); +} diff --git a/thirdparty/rocksdb/db/db_iter_test.cc b/thirdparty/rocksdb/db/db_iter_test.cc index 6db3b4a9bb..29fbd32086 100644 --- a/thirdparty/rocksdb/db/db_iter_test.cc +++ b/thirdparty/rocksdb/db/db_iter_test.cc @@ -36,7 +36,9 @@ class TestIterator : public InternalIterator { valid_(false), sequence_number_(0), iter_(0), - cmp(comparator) {} + cmp(comparator) { + data_.reserve(16); + } void AddPut(std::string argkey, std::string argvalue) { Add(argkey, kTypeValue, argvalue); @@ -84,26 +86,61 @@ class TestIterator : public InternalIterator { }); } - virtual bool Valid() const override { + // Removes the key from the set of keys over which this iterator iterates. + // Not to be confused with AddDeletion(). + // If the iterator is currently positioned on this key, the deletion will + // apply next time the iterator moves. + // Used for simulating ForwardIterator updating to a new version that doesn't + // have some of the keys (e.g. after compaction with a filter). + void Vanish(std::string _key) { + if (valid_ && data_[iter_].first == _key) { + delete_current_ = true; + return; + } + for (auto it = data_.begin(); it != data_.end(); ++it) { + ParsedInternalKey ikey; + bool ok __attribute__((__unused__)) = ParseInternalKey(it->first, &ikey); + assert(ok); + if (ikey.user_key != _key) { + continue; + } + if (valid_ && data_.begin() + iter_ > it) { + --iter_; + } + data_.erase(it); + return; + } + assert(false); + } + + // Number of operations done on this iterator since construction. + size_t steps() const { return steps_; } + + bool Valid() const override { assert(initialized_); return valid_; } - virtual void SeekToFirst() override { + void SeekToFirst() override { assert(initialized_); + ++steps_; + DeleteCurrentIfNeeded(); valid_ = (data_.size() > 0); iter_ = 0; } - virtual void SeekToLast() override { + void SeekToLast() override { assert(initialized_); + ++steps_; + DeleteCurrentIfNeeded(); valid_ = (data_.size() > 0); iter_ = data_.size() - 1; } - virtual void Seek(const Slice& target) override { + void Seek(const Slice& target) override { assert(initialized_); SeekToFirst(); + ++steps_; if (!valid_) { return; } @@ -117,22 +154,33 @@ class TestIterator : public InternalIterator { } } - virtual void SeekForPrev(const Slice& target) override { + void SeekForPrev(const Slice& target) override { assert(initialized_); + DeleteCurrentIfNeeded(); SeekForPrevImpl(target, &cmp); } - virtual void Next() override { + void Next() override { assert(initialized_); - if (data_.empty() || (iter_ == data_.size() - 1)) { - valid_ = false; + assert(valid_); + assert(iter_ < data_.size()); + + ++steps_; + if (delete_current_) { + DeleteCurrentIfNeeded(); } else { ++iter_; } + valid_ = iter_ < data_.size(); } - virtual void Prev() override { + void Prev() override { assert(initialized_); + assert(valid_); + assert(iter_ < data_.size()); + + ++steps_; + DeleteCurrentIfNeeded(); if (iter_ == 0) { valid_ = false; } else { @@ -140,32 +188,42 @@ class TestIterator : public InternalIterator { } } - virtual Slice key() const override { + Slice key() const override { assert(initialized_); return data_[iter_].first; } - virtual Slice value() const override { + Slice value() const override { assert(initialized_); return data_[iter_].second; } - virtual Status status() const override { + Status status() const override { assert(initialized_); return Status::OK(); } - virtual bool IsKeyPinned() const override { return true; } - virtual bool IsValuePinned() const override { return true; } + bool IsKeyPinned() const override { return true; } + bool IsValuePinned() const override { return true; } private: bool initialized_; bool valid_; size_t sequence_number_; size_t iter_; + size_t steps_ = 0; InternalKeyComparator cmp; std::vector> data_; + bool delete_current_ = false; + + void DeleteCurrentIfNeeded() { + if (!delete_current_) { + return; + } + data_.erase(data_.begin() + iter_); + delete_current_ = false; + } }; class DBIteratorTest : public testing::Test { @@ -178,7 +236,7 @@ class DBIteratorTest : public testing::Test { TEST_F(DBIteratorTest, DBIteratorPrevNext) { Options options; ImmutableCFOptions cf_options = ImmutableCFOptions(options); - + MutableCFOptions mutable_cf_options = MutableCFOptions(options); { TestIterator* internal_iter = new TestIterator(BytewiseComparator()); internal_iter->AddDeletion("a"); @@ -191,9 +249,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { internal_iter->Finish(); ReadOptions ro; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -223,9 +282,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { internal_iter->Finish(); ReadOptions ro; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -249,9 +309,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -281,9 +342,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -316,9 +378,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(!db_iter->Valid()); @@ -345,9 +408,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 7, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 7, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); SetPerfLevel(kEnableCount); ASSERT_TRUE(GetPerfLevel() == kEnableCount); @@ -356,7 +420,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(static_cast(get_perf_context()->internal_key_skipped_count), 7); + ASSERT_EQ(static_cast(get_perf_context()->internal_key_skipped_count), 1); ASSERT_EQ(db_iter->key().ToString(), "b"); SetPerfLevel(kDisable); @@ -382,9 +446,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 4, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 4, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -407,9 +472,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(!db_iter->Valid()); @@ -429,9 +495,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -464,9 +531,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { ReadOptions ro; ro.iterate_upper_bound = &prefix; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 7, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 7, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); SetPerfLevel(kEnableCount); ASSERT_TRUE(GetPerfLevel() == kEnableCount); @@ -475,7 +543,7 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); - ASSERT_EQ(static_cast(get_perf_context()->internal_delete_skipped_count), 1); + ASSERT_EQ(static_cast(get_perf_context()->internal_delete_skipped_count), 0); ASSERT_EQ(db_iter->key().ToString(), "b"); SetPerfLevel(kDisable); @@ -493,9 +561,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { internal_iter->Finish(); ReadOptions ro; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -535,9 +604,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { internal_iter->Finish(); ReadOptions ro; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 2, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "b"); @@ -566,9 +636,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { internal_iter->Finish(); ReadOptions ro; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "c"); @@ -589,15 +660,17 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) { TEST_F(DBIteratorTest, DBIteratorEmpty) { Options options; ImmutableCFOptions cf_options = ImmutableCFOptions(options); + MutableCFOptions mutable_cf_options = MutableCFOptions(options); ReadOptions ro; { TestIterator* internal_iter = new TestIterator(BytewiseComparator()); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 0, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 0, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(!db_iter->Valid()); } @@ -606,9 +679,10 @@ TEST_F(DBIteratorTest, DBIteratorEmpty) { TestIterator* internal_iter = new TestIterator(BytewiseComparator()); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 0, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 0, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(!db_iter->Valid()); } @@ -629,8 +703,9 @@ TEST_F(DBIteratorTest, DBIteratorUseSkipCountSkips) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 2, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 2, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "c"); @@ -659,6 +734,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { Options options; options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); ImmutableCFOptions cf_options = ImmutableCFOptions(options); + MutableCFOptions mutable_cf_options = MutableCFOptions(options); { for (size_t i = 0; i < 200; ++i) { @@ -672,8 +748,9 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { options.statistics = rocksdb::CreateDBStatistics(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, i + 2, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, i + 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -707,8 +784,9 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, i + 2, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, i + 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -735,8 +813,9 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, 202, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 202, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -767,8 +846,9 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { internal_iter->AddPut("c", "200"); internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, i, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, i, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(!db_iter->Valid()); @@ -782,9 +862,10 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { } internal_iter->AddPut("c", "200"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 200, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 200, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "c"); @@ -817,8 +898,9 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, i + 2, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, i + 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -851,8 +933,9 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, i + 2, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, i + 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -882,6 +965,7 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) { TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { Options options; ImmutableCFOptions cf_options = ImmutableCFOptions(options); + MutableCFOptions mutable_cf_options = MutableCFOptions(options); ReadOptions ro; // Basic test case ... Make sure explicityly passing the default value works. @@ -898,9 +982,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { internal_iter->Finish(); ro.max_skippable_internal_keys = 0; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -944,9 +1029,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { internal_iter->Finish(); ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -988,9 +1074,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { internal_iter->Finish(); ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -1026,9 +1113,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { internal_iter->Finish(); ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -1061,9 +1149,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { internal_iter->Finish(); ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -1091,9 +1180,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { internal_iter->Finish(); ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -1128,9 +1218,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { internal_iter->Finish(); ro.max_skippable_internal_keys = 2; - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -1166,8 +1257,9 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { ro.max_skippable_internal_keys = i; std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, 2 * i + 1, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 2 * i + 1, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -1219,8 +1311,9 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) { options.max_sequential_skip_in_iterations = 1000; ro.max_skippable_internal_keys = i; std::unique_ptr db_iter(NewDBIterator( - env_, ro, cf_options, BytewiseComparator(), internal_iter, 2 * i + 1, - options.max_sequential_skip_in_iterations)); + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 2 * i + 1, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); @@ -1257,8 +1350,9 @@ TEST_F(DBIteratorTest, DBIterator1) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 1, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 1, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1284,8 +1378,9 @@ TEST_F(DBIteratorTest, DBIterator2) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 0, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 0, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1308,8 +1403,9 @@ TEST_F(DBIteratorTest, DBIterator3) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 2, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 2, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1332,8 +1428,9 @@ TEST_F(DBIteratorTest, DBIterator4) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 4, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 4, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1351,6 +1448,7 @@ TEST_F(DBIteratorTest, DBIterator5) { Options options; options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); ImmutableCFOptions cf_options = ImmutableCFOptions(options); + MutableCFOptions mutable_cf_options = MutableCFOptions(options); { TestIterator* internal_iter = new TestIterator(BytewiseComparator()); @@ -1363,9 +1461,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 0, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 0, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1385,9 +1484,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 1, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 1, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1407,9 +1507,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 2, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1429,9 +1530,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 3, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 3, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1451,9 +1553,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 4, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 4, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1473,9 +1576,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 5, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 5, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1495,9 +1599,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 6, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 6, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1515,9 +1620,10 @@ TEST_F(DBIteratorTest, DBIterator5) { internal_iter->AddMerge("a", "merge_2"); internal_iter->AddPut("b", "val_b"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 10, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 10, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->Seek("b"); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "b"); @@ -1532,6 +1638,7 @@ TEST_F(DBIteratorTest, DBIterator6) { Options options; options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); ImmutableCFOptions cf_options = ImmutableCFOptions(options); + MutableCFOptions mutable_cf_options = MutableCFOptions(options); { TestIterator* internal_iter = new TestIterator(BytewiseComparator()); @@ -1544,9 +1651,10 @@ TEST_F(DBIteratorTest, DBIterator6) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 0, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 0, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1566,9 +1674,10 @@ TEST_F(DBIteratorTest, DBIterator6) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 1, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 1, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1588,9 +1697,10 @@ TEST_F(DBIteratorTest, DBIterator6) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 2, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1610,9 +1720,10 @@ TEST_F(DBIteratorTest, DBIterator6) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 3, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 3, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(!db_iter->Valid()); } @@ -1628,9 +1739,10 @@ TEST_F(DBIteratorTest, DBIterator6) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 4, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 4, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1650,9 +1762,10 @@ TEST_F(DBIteratorTest, DBIterator6) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 5, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 5, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1672,9 +1785,10 @@ TEST_F(DBIteratorTest, DBIterator6) { internal_iter->AddMerge("a", "merge_6"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 6, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 6, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1689,6 +1803,7 @@ TEST_F(DBIteratorTest, DBIterator7) { Options options; options.merge_operator = MergeOperators::CreateFromStringId("stringappend"); ImmutableCFOptions cf_options = ImmutableCFOptions(options); + MutableCFOptions mutable_cf_options = MutableCFOptions(options); { TestIterator* internal_iter = new TestIterator(BytewiseComparator()); @@ -1713,9 +1828,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 0, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 0, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -1747,9 +1863,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 2, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 2, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -1787,9 +1904,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 4, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 4, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -1827,9 +1945,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 5, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 5, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -1872,9 +1991,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 6, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 6, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -1918,9 +2038,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 7, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 7, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -1958,9 +2079,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 9, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 9, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -2004,9 +2126,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 13, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 13, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -2051,9 +2174,10 @@ TEST_F(DBIteratorTest, DBIterator7) { internal_iter->AddDeletion("c"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, cf_options, BytewiseComparator(), internal_iter, - 14, options.max_sequential_skip_in_iterations)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, cf_options, mutable_cf_options, BytewiseComparator(), + internal_iter, 14, options.max_sequential_skip_in_iterations, + nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -2082,8 +2206,9 @@ TEST_F(DBIteratorTest, DBIterator8) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 10, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "b"); @@ -2112,8 +2237,9 @@ TEST_F(DBIteratorTest, DBIterator9) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 10, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); @@ -2178,8 +2304,9 @@ TEST_F(DBIteratorTest, DBIterator10) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 10, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->Seek("c"); ASSERT_TRUE(db_iter->Valid()); @@ -2216,9 +2343,10 @@ TEST_F(DBIteratorTest, SeekToLastOccurrenceSeq0) { internal_iter->AddPut("b", "2"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 10, 0 /* force seek */)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10, 0 /* force seek */, + nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -2245,8 +2373,9 @@ TEST_F(DBIteratorTest, DBIterator11) { internal_iter->Finish(); std::unique_ptr db_iter(NewDBIterator( - env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 1, options.max_sequential_skip_in_iterations)); + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 1, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); db_iter->SeekToFirst(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "a"); @@ -2270,9 +2399,9 @@ TEST_F(DBIteratorTest, DBIterator12) { internal_iter->AddSingleDeletion("b"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 10, 0)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10, 0, nullptr /*read_callback*/)); db_iter->SeekToLast(); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "c"); @@ -2307,9 +2436,9 @@ TEST_F(DBIteratorTest, DBIterator13) { internal_iter->AddPut(key, "8"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 2, 3)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 2, 3, nullptr /*read_callback*/)); db_iter->Seek("b"); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), key); @@ -2335,9 +2464,9 @@ TEST_F(DBIteratorTest, DBIterator14) { internal_iter->AddPut("c", "9"); internal_iter->Finish(); - std::unique_ptr db_iter( - NewDBIterator(env_, ro, ImmutableCFOptions(options), BytewiseComparator(), - internal_iter, 4, 1)); + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 4, 1, nullptr /*read_callback*/)); db_iter->Seek("b"); ASSERT_TRUE(db_iter->Valid()); ASSERT_EQ(db_iter->key().ToString(), "b"); @@ -2347,6 +2476,80 @@ TEST_F(DBIteratorTest, DBIterator14) { ASSERT_EQ(db_iter->value().ToString(), "4"); } +TEST_F(DBIteratorTest, DBIteratorTestDifferentialSnapshots) { + { // test that KVs earlier that iter_start_seqnum are filtered out + ReadOptions ro; + ro.iter_start_seqnum=5; + Options options; + options.statistics = rocksdb::CreateDBStatistics(); + + TestIterator* internal_iter = new TestIterator(BytewiseComparator()); + for (size_t i = 0; i < 10; ++i) { + internal_iter->AddPut(std::to_string(i), std::to_string(i) + "a"); + internal_iter->AddPut(std::to_string(i), std::to_string(i) + "b"); + internal_iter->AddPut(std::to_string(i), std::to_string(i) + "c"); + } + internal_iter->Finish(); + + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 13, + options.max_sequential_skip_in_iterations, nullptr)); + // Expecting InternalKeys in [5,8] range with correct type + int seqnums[4] = {5,8,11,13}; + std::string user_keys[4] = {"1","2","3","4"}; + std::string values[4] = {"1c", "2c", "3c", "4b"}; + int i = 0; + for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { + FullKey fkey; + ParseFullKey(db_iter->key(), &fkey); + ASSERT_EQ(user_keys[i], fkey.user_key.ToString()); + ASSERT_EQ(EntryType::kEntryPut, fkey.type); + ASSERT_EQ(seqnums[i], fkey.sequence); + ASSERT_EQ(values[i], db_iter->value().ToString()); + i++; + } + ASSERT_EQ(i, 4); + } + + { // Test that deletes are returned correctly as internal KVs + ReadOptions ro; + ro.iter_start_seqnum=5; + Options options; + options.statistics = rocksdb::CreateDBStatistics(); + + TestIterator* internal_iter = new TestIterator(BytewiseComparator()); + for (size_t i = 0; i < 10; ++i) { + internal_iter->AddPut(std::to_string(i), std::to_string(i) + "a"); + internal_iter->AddPut(std::to_string(i), std::to_string(i) + "b"); + internal_iter->AddDeletion(std::to_string(i)); + } + internal_iter->Finish(); + + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 13, + options.max_sequential_skip_in_iterations, nullptr)); + // Expecting InternalKeys in [5,8] range with correct type + int seqnums[4] = {5,8,11,13}; + EntryType key_types[4] = {EntryType::kEntryDelete,EntryType::kEntryDelete, + EntryType::kEntryDelete,EntryType::kEntryPut}; + std::string user_keys[4] = {"1","2","3","4"}; + std::string values[4] = {"", "", "", "4b"}; + int i = 0; + for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { + FullKey fkey; + ParseFullKey(db_iter->key(), &fkey); + ASSERT_EQ(user_keys[i], fkey.user_key.ToString()); + ASSERT_EQ(key_types[i], fkey.type); + ASSERT_EQ(seqnums[i], fkey.sequence); + ASSERT_EQ(values[i], db_iter->value().ToString()); + i++; + } + ASSERT_EQ(i, 4); + } +} + class DBIterWithMergeIterTest : public testing::Test { public: DBIterWithMergeIterTest() @@ -2373,10 +2576,11 @@ class DBIterWithMergeIterTest : public testing::Test { InternalIterator* merge_iter = NewMergingIterator(&icomp_, &child_iters[0], 2u); - db_iter_.reset(NewDBIterator(env_, ro_, ImmutableCFOptions(options_), - BytewiseComparator(), merge_iter, - 8 /* read data earlier than seqId 8 */, - 3 /* max iterators before reseek */)); + db_iter_.reset(NewDBIterator( + env_, ro_, ImmutableCFOptions(options_), MutableCFOptions(options_), + BytewiseComparator(), merge_iter, + 8 /* read data earlier than seqId 8 */, + 3 /* max iterators before reseek */, nullptr /*read_callback*/)); } Env* env_; @@ -2458,8 +2662,8 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace1) { // MergeIterator::Prev() realized the mem table iterator is at its end // and before an SeekToLast() is called. rocksdb::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforeSeekToLast", - [&](void* arg) { internal_iter2_->Add("z", kTypeValue, "7", 12u); }); + "MergeIterator::Prev:BeforePrev", + [&](void* /*arg*/) { internal_iter2_->Add("z", kTypeValue, "7", 12u); }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); db_iter_->Prev(); @@ -2494,7 +2698,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace2) { // mem table after MergeIterator::Prev() realized the mem tableiterator is at // its end and before an SeekToLast() is called. rocksdb::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforeSeekToLast", [&](void* arg) { + "MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) { internal_iter2_->Add("z", kTypeValue, "7", 12u); internal_iter2_->Add("z", kTypeValue, "7", 11u); }); @@ -2532,7 +2736,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace3) { // mem table after MergeIterator::Prev() realized the mem table iterator is at // its end and before an SeekToLast() is called. rocksdb::SyncPoint::GetInstance()->SetCallBack( - "MergeIterator::Prev:BeforeSeekToLast", [&](void* arg) { + "MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) { internal_iter2_->Add("z", kTypeValue, "7", 16u, true); internal_iter2_->Add("z", kTypeValue, "7", 15u, true); internal_iter2_->Add("z", kTypeValue, "7", 14u, true); @@ -2796,6 +3000,173 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace8) { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } + + +TEST_F(DBIteratorTest, SeekPrefixTombstones) { + ReadOptions ro; + Options options; + options.prefix_extractor.reset(NewNoopTransform()); + TestIterator* internal_iter = new TestIterator(BytewiseComparator()); + internal_iter->AddDeletion("b"); + internal_iter->AddDeletion("c"); + internal_iter->AddDeletion("d"); + internal_iter->AddDeletion("e"); + internal_iter->AddDeletion("f"); + internal_iter->AddDeletion("g"); + internal_iter->Finish(); + + ro.prefix_same_as_start = true; + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); + + int skipped_keys = 0; + + get_perf_context()->Reset(); + db_iter->SeekForPrev("z"); + skipped_keys = + static_cast(get_perf_context()->internal_key_skipped_count); + ASSERT_EQ(skipped_keys, 0); + + get_perf_context()->Reset(); + db_iter->Seek("a"); + skipped_keys = + static_cast(get_perf_context()->internal_key_skipped_count); + ASSERT_EQ(skipped_keys, 0); +} + +TEST_F(DBIteratorTest, SeekToFirstLowerBound) { + const int kNumKeys = 3; + for (int i = 0; i < kNumKeys + 2; ++i) { + // + 2 for two special cases: lower bound before and lower bound after the + // internal iterator's keys + TestIterator* internal_iter = new TestIterator(BytewiseComparator()); + for (int j = 1; j <= kNumKeys; ++j) { + internal_iter->AddPut(std::to_string(j), "val"); + } + internal_iter->Finish(); + + ReadOptions ro; + auto lower_bound_str = std::to_string(i); + Slice lower_bound(lower_bound_str); + ro.iterate_lower_bound = &lower_bound; + Options options; + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10 /* sequence */, + options.max_sequential_skip_in_iterations, + nullptr /* read_callback */)); + + db_iter->SeekToFirst(); + if (i == kNumKeys + 1) { + // lower bound was beyond the last key + ASSERT_FALSE(db_iter->Valid()); + } else { + ASSERT_TRUE(db_iter->Valid()); + int expected; + if (i == 0) { + // lower bound was before the first key + expected = 1; + } else { + // lower bound was at the ith key + expected = i; + } + ASSERT_EQ(std::to_string(expected), db_iter->key().ToString()); + } + } +} + +TEST_F(DBIteratorTest, PrevLowerBound) { + const int kNumKeys = 3; + const int kLowerBound = 2; + TestIterator* internal_iter = new TestIterator(BytewiseComparator()); + for (int j = 1; j <= kNumKeys; ++j) { + internal_iter->AddPut(std::to_string(j), "val"); + } + internal_iter->Finish(); + + ReadOptions ro; + auto lower_bound_str = std::to_string(kLowerBound); + Slice lower_bound(lower_bound_str); + ro.iterate_lower_bound = &lower_bound; + Options options; + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10 /* sequence */, + options.max_sequential_skip_in_iterations, nullptr /* read_callback */)); + + db_iter->SeekToLast(); + for (int i = kNumKeys; i >= kLowerBound; --i) { + ASSERT_TRUE(db_iter->Valid()); + ASSERT_EQ(std::to_string(i), db_iter->key().ToString()); + db_iter->Prev(); + } + ASSERT_FALSE(db_iter->Valid()); +} + +TEST_F(DBIteratorTest, SeekLessLowerBound) { + const int kNumKeys = 3; + const int kLowerBound = 2; + TestIterator* internal_iter = new TestIterator(BytewiseComparator()); + for (int j = 1; j <= kNumKeys; ++j) { + internal_iter->AddPut(std::to_string(j), "val"); + } + internal_iter->Finish(); + + ReadOptions ro; + auto lower_bound_str = std::to_string(kLowerBound); + Slice lower_bound(lower_bound_str); + ro.iterate_lower_bound = &lower_bound; + Options options; + std::unique_ptr db_iter(NewDBIterator( + env_, ro, ImmutableCFOptions(options), MutableCFOptions(options), + BytewiseComparator(), internal_iter, 10 /* sequence */, + options.max_sequential_skip_in_iterations, nullptr /* read_callback */)); + + auto before_lower_bound_str = std::to_string(kLowerBound - 1); + Slice before_lower_bound(lower_bound_str); + + db_iter->Seek(before_lower_bound); + ASSERT_TRUE(db_iter->Valid()); + ASSERT_EQ(lower_bound_str, db_iter->key().ToString()); +} + +TEST_F(DBIteratorTest, ReverseToForwardWithDisappearingKeys) { + Options options; + options.prefix_extractor.reset(NewCappedPrefixTransform(0)); + + TestIterator* internal_iter = new TestIterator(BytewiseComparator()); + internal_iter->AddPut("a", "A"); + internal_iter->AddPut("b", "B"); + for (int i = 0; i < 100; ++i) { + internal_iter->AddPut("c" + ToString(i), ""); + } + internal_iter->Finish(); + + std::unique_ptr db_iter(NewDBIterator( + env_, ReadOptions(), ImmutableCFOptions(options), + MutableCFOptions(options), BytewiseComparator(), internal_iter, 10, + options.max_sequential_skip_in_iterations, nullptr /*read_callback*/)); + + db_iter->SeekForPrev("a"); + ASSERT_TRUE(db_iter->Valid()); + ASSERT_OK(db_iter->status()); + ASSERT_EQ("a", db_iter->key().ToString()); + + internal_iter->Vanish("a"); + db_iter->Next(); + ASSERT_TRUE(db_iter->Valid()); + ASSERT_OK(db_iter->status()); + ASSERT_EQ("b", db_iter->key().ToString()); + + // A (sort of) bug used to cause DBIter to pointlessly drag the internal + // iterator all the way to the end. But this doesn't really matter at the time + // of writing because the only iterator that can see disappearing keys is + // ForwardIterator, which doesn't support SeekForPrev(). + EXPECT_LT(internal_iter->steps(), 20); +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_iterator_test.cc b/thirdparty/rocksdb/db/db_iterator_test.cc index d3bd164a2c..6a5188c775 100644 --- a/thirdparty/rocksdb/db/db_iterator_test.cc +++ b/thirdparty/rocksdb/db/db_iterator_test.cc @@ -9,6 +9,7 @@ #include +#include "db/db_iter.h" #include "db/db_test_util.h" #include "port/port.h" #include "port/stack_trace.h" @@ -17,20 +18,57 @@ namespace rocksdb { -class DBIteratorTest : public DBTestBase { +// A dumb ReadCallback which saying every key is committed. +class DummyReadCallback : public ReadCallback { + public: + DummyReadCallback() : ReadCallback(kMaxSequenceNumber) {} + bool IsVisibleFullCheck(SequenceNumber /*seq*/) override { return true; } + void SetSnapshot(SequenceNumber seq) { max_visible_seq_ = seq; } +}; + +// Test param: +// bool: whether to pass read_callback to NewIterator(). +class DBIteratorTest : public DBTestBase, + public testing::WithParamInterface { public: DBIteratorTest() : DBTestBase("/db_iterator_test") {} + + Iterator* NewIterator(const ReadOptions& read_options, + ColumnFamilyHandle* column_family = nullptr) { + if (column_family == nullptr) { + column_family = db_->DefaultColumnFamily(); + } + auto* cfd = reinterpret_cast(column_family)->cfd(); + SequenceNumber seq = read_options.snapshot != nullptr + ? read_options.snapshot->GetSequenceNumber() + : db_->GetLatestSequenceNumber(); + bool use_read_callback = GetParam(); + DummyReadCallback* read_callback = nullptr; + if (use_read_callback) { + read_callback = new DummyReadCallback(); + read_callback->SetSnapshot(seq); + InstrumentedMutexLock lock(&mutex_); + read_callbacks_.push_back( + std::unique_ptr(read_callback)); + } + return dbfull()->NewIteratorImpl(read_options, cfd, seq, read_callback); + } + + private: + InstrumentedMutex mutex_; + std::vector> read_callbacks_; }; class FlushBlockEveryKeyPolicy : public FlushBlockPolicy { public: - virtual bool Update(const Slice& key, const Slice& value) override { + bool Update(const Slice& /*key*/, const Slice& /*value*/) override { if (!start_) { start_ = true; return false; } return true; } + private: bool start_ = false; }; @@ -44,34 +82,41 @@ class FlushBlockEveryKeyPolicyFactory : public FlushBlockPolicyFactory { } FlushBlockPolicy* NewFlushBlockPolicy( - const BlockBasedTableOptions& table_options, - const BlockBuilder& data_block_builder) const override { + const BlockBasedTableOptions& /*table_options*/, + const BlockBuilder& /*data_block_builder*/) const override { return new FlushBlockEveryKeyPolicy; } }; -TEST_F(DBIteratorTest, IteratorProperty) { +TEST_P(DBIteratorTest, IteratorProperty) { // The test needs to be changed if kPersistedTier is supported in iterator. Options options = CurrentOptions(); CreateAndReopenWithCF({"pikachu"}, options); Put(1, "1", "2"); + Delete(1, "2"); ReadOptions ropt; ropt.pin_data = false; { - unique_ptr iter(db_->NewIterator(ropt, handles_[1])); + std::unique_ptr iter(NewIterator(ropt, handles_[1])); iter->SeekToFirst(); std::string prop_value; ASSERT_NOK(iter->GetProperty("non_existing.value", &prop_value)); ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); ASSERT_EQ("0", prop_value); + ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value)); + ASSERT_EQ("1", prop_value); iter->Next(); ASSERT_OK(iter->GetProperty("rocksdb.iterator.is-key-pinned", &prop_value)); ASSERT_EQ("Iterator is not valid.", prop_value); + + // Get internal key at which the iteration stopped (tombstone in this case). + ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value)); + ASSERT_EQ("2", prop_value); } Close(); } -TEST_F(DBIteratorTest, PersistedTierOnIterator) { +TEST_P(DBIteratorTest, PersistedTierOnIterator) { // The test needs to be changed if kPersistedTier is supported in iterator. Options options = CurrentOptions(); CreateAndReopenWithCF({"pikachu"}, options); @@ -87,7 +132,7 @@ TEST_F(DBIteratorTest, PersistedTierOnIterator) { Close(); } -TEST_F(DBIteratorTest, NonBlockingIteration) { +TEST_P(DBIteratorTest, NonBlockingIteration) { do { ReadOptions non_blocking_opts, regular_opts; Options options = CurrentOptions(); @@ -99,7 +144,7 @@ TEST_F(DBIteratorTest, NonBlockingIteration) { // scan using non-blocking iterator. We should find it because // it is in memtable. - Iterator* iter = db_->NewIterator(non_blocking_opts, handles_[1]); + Iterator* iter = NewIterator(non_blocking_opts, handles_[1]); int count = 0; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { ASSERT_OK(iter->status()); @@ -116,7 +161,7 @@ TEST_F(DBIteratorTest, NonBlockingIteration) { // kvs. Neither does it do any IOs to storage. uint64_t numopen = TestGetTickerCount(options, NO_FILE_OPENS); uint64_t cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD); - iter = db_->NewIterator(non_blocking_opts, handles_[1]); + iter = NewIterator(non_blocking_opts, handles_[1]); count = 0; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { count++; @@ -133,7 +178,7 @@ TEST_F(DBIteratorTest, NonBlockingIteration) { // 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]); + iter = NewIterator(non_blocking_opts, handles_[1]); count = 0; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { ASSERT_OK(iter->status()); @@ -146,79 +191,10 @@ TEST_F(DBIteratorTest, NonBlockingIteration) { // 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)); + } while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipMmapReads)); } -#ifndef ROCKSDB_LITE -TEST_F(DBIteratorTest, ManagedNonBlockingIteration) { - 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)); -} -#endif // ROCKSDB_LITE - -TEST_F(DBIteratorTest, IterSeekBeforePrev) { +TEST_P(DBIteratorTest, IterSeekBeforePrev) { ASSERT_OK(Put("a", "b")); ASSERT_OK(Put("c", "d")); dbfull()->Flush(FlushOptions()); @@ -226,7 +202,7 @@ TEST_F(DBIteratorTest, IterSeekBeforePrev) { ASSERT_OK(Put("1", "h")); dbfull()->Flush(FlushOptions()); ASSERT_OK(Put("2", "j")); - auto iter = db_->NewIterator(ReadOptions()); + auto iter = NewIterator(ReadOptions()); iter->Seek(Slice("c")); iter->Prev(); iter->Seek(Slice("a")); @@ -234,7 +210,7 @@ TEST_F(DBIteratorTest, IterSeekBeforePrev) { delete iter; } -TEST_F(DBIteratorTest, IterSeekForPrevBeforeNext) { +TEST_P(DBIteratorTest, IterSeekForPrevBeforeNext) { ASSERT_OK(Put("a", "b")); ASSERT_OK(Put("c", "d")); dbfull()->Flush(FlushOptions()); @@ -242,7 +218,7 @@ TEST_F(DBIteratorTest, IterSeekForPrevBeforeNext) { ASSERT_OK(Put("1", "h")); dbfull()->Flush(FlushOptions()); ASSERT_OK(Put("2", "j")); - auto iter = db_->NewIterator(ReadOptions()); + auto iter = NewIterator(ReadOptions()); iter->SeekForPrev(Slice("0")); iter->Next(); iter->SeekForPrev(Slice("1")); @@ -256,7 +232,7 @@ std::string MakeLongKey(size_t length, char c) { } } // namespace -TEST_F(DBIteratorTest, IterLongKeys) { +TEST_P(DBIteratorTest, IterLongKeys) { ASSERT_OK(Put(MakeLongKey(20, 0), "0")); ASSERT_OK(Put(MakeLongKey(32, 2), "2")); ASSERT_OK(Put("a", "b")); @@ -264,7 +240,7 @@ TEST_F(DBIteratorTest, IterLongKeys) { 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()); + auto iter = NewIterator(ReadOptions()); // Create a key that needs to be skipped for Seq too new iter->Seek(MakeLongKey(20, 0)); @@ -286,7 +262,7 @@ TEST_F(DBIteratorTest, IterLongKeys) { ASSERT_EQ(IterStatus(iter), MakeLongKey(50, 1) + "->1"); delete iter; - iter = db_->NewIterator(ReadOptions()); + iter = NewIterator(ReadOptions()); iter->Seek(MakeLongKey(50, 1)); ASSERT_EQ(IterStatus(iter), MakeLongKey(50, 1) + "->1"); iter->Next(); @@ -296,13 +272,13 @@ TEST_F(DBIteratorTest, IterLongKeys) { delete iter; } -TEST_F(DBIteratorTest, IterNextWithNewerSeq) { +TEST_P(DBIteratorTest, IterNextWithNewerSeq) { 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()); + auto iter = 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; @@ -322,13 +298,13 @@ TEST_F(DBIteratorTest, IterNextWithNewerSeq) { delete iter; } -TEST_F(DBIteratorTest, IterPrevWithNewerSeq) { +TEST_P(DBIteratorTest, IterPrevWithNewerSeq) { 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()); + auto iter = 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; @@ -353,14 +329,14 @@ TEST_F(DBIteratorTest, IterPrevWithNewerSeq) { delete iter; } -TEST_F(DBIteratorTest, IterPrevWithNewerSeq2) { +TEST_P(DBIteratorTest, IterPrevWithNewerSeq2) { ASSERT_OK(Put("0", "0")); dbfull()->Flush(FlushOptions()); ASSERT_OK(Put("a", "b")); ASSERT_OK(Put("c", "d")); ASSERT_OK(Put("e", "f")); - auto iter = db_->NewIterator(ReadOptions()); - auto iter2 = db_->NewIterator(ReadOptions()); + auto iter = NewIterator(ReadOptions()); + auto iter2 = NewIterator(ReadOptions()); iter->Seek(Slice("c")); iter2->SeekForPrev(Slice("d")); ASSERT_EQ(IterStatus(iter), "c->d"); @@ -382,10 +358,10 @@ TEST_F(DBIteratorTest, IterPrevWithNewerSeq2) { delete iter2; } -TEST_F(DBIteratorTest, IterEmpty) { +TEST_P(DBIteratorTest, IterEmpty) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]); + Iterator* iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToFirst(); ASSERT_EQ(IterStatus(iter), "(invalid)"); @@ -403,11 +379,11 @@ TEST_F(DBIteratorTest, IterEmpty) { } while (ChangeCompactOptions()); } -TEST_F(DBIteratorTest, IterSingle) { +TEST_P(DBIteratorTest, IterSingle) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); ASSERT_OK(Put(1, "a", "va")); - Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]); + Iterator* iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToFirst(); ASSERT_EQ(IterStatus(iter), "a->va"); @@ -454,13 +430,13 @@ TEST_F(DBIteratorTest, IterSingle) { } while (ChangeCompactOptions()); } -TEST_F(DBIteratorTest, IterMulti) { +TEST_P(DBIteratorTest, IterMulti) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); 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]); + Iterator* iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToFirst(); ASSERT_EQ(IterStatus(iter), "a->va"); @@ -553,7 +529,7 @@ TEST_F(DBIteratorTest, IterMulti) { // Check that we can skip over a run of user keys // by using reseek rather than sequential scan -TEST_F(DBIteratorTest, IterReseek) { +TEST_P(DBIteratorTest, IterReseek) { anon::OptionsOverride options_override; options_override.skip_policy = kSkipNoSnapshot; Options options = CurrentOptions(options_override); @@ -570,7 +546,7 @@ TEST_F(DBIteratorTest, IterReseek) { 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]); + Iterator* iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToFirst(); ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); ASSERT_EQ(IterStatus(iter), "a->two"); @@ -582,7 +558,7 @@ TEST_F(DBIteratorTest, IterReseek) { // insert a total of three keys with same userkey and verify // that reseek is still not invoked. ASSERT_OK(Put(1, "a", "three")); - iter = db_->NewIterator(ReadOptions(), handles_[1]); + iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToFirst(); ASSERT_EQ(IterStatus(iter), "a->three"); iter->Next(); @@ -593,7 +569,7 @@ TEST_F(DBIteratorTest, IterReseek) { // insert a total of four keys with same userkey and verify // that reseek is invoked. ASSERT_OK(Put(1, "a", "four")); - iter = db_->NewIterator(ReadOptions(), handles_[1]); + iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToFirst(); ASSERT_EQ(IterStatus(iter), "a->four"); ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), 0); @@ -610,7 +586,7 @@ TEST_F(DBIteratorTest, IterReseek) { // Insert another version of b and assert that reseek is not invoked ASSERT_OK(Put(1, "b", "btwo")); - iter = db_->NewIterator(ReadOptions(), handles_[1]); + iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToLast(); ASSERT_EQ(IterStatus(iter), "b->btwo"); ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), @@ -625,7 +601,7 @@ TEST_F(DBIteratorTest, IterReseek) { // of b and 4 versions of a. ASSERT_OK(Put(1, "b", "bthree")); ASSERT_OK(Put(1, "b", "bfour")); - iter = db_->NewIterator(ReadOptions(), handles_[1]); + iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToLast(); ASSERT_EQ(IterStatus(iter), "b->bfour"); ASSERT_EQ(TestGetTickerCount(options, NUMBER_OF_RESEEKS_IN_ITERATION), @@ -639,7 +615,7 @@ TEST_F(DBIteratorTest, IterReseek) { delete iter; } -TEST_F(DBIteratorTest, IterSmallAndLargeMix) { +TEST_P(DBIteratorTest, IterSmallAndLargeMix) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); ASSERT_OK(Put(1, "a", "va")); @@ -648,7 +624,7 @@ TEST_F(DBIteratorTest, IterSmallAndLargeMix) { ASSERT_OK(Put(1, "d", std::string(100000, 'd'))); ASSERT_OK(Put(1, "e", std::string(100000, 'e'))); - Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]); + Iterator* iter = NewIterator(ReadOptions(), handles_[1]); iter->SeekToFirst(); ASSERT_EQ(IterStatus(iter), "a->va"); @@ -680,7 +656,7 @@ TEST_F(DBIteratorTest, IterSmallAndLargeMix) { } while (ChangeCompactOptions()); } -TEST_F(DBIteratorTest, IterMultiWithDelete) { +TEST_P(DBIteratorTest, IterMultiWithDelete) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); ASSERT_OK(Put(1, "ka", "va")); @@ -689,7 +665,7 @@ TEST_F(DBIteratorTest, IterMultiWithDelete) { ASSERT_OK(Delete(1, "kb")); ASSERT_EQ("NOT_FOUND", Get(1, "kb")); - Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]); + Iterator* iter = NewIterator(ReadOptions(), handles_[1]); iter->Seek("kc"); ASSERT_EQ(IterStatus(iter), "kc->vc"); if (!CurrentOptions().merge_operator) { @@ -706,7 +682,7 @@ TEST_F(DBIteratorTest, IterMultiWithDelete) { } while (ChangeOptions()); } -TEST_F(DBIteratorTest, IterPrevMaxSkip) { +TEST_P(DBIteratorTest, IterPrevMaxSkip) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); for (int i = 0; i < 2; i++) { @@ -736,7 +712,7 @@ TEST_F(DBIteratorTest, IterPrevMaxSkip) { } while (ChangeOptions(kSkipMergePut | kSkipNoSeekToLast)); } -TEST_F(DBIteratorTest, IterWithSnapshot) { +TEST_P(DBIteratorTest, IterWithSnapshot) { anon::OptionsOverride options_override; options_override.skip_policy = kSkipNoSnapshot; do { @@ -750,7 +726,7 @@ TEST_F(DBIteratorTest, IterWithSnapshot) { const Snapshot* snapshot = db_->GetSnapshot(); ReadOptions options; options.snapshot = snapshot; - Iterator* iter = db_->NewIterator(options, handles_[1]); + Iterator* iter = NewIterator(options, handles_[1]); ASSERT_OK(Put(1, "key0", "val0")); // Put more values after the snapshot @@ -799,17 +775,16 @@ TEST_F(DBIteratorTest, IterWithSnapshot) { } db_->ReleaseSnapshot(snapshot); delete iter; - // skip as HashCuckooRep does not support snapshot - } while (ChangeOptions(kSkipHashCuckoo)); + } while (ChangeOptions()); } -TEST_F(DBIteratorTest, IteratorPinsRef) { +TEST_P(DBIteratorTest, IteratorPinsRef) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); Put(1, "foo", "hello"); // Get iterator that will yield the current contents of the DB. - Iterator* iter = db_->NewIterator(ReadOptions(), handles_[1]); + Iterator* iter = NewIterator(ReadOptions(), handles_[1]); // Write to force compactions Put(1, "foo", "newvalue1"); @@ -829,7 +804,9 @@ TEST_F(DBIteratorTest, IteratorPinsRef) { } while (ChangeCompactOptions()); } -TEST_F(DBIteratorTest, DBIteratorBoundTest) { +// SetOptions not defined in ROCKSDB LITE +#ifndef ROCKSDB_LITE +TEST_P(DBIteratorTest, DBIteratorBoundTest) { Options options = CurrentOptions(); options.env = env_; options.create_if_missing = true; @@ -846,7 +823,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { ReadOptions ro; ro.iterate_upper_bound = nullptr; - std::unique_ptr iter(db_->NewIterator(ro)); + std::unique_ptr iter(NewIterator(ro)); iter->Seek("foo"); @@ -883,7 +860,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { Slice prefix("foo2"); ro.iterate_upper_bound = &prefix; - std::unique_ptr iter(db_->NewIterator(ro)); + std::unique_ptr iter(NewIterator(ro)); iter->Seek("foo"); @@ -905,7 +882,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { Slice prefix("foo"); ro.iterate_upper_bound = &prefix; - std::unique_ptr iter(db_->NewIterator(ro)); + std::unique_ptr iter(NewIterator(ro)); iter->SeekToLast(); ASSERT_TRUE(iter->Valid()); @@ -913,9 +890,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { } // prefix is the first letter of the key - options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - - DestroyAndReopen(options); + ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:1"}})); ASSERT_OK(Put("a", "0")); ASSERT_OK(Put("foo", "bar")); ASSERT_OK(Put("foo1", "bar1")); @@ -929,7 +904,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { Slice upper_bound("g"); ro.iterate_upper_bound = &upper_bound; - std::unique_ptr iter(db_->NewIterator(ro)); + std::unique_ptr iter(NewIterator(ro)); iter->Seek("foo"); @@ -962,7 +937,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { ReadOptions ro; ro.iterate_upper_bound = nullptr; - std::unique_ptr iter(db_->NewIterator(ro)); + std::unique_ptr iter(NewIterator(ro)); iter->Seek("b"); ASSERT_TRUE(iter->Valid()); @@ -982,7 +957,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { Slice prefix("c"); ro.iterate_upper_bound = &prefix; - iter.reset(db_->NewIterator(ro)); + iter.reset(NewIterator(ro)); get_perf_context()->Reset(); @@ -1003,7 +978,62 @@ TEST_F(DBIteratorTest, DBIteratorBoundTest) { } } -TEST_F(DBIteratorTest, DBIteratorBoundOptimizationTest) { +TEST_P(DBIteratorTest, DBIteratorBoundMultiSeek) { + Options options = CurrentOptions(); + options.env = env_; + options.create_if_missing = true; + options.statistics = rocksdb::CreateDBStatistics(); + options.prefix_extractor = nullptr; + DestroyAndReopen(options); + ASSERT_OK(Put("a", "0")); + ASSERT_OK(Put("z", "0")); + ASSERT_OK(Flush()); + ASSERT_OK(Put("foo1", "bar1")); + ASSERT_OK(Put("foo2", "bar2")); + ASSERT_OK(Put("foo3", "bar3")); + ASSERT_OK(Put("foo4", "bar4")); + + { + std::string up_str = "foo5"; + Slice up(up_str); + ReadOptions ro; + ro.iterate_upper_bound = &up; + std::unique_ptr iter(NewIterator(ro)); + + iter->Seek("foo1"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(Slice("foo1")), 0); + + uint64_t prev_block_cache_hit = + TestGetTickerCount(options, BLOCK_CACHE_HIT); + uint64_t prev_block_cache_miss = + TestGetTickerCount(options, BLOCK_CACHE_MISS); + + ASSERT_GT(prev_block_cache_hit + prev_block_cache_miss, 0); + + iter->Seek("foo4"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(Slice("foo4")), 0); + ASSERT_EQ(prev_block_cache_hit, + TestGetTickerCount(options, BLOCK_CACHE_HIT)); + ASSERT_EQ(prev_block_cache_miss, + TestGetTickerCount(options, BLOCK_CACHE_MISS)); + + iter->Seek("foo2"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(Slice("foo2")), 0); + iter->Next(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(Slice("foo3")), 0); + ASSERT_EQ(prev_block_cache_hit, + TestGetTickerCount(options, BLOCK_CACHE_HIT)); + ASSERT_EQ(prev_block_cache_miss, + TestGetTickerCount(options, BLOCK_CACHE_MISS)); + } +} +#endif + +TEST_P(DBIteratorTest, DBIteratorBoundOptimizationTest) { int upper_bound_hits = 0; Options options = CurrentOptions(); rocksdb::SyncPoint::GetInstance()->SetCallBack( @@ -1031,7 +1061,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundOptimizationTest) { ReadOptions ro; ro.iterate_upper_bound = &ub; - std::unique_ptr iter(db_->NewIterator(ro)); + std::unique_ptr iter(NewIterator(ro)); iter->Seek("foo"); ASSERT_TRUE(iter->Valid()); @@ -1049,7 +1079,7 @@ TEST_F(DBIteratorTest, DBIteratorBoundOptimizationTest) { } // TODO(3.13): fix the issue of Seek() + Prev() which might not necessary // return the biggest key which is smaller than the seek key. -TEST_F(DBIteratorTest, PrevAfterAndNextAfterMerge) { +TEST_P(DBIteratorTest, PrevAfterAndNextAfterMerge) { Options options; options.create_if_missing = true; options.merge_operator = MergeOperators::CreatePutOperator(); @@ -1062,7 +1092,7 @@ TEST_F(DBIteratorTest, PrevAfterAndNextAfterMerge) { db_->Merge(wopts, "2", "data2"); db_->Merge(wopts, "3", "data3"); - std::unique_ptr it(db_->NewIterator(ReadOptions())); + std::unique_ptr it(NewIterator(ReadOptions())); it->Seek("2"); ASSERT_TRUE(it->Valid()); @@ -1081,7 +1111,8 @@ TEST_F(DBIteratorTest, PrevAfterAndNextAfterMerge) { ASSERT_EQ("2", it->key().ToString()); } -TEST_F(DBIteratorTest, PinnedDataIteratorRandomized) { +class DBIteratorTestForPinnedData : public DBIteratorTest { + public: enum TestConfig { NORMAL, CLOSE_AND_OPEN, @@ -1089,19 +1120,19 @@ TEST_F(DBIteratorTest, PinnedDataIteratorRandomized) { FLUSH_EVERY_1000, MAX }; + DBIteratorTestForPinnedData() : DBIteratorTest() {} + void PinnedDataIteratorRandomized(TestConfig run_config) { + // Generate Random data + Random rnd(301); + + int puts = 100000; + int key_pool = static_cast(puts * 0.7); + int key_size = 100; + int val_size = 1000; + int seeks_percentage = 20; // 20% of keys will be used to test seek() + int delete_percentage = 20; // 20% of keys will be deleted + int merge_percentage = 20; // 20% of keys will be added using Merge() - // Generate Random data - Random rnd(301); - - int puts = 100000; - int key_pool = static_cast(puts * 0.7); - int key_size = 100; - int val_size = 1000; - int seeks_percentage = 20; // 20% of keys will be used to test seek() - int delete_percentage = 20; // 20% of keys will be deleted - int merge_percentage = 20; // 20% of keys will be added using Merge() - - for (int run_config = 0; run_config < TestConfig::MAX; run_config++) { Options options = CurrentOptions(); BlockBasedTableOptions table_options; table_options.use_delta_encoding = false; @@ -1157,7 +1188,7 @@ TEST_F(DBIteratorTest, PinnedDataIteratorRandomized) { ReadOptions ro; ro.pin_data = true; - auto iter = db_->NewIterator(ro); + auto iter = NewIterator(ro); { // Test Seek to random keys @@ -1246,11 +1277,28 @@ TEST_F(DBIteratorTest, PinnedDataIteratorRandomized) { } delete iter; - } +} +}; + +TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedNormal) { + PinnedDataIteratorRandomized(TestConfig::NORMAL); +} + +TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedCLoseAndOpen) { + PinnedDataIteratorRandomized(TestConfig::CLOSE_AND_OPEN); +} + +TEST_P(DBIteratorTestForPinnedData, + PinnedDataIteratorRandomizedCompactBeforeRead) { + PinnedDataIteratorRandomized(TestConfig::COMPACT_BEFORE_READ); +} + +TEST_P(DBIteratorTestForPinnedData, PinnedDataIteratorRandomizedFlush) { + PinnedDataIteratorRandomized(TestConfig::FLUSH_EVERY_1000); } #ifndef ROCKSDB_LITE -TEST_F(DBIteratorTest, PinnedDataIteratorMultipleFiles) { +TEST_P(DBIteratorTest, PinnedDataIteratorMultipleFiles) { Options options = CurrentOptions(); BlockBasedTableOptions table_options; table_options.use_delta_encoding = false; @@ -1299,7 +1347,7 @@ TEST_F(DBIteratorTest, PinnedDataIteratorMultipleFiles) { ReadOptions ro; ro.pin_data = true; - auto iter = db_->NewIterator(ro); + auto iter = NewIterator(ro); std::vector> results; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { @@ -1321,7 +1369,7 @@ TEST_F(DBIteratorTest, PinnedDataIteratorMultipleFiles) { } #endif -TEST_F(DBIteratorTest, PinnedDataIteratorMergeOperator) { +TEST_P(DBIteratorTest, PinnedDataIteratorMergeOperator) { Options options = CurrentOptions(); BlockBasedTableOptions table_options; table_options.use_delta_encoding = false; @@ -1354,7 +1402,7 @@ TEST_F(DBIteratorTest, PinnedDataIteratorMergeOperator) { ReadOptions ro; ro.pin_data = true; - auto iter = db_->NewIterator(ro); + auto iter = NewIterator(ro); std::vector> results; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { @@ -1381,7 +1429,7 @@ TEST_F(DBIteratorTest, PinnedDataIteratorMergeOperator) { delete iter; } -TEST_F(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) { +TEST_P(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) { Options options = CurrentOptions(); BlockBasedTableOptions table_options; table_options.use_delta_encoding = false; @@ -1401,7 +1449,7 @@ TEST_F(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) { ReadOptions ro; ro.pin_data = true; - auto iter = db_->NewIterator(ro); + auto iter = NewIterator(ro); // Delete 50% of the keys and update the other 50% for (auto& kv : true_data) { @@ -1431,7 +1479,27 @@ TEST_F(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) { delete iter; } -TEST_F(DBIteratorTest, IterSeekForPrevCrossingFiles) { +class SliceTransformLimitedDomainGeneric : public SliceTransform { + const char* Name() const override { + return "SliceTransformLimitedDomainGeneric"; + } + + Slice Transform(const Slice& src) const override { + return Slice(src.data(), 1); + } + + bool InDomain(const Slice& src) const override { + // prefix will be x???? + return src.size() >= 1; + } + + bool InRange(const Slice& dst) const override { + // prefix will be x???? + return dst.size() == 1; + } +}; + +TEST_P(DBIteratorTest, IterSeekForPrevCrossingFiles) { Options options = CurrentOptions(); options.prefix_extractor.reset(NewFixedPrefixTransform(1)); options.disable_auto_compactions = true; @@ -1460,7 +1528,62 @@ TEST_F(DBIteratorTest, IterSeekForPrevCrossingFiles) { MoveFilesToLevel(1); { ReadOptions ro; - Iterator* iter = db_->NewIterator(ro); + Iterator* iter = NewIterator(ro); + + iter->SeekForPrev("a4"); + ASSERT_EQ(iter->key().ToString(), "a3"); + ASSERT_EQ(iter->value().ToString(), "va3"); + + iter->SeekForPrev("c2"); + ASSERT_EQ(iter->key().ToString(), "b3"); + iter->SeekForPrev("d3"); + ASSERT_EQ(iter->key().ToString(), "d2"); + iter->SeekForPrev("b5"); + ASSERT_EQ(iter->key().ToString(), "b4"); + delete iter; + } + + { + ReadOptions ro; + ro.prefix_same_as_start = true; + Iterator* iter = NewIterator(ro); + iter->SeekForPrev("c2"); + ASSERT_TRUE(!iter->Valid()); + delete iter; + } +} + +TEST_P(DBIteratorTest, IterSeekForPrevCrossingFilesCustomPrefixExtractor) { + Options options = CurrentOptions(); + options.prefix_extractor = + std::make_shared(); + options.disable_auto_compactions = true; + // Enable prefix bloom for SST files + BlockBasedTableOptions table_options; + table_options.filter_policy.reset(NewBloomFilterPolicy(10, true)); + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + DestroyAndReopen(options); + + ASSERT_OK(Put("a1", "va1")); + ASSERT_OK(Put("a2", "va2")); + ASSERT_OK(Put("a3", "va3")); + ASSERT_OK(Flush()); + + ASSERT_OK(Put("b1", "vb1")); + ASSERT_OK(Put("b2", "vb2")); + ASSERT_OK(Put("b3", "vb3")); + ASSERT_OK(Flush()); + + ASSERT_OK(Put("b4", "vb4")); + ASSERT_OK(Put("d1", "vd1")); + ASSERT_OK(Put("d2", "vd2")); + ASSERT_OK(Put("d4", "vd4")); + ASSERT_OK(Flush()); + + MoveFilesToLevel(1); + { + ReadOptions ro; + Iterator* iter = NewIterator(ro); iter->SeekForPrev("a4"); ASSERT_EQ(iter->key().ToString(), "a3"); @@ -1478,14 +1601,14 @@ TEST_F(DBIteratorTest, IterSeekForPrevCrossingFiles) { { ReadOptions ro; ro.prefix_same_as_start = true; - Iterator* iter = db_->NewIterator(ro); + Iterator* iter = NewIterator(ro); iter->SeekForPrev("c2"); ASSERT_TRUE(!iter->Valid()); delete iter; } } -TEST_F(DBIteratorTest, IterPrevKeyCrossingBlocks) { +TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocks) { Options options = CurrentOptions(); BlockBasedTableOptions table_options; table_options.block_size = 1; // every block will contain one entry @@ -1527,7 +1650,7 @@ TEST_F(DBIteratorTest, IterPrevKeyCrossingBlocks) { { ReadOptions ro; ro.fill_cache = false; - Iterator* iter = db_->NewIterator(ro); + Iterator* iter = NewIterator(ro); iter->SeekToLast(); ASSERT_EQ(iter->key().ToString(), "key5"); @@ -1553,7 +1676,7 @@ TEST_F(DBIteratorTest, IterPrevKeyCrossingBlocks) { } } -TEST_F(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) { +TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) { Options options = CurrentOptions(); options.merge_operator = MergeOperators::CreateStringAppendTESTOperator(); options.disable_auto_compactions = true; @@ -1629,7 +1752,7 @@ TEST_F(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) { { ReadOptions ro; ro.fill_cache = false; - Iterator* iter = db_->NewIterator(ro); + Iterator* iter = NewIterator(ro); auto data_iter = true_data.rbegin(); for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { @@ -1645,7 +1768,7 @@ TEST_F(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) { { ReadOptions ro; ro.fill_cache = false; - Iterator* iter = db_->NewIterator(ro); + Iterator* iter = NewIterator(ro); auto data_iter = true_data.rbegin(); int entries_right = 0; @@ -1700,7 +1823,7 @@ TEST_F(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) { } } -TEST_F(DBIteratorTest, IteratorWithLocalStatistics) { +TEST_P(DBIteratorTest, IteratorWithLocalStatistics) { Options options = CurrentOptions(); options.statistics = rocksdb::CreateDBStatistics(); DestroyAndReopen(options); @@ -1721,7 +1844,7 @@ TEST_F(DBIteratorTest, IteratorWithLocalStatistics) { std::function reader_func_next = [&]() { SetPerfLevel(kEnableCount); get_perf_context()->Reset(); - Iterator* iter = db_->NewIterator(ReadOptions()); + Iterator* iter = NewIterator(ReadOptions()); iter->SeekToFirst(); // Seek will bump ITER_BYTES_READ @@ -1748,7 +1871,7 @@ TEST_F(DBIteratorTest, IteratorWithLocalStatistics) { std::function reader_func_prev = [&]() { SetPerfLevel(kEnableCount); - Iterator* iter = db_->NewIterator(ReadOptions()); + Iterator* iter = NewIterator(ReadOptions()); iter->SeekToLast(); // Seek will bump ITER_BYTES_READ @@ -1794,7 +1917,7 @@ TEST_F(DBIteratorTest, IteratorWithLocalStatistics) { } -TEST_F(DBIteratorTest, ReadAhead) { +TEST_P(DBIteratorTest, ReadAhead) { Options options; env_->count_random_reads_ = true; options.env = env_; @@ -1831,26 +1954,30 @@ TEST_F(DBIteratorTest, ReadAhead) { env_->random_read_bytes_counter_ = 0; options.statistics->setTickerCount(NO_FILE_OPENS, 0); ReadOptions read_options; - auto* iter = db_->NewIterator(read_options); + auto* iter = NewIterator(read_options); iter->SeekToFirst(); int64_t num_file_opens = TestGetTickerCount(options, NO_FILE_OPENS); size_t bytes_read = env_->random_read_bytes_counter_; delete iter; + int64_t num_file_closes = TestGetTickerCount(options, NO_FILE_CLOSES); env_->random_read_bytes_counter_ = 0; options.statistics->setTickerCount(NO_FILE_OPENS, 0); read_options.readahead_size = 1024 * 10; - iter = db_->NewIterator(read_options); + iter = NewIterator(read_options); iter->SeekToFirst(); int64_t num_file_opens_readahead = TestGetTickerCount(options, NO_FILE_OPENS); size_t bytes_read_readahead = env_->random_read_bytes_counter_; delete iter; + int64_t num_file_closes_readahead = + TestGetTickerCount(options, NO_FILE_CLOSES); ASSERT_EQ(num_file_opens + 3, num_file_opens_readahead); + ASSERT_EQ(num_file_closes + 3, num_file_closes_readahead); ASSERT_GT(bytes_read_readahead, bytes_read); ASSERT_GT(bytes_read_readahead, read_options.readahead_size * 3); // Verify correctness. - iter = db_->NewIterator(read_options); + iter = NewIterator(read_options); int count = 0; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { ASSERT_EQ(value, iter->value()); @@ -1867,7 +1994,7 @@ TEST_F(DBIteratorTest, ReadAhead) { // Insert a key, create a snapshot iterator, overwrite key lots of times, // seek to a smaller key. Expect DBIter to fall back to a seek instead of // going through all the overwrites linearly. -TEST_F(DBIteratorTest, DBIteratorSkipRecentDuplicatesTest) { +TEST_P(DBIteratorTest, DBIteratorSkipRecentDuplicatesTest) { Options options = CurrentOptions(); options.env = env_; options.create_if_missing = true; @@ -1882,7 +2009,7 @@ TEST_F(DBIteratorTest, DBIteratorSkipRecentDuplicatesTest) { // Create iterator. ReadOptions ro; - std::unique_ptr iter(db_->NewIterator(ro)); + std::unique_ptr iter(NewIterator(ro)); // Insert a lot. for (int i = 0; i < 100; ++i) { @@ -1920,10 +2047,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipRecentDuplicatesTest) { NUMBER_OF_RESEEKS_IN_ITERATION)); } -TEST_F(DBIteratorTest, Refresh) { +TEST_P(DBIteratorTest, Refresh) { ASSERT_OK(Put("x", "y")); - std::unique_ptr iter(db_->NewIterator(ReadOptions())); + std::unique_ptr iter(NewIterator(ReadOptions())); iter->Seek(Slice("a")); ASSERT_TRUE(iter->Valid()); ASSERT_EQ(iter->key().compare(Slice("x")), 0); @@ -1979,6 +2106,516 @@ TEST_F(DBIteratorTest, Refresh) { iter.reset(); } +TEST_P(DBIteratorTest, RefreshWithSnapshot) { + ASSERT_OK(Put("x", "y")); + const Snapshot* snapshot = db_->GetSnapshot(); + ReadOptions options; + options.snapshot = snapshot; + Iterator* iter = NewIterator(options); + + iter->Seek(Slice("a")); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(Slice("x")), 0); + iter->Next(); + ASSERT_FALSE(iter->Valid()); + + ASSERT_OK(Put("c", "d")); + + iter->Seek(Slice("a")); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(Slice("x")), 0); + iter->Next(); + ASSERT_FALSE(iter->Valid()); + + Status s; + s = iter->Refresh(); + ASSERT_TRUE(s.IsNotSupported()); + db_->ReleaseSnapshot(snapshot); + delete iter; +} + +TEST_P(DBIteratorTest, CreationFailure) { + SyncPoint::GetInstance()->SetCallBack( + "DBImpl::NewInternalIterator:StatusCallback", [](void* arg) { + *(reinterpret_cast(arg)) = Status::Corruption("test status"); + }); + SyncPoint::GetInstance()->EnableProcessing(); + + Iterator* iter = NewIterator(ReadOptions()); + ASSERT_FALSE(iter->Valid()); + ASSERT_TRUE(iter->status().IsCorruption()); + delete iter; +} + +TEST_P(DBIteratorTest, UpperBoundWithChangeDirection) { + Options options = CurrentOptions(); + options.max_sequential_skip_in_iterations = 3; + DestroyAndReopen(options); + + // write a bunch of kvs to the database. + ASSERT_OK(Put("a", "1")); + ASSERT_OK(Put("y", "1")); + ASSERT_OK(Put("y1", "1")); + ASSERT_OK(Put("y2", "1")); + ASSERT_OK(Put("y3", "1")); + ASSERT_OK(Put("z", "1")); + ASSERT_OK(Flush()); + ASSERT_OK(Put("a", "1")); + ASSERT_OK(Put("z", "1")); + ASSERT_OK(Put("bar", "1")); + ASSERT_OK(Put("foo", "1")); + + std::string upper_bound = "x"; + Slice ub_slice(upper_bound); + ReadOptions ro; + ro.iterate_upper_bound = &ub_slice; + ro.max_skippable_internal_keys = 1000; + + Iterator* iter = NewIterator(ro); + iter->Seek("foo"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("foo", iter->key().ToString()); + + iter->Prev(); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("bar", iter->key().ToString()); + + delete iter; +} + +TEST_P(DBIteratorTest, TableFilter) { + ASSERT_OK(Put("a", "1")); + dbfull()->Flush(FlushOptions()); + ASSERT_OK(Put("b", "2")); + ASSERT_OK(Put("c", "3")); + dbfull()->Flush(FlushOptions()); + ASSERT_OK(Put("d", "4")); + ASSERT_OK(Put("e", "5")); + ASSERT_OK(Put("f", "6")); + dbfull()->Flush(FlushOptions()); + + // Ensure the table_filter callback is called once for each table. + { + std::set unseen{1, 2, 3}; + ReadOptions opts; + opts.table_filter = [&](const TableProperties& props) { + auto it = unseen.find(props.num_entries); + if (it == unseen.end()) { + ADD_FAILURE() << "saw table properties with an unexpected " + << props.num_entries << " entries"; + } else { + unseen.erase(it); + } + return true; + }; + auto iter = NewIterator(opts); + iter->SeekToFirst(); + ASSERT_EQ(IterStatus(iter), "a->1"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "b->2"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "c->3"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "d->4"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "e->5"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "f->6"); + iter->Next(); + ASSERT_FALSE(iter->Valid()); + ASSERT_TRUE(unseen.empty()); + delete iter; + } + + // Ensure returning false in the table_filter hides the keys from that table + // during iteration. + { + ReadOptions opts; + opts.table_filter = [](const TableProperties& props) { + return props.num_entries != 2; + }; + auto iter = NewIterator(opts); + iter->SeekToFirst(); + ASSERT_EQ(IterStatus(iter), "a->1"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "d->4"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "e->5"); + iter->Next(); + ASSERT_EQ(IterStatus(iter), "f->6"); + iter->Next(); + ASSERT_FALSE(iter->Valid()); + delete iter; + } +} + +TEST_P(DBIteratorTest, UpperBoundWithPrevReseek) { + Options options = CurrentOptions(); + options.max_sequential_skip_in_iterations = 3; + DestroyAndReopen(options); + + // write a bunch of kvs to the database. + ASSERT_OK(Put("a", "1")); + ASSERT_OK(Put("y", "1")); + ASSERT_OK(Put("z", "1")); + ASSERT_OK(Flush()); + ASSERT_OK(Put("a", "1")); + ASSERT_OK(Put("z", "1")); + ASSERT_OK(Put("bar", "1")); + ASSERT_OK(Put("foo", "1")); + ASSERT_OK(Put("foo", "2")); + + ASSERT_OK(Put("foo", "3")); + ASSERT_OK(Put("foo", "4")); + ASSERT_OK(Put("foo", "5")); + const Snapshot* snapshot = db_->GetSnapshot(); + ASSERT_OK(Put("foo", "6")); + + std::string upper_bound = "x"; + Slice ub_slice(upper_bound); + ReadOptions ro; + ro.snapshot = snapshot; + ro.iterate_upper_bound = &ub_slice; + + Iterator* iter = NewIterator(ro); + iter->SeekForPrev("goo"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("foo", iter->key().ToString()); + iter->Prev(); + + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("bar", iter->key().ToString()); + + delete iter; + db_->ReleaseSnapshot(snapshot); +} + +TEST_P(DBIteratorTest, SkipStatistics) { + Options options = CurrentOptions(); + options.statistics = rocksdb::CreateDBStatistics(); + DestroyAndReopen(options); + + int skip_count = 0; + + // write a bunch of kvs to the database. + ASSERT_OK(Put("a", "1")); + ASSERT_OK(Put("b", "1")); + ASSERT_OK(Put("c", "1")); + ASSERT_OK(Flush()); + ASSERT_OK(Put("d", "1")); + ASSERT_OK(Put("e", "1")); + ASSERT_OK(Put("f", "1")); + ASSERT_OK(Put("a", "2")); + ASSERT_OK(Put("b", "2")); + ASSERT_OK(Flush()); + ASSERT_OK(Delete("d")); + ASSERT_OK(Delete("e")); + ASSERT_OK(Delete("f")); + + Iterator* iter = NewIterator(ReadOptions()); + int count = 0; + for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { + ASSERT_OK(iter->status()); + count++; + } + ASSERT_EQ(count, 3); + delete iter; + skip_count += 8; // 3 deletes + 3 original keys + 2 lower in sequence + ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); + + iter = NewIterator(ReadOptions()); + count = 0; + for (iter->SeekToLast(); iter->Valid(); iter->Prev()) { + ASSERT_OK(iter->status()); + count++; + } + ASSERT_EQ(count, 3); + delete iter; + skip_count += 8; // Same as above, but in reverse order + ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); + + ASSERT_OK(Put("aa", "1")); + ASSERT_OK(Put("ab", "1")); + ASSERT_OK(Put("ac", "1")); + ASSERT_OK(Put("ad", "1")); + ASSERT_OK(Flush()); + ASSERT_OK(Delete("ab")); + ASSERT_OK(Delete("ac")); + ASSERT_OK(Delete("ad")); + + ReadOptions ro; + Slice prefix("b"); + ro.iterate_upper_bound = &prefix; + + iter = NewIterator(ro); + count = 0; + for(iter->Seek("aa"); iter->Valid(); iter->Next()) { + ASSERT_OK(iter->status()); + count++; + } + ASSERT_EQ(count, 1); + delete iter; + skip_count += 6; // 3 deletes + 3 original keys + ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); + + iter = NewIterator(ro); + count = 0; + for(iter->SeekToLast(); iter->Valid(); iter->Prev()) { + ASSERT_OK(iter->status()); + count++; + } + ASSERT_EQ(count, 2); + delete iter; + // 3 deletes + 3 original keys + lower sequence of "a" + skip_count += 7; + ASSERT_EQ(skip_count, TestGetTickerCount(options, NUMBER_ITER_SKIP)); +} + +TEST_P(DBIteratorTest, SeekAfterHittingManyInternalKeys) { + Options options = CurrentOptions(); + DestroyAndReopen(options); + ReadOptions ropts; + ropts.max_skippable_internal_keys = 2; + + Put("1", "val_1"); + // Add more tombstones than max_skippable_internal_keys so that Next() fails. + Delete("2"); + Delete("3"); + Delete("4"); + Delete("5"); + Put("6", "val_6"); + + std::unique_ptr iter(NewIterator(ropts)); + iter->SeekToFirst(); + + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().ToString(), "1"); + ASSERT_EQ(iter->value().ToString(), "val_1"); + + // This should fail as incomplete due to too many non-visible internal keys on + // the way to the next valid user key. + iter->Next(); + ASSERT_TRUE(!iter->Valid()); + ASSERT_TRUE(iter->status().IsIncomplete()); + + // Get the internal key at which Next() failed. + std::string prop_value; + ASSERT_OK(iter->GetProperty("rocksdb.iterator.internal-key", &prop_value)); + ASSERT_EQ("4", prop_value); + + // Create a new iterator to seek to the internal key. + std::unique_ptr iter2(NewIterator(ropts)); + iter2->Seek(prop_value); + ASSERT_TRUE(iter2->Valid()); + ASSERT_OK(iter2->status()); + + ASSERT_EQ(iter2->key().ToString(), "6"); + ASSERT_EQ(iter2->value().ToString(), "val_6"); +} + +// Reproduces a former bug where iterator would skip some records when DBIter +// re-seeks subiterator with Incomplete status. +TEST_P(DBIteratorTest, NonBlockingIterationBugRepro) { + Options options = CurrentOptions(); + BlockBasedTableOptions table_options; + // Make sure the sst file has more than one block. + table_options.flush_block_policy_factory = + std::make_shared(); + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + DestroyAndReopen(options); + + // Two records in sst file, each in its own block. + Put("b", ""); + Put("d", ""); + Flush(); + + // Create a nonblocking iterator before writing to memtable. + ReadOptions ropt; + ropt.read_tier = kBlockCacheTier; + std::unique_ptr iter(NewIterator(ropt)); + + // Overwrite a key in memtable many times to hit + // max_sequential_skip_in_iterations (which is 8 by default). + for (int i = 0; i < 20; ++i) { + Put("c", ""); + } + + // Load the second block in sst file into the block cache. + { + std::unique_ptr iter2(NewIterator(ReadOptions())); + iter2->Seek("d"); + } + + // Finally seek the nonblocking iterator. + iter->Seek("a"); + // With the bug, the status used to be OK, and the iterator used to point to + // "d". + EXPECT_TRUE(iter->status().IsIncomplete()); +} + +TEST_P(DBIteratorTest, SeekBackwardAfterOutOfUpperBound) { + Put("a", ""); + Put("b", ""); + Flush(); + + ReadOptions ropt; + Slice ub = "b"; + ropt.iterate_upper_bound = &ub; + + std::unique_ptr it(dbfull()->NewIterator(ropt)); + it->SeekForPrev("a"); + ASSERT_TRUE(it->Valid()); + ASSERT_OK(it->status()); + ASSERT_EQ("a", it->key().ToString()); + it->Next(); + ASSERT_FALSE(it->Valid()); + ASSERT_OK(it->status()); + it->SeekForPrev("a"); + ASSERT_OK(it->status()); + + ASSERT_TRUE(it->Valid()); + ASSERT_EQ("a", it->key().ToString()); +} + +INSTANTIATE_TEST_CASE_P(DBIteratorTestInstance, DBIteratorTest, + testing::Values(true, false)); + +// Tests how DBIter work with ReadCallback +class DBIteratorWithReadCallbackTest : public DBIteratorTest {}; + +TEST_F(DBIteratorWithReadCallbackTest, ReadCallback) { + class TestReadCallback : public ReadCallback { + public: + explicit TestReadCallback(SequenceNumber _max_visible_seq) + : ReadCallback(_max_visible_seq) {} + + bool IsVisibleFullCheck(SequenceNumber seq) override { + return seq <= max_visible_seq_; + } + }; + + ASSERT_OK(Put("foo", "v1")); + ASSERT_OK(Put("foo", "v2")); + ASSERT_OK(Put("foo", "v3")); + ASSERT_OK(Put("a", "va")); + ASSERT_OK(Put("z", "vz")); + SequenceNumber seq1 = db_->GetLatestSequenceNumber(); + TestReadCallback callback1(seq1); + ASSERT_OK(Put("foo", "v4")); + ASSERT_OK(Put("foo", "v5")); + ASSERT_OK(Put("bar", "v7")); + + SequenceNumber seq2 = db_->GetLatestSequenceNumber(); + auto* cfd = + reinterpret_cast(db_->DefaultColumnFamily()) + ->cfd(); + // The iterator are suppose to see data before seq1. + Iterator* iter = + dbfull()->NewIteratorImpl(ReadOptions(), cfd, seq2, &callback1); + + // Seek + // The latest value of "foo" before seq1 is "v3" + iter->Seek("foo"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("foo", iter->key()); + ASSERT_EQ("v3", iter->value()); + // "bar" is not visible to the iterator. It will move on to the next key + // "foo". + iter->Seek("bar"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("foo", iter->key()); + ASSERT_EQ("v3", iter->value()); + + // Next + // Seek to "a" + iter->Seek("a"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("va", iter->value()); + // "bar" is not visible to the iterator. It will move on to the next key + // "foo". + iter->Next(); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("foo", iter->key()); + ASSERT_EQ("v3", iter->value()); + + // Prev + // Seek to "z" + iter->Seek("z"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("vz", iter->value()); + // The previous key is "foo", which is visible to the iterator. + iter->Prev(); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("foo", iter->key()); + ASSERT_EQ("v3", iter->value()); + // "bar" is not visible to the iterator. It will move on to the next key "a". + iter->Prev(); // skipping "bar" + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("a", iter->key()); + ASSERT_EQ("va", iter->value()); + + // SeekForPrev + // The previous key is "foo", which is visible to the iterator. + iter->SeekForPrev("y"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("foo", iter->key()); + ASSERT_EQ("v3", iter->value()); + // "bar" is not visible to the iterator. It will move on to the next key "a". + iter->SeekForPrev("bar"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("a", iter->key()); + ASSERT_EQ("va", iter->value()); + + delete iter; + + // Prev beyond max_sequential_skip_in_iterations + uint64_t num_versions = + CurrentOptions().max_sequential_skip_in_iterations + 10; + for (uint64_t i = 0; i < num_versions; i++) { + ASSERT_OK(Put("bar", ToString(i))); + } + SequenceNumber seq3 = db_->GetLatestSequenceNumber(); + TestReadCallback callback2(seq3); + ASSERT_OK(Put("bar", "v8")); + SequenceNumber seq4 = db_->GetLatestSequenceNumber(); + + // The iterator is suppose to see data before seq3. + iter = dbfull()->NewIteratorImpl(ReadOptions(), cfd, seq4, &callback2); + // Seek to "z", which is visible. + iter->Seek("z"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("vz", iter->value()); + // Previous key is "foo" and the last value "v5" is visible. + iter->Prev(); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("foo", iter->key()); + ASSERT_EQ("v5", iter->value()); + // Since the number of values of "bar" is more than + // max_sequential_skip_in_iterations, Prev() will ultimately fallback to + // seek in forward direction. Here we test the fallback seek is correct. + // The last visible value should be (num_versions - 1), as "v8" is not + // visible. + iter->Prev(); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + ASSERT_EQ("bar", iter->key()); + ASSERT_EQ(ToString(num_versions - 1), iter->value()); + + delete iter; +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_log_iter_test.cc b/thirdparty/rocksdb/db/db_log_iter_test.cc index e7f94c4c42..45642bc7ae 100644 --- a/thirdparty/rocksdb/db/db_log_iter_test.cc +++ b/thirdparty/rocksdb/db/db_log_iter_test.cc @@ -23,7 +23,7 @@ class DBTestXactLogIterator : public DBTestBase { std::unique_ptr OpenTransactionLogIter( const SequenceNumber seq) { - unique_ptr iter; + std::unique_ptr iter; Status status = dbfull()->GetUpdatesSince(seq, &iter); EXPECT_OK(status); EXPECT_TRUE(iter->Valid()); @@ -249,22 +249,20 @@ TEST_F(DBTestXactLogIterator, TransactionLogIteratorBlobs) { auto res = OpenTransactionLogIter(0)->GetBatch(); struct Handler : public WriteBatch::Handler { std::string seen; - virtual Status PutCF(uint32_t cf, const Slice& key, - const Slice& value) override { + Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override { seen += "Put(" + ToString(cf) + ", " + key.ToString() + ", " + ToString(value.size()) + ")"; return Status::OK(); } - virtual Status MergeCF(uint32_t cf, const Slice& key, - const Slice& value) override { + Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override { seen += "Merge(" + ToString(cf) + ", " + key.ToString() + ", " + ToString(value.size()) + ")"; return Status::OK(); } - virtual void LogData(const Slice& blob) override { + void LogData(const Slice& blob) override { seen += "LogData(" + blob.ToString() + ")"; } - virtual Status DeleteCF(uint32_t cf, const Slice& key) override { + Status DeleteCF(uint32_t cf, const Slice& key) override { seen += "Delete(" + ToString(cf) + ", " + key.ToString() + ")"; return Status::OK(); } @@ -289,6 +287,8 @@ int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); #else + (void) argc; + (void) argv; return 0; #endif } diff --git a/thirdparty/rocksdb/db/db_memtable_test.cc b/thirdparty/rocksdb/db/db_memtable_test.cc index 63d274f6ab..294d0f581b 100644 --- a/thirdparty/rocksdb/db/db_memtable_test.cc +++ b/thirdparty/rocksdb/db/db_memtable_test.cc @@ -8,6 +8,7 @@ #include "db/db_test_util.h" #include "db/memtable.h" +#include "db/range_del_aggregator.h" #include "port/stack_trace.h" #include "rocksdb/memtablerep.h" #include "rocksdb/slice_transform.h" @@ -24,37 +25,32 @@ class MockMemTableRep : public MemTableRep { explicit MockMemTableRep(Allocator* allocator, MemTableRep* rep) : MemTableRep(allocator), rep_(rep), num_insert_with_hint_(0) {} - virtual KeyHandle Allocate(const size_t len, char** buf) override { + KeyHandle Allocate(const size_t len, char** buf) override { return rep_->Allocate(len, buf); } - virtual void Insert(KeyHandle handle) override { - return rep_->Insert(handle); - } + void Insert(KeyHandle handle) override { rep_->Insert(handle); } - virtual void InsertWithHint(KeyHandle handle, void** hint) override { + void InsertWithHint(KeyHandle handle, void** hint) override { num_insert_with_hint_++; - ASSERT_NE(nullptr, hint); + EXPECT_NE(nullptr, hint); last_hint_in_ = *hint; rep_->InsertWithHint(handle, hint); last_hint_out_ = *hint; } - virtual bool Contains(const char* key) const override { - return rep_->Contains(key); - } + bool Contains(const char* key) const override { return rep_->Contains(key); } - virtual void Get(const LookupKey& k, void* callback_args, - bool (*callback_func)(void* arg, - const char* entry)) override { + void Get(const LookupKey& k, void* callback_args, + bool (*callback_func)(void* arg, const char* entry)) override { rep_->Get(k, callback_args, callback_func); } - virtual size_t ApproximateMemoryUsage() override { + size_t ApproximateMemoryUsage() override { return rep_->ApproximateMemoryUsage(); } - virtual Iterator* GetIterator(Arena* arena) override { + Iterator* GetIterator(Arena* arena) override { return rep_->GetIterator(arena); } @@ -71,10 +67,10 @@ class MockMemTableRep : public MemTableRep { class MockMemTableRepFactory : public MemTableRepFactory { public: - virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp, - Allocator* allocator, - const SliceTransform* transform, - Logger* logger) override { + MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp, + Allocator* allocator, + const SliceTransform* transform, + Logger* logger) override { SkipListFactory factory; MemTableRep* skiplist_rep = factory.CreateMemTableRep(cmp, allocator, transform, logger); @@ -82,16 +78,16 @@ class MockMemTableRepFactory : public MemTableRepFactory { return mock_rep_; } - virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp, - Allocator* allocator, - const SliceTransform* transform, - Logger* logger, - uint32_t column_family_id) override { + MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator& cmp, + Allocator* allocator, + const SliceTransform* transform, + Logger* logger, + uint32_t column_family_id) override { last_column_family_id_ = column_family_id; return CreateMemTableRep(cmp, allocator, transform, logger); } - virtual const char* Name() const override { return "MockMemTableRepFactory"; } + const char* Name() const override { return "MockMemTableRepFactory"; } MockMemTableRep* rep() { return mock_rep_; } @@ -107,9 +103,9 @@ class MockMemTableRepFactory : public MemTableRepFactory { class TestPrefixExtractor : public SliceTransform { public: - virtual const char* Name() const override { return "TestPrefixExtractor"; } + const char* Name() const override { return "TestPrefixExtractor"; } - virtual Slice Transform(const Slice& key) const override { + Slice Transform(const Slice& key) const override { const char* p = separator(key); if (p == nullptr) { return Slice(); @@ -117,11 +113,11 @@ class TestPrefixExtractor : public SliceTransform { return Slice(key.data(), p - key.data() + 1); } - virtual bool InDomain(const Slice& key) const override { + bool InDomain(const Slice& key) const override { return separator(key) != nullptr; } - virtual bool InRange(const Slice& key) const override { return false; } + bool InRange(const Slice& /*key*/) const override { return false; } private: const char* separator(const Slice& key) const { @@ -129,6 +125,85 @@ class TestPrefixExtractor : public SliceTransform { } }; +// Test that ::Add properly returns false when inserting duplicate keys +TEST_F(DBMemTableTest, DuplicateSeq) { + SequenceNumber seq = 123; + std::string value; + Status s; + MergeContext merge_context; + Options options; + InternalKeyComparator ikey_cmp(options.comparator); + ReadRangeDelAggregator range_del_agg(&ikey_cmp, + kMaxSequenceNumber /* upper_bound */); + + // Create a MemTable + InternalKeyComparator cmp(BytewiseComparator()); + auto factory = std::make_shared(); + options.memtable_factory = factory; + ImmutableCFOptions ioptions(options); + WriteBufferManager wb(options.db_write_buffer_size); + MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, + kMaxSequenceNumber, 0 /* column_family_id */); + + // Write some keys and make sure it returns false on duplicates + bool res; + res = mem->Add(seq, kTypeValue, "key", "value2"); + ASSERT_TRUE(res); + res = mem->Add(seq, kTypeValue, "key", "value2"); + ASSERT_FALSE(res); + // Changing the type should still cause the duplicatae key + res = mem->Add(seq, kTypeMerge, "key", "value2"); + ASSERT_FALSE(res); + // Changing the seq number will make the key fresh + res = mem->Add(seq + 1, kTypeMerge, "key", "value2"); + ASSERT_TRUE(res); + // Test with different types for duplicate keys + res = mem->Add(seq, kTypeDeletion, "key", ""); + ASSERT_FALSE(res); + res = mem->Add(seq, kTypeSingleDeletion, "key", ""); + ASSERT_FALSE(res); + + // Test the duplicate keys under stress + for (int i = 0; i < 10000; i++) { + bool insert_dup = i % 10 == 1; + if (!insert_dup) { + seq++; + } + res = mem->Add(seq, kTypeValue, "foo", "value" + ToString(seq)); + if (insert_dup) { + ASSERT_FALSE(res); + } else { + ASSERT_TRUE(res); + } + } + delete mem; + + // Test with InsertWithHint + options.memtable_insert_with_hint_prefix_extractor.reset( + new TestPrefixExtractor()); // which uses _ to extract the prefix + ioptions = ImmutableCFOptions(options); + mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, + kMaxSequenceNumber, 0 /* column_family_id */); + // Insert a duplicate key with _ in it + res = mem->Add(seq, kTypeValue, "key_1", "value"); + ASSERT_TRUE(res); + res = mem->Add(seq, kTypeValue, "key_1", "value"); + ASSERT_FALSE(res); + delete mem; + + // Test when InsertConcurrently will be invoked + options.allow_concurrent_memtable_write = true; + ioptions = ImmutableCFOptions(options); + mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb, + kMaxSequenceNumber, 0 /* column_family_id */); + MemTablePostProcessInfo post_process_info; + res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info); + ASSERT_TRUE(res); + res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info); + ASSERT_FALSE(res); + delete mem; +} + TEST_F(DBMemTableTest, InsertWithHint) { Options options; options.allow_concurrent_memtable_write = false; diff --git a/thirdparty/rocksdb/db/db_merge_operator_test.cc b/thirdparty/rocksdb/db/db_merge_operator_test.cc index de28619106..2b5e4a445e 100644 --- a/thirdparty/rocksdb/db/db_merge_operator_test.cc +++ b/thirdparty/rocksdb/db/db_merge_operator_test.cc @@ -8,16 +8,128 @@ #include "db/db_test_util.h" #include "db/forward_iterator.h" #include "port/stack_trace.h" +#include "rocksdb/merge_operator.h" #include "utilities/merge_operators.h" +#include "utilities/merge_operators/string_append/stringappend2.h" namespace rocksdb { +class TestReadCallback : public ReadCallback { + public: + TestReadCallback(SnapshotChecker* snapshot_checker, + SequenceNumber snapshot_seq) + : ReadCallback(snapshot_seq), + snapshot_checker_(snapshot_checker), + snapshot_seq_(snapshot_seq) {} + + bool IsVisibleFullCheck(SequenceNumber seq) override { + return snapshot_checker_->CheckInSnapshot(seq, snapshot_seq_) == + SnapshotCheckerResult::kInSnapshot; + } + + private: + SnapshotChecker* snapshot_checker_; + SequenceNumber snapshot_seq_; +}; + // Test merge operator functionality. class DBMergeOperatorTest : public DBTestBase { public: DBMergeOperatorTest() : DBTestBase("/db_merge_operator_test") {} + + std::string GetWithReadCallback(SnapshotChecker* snapshot_checker, + const Slice& key, + const Snapshot* snapshot = nullptr) { + SequenceNumber seq = snapshot == nullptr ? db_->GetLatestSequenceNumber() + : snapshot->GetSequenceNumber(); + TestReadCallback read_callback(snapshot_checker, seq); + ReadOptions read_opt; + read_opt.snapshot = snapshot; + PinnableSlice value; + Status s = + dbfull()->GetImpl(read_opt, db_->DefaultColumnFamily(), key, &value, + nullptr /*value_found*/, &read_callback); + if (!s.ok()) { + return s.ToString(); + } + return value.ToString(); + } }; +TEST_F(DBMergeOperatorTest, LimitMergeOperands) { + class LimitedStringAppendMergeOp : public StringAppendTESTOperator { + public: + LimitedStringAppendMergeOp(int limit, char delim) + : StringAppendTESTOperator(delim), limit_(limit) {} + + const char* Name() const override { + return "DBMergeOperatorTest::LimitedStringAppendMergeOp"; + } + + bool ShouldMerge(const std::vector& operands) const override { + if (operands.size() > 0 && limit_ > 0 && operands.size() >= limit_) { + return true; + } + return false; + } + + private: + size_t limit_ = 0; + }; + + Options options; + options.create_if_missing = true; + // Use only the latest two merge operands. + options.merge_operator = + std::make_shared(2, ','); + options.env = env_; + Reopen(options); + // All K1 values are in memtable. + ASSERT_OK(Merge("k1", "a")); + ASSERT_OK(Merge("k1", "b")); + ASSERT_OK(Merge("k1", "c")); + ASSERT_OK(Merge("k1", "d")); + std::string value; + ASSERT_TRUE(db_->Get(ReadOptions(), "k1", &value).ok()); + // Make sure that only the latest two merge operands are used. If this was + // not the case the value would be "a,b,c,d". + ASSERT_EQ(value, "c,d"); + + // All K2 values are flushed to L0 into a single file. + ASSERT_OK(Merge("k2", "a")); + ASSERT_OK(Merge("k2", "b")); + ASSERT_OK(Merge("k2", "c")); + ASSERT_OK(Merge("k2", "d")); + ASSERT_OK(Flush()); + ASSERT_TRUE(db_->Get(ReadOptions(), "k2", &value).ok()); + ASSERT_EQ(value, "c,d"); + + // All K3 values are flushed and are in different files. + ASSERT_OK(Merge("k3", "ab")); + ASSERT_OK(Flush()); + ASSERT_OK(Merge("k3", "bc")); + ASSERT_OK(Flush()); + ASSERT_OK(Merge("k3", "cd")); + ASSERT_OK(Flush()); + ASSERT_OK(Merge("k3", "de")); + ASSERT_TRUE(db_->Get(ReadOptions(), "k3", &value).ok()); + ASSERT_EQ(value, "cd,de"); + + // All K4 values are in different levels + ASSERT_OK(Merge("k4", "ab")); + ASSERT_OK(Flush()); + MoveFilesToLevel(4); + ASSERT_OK(Merge("k4", "bc")); + ASSERT_OK(Flush()); + MoveFilesToLevel(3); + ASSERT_OK(Merge("k4", "cd")); + ASSERT_OK(Flush()); + MoveFilesToLevel(1); + ASSERT_OK(Merge("k4", "de")); + ASSERT_TRUE(db_->Get(ReadOptions(), "k4", &value).ok()); + ASSERT_EQ(value, "cd,de"); +} + TEST_F(DBMergeOperatorTest, MergeErrorOnRead) { Options options; options.create_if_missing = true; @@ -57,16 +169,33 @@ TEST_F(DBMergeOperatorTest, MergeErrorOnIteration) { ASSERT_OK(Merge("k1", "v1")); ASSERT_OK(Merge("k1", "corrupted")); ASSERT_OK(Put("k2", "v2")); - VerifyDBFromMap({{"k1", ""}, {"k2", "v2"}}, nullptr, false, - {{"k1", Status::Corruption()}}); + auto* iter = db_->NewIterator(ReadOptions()); + iter->Seek("k1"); + ASSERT_FALSE(iter->Valid()); + ASSERT_TRUE(iter->status().IsCorruption()); + delete iter; + iter = db_->NewIterator(ReadOptions()); + iter->Seek("k2"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + iter->Prev(); + ASSERT_FALSE(iter->Valid()); + ASSERT_TRUE(iter->status().IsCorruption()); + delete iter; VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v1"}, {"k2", "v2"}}); DestroyAndReopen(options); ASSERT_OK(Merge("k1", "v1")); ASSERT_OK(Put("k2", "v2")); ASSERT_OK(Merge("k2", "corrupted")); - VerifyDBFromMap({{"k1", "v1"}, {"k2", ""}}, nullptr, false, - {{"k2", Status::Corruption()}}); + iter = db_->NewIterator(ReadOptions()); + iter->Seek("k1"); + ASSERT_TRUE(iter->Valid()); + ASSERT_OK(iter->status()); + iter->Next(); + ASSERT_FALSE(iter->Valid()); + ASSERT_TRUE(iter->status().IsCorruption()); + delete iter; VerifyDBInternal({{"k1", "v1"}, {"k2", "corrupted"}, {"k2", "v2"}}); } @@ -158,7 +287,7 @@ TEST_P(MergeOperatorPinningTest, Randomized) { Random rnd(301); std::map true_data; - const int kTotalMerges = 10000; + const int kTotalMerges = 5000; // Every key gets ~10 operands const int kKeyRange = kTotalMerges / 10; const int kOperandSize = 20; @@ -205,8 +334,7 @@ TEST_P(MergeOperatorPinningTest, Randomized) { VerifyDBFromMap(true_data); - // Skip HashCuckoo since it does not support merge operators - } while (ChangeOptions(kSkipMergePut | kSkipHashCuckoo)); + } while (ChangeOptions(kSkipMergePut)); } class MergeOperatorHook : public MergeOperator { @@ -214,15 +342,15 @@ class MergeOperatorHook : public MergeOperator { explicit MergeOperatorHook(std::shared_ptr _merge_op) : merge_op_(_merge_op) {} - virtual bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { + bool FullMergeV2(const MergeOperationInput& merge_in, + MergeOperationOutput* merge_out) const override { before_merge_(); bool res = merge_op_->FullMergeV2(merge_in, merge_out); after_merge_(); return res; } - virtual const char* Name() const override { return merge_op_->Name(); } + const char* Name() const override { return merge_op_->Name(); } std::shared_ptr merge_op_; std::function before_merge_ = []() {}; @@ -356,8 +484,159 @@ TEST_P(MergeOperatorPinningTest, TailingIterator) { writer_thread.join(); reader_thread.join(); } + +TEST_F(DBMergeOperatorTest, TailingIteratorMemtableUnrefedBySomeoneElse) { + Options options = CurrentOptions(); + options.merge_operator = MergeOperators::CreateStringAppendOperator(); + DestroyAndReopen(options); + + // Overview of the test: + // * There are two merge operands for the same key: one in an sst file, + // another in a memtable. + // * Seek a tailing iterator to this key. + // * As part of the seek, the iterator will: + // (a) first visit the operand in the memtable and tell ForwardIterator + // to pin this operand, then + // (b) move on to the operand in the sst file, then pass both operands + // to merge operator. + // * The memtable may get flushed and unreferenced by another thread between + // (a) and (b). The test simulates it by flushing the memtable inside a + // SyncPoint callback located between (a) and (b). + // * In this case it's ForwardIterator's responsibility to keep the memtable + // pinned until (b) is complete. There used to be a bug causing + // ForwardIterator to not pin it in some circumstances. This test + // reproduces it. + + db_->Merge(WriteOptions(), "key", "sst"); + db_->Flush(FlushOptions()); // Switch to SuperVersion A + db_->Merge(WriteOptions(), "key", "memtable"); + + // Pin SuperVersion A + std::unique_ptr someone_else(db_->NewIterator(ReadOptions())); + + bool pushed_first_operand = false; + bool stepped_to_next_operand = false; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBIter::MergeValuesNewToOld:PushedFirstOperand", [&](void*) { + EXPECT_FALSE(pushed_first_operand); + pushed_first_operand = true; + db_->Flush(FlushOptions()); // Switch to SuperVersion B + }); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBIter::MergeValuesNewToOld:SteppedToNextOperand", [&](void*) { + EXPECT_FALSE(stepped_to_next_operand); + stepped_to_next_operand = true; + someone_else.reset(); // Unpin SuperVersion A + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + ReadOptions ro; + ro.tailing = true; + std::unique_ptr iter(db_->NewIterator(ro)); + iter->Seek("key"); + + ASSERT_TRUE(iter->status().ok()); + ASSERT_TRUE(iter->Valid()); + EXPECT_EQ(std::string("sst,memtable"), iter->value().ToString()); + EXPECT_TRUE(pushed_first_operand); + EXPECT_TRUE(stepped_to_next_operand); +} #endif // ROCKSDB_LITE +TEST_F(DBMergeOperatorTest, SnapshotCheckerAndReadCallback) { + Options options = CurrentOptions(); + options.merge_operator = MergeOperators::CreateStringAppendOperator(); + DestroyAndReopen(options); + + class TestSnapshotChecker : public SnapshotChecker { + public: + SnapshotCheckerResult CheckInSnapshot( + SequenceNumber seq, SequenceNumber snapshot_seq) const override { + return IsInSnapshot(seq, snapshot_seq) + ? SnapshotCheckerResult::kInSnapshot + : SnapshotCheckerResult::kNotInSnapshot; + } + + bool IsInSnapshot(SequenceNumber seq, SequenceNumber snapshot_seq) const { + switch (snapshot_seq) { + case 0: + return seq == 0; + case 1: + return seq <= 1; + case 2: + // seq = 2 not visible to snapshot with seq = 2 + return seq <= 1; + case 3: + return seq <= 3; + case 4: + // seq = 4 not visible to snpahost with seq = 4 + return seq <= 3; + default: + // seq >=4 is uncommitted + return seq <= 4; + }; + } + }; + TestSnapshotChecker* snapshot_checker = new TestSnapshotChecker(); + dbfull()->SetSnapshotChecker(snapshot_checker); + + std::string value; + ASSERT_OK(Merge("foo", "v1")); + ASSERT_EQ(1, db_->GetLatestSequenceNumber()); + ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo")); + ASSERT_OK(Merge("foo", "v2")); + ASSERT_EQ(2, db_->GetLatestSequenceNumber()); + // v2 is not visible to latest snapshot, which has seq = 2. + ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo")); + // Take a snapshot with seq = 2. + const Snapshot* snapshot1 = db_->GetSnapshot(); + ASSERT_EQ(2, snapshot1->GetSequenceNumber()); + // v2 is not visible to snapshot1, which has seq = 2 + ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); + + // Verify flush doesn't alter the result. + ASSERT_OK(Flush()); + ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); + ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo")); + + ASSERT_OK(Merge("foo", "v3")); + ASSERT_EQ(3, db_->GetLatestSequenceNumber()); + ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo")); + ASSERT_OK(Merge("foo", "v4")); + ASSERT_EQ(4, db_->GetLatestSequenceNumber()); + // v4 is not visible to latest snapshot, which has seq = 4. + ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo")); + const Snapshot* snapshot2 = db_->GetSnapshot(); + ASSERT_EQ(4, snapshot2->GetSequenceNumber()); + // v4 is not visible to snapshot2, which has seq = 4. + ASSERT_EQ("v1,v2,v3", + GetWithReadCallback(snapshot_checker, "foo", snapshot2)); + + // Verify flush doesn't alter the result. + ASSERT_OK(Flush()); + ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); + ASSERT_EQ("v1,v2,v3", + GetWithReadCallback(snapshot_checker, "foo", snapshot2)); + ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo")); + + ASSERT_OK(Merge("foo", "v5")); + ASSERT_EQ(5, db_->GetLatestSequenceNumber()); + // v5 is uncommitted + ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo")); + + // full manual compaction. + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + + // Verify compaction doesn't alter the result. + ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1)); + ASSERT_EQ("v1,v2,v3", + GetWithReadCallback(snapshot_checker, "foo", snapshot2)); + ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo")); + + db_->ReleaseSnapshot(snapshot1); + db_->ReleaseSnapshot(snapshot2); +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_options_test.cc b/thirdparty/rocksdb/db/db_options_test.cc index 243748f9fa..a7ecf12744 100644 --- a/thirdparty/rocksdb/db/db_options_test.cc +++ b/thirdparty/rocksdb/db/db_options_test.cc @@ -18,12 +18,15 @@ #include "rocksdb/cache.h" #include "rocksdb/convenience.h" #include "rocksdb/rate_limiter.h" +#include "rocksdb/stats_history.h" #include "util/random.h" #include "util/sync_point.h" #include "util/testutil.h" namespace rocksdb { +const int kMicrosInSec = 1000000; + class DBOptionsTest : public DBTestBase { public: DBOptionsTest() : DBTestBase("/db_options_test") {} @@ -117,6 +120,150 @@ TEST_F(DBOptionsTest, GetLatestCFOptions) { GetMutableCFOptionsMap(dbfull()->GetOptions(handles_[1]))); } +TEST_F(DBOptionsTest, SetBytesPerSync) { + const size_t kValueSize = 1024 * 1024; // 1MB + Options options; + options.create_if_missing = true; + options.bytes_per_sync = 1024 * 1024; + options.use_direct_reads = false; + options.write_buffer_size = 400 * kValueSize; + options.disable_auto_compactions = true; + options.compression = kNoCompression; + options.env = env_; + Reopen(options); + int counter = 0; + int low_bytes_per_sync = 0; + int i = 0; + const std::string kValue(kValueSize, 'v'); + ASSERT_EQ(options.bytes_per_sync, dbfull()->GetDBOptions().bytes_per_sync); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) { + counter++; + }); + + WriteOptions write_opts; + // should sync approximately 40MB/1MB ~= 40 times. + for (i = 0; i < 40; i++) { + Put(Key(i), kValue, write_opts); + } + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + low_bytes_per_sync = counter; + ASSERT_GT(low_bytes_per_sync, 35); + ASSERT_LT(low_bytes_per_sync, 45); + + counter = 0; + // 8388608 = 8 * 1024 * 1024 + ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "8388608"}})); + ASSERT_EQ(8388608, dbfull()->GetDBOptions().bytes_per_sync); + // should sync approximately 40MB*2/8MB ~= 10 times. + // data will be 40*2MB because of previous Puts too. + for (i = 0; i < 40; i++) { + Put(Key(i), kValue, write_opts); + } + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_GT(counter, 5); + ASSERT_LT(counter, 15); + + // Redundant assert. But leaving it here just to get the point across that + // low_bytes_per_sync > counter. + ASSERT_GT(low_bytes_per_sync, counter); +} + +TEST_F(DBOptionsTest, SetWalBytesPerSync) { + const size_t kValueSize = 1024 * 1024 * 3; + Options options; + options.create_if_missing = true; + options.wal_bytes_per_sync = 512; + options.write_buffer_size = 100 * kValueSize; + options.disable_auto_compactions = true; + options.compression = kNoCompression; + options.env = env_; + Reopen(options); + ASSERT_EQ(512, dbfull()->GetDBOptions().wal_bytes_per_sync); + int counter = 0; + int low_bytes_per_sync = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "WritableFileWriter::RangeSync:0", [&](void* /*arg*/) { + counter++; + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + const std::string kValue(kValueSize, 'v'); + int i = 0; + for (; i < 10; i++) { + Put(Key(i), kValue); + } + // Do not flush. If we flush here, SwitchWAL will reuse old WAL file since its + // empty and will not get the new wal_bytes_per_sync value. + low_bytes_per_sync = counter; + //5242880 = 1024 * 1024 * 5 + ASSERT_OK(dbfull()->SetDBOptions({{"wal_bytes_per_sync", "5242880"}})); + ASSERT_EQ(5242880, dbfull()->GetDBOptions().wal_bytes_per_sync); + counter = 0; + i = 0; + for (; i < 10; i++) { + Put(Key(i), kValue); + } + ASSERT_GT(counter, 0); + ASSERT_GT(low_bytes_per_sync, 0); + ASSERT_GT(low_bytes_per_sync, counter); +} + +TEST_F(DBOptionsTest, WritableFileMaxBufferSize) { + Options options; + options.create_if_missing = true; + options.writable_file_max_buffer_size = 1024 * 1024; + options.level0_file_num_compaction_trigger = 3; + options.max_manifest_file_size = 1; + options.env = env_; + int buffer_size = 1024 * 1024; + Reopen(options); + ASSERT_EQ(buffer_size, + dbfull()->GetDBOptions().writable_file_max_buffer_size); + + std::atomic match_cnt(0); + std::atomic unmatch_cnt(0); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "WritableFileWriter::WritableFileWriter:0", [&](void* arg) { + int value = static_cast(reinterpret_cast(arg)); + if (value == buffer_size) { + match_cnt++; + } else { + unmatch_cnt++; + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + int i = 0; + for (; i < 3; i++) { + ASSERT_OK(Put("foo", ToString(i))); + ASSERT_OK(Put("bar", ToString(i))); + Flush(); + } + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(unmatch_cnt, 0); + ASSERT_GE(match_cnt, 11); + + ASSERT_OK( + dbfull()->SetDBOptions({{"writable_file_max_buffer_size", "524288"}})); + buffer_size = 512 * 1024; + match_cnt = 0; + unmatch_cnt = 0; // SetDBOptions() will create a WriteableFileWriter + + ASSERT_EQ(buffer_size, + dbfull()->GetDBOptions().writable_file_max_buffer_size); + i = 0; + for (; i < 3; i++) { + ASSERT_OK(Put("foo", ToString(i))); + ASSERT_OK(Put("bar", ToString(i))); + Flush(); + } + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(unmatch_cnt, 0); + ASSERT_GE(match_cnt, 11); +} + TEST_F(DBOptionsTest, SetOptionsAndReopen) { Random rnd(1044); auto rand_opts = GetRandomizedMutableCFOptionsMap(&rnd); @@ -364,10 +511,290 @@ TEST_F(DBOptionsTest, SetStatsDumpPeriodSec) { for (int i = 0; i < 20; i++) { int num = rand() % 5000 + 1; - ASSERT_OK(dbfull()->SetDBOptions( - {{"stats_dump_period_sec", std::to_string(num)}})); + ASSERT_OK( + dbfull()->SetDBOptions({{"stats_dump_period_sec", ToString(num)}})); ASSERT_EQ(num, dbfull()->GetDBOptions().stats_dump_period_sec); } + Close(); +} + +TEST_F(DBOptionsTest, RunStatsDumpPeriodSec) { + Options options; + options.create_if_missing = true; + options.stats_dump_period_sec = 5; + std::unique_ptr mock_env; + mock_env.reset(new rocksdb::MockTimeEnv(env_)); + mock_env->set_current_time(0); // in seconds + options.env = mock_env.get(); + int counter = 0; + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); +#if defined(OS_MACOSX) && !defined(NDEBUG) + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { + uint64_t time_us = *reinterpret_cast(arg); + if (time_us < mock_env->RealNowMicros()) { + *reinterpret_cast(arg) = mock_env->RealNowMicros() + 1000; + } + }); +#endif // OS_MACOSX && !NDEBUG + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::DumpStats:1", [&](void* /*arg*/) { + counter++; + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + Reopen(options); + ASSERT_EQ(5, dbfull()->GetDBOptions().stats_dump_period_sec); + dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(5); }); + ASSERT_GE(counter, 1); + + // Test cacel job through SetOptions + ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "0"}})); + int old_val = counter; + for (int i = 6; i < 20; ++i) { + dbfull()->TEST_WaitForDumpStatsRun([&] { mock_env->set_current_time(i); }); + } + ASSERT_EQ(counter, old_val); + Close(); +} + +// Test persistent stats background thread scheduling and cancelling +TEST_F(DBOptionsTest, StatsPersistScheduling) { + Options options; + options.create_if_missing = true; + options.stats_persist_period_sec = 5; + std::unique_ptr mock_env; + mock_env.reset(new rocksdb::MockTimeEnv(env_)); + mock_env->set_current_time(0); // in seconds + options.env = mock_env.get(); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); +#if defined(OS_MACOSX) && !defined(NDEBUG) + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { + uint64_t time_us = *reinterpret_cast(arg); + if (time_us < mock_env->RealNowMicros()) { + *reinterpret_cast(arg) = mock_env->RealNowMicros() + 1000; + } + }); +#endif // OS_MACOSX && !NDEBUG + int counter = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + Reopen(options); + ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec); + dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); + ASSERT_GE(counter, 1); + + // Test cacel job through SetOptions + ASSERT_TRUE(dbfull()->TEST_IsPersistentStatsEnabled()); + ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}})); + ASSERT_FALSE(dbfull()->TEST_IsPersistentStatsEnabled()); + Close(); +} + +// Test enabling persistent stats for the first time +TEST_F(DBOptionsTest, PersistentStatsFreshInstall) { + Options options; + options.create_if_missing = true; + options.stats_persist_period_sec = 0; + std::unique_ptr mock_env; + mock_env.reset(new rocksdb::MockTimeEnv(env_)); + mock_env->set_current_time(0); // in seconds + options.env = mock_env.get(); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); +#if defined(OS_MACOSX) && !defined(NDEBUG) + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { + uint64_t time_us = *reinterpret_cast(arg); + if (time_us < mock_env->RealNowMicros()) { + *reinterpret_cast(arg) = mock_env->RealNowMicros() + 1000; + } + }); +#endif // OS_MACOSX && !NDEBUG + int counter = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + Reopen(options); + ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "5"}})); + ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec); + dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); + ASSERT_GE(counter, 1); + Close(); +} + +TEST_F(DBOptionsTest, SetOptionsStatsPersistPeriodSec) { + Options options; + options.create_if_missing = true; + options.stats_persist_period_sec = 5; + options.env = env_; + Reopen(options); + ASSERT_EQ(5, dbfull()->GetDBOptions().stats_persist_period_sec); + + ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "12345"}})); + ASSERT_EQ(12345, dbfull()->GetDBOptions().stats_persist_period_sec); + ASSERT_NOK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "abcde"}})); + ASSERT_EQ(12345, dbfull()->GetDBOptions().stats_persist_period_sec); +} + +TEST_F(DBOptionsTest, GetStatsHistory) { + Options options; + options.create_if_missing = true; + options.stats_persist_period_sec = 5; + options.statistics = rocksdb::CreateDBStatistics(); + std::unique_ptr mock_env; + mock_env.reset(new rocksdb::MockTimeEnv(env_)); + mock_env->set_current_time(0); // in seconds + options.env = mock_env.get(); +#if defined(OS_MACOSX) && !defined(NDEBUG) + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { + uint64_t time_us = *reinterpret_cast(arg); + if (time_us < mock_env->RealNowMicros()) { + *reinterpret_cast(arg) = mock_env->RealNowMicros() + 1000; + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); +#endif // OS_MACOSX && !NDEBUG + + CreateColumnFamilies({"pikachu"}, options); + ASSERT_OK(Put("foo", "bar")); + ReopenWithColumnFamilies({"default", "pikachu"}, options); + + int mock_time = 1; + // Wait for stats persist to finish + dbfull()->TEST_WaitForPersistStatsRun([&] { mock_env->set_current_time(5); }); + std::unique_ptr stats_iter; + db_->GetStatsHistory(0, 6 * kMicrosInSec, &stats_iter); + ASSERT_TRUE(stats_iter != nullptr); + // disabled stats snapshots + ASSERT_OK(dbfull()->SetDBOptions({{"stats_persist_period_sec", "0"}})); + size_t stats_count = 0; + for (; stats_iter->Valid(); stats_iter->Next()) { + auto stats_map = stats_iter->GetStatsMap(); + stats_count += stats_map.size(); + } + ASSERT_GT(stats_count, 0); + // Wait a bit and verify no more stats are found + for (mock_time = 6; mock_time < 20; ++mock_time) { + dbfull()->TEST_WaitForPersistStatsRun( + [&] { mock_env->set_current_time(mock_time); }); + } + db_->GetStatsHistory(0, 20 * kMicrosInSec, &stats_iter); + ASSERT_TRUE(stats_iter != nullptr); + size_t stats_count_new = 0; + for (; stats_iter->Valid(); stats_iter->Next()) { + stats_count_new += stats_iter->GetStatsMap().size(); + } + ASSERT_EQ(stats_count_new, stats_count); + Close(); +} + +TEST_F(DBOptionsTest, InMemoryStatsHistoryPurging) { + Options options; + options.create_if_missing = true; + options.statistics = rocksdb::CreateDBStatistics(); + options.stats_persist_period_sec = 1; + std::unique_ptr mock_env; + mock_env.reset(new rocksdb::MockTimeEnv(env_)); + mock_env->set_current_time(0); // in seconds + options.env = mock_env.get(); +#if defined(OS_MACOSX) && !defined(NDEBUG) + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "InstrumentedCondVar::TimedWaitInternal", [&](void* arg) { + uint64_t time_us = *reinterpret_cast(arg); + if (time_us < mock_env->RealNowMicros()) { + *reinterpret_cast(arg) = mock_env->RealNowMicros() + 1000; + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); +#endif // OS_MACOSX && !NDEBUG + + CreateColumnFamilies({"pikachu"}, options); + ASSERT_OK(Put("foo", "bar")); + ReopenWithColumnFamilies({"default", "pikachu"}, options); + // some random operation to populate statistics + ASSERT_OK(Delete("foo")); + ASSERT_OK(Put("sol", "sol")); + ASSERT_OK(Put("epic", "epic")); + ASSERT_OK(Put("ltd", "ltd")); + ASSERT_EQ("sol", Get("sol")); + ASSERT_EQ("epic", Get("epic")); + ASSERT_EQ("ltd", Get("ltd")); + Iterator* iterator = db_->NewIterator(ReadOptions()); + for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { + ASSERT_TRUE(iterator->key() == iterator->value()); + } + delete iterator; + ASSERT_OK(Flush()); + ASSERT_OK(Delete("sol")); + db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); + int mock_time = 1; + // Wait for stats persist to finish + for (; mock_time < 5; ++mock_time) { + dbfull()->TEST_WaitForPersistStatsRun( + [&] { mock_env->set_current_time(mock_time); }); + } + + // second round of ops + ASSERT_OK(Put("saigon", "saigon")); + ASSERT_OK(Put("noodle talk", "noodle talk")); + ASSERT_OK(Put("ping bistro", "ping bistro")); + iterator = db_->NewIterator(ReadOptions()); + for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) { + ASSERT_TRUE(iterator->key() == iterator->value()); + } + delete iterator; + ASSERT_OK(Flush()); + db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); + for (; mock_time < 10; ++mock_time) { + dbfull()->TEST_WaitForPersistStatsRun( + [&] { mock_env->set_current_time(mock_time); }); + } + std::unique_ptr stats_iter; + db_->GetStatsHistory(0, 10 * kMicrosInSec, &stats_iter); + ASSERT_TRUE(stats_iter != nullptr); + size_t stats_count = 0; + int slice_count = 0; + for (; stats_iter->Valid(); stats_iter->Next()) { + slice_count++; + auto stats_map = stats_iter->GetStatsMap(); + stats_count += stats_map.size(); + } + size_t stats_history_size = dbfull()->TEST_EstiamteStatsHistorySize(); + ASSERT_GE(slice_count, 9); + ASSERT_GE(stats_history_size, 12000); + // capping memory cost at 12000 bytes since one slice is around 10000~12000 + ASSERT_OK(dbfull()->SetDBOptions({{"stats_history_buffer_size", "12000"}})); + ASSERT_EQ(12000, dbfull()->GetDBOptions().stats_history_buffer_size); + // Wait for stats persist to finish + for (; mock_time < 20; ++mock_time) { + dbfull()->TEST_WaitForPersistStatsRun( + [&] { mock_env->set_current_time(mock_time); }); + } + db_->GetStatsHistory(0, 20 * kMicrosInSec, &stats_iter); + ASSERT_TRUE(stats_iter != nullptr); + size_t stats_count_reopen = 0; + slice_count = 0; + for (; stats_iter->Valid(); stats_iter->Next()) { + slice_count++; + auto stats_map = stats_iter->GetStatsMap(); + stats_count_reopen += stats_map.size(); + } + size_t stats_history_size_reopen = dbfull()->TEST_EstiamteStatsHistorySize(); + // only one slice can fit under the new stats_history_buffer_size + ASSERT_LT(slice_count, 2); + ASSERT_TRUE(stats_history_size_reopen < 12000 && + stats_history_size_reopen > 0); + ASSERT_TRUE(stats_count_reopen < stats_count && stats_count_reopen > 0); + Close(); } static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) { @@ -375,8 +802,13 @@ static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) { JobContext job_context(0); dbfull->FindObsoleteFiles(&job_context, false); ASSERT_EQ(empty, job_context.full_scan_candidate_files.empty()); - job_context.Clean(); dbfull->TEST_UnlockMutex(); + if (job_context.HaveSomethingToDelete()) { + // fulfill the contract of FindObsoleteFiles by calling PurgeObsoleteFiles + // afterwards; otherwise the test may hang on shutdown + dbfull->PurgeObsoleteFiles(job_context); + } + job_context.Clean(); } TEST_F(DBOptionsTest, DeleteObsoleteFilesPeriodChange) { @@ -441,6 +873,140 @@ TEST_F(DBOptionsTest, SanitizeDelayedWriteRate) { ASSERT_EQ(31 * 1024 * 1024, dbfull()->GetDBOptions().delayed_write_rate); } +TEST_F(DBOptionsTest, SetFIFOCompactionOptions) { + Options options; + options.compaction_style = kCompactionStyleFIFO; + options.write_buffer_size = 10 << 10; // 10KB + options.arena_block_size = 4096; + options.compression = kNoCompression; + options.create_if_missing = true; + options.compaction_options_fifo.allow_compaction = false; + env_->time_elapse_only_sleep_ = false; + options.env = env_; + + // Test dynamically changing ttl. + env_->addon_time_.store(0); + options.ttl = 1 * 60 * 60; // 1 hour + ASSERT_OK(TryReopen(options)); + + Random rnd(301); + for (int i = 0; i < 10; i++) { + // Generate and flush a file about 10KB. + for (int j = 0; j < 10; j++) { + ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980))); + } + Flush(); + } + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + ASSERT_EQ(NumTableFilesAtLevel(0), 10); + + // Add 61 seconds to the time. + env_->addon_time_.fetch_add(61); + + // No files should be compacted as ttl is set to 1 hour. + ASSERT_EQ(dbfull()->GetOptions().ttl, 3600); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + ASSERT_EQ(NumTableFilesAtLevel(0), 10); + + // Set ttl to 1 minute. So all files should get deleted. + ASSERT_OK(dbfull()->SetOptions({{"ttl", "60"}})); + ASSERT_EQ(dbfull()->GetOptions().ttl, 60); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + ASSERT_EQ(NumTableFilesAtLevel(0), 0); + + // Test dynamically changing compaction_options_fifo.max_table_files_size + env_->addon_time_.store(0); + options.compaction_options_fifo.max_table_files_size = 500 << 10; // 00KB + options.ttl = 0; + DestroyAndReopen(options); + + for (int i = 0; i < 10; i++) { + // Generate and flush a file about 10KB. + for (int j = 0; j < 10; j++) { + ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980))); + } + Flush(); + } + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + ASSERT_EQ(NumTableFilesAtLevel(0), 10); + + // No files should be compacted as max_table_files_size is set to 500 KB. + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 500 << 10); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + ASSERT_EQ(NumTableFilesAtLevel(0), 10); + + // Set max_table_files_size to 12 KB. So only 1 file should remain now. + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_fifo", "{max_table_files_size=12288;}"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 12 << 10); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + ASSERT_EQ(NumTableFilesAtLevel(0), 1); + + // Test dynamically changing compaction_options_fifo.allow_compaction + options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB + options.ttl = 0; + options.compaction_options_fifo.allow_compaction = false; + options.level0_file_num_compaction_trigger = 6; + DestroyAndReopen(options); + + for (int i = 0; i < 10; i++) { + // Generate and flush a file about 10KB. + for (int j = 0; j < 10; j++) { + ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980))); + } + Flush(); + } + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + ASSERT_EQ(NumTableFilesAtLevel(0), 10); + + // No files should be compacted as max_table_files_size is set to 500 KB and + // allow_compaction is false + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + false); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + ASSERT_EQ(NumTableFilesAtLevel(0), 10); + + // Set allow_compaction to true. So number of files should be between 1 and 5. + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_fifo", "{allow_compaction=true;}"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + true); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + ASSERT_GE(NumTableFilesAtLevel(0), 1); + ASSERT_LE(NumTableFilesAtLevel(0), 5); +} + +TEST_F(DBOptionsTest, CompactionReadaheadSizeChange) { + SpecialEnv env(env_); + Options options; + options.env = &env; + + options.compaction_readahead_size = 0; + options.new_table_reader_for_compaction_inputs = true; + options.level0_file_num_compaction_trigger = 2; + const std::string kValue(1024, 'v'); + Reopen(options); + + ASSERT_EQ(0, dbfull()->GetDBOptions().compaction_readahead_size); + ASSERT_OK(dbfull()->SetDBOptions({{"compaction_readahead_size", "256"}})); + ASSERT_EQ(256, dbfull()->GetDBOptions().compaction_readahead_size); + for (int i = 0; i < 1024; i++) { + Put(Key(i), kValue); + } + Flush(); + for (int i = 0; i < 1024 * 2; i++) { + Put(Key(i), kValue); + } + Flush(); + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(256, env_->compaction_readahead_size_); + Close(); +} #endif // ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_properties_test.cc b/thirdparty/rocksdb/db/db_properties_test.cc index 0da64b1365..1a988f5ea4 100644 --- a/thirdparty/rocksdb/db/db_properties_test.cc +++ b/thirdparty/rocksdb/db/db_properties_test.cc @@ -14,6 +14,7 @@ #include "db/db_test_util.h" #include "port/stack_trace.h" +#include "rocksdb/listener.h" #include "rocksdb/options.h" #include "rocksdb/perf_context.h" #include "rocksdb/perf_level.h" @@ -68,27 +69,27 @@ TEST_F(DBPropertiesTest, Empty) { ASSERT_OK(db_->DisableFileDeletions()); ASSERT_TRUE( dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("1", num); + ASSERT_EQ("0", num); ASSERT_OK(db_->DisableFileDeletions()); ASSERT_TRUE( dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("2", num); + ASSERT_EQ("0", num); ASSERT_OK(db_->DisableFileDeletions()); ASSERT_TRUE( dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("3", num); + ASSERT_EQ("0", num); ASSERT_OK(db_->EnableFileDeletions(false)); ASSERT_TRUE( dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("2", num); + ASSERT_EQ("0", num); ASSERT_OK(db_->EnableFileDeletions()); ASSERT_TRUE( dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num)); - ASSERT_EQ("0", num); + ASSERT_EQ("1", num); } while (ChangeOptions()); } @@ -169,6 +170,9 @@ void ResetTableProperties(TableProperties* tp) { tp->raw_value_size = 0; tp->num_data_blocks = 0; tp->num_entries = 0; + tp->num_deletions = 0; + tp->num_merge_operands = 0; + tp->num_range_deletions = 0; } void ParseTablePropertiesString(std::string tp_string, TableProperties* tp) { @@ -176,16 +180,19 @@ void ParseTablePropertiesString(std::string tp_string, TableProperties* tp) { std::replace(tp_string.begin(), tp_string.end(), ';', ' '); std::replace(tp_string.begin(), tp_string.end(), '=', ' '); ResetTableProperties(tp); - sscanf(tp_string.c_str(), - "# data blocks %" SCNu64 " # entries %" SCNu64 " raw key size %" SCNu64 + "# data blocks %" SCNu64 " # entries %" SCNu64 " # deletions %" SCNu64 + " # merge operands %" SCNu64 " # range deletions %" SCNu64 + " raw key size %" SCNu64 " raw average key size %lf " " raw value size %" SCNu64 " raw average value size %lf " - " data block size %" SCNu64 " index block size %" SCNu64 - " filter block size %" SCNu64, - &tp->num_data_blocks, &tp->num_entries, &tp->raw_key_size, + " data block size %" SCNu64 " index block size (user-key? %" SCNu64 + ", delta-value? %" SCNu64 ") %" SCNu64 " filter block size %" SCNu64, + &tp->num_data_blocks, &tp->num_entries, &tp->num_deletions, + &tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size, &dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size, + &tp->index_key_is_user_key, &tp->index_value_is_delta_encoded, &tp->index_size, &tp->filter_size); } @@ -214,30 +221,50 @@ void VerifyTableProperties(const TableProperties& base_tp, VerifySimilar(base_tp.filter_size, new_tp.filter_size, filter_size_bias); VerifySimilar(base_tp.num_data_blocks, new_tp.num_data_blocks, num_data_blocks_bias); + ASSERT_EQ(base_tp.raw_key_size, new_tp.raw_key_size); ASSERT_EQ(base_tp.raw_value_size, new_tp.raw_value_size); ASSERT_EQ(base_tp.num_entries, new_tp.num_entries); + ASSERT_EQ(base_tp.num_deletions, new_tp.num_deletions); + ASSERT_EQ(base_tp.num_range_deletions, new_tp.num_range_deletions); + + // Merge operands may become Puts, so we only have an upper bound the exact + // number of merge operands. + ASSERT_GE(base_tp.num_merge_operands, new_tp.num_merge_operands); } -void GetExpectedTableProperties(TableProperties* expected_tp, - const int kKeySize, const int kValueSize, - const int kKeysPerTable, const int kTableCount, - const int kBloomBitsPerKey, - const size_t kBlockSize) { - const int kKeyCount = kTableCount * kKeysPerTable; +void GetExpectedTableProperties( + TableProperties* expected_tp, const int kKeySize, const int kValueSize, + const int kPutsPerTable, const int kDeletionsPerTable, + const int kMergeOperandsPerTable, const int kRangeDeletionsPerTable, + const int kTableCount, const int kBloomBitsPerKey, const size_t kBlockSize, + const bool index_key_is_user_key, const bool value_delta_encoding) { + const int kKeysPerTable = + kPutsPerTable + kDeletionsPerTable + kMergeOperandsPerTable; + const int kPutCount = kTableCount * kPutsPerTable; + const int kDeletionCount = kTableCount * kDeletionsPerTable; + const int kMergeCount = kTableCount * kMergeOperandsPerTable; + const int kRangeDeletionCount = kTableCount * kRangeDeletionsPerTable; + const int kKeyCount = kPutCount + kDeletionCount + kMergeCount + kRangeDeletionCount; const int kAvgSuccessorSize = kKeySize / 5; const int kEncodingSavePerKey = kKeySize / 4; expected_tp->raw_key_size = kKeyCount * (kKeySize + 8); - expected_tp->raw_value_size = kKeyCount * kValueSize; + expected_tp->raw_value_size = + (kPutCount + kMergeCount + kRangeDeletionCount) * kValueSize; expected_tp->num_entries = kKeyCount; + expected_tp->num_deletions = kDeletionCount + kRangeDeletionCount; + expected_tp->num_merge_operands = kMergeCount; + expected_tp->num_range_deletions = kRangeDeletionCount; expected_tp->num_data_blocks = - kTableCount * - (kKeysPerTable * (kKeySize - kEncodingSavePerKey + kValueSize)) / + kTableCount * (kKeysPerTable * (kKeySize - kEncodingSavePerKey + kValueSize)) / kBlockSize; expected_tp->data_size = kTableCount * (kKeysPerTable * (kKeySize + 8 + kValueSize)); expected_tp->index_size = - expected_tp->num_data_blocks * (kAvgSuccessorSize + 8); + expected_tp->num_data_blocks * + (kAvgSuccessorSize + (index_key_is_user_key ? 0 : 8) - + // discount 1 byte as value size is not encoded in value delta encoding + (value_delta_encoding ? 1 : 0)); expected_tp->filter_size = kTableCount * (kKeysPerTable * kBloomBitsPerKey / 8); } @@ -250,8 +277,11 @@ TEST_F(DBPropertiesTest, ValidatePropertyInfo) { ASSERT_TRUE(ppt_name_and_info.first.empty() || !isdigit(ppt_name_and_info.first.back())); - ASSERT_TRUE((ppt_name_and_info.second.handle_string == nullptr) != - (ppt_name_and_info.second.handle_int == nullptr)); + int count = 0; + count += (ppt_name_and_info.second.handle_string == nullptr) ? 0 : 1; + count += (ppt_name_and_info.second.handle_int == nullptr) ? 0 : 1; + count += (ppt_name_and_info.second.handle_string_dbimpl == nullptr) ? 0 : 1; + ASSERT_TRUE(count == 1); } } @@ -286,7 +316,10 @@ TEST_F(DBPropertiesTest, ValidateSampleNumber) { TEST_F(DBPropertiesTest, AggregatedTableProperties) { for (int kTableCount = 40; kTableCount <= 100; kTableCount += 30) { - const int kKeysPerTable = 100; + const int kDeletionsPerTable = 5; + const int kMergeOperandsPerTable = 15; + const int kRangeDeletionsPerTable = 5; + const int kPutsPerTable = 100; const int kKeySize = 80; const int kValueSize = 200; const int kBloomBitsPerKey = 20; @@ -295,6 +328,8 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) { options.level0_file_num_compaction_trigger = 8; options.compression = kNoCompression; options.create_if_missing = true; + options.preserve_deletes = true; + options.merge_operator.reset(new TestPutOperator()); BlockBasedTableOptions table_options; table_options.filter_policy.reset( @@ -304,24 +339,44 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) { DestroyAndReopen(options); + // Hold open a snapshot to prevent range tombstones from being compacted + // away. + ManagedSnapshot snapshot(db_); + Random rnd(5632); for (int table = 1; table <= kTableCount; ++table) { - for (int i = 0; i < kKeysPerTable; ++i) { + for (int i = 0; i < kPutsPerTable; ++i) { db_->Put(WriteOptions(), RandomString(&rnd, kKeySize), RandomString(&rnd, kValueSize)); } + for (int i = 0; i < kDeletionsPerTable; i++) { + db_->Delete(WriteOptions(), RandomString(&rnd, kKeySize)); + } + for (int i = 0; i < kMergeOperandsPerTable; i++) { + db_->Merge(WriteOptions(), RandomString(&rnd, kKeySize), + RandomString(&rnd, kValueSize)); + } + for (int i = 0; i < kRangeDeletionsPerTable; i++) { + std::string start = RandomString(&rnd, kKeySize); + std::string end = start; + end.resize(kValueSize); + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end); + } db_->Flush(FlushOptions()); } std::string property; db_->GetProperty(DB::Properties::kAggregatedTableProperties, &property); - - TableProperties expected_tp; - GetExpectedTableProperties(&expected_tp, kKeySize, kValueSize, - kKeysPerTable, kTableCount, kBloomBitsPerKey, - table_options.block_size); - TableProperties output_tp; ParseTablePropertiesString(property, &output_tp); + bool index_key_is_user_key = output_tp.index_key_is_user_key > 0; + bool value_is_delta_encoded = output_tp.index_value_is_delta_encoded > 0; + + TableProperties expected_tp; + GetExpectedTableProperties( + &expected_tp, kKeySize, kValueSize, kPutsPerTable, kDeletionsPerTable, + kMergeOperandsPerTable, kRangeDeletionsPerTable, kTableCount, + kBloomBitsPerKey, table_options.block_size, index_key_is_user_key, + value_is_delta_encoded); VerifyTableProperties(expected_tp, output_tp); } @@ -337,7 +392,15 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) { options.target_file_size_base = 98 << 10; options.max_write_buffer_number = 2; options.statistics = rocksdb::CreateDBStatistics(); - options.max_open_files = 100; + options.max_open_files = 11; // Make sure no proloading of table readers + + // RocksDB sanitize max open files to at least 20. Modify it back. + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { + int* max_open_files = static_cast(arg); + *max_open_files = 11; + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); BlockBasedTableOptions table_options; table_options.no_block_cache = true; @@ -370,6 +433,13 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) { for (int key = 0; key < key_index; key++) { Get(Key(key)); } + + // Test for getting immutable_db_options_.statistics + ASSERT_TRUE(dbfull()->GetProperty(dbfull()->DefaultColumnFamily(), + "rocksdb.options-statistics", &prop)); + ASSERT_NE(std::string::npos, prop.find("rocksdb.block.cache.miss")); + ASSERT_EQ(std::string::npos, prop.find("rocksdb.db.f.micros")); + ASSERT_TRUE(dbfull()->GetProperty(dbfull()->DefaultColumnFamily(), "rocksdb.cf-file-histogram", &prop)); ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram")); @@ -378,12 +448,13 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) { // Reopen and issue iterating. See thee latency tracked ReopenWithColumnFamilies({"default", "pikachu"}, options); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); ASSERT_TRUE(dbfull()->GetProperty("rocksdb.cf-file-histogram", &prop)); ASSERT_EQ(std::string::npos, prop.find("** Level 0 read latency histogram")); ASSERT_EQ(std::string::npos, prop.find("** Level 1 read latency histogram")); ASSERT_EQ(std::string::npos, prop.find("** Level 2 read latency histogram")); { - unique_ptr iter(db_->NewIterator(ReadOptions())); + std::unique_ptr iter(db_->NewIterator(ReadOptions())); for (iter->Seek(Key(0)); iter->Valid(); iter->Next()) { } } @@ -436,7 +507,10 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) { TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) { const int kTableCount = 100; - const int kKeysPerTable = 10; + const int kDeletionsPerTable = 2; + const int kMergeOperandsPerTable = 2; + const int kRangeDeletionsPerTable = 2; + const int kPutsPerTable = 10; const int kKeySize = 50; const int kValueSize = 400; const int kMaxLevel = 7; @@ -452,6 +526,8 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) { options.max_bytes_for_level_multiplier = 2; // This ensures there no compaction happening when we call GetProperty(). options.disable_auto_compactions = true; + options.preserve_deletes = true; + options.merge_operator.reset(new TestPutOperator()); BlockBasedTableOptions table_options; table_options.filter_policy.reset( @@ -461,15 +537,31 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) { DestroyAndReopen(options); + // Hold open a snapshot to prevent range tombstones from being compacted away. + ManagedSnapshot snapshot(db_); + std::string level_tp_strings[kMaxLevel]; std::string tp_string; TableProperties level_tps[kMaxLevel]; TableProperties tp, sum_tp, expected_tp; for (int table = 1; table <= kTableCount; ++table) { - for (int i = 0; i < kKeysPerTable; ++i) { + for (int i = 0; i < kPutsPerTable; ++i) { db_->Put(WriteOptions(), RandomString(&rnd, kKeySize), RandomString(&rnd, kValueSize)); } + for (int i = 0; i < kDeletionsPerTable; i++) { + db_->Delete(WriteOptions(), RandomString(&rnd, kKeySize)); + } + for (int i = 0; i < kMergeOperandsPerTable; i++) { + db_->Merge(WriteOptions(), RandomString(&rnd, kKeySize), + RandomString(&rnd, kValueSize)); + } + for (int i = 0; i < kRangeDeletionsPerTable; i++) { + std::string start = RandomString(&rnd, kKeySize); + std::string end = start; + end.resize(kValueSize); + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end); + } db_->Flush(FlushOptions()); db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); ResetTableProperties(&sum_tp); @@ -485,9 +577,14 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) { sum_tp.raw_value_size += level_tps[level].raw_value_size; sum_tp.num_data_blocks += level_tps[level].num_data_blocks; sum_tp.num_entries += level_tps[level].num_entries; + sum_tp.num_deletions += level_tps[level].num_deletions; + sum_tp.num_merge_operands += level_tps[level].num_merge_operands; + sum_tp.num_range_deletions += level_tps[level].num_range_deletions; } db_->GetProperty(DB::Properties::kAggregatedTableProperties, &tp_string); ParseTablePropertiesString(tp_string, &tp); + bool index_key_is_user_key = tp.index_key_is_user_key > 0; + bool value_is_delta_encoded = tp.index_value_is_delta_encoded > 0; ASSERT_EQ(sum_tp.data_size, tp.data_size); ASSERT_EQ(sum_tp.index_size, tp.index_size); ASSERT_EQ(sum_tp.filter_size, tp.filter_size); @@ -495,13 +592,18 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) { ASSERT_EQ(sum_tp.raw_value_size, tp.raw_value_size); ASSERT_EQ(sum_tp.num_data_blocks, tp.num_data_blocks); ASSERT_EQ(sum_tp.num_entries, tp.num_entries); + ASSERT_EQ(sum_tp.num_deletions, tp.num_deletions); + ASSERT_EQ(sum_tp.num_merge_operands, tp.num_merge_operands); + ASSERT_EQ(sum_tp.num_range_deletions, tp.num_range_deletions); if (table > 3) { - GetExpectedTableProperties(&expected_tp, kKeySize, kValueSize, - kKeysPerTable, table, kBloomBitsPerKey, - table_options.block_size); + GetExpectedTableProperties( + &expected_tp, kKeySize, kValueSize, kPutsPerTable, kDeletionsPerTable, + kMergeOperandsPerTable, kRangeDeletionsPerTable, table, + kBloomBitsPerKey, table_options.block_size, index_key_is_user_key, + value_is_delta_encoded); // Gives larger bias here as index block size, filter block size, // and data block size become much harder to estimate in this test. - VerifyTableProperties(tp, expected_tp, 0.5, 0.4, 0.4, 0.25); + VerifyTableProperties(expected_tp, tp, 0.5, 0.4, 0.4, 0.25); } } } @@ -985,13 +1087,14 @@ class CountingUserTblPropCollector : public TablePropertiesCollector { return Status::OK(); } - Status AddUserKey(const Slice& user_key, const Slice& value, EntryType type, - SequenceNumber seq, uint64_t file_size) override { + 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 { + UserCollectedProperties GetReadableProperties() const override { return UserCollectedProperties{}; } @@ -1007,7 +1110,7 @@ class CountingUserTblPropCollectorFactory uint32_t expected_column_family_id) : expected_column_family_id_(expected_column_family_id), num_created_(0) {} - virtual TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollector* CreateTablePropertiesCollector( TablePropertiesCollectorFactory::Context context) override { EXPECT_EQ(expected_column_family_id_, context.column_family_id); num_created_++; @@ -1027,8 +1130,9 @@ 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 { + Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/, + EntryType type, SequenceNumber /*seq*/, + uint64_t /*file_size*/) override { if (type == kEntryDelete) { num_deletes_++; } @@ -1054,8 +1158,8 @@ class CountingDeleteTabPropCollector : public TablePropertiesCollector { class CountingDeleteTabPropCollectorFactory : public TablePropertiesCollectorFactory { public: - virtual TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context context) override { + TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollectorFactory::Context /*context*/) override { return new CountingDeleteTabPropCollector(); } const char* Name() const override { @@ -1328,7 +1432,7 @@ TEST_F(DBPropertiesTest, EstimateOldestKeyTime) { } options.compaction_style = kCompactionStyleFIFO; - options.compaction_options_fifo.ttl = 300; + options.ttl = 300; options.compaction_options_fifo.allow_compaction = false; DestroyAndReopen(options); @@ -1383,6 +1487,215 @@ TEST_F(DBPropertiesTest, EstimateOldestKeyTime) { Close(); } +TEST_F(DBPropertiesTest, SstFilesSize) { + struct TestListener : public EventListener { + void OnCompactionCompleted(DB* db, + const CompactionJobInfo& /*info*/) override { + assert(callback_triggered == false); + assert(size_before_compaction > 0); + callback_triggered = true; + uint64_t total_sst_size = 0; + uint64_t live_sst_size = 0; + bool ok = db->GetIntProperty(DB::Properties::kTotalSstFilesSize, + &total_sst_size); + ASSERT_TRUE(ok); + // total_sst_size include files before and after compaction. + ASSERT_GT(total_sst_size, size_before_compaction); + ok = + db->GetIntProperty(DB::Properties::kLiveSstFilesSize, &live_sst_size); + ASSERT_TRUE(ok); + // live_sst_size only include files after compaction. + ASSERT_GT(live_sst_size, 0); + ASSERT_LT(live_sst_size, size_before_compaction); + } + + uint64_t size_before_compaction = 0; + bool callback_triggered = false; + }; + std::shared_ptr listener = std::make_shared(); + + Options options; + options.disable_auto_compactions = true; + options.listeners.push_back(listener); + Reopen(options); + + for (int i = 0; i < 10; i++) { + ASSERT_OK(Put("key" + ToString(i), std::string(1000, 'v'))); + } + ASSERT_OK(Flush()); + for (int i = 0; i < 5; i++) { + ASSERT_OK(Delete("key" + ToString(i))); + } + ASSERT_OK(Flush()); + uint64_t sst_size; + bool ok = db_->GetIntProperty(DB::Properties::kTotalSstFilesSize, &sst_size); + ASSERT_TRUE(ok); + ASSERT_GT(sst_size, 0); + listener->size_before_compaction = sst_size; + // Compact to clean all keys and trigger listener. + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_TRUE(listener->callback_triggered); +} + +TEST_F(DBPropertiesTest, MinObsoleteSstNumberToKeep) { + class TestListener : public EventListener { + public: + void OnTableFileCreated(const TableFileCreationInfo& info) override { + if (info.reason == TableFileCreationReason::kCompaction) { + // Verify the property indicates that SSTs created by a running + // compaction cannot be deleted. + uint64_t created_file_num; + FileType created_file_type; + std::string filename = + info.file_path.substr(info.file_path.rfind('/') + 1); + ASSERT_TRUE( + ParseFileName(filename, &created_file_num, &created_file_type)); + ASSERT_EQ(kTableFile, created_file_type); + + uint64_t keep_sst_lower_bound; + ASSERT_TRUE( + db_->GetIntProperty(DB::Properties::kMinObsoleteSstNumberToKeep, + &keep_sst_lower_bound)); + + ASSERT_LE(keep_sst_lower_bound, created_file_num); + validated_ = true; + } + } + + void SetDB(DB* db) { db_ = db; } + + int GetNumCompactions() { return num_compactions_; } + + // True if we've verified the property for at least one output file + bool Validated() { return validated_; } + + private: + int num_compactions_ = 0; + bool validated_ = false; + DB* db_ = nullptr; + }; + + const int kNumL0Files = 4; + + std::shared_ptr listener = std::make_shared(); + + Options options = CurrentOptions(); + options.listeners.push_back(listener); + options.level0_file_num_compaction_trigger = kNumL0Files; + DestroyAndReopen(options); + listener->SetDB(db_); + + for (int i = 0; i < kNumL0Files; ++i) { + // Make sure they overlap in keyspace to prevent trivial move + Put("key1", "val"); + Put("key2", "val"); + Flush(); + } + dbfull()->TEST_WaitForCompact(); + ASSERT_TRUE(listener->Validated()); +} + +TEST_F(DBPropertiesTest, BlockCacheProperties) { + Options options; + uint64_t value; + + // Block cache properties are not available for tables other than + // block-based table. + options.table_factory.reset(NewPlainTableFactory()); + Reopen(options); + ASSERT_FALSE( + db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_FALSE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + ASSERT_FALSE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + + options.table_factory.reset(NewCuckooTableFactory()); + Reopen(options); + ASSERT_FALSE( + db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_FALSE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + ASSERT_FALSE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + + // Block cache properties are not available if block cache is not used. + BlockBasedTableOptions table_options; + table_options.no_block_cache = true; + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + Reopen(options); + ASSERT_FALSE( + db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_FALSE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + ASSERT_FALSE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + + // Test with empty block cache. + constexpr size_t kCapacity = 100; + auto block_cache = NewLRUCache(kCapacity, 0 /*num_shard_bits*/); + table_options.block_cache = block_cache; + table_options.no_block_cache = false; + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + Reopen(options); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_EQ(kCapacity, value); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + ASSERT_EQ(0, value); + ASSERT_TRUE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + ASSERT_EQ(0, value); + + // Insert unpinned item to the cache and check size. + constexpr size_t kSize1 = 50; + block_cache->Insert("item1", nullptr /*value*/, kSize1, nullptr /*deleter*/); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_EQ(kCapacity, value); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + ASSERT_EQ(kSize1, value); + ASSERT_TRUE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + ASSERT_EQ(0, value); + + // Insert pinned item to the cache and check size. + constexpr size_t kSize2 = 30; + Cache::Handle* item2 = nullptr; + block_cache->Insert("item2", nullptr /*value*/, kSize2, nullptr /*deleter*/, + &item2); + ASSERT_NE(nullptr, item2); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_EQ(kCapacity, value); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + ASSERT_EQ(kSize1 + kSize2, value); + ASSERT_TRUE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + ASSERT_EQ(kSize2, value); + + // Insert another pinned item to make the cache over-sized. + constexpr size_t kSize3 = 80; + Cache::Handle* item3 = nullptr; + block_cache->Insert("item3", nullptr /*value*/, kSize3, nullptr /*deleter*/, + &item3); + ASSERT_NE(nullptr, item2); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_EQ(kCapacity, value); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + // Item 1 is evicted. + ASSERT_EQ(kSize2 + kSize3, value); + ASSERT_TRUE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + ASSERT_EQ(kSize2 + kSize3, value); + + // Check size after release. + block_cache->Release(item2); + block_cache->Release(item3); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheCapacity, &value)); + ASSERT_EQ(kCapacity, value); + ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kBlockCacheUsage, &value)); + // item2 will be evicted, while item3 remain in cache after release. + ASSERT_EQ(kSize3, value); + ASSERT_TRUE( + db_->GetIntProperty(DB::Properties::kBlockCachePinnedUsage, &value)); + ASSERT_EQ(0, value); +} + #endif // ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_range_del_test.cc b/thirdparty/rocksdb/db/db_range_del_test.cc index 982cbb85ab..ebe9366df5 100644 --- a/thirdparty/rocksdb/db/db_range_del_test.cc +++ b/thirdparty/rocksdb/db/db_range_del_test.cc @@ -27,47 +27,53 @@ class DBRangeDelTest : public DBTestBase { // ROCKSDB_LITE #ifndef ROCKSDB_LITE TEST_F(DBRangeDelTest, NonBlockBasedTableNotSupported) { - if (!IsMemoryMappedAccessSupported()) { - return; + // TODO: figure out why MmapReads trips the iterator pinning assertion in + // RangeDelAggregator. Ideally it would be supported; otherwise it should at + // least be explicitly unsupported. + for (auto config : {kPlainTableAllBytesPrefix, /* kWalDirAndMmapReads */}) { + option_config_ = config; + DestroyAndReopen(CurrentOptions()); + ASSERT_TRUE(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + "dr1", "dr1") + .IsNotSupported()); } - Options opts = CurrentOptions(); - opts.table_factory.reset(new PlainTableFactory()); - opts.prefix_extractor.reset(NewNoopTransform()); - opts.allow_mmap_reads = true; - opts.max_sequential_skip_in_iterations = 999999; - Reopen(opts); - - ASSERT_TRUE( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1", "dr1") - .IsNotSupported()); } TEST_F(DBRangeDelTest, FlushOutputHasOnlyRangeTombstones) { - ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1", - "dr2")); - ASSERT_OK(db_->Flush(FlushOptions())); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); + do { + DestroyAndReopen(CurrentOptions()); + ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + "dr1", "dr2")); + ASSERT_OK(db_->Flush(FlushOptions())); + ASSERT_EQ(1, NumTableFilesAtLevel(0)); + } while (ChangeOptions(kRangeDelSkipConfigs)); } TEST_F(DBRangeDelTest, CompactionOutputHasOnlyRangeTombstone) { - Options opts = CurrentOptions(); - opts.disable_auto_compactions = true; - opts.statistics = CreateDBStatistics(); - Reopen(opts); + do { + Options opts = CurrentOptions(); + opts.disable_auto_compactions = true; + opts.statistics = CreateDBStatistics(); + DestroyAndReopen(opts); - // snapshot protects range tombstone from dropping due to becoming obsolete. - const Snapshot* snapshot = db_->GetSnapshot(); - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"); - db_->Flush(FlushOptions()); + // snapshot protects range tombstone from dropping due to becoming obsolete. + const Snapshot* snapshot = db_->GetSnapshot(); + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"); + db_->Flush(FlushOptions()); - ASSERT_EQ(1, NumTableFilesAtLevel(0)); - ASSERT_EQ(0, NumTableFilesAtLevel(1)); - dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, - true /* disallow_trivial_move */); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(1)); - ASSERT_EQ(0, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE)); - db_->ReleaseSnapshot(snapshot); + ASSERT_EQ(1, NumTableFilesAtLevel(0)); + ASSERT_EQ(0, NumTableFilesAtLevel(1)); + dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr, + true /* disallow_trivial_move */); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_EQ(1, NumTableFilesAtLevel(1)); + ASSERT_EQ(0, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE)); + db_->ReleaseSnapshot(snapshot); + // Skip cuckoo memtables, which do not support snapshots. Skip non-leveled + // compactions as the above assertions about the number of files in a level + // do not hold true. + } while (ChangeOptions(kRangeDelSkipConfigs | kSkipUniversalCompaction | + kSkipFIFOCompaction)); } TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) { @@ -185,7 +191,7 @@ TEST_F(DBRangeDelTest, SentinelsOmittedFromOutputFile) { std::vector> files; dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files); - ASSERT_GT(files[0][0].smallest_seqno, 0); + ASSERT_GT(files[0][0].fd.smallest_seqno, 0); db_->ReleaseSnapshot(snapshot); } @@ -433,8 +439,8 @@ TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) { reinterpret_cast(db_->DefaultColumnFamily()) ->cfd(), 1 /* input_level */, 2 /* output_level */, 0 /* output_path_id */, - nullptr /* begin */, nullptr /* end */, true /* exclusive */, - true /* disallow_trivial_move */)); + 0 /* max_subcompactions */, nullptr /* begin */, nullptr /* end */, + true /* exclusive */, true /* disallow_trivial_move */)); } #endif // ROCKSDB_LITE @@ -484,6 +490,30 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) { ASSERT_EQ(expected, actual); } +TEST_F(DBRangeDelTest, PutDeleteRangeMergeFlush) { + // Test the sequence of operations: (1) Put, (2) DeleteRange, (3) Merge, (4) + // Flush. The `CompactionIterator` previously had a bug where we forgot to + // check for covering range tombstones when processing the (1) Put, causing + // it to reappear after the flush. + Options opts = CurrentOptions(); + opts.merge_operator = MergeOperators::CreateUInt64AddOperator(); + Reopen(opts); + + std::string val; + PutFixed64(&val, 1); + ASSERT_OK(db_->Put(WriteOptions(), "key", val)); + ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + "key", "key_")); + ASSERT_OK(db_->Merge(WriteOptions(), "key", val)); + ASSERT_OK(db_->Flush(FlushOptions())); + + ReadOptions read_opts; + std::string expected, actual; + ASSERT_OK(db_->Get(read_opts, "key", &actual)); + PutFixed64(&expected, 1); + ASSERT_EQ(expected, actual); +} + // NumTableFilesAtLevel() is not supported in ROCKSDB_LITE #ifndef ROCKSDB_LITE TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) { @@ -496,12 +526,12 @@ TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) { Reopen(opts); db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1", - "dr1"); // obsolete after compaction + "dr10"); // obsolete after compaction db_->Put(WriteOptions(), "key", "val"); db_->Flush(FlushOptions()); const Snapshot* snapshot = db_->GetSnapshot(); db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr2", - "dr2"); // protected by snapshot + "dr20"); // protected by snapshot db_->Put(WriteOptions(), "key", "val"); db_->Flush(FlushOptions()); @@ -590,48 +620,56 @@ TEST_F(DBRangeDelTest, TableEvictedDuringScan) { } TEST_F(DBRangeDelTest, GetCoveredKeyFromMutableMemtable) { - db_->Put(WriteOptions(), "key", "val"); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); + do { + DestroyAndReopen(CurrentOptions()); + db_->Put(WriteOptions(), "key", "val"); + ASSERT_OK( + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ReadOptions read_opts; - std::string value; - ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); + ReadOptions read_opts; + std::string value; + ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); + } while (ChangeOptions(kRangeDelSkipConfigs)); } TEST_F(DBRangeDelTest, GetCoveredKeyFromImmutableMemtable) { - Options opts = CurrentOptions(); - opts.max_write_buffer_number = 3; - opts.min_write_buffer_number_to_merge = 2; - // SpecialSkipListFactory lets us specify maximum number of elements the - // memtable can hold. It switches the active memtable to immutable (flush is - // prevented by the above options) upon inserting an element that would - // overflow the memtable. - opts.memtable_factory.reset(new SpecialSkipListFactory(1)); - Reopen(opts); - - db_->Put(WriteOptions(), "key", "val"); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - db_->Put(WriteOptions(), "blah", "val"); + do { + Options opts = CurrentOptions(); + opts.max_write_buffer_number = 3; + opts.min_write_buffer_number_to_merge = 2; + // SpecialSkipListFactory lets us specify maximum number of elements the + // memtable can hold. It switches the active memtable to immutable (flush is + // prevented by the above options) upon inserting an element that would + // overflow the memtable. + opts.memtable_factory.reset(new SpecialSkipListFactory(1)); + DestroyAndReopen(opts); + + db_->Put(WriteOptions(), "key", "val"); + ASSERT_OK( + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); + db_->Put(WriteOptions(), "blah", "val"); - ReadOptions read_opts; - std::string value; - ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); + ReadOptions read_opts; + std::string value; + ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); + } while (ChangeOptions(kRangeDelSkipConfigs)); } TEST_F(DBRangeDelTest, GetCoveredKeyFromSst) { - db_->Put(WriteOptions(), "key", "val"); - // snapshot prevents key from being deleted during flush - const Snapshot* snapshot = db_->GetSnapshot(); - ASSERT_OK( - db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); - ASSERT_OK(db_->Flush(FlushOptions())); + do { + DestroyAndReopen(CurrentOptions()); + db_->Put(WriteOptions(), "key", "val"); + // snapshot prevents key from being deleted during flush + const Snapshot* snapshot = db_->GetSnapshot(); + ASSERT_OK( + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z")); + ASSERT_OK(db_->Flush(FlushOptions())); - ReadOptions read_opts; - std::string value; - ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); - db_->ReleaseSnapshot(snapshot); + ReadOptions read_opts; + std::string value; + ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound()); + db_->ReleaseSnapshot(snapshot); + } while (ChangeOptions(kRangeDelSkipConfigs)); } TEST_F(DBRangeDelTest, GetCoveredMergeOperandFromMemtable) { @@ -895,11 +933,14 @@ TEST_F(DBRangeDelTest, MemtableBloomFilter) { } TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) { - // make sure compaction treats files containing a split range deletion in the - // input level as an atomic unit. I.e., compacting any input-level file(s) - // containing a portion of the range deletion causes all other input-level - // files containing portions of that same range deletion to be included in the - // compaction. + // This test originally verified that compaction treated files containing a + // split range deletion in the input level as an atomic unit. I.e., + // compacting any input-level file(s) containing a portion of the range + // deletion causes all other input-level files containing portions of that + // same range deletion to be included in the compaction. Range deletion + // tombstones are now truncated to sstable boundaries which removed the need + // for that behavior (which could lead to excessively large + // compactions). const int kNumFilesPerLevel = 4, kValueBytes = 4 << 10; Options options = CurrentOptions(); options.compression = kNoCompression; @@ -946,22 +987,116 @@ TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) { if (i == 0) { ASSERT_OK(db_->CompactFiles( CompactionOptions(), {meta.levels[1].files[0].name}, 2 /* level */)); + ASSERT_EQ(0, NumTableFilesAtLevel(1)); } else if (i == 1) { auto begin_str = Key(0), end_str = Key(1); Slice begin = begin_str, end = end_str; ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin, &end)); + ASSERT_EQ(3, NumTableFilesAtLevel(1)); } else if (i == 2) { ASSERT_OK(db_->SetOptions(db_->DefaultColumnFamily(), {{"max_bytes_for_level_base", "10000"}})); dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(1, NumTableFilesAtLevel(1)); } - ASSERT_EQ(0, NumTableFilesAtLevel(1)); ASSERT_GT(NumTableFilesAtLevel(2), 0); db_->ReleaseSnapshot(snapshot); } } +TEST_F(DBRangeDelTest, RangeTombstoneEndKeyAsSstableUpperBound) { + // Test the handling of the range-tombstone end-key as the + // upper-bound for an sstable. + + const int kNumFilesPerLevel = 2, kValueBytes = 4 << 10; + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.level0_file_num_compaction_trigger = kNumFilesPerLevel; + options.memtable_factory.reset( + new SpecialSkipListFactory(2 /* num_entries_flush */)); + options.target_file_size_base = kValueBytes; + options.disable_auto_compactions = true; + + DestroyAndReopen(options); + + // Create an initial sstable at L2: + // [key000000#1,1, key000000#1,1] + ASSERT_OK(Put(Key(0), "")); + ASSERT_OK(db_->Flush(FlushOptions())); + MoveFilesToLevel(2); + ASSERT_EQ(1, NumTableFilesAtLevel(2)); + + // A snapshot protects the range tombstone from dropping due to + // becoming obsolete. + const Snapshot* snapshot = db_->GetSnapshot(); + db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + Key(0), Key(2 * kNumFilesPerLevel)); + + // Create 2 additional sstables in L0. Note that the first sstable + // contains the range tombstone. + // [key000000#3,1, key000004#72057594037927935,15] + // [key000001#5,1, key000002#6,1] + Random rnd(301); + std::string value = RandomString(&rnd, kValueBytes); + for (int j = 0; j < kNumFilesPerLevel; ++j) { + // Give files overlapping key-ranges to prevent a trivial move when we + // compact from L0 to L1. + ASSERT_OK(Put(Key(j), value)); + ASSERT_OK(Put(Key(2 * kNumFilesPerLevel - 1 - j), value)); + ASSERT_OK(db_->Flush(FlushOptions())); + ASSERT_EQ(j + 1, NumTableFilesAtLevel(0)); + } + // Compact the 2 L0 sstables to L1, resulting in the following LSM. There + // are 2 sstables generated in L1 due to the target_file_size_base setting. + // L1: + // [key000000#3,1, key000002#72057594037927935,15] + // [key000002#6,1, key000004#72057594037927935,15] + // L2: + // [key000000#1,1, key000000#1,1] + MoveFilesToLevel(1); + ASSERT_EQ(2, NumTableFilesAtLevel(1)); + + { + // Compact the second sstable in L1: + // L1: + // [key000000#3,1, key000002#72057594037927935,15] + // L2: + // [key000000#1,1, key000000#1,1] + // [key000002#6,1, key000004#72057594037927935,15] + // + // At the same time, verify the compaction does not cause the key at the + // endpoint (key000002#6,1) to disappear. + ASSERT_EQ(value, Get(Key(2))); + auto begin_str = Key(3); + const rocksdb::Slice begin = begin_str; + dbfull()->TEST_CompactRange(1, &begin, nullptr); + ASSERT_EQ(1, NumTableFilesAtLevel(1)); + ASSERT_EQ(2, NumTableFilesAtLevel(2)); + ASSERT_EQ(value, Get(Key(2))); + } + + { + // Compact the first sstable in L1. This should be copacetic, but + // was previously resulting in overlapping sstables in L2 due to + // mishandling of the range tombstone end-key when used as the + // largest key for an sstable. The resulting LSM structure should + // be: + // + // L2: + // [key000000#1,1, key000001#72057594037927935,15] + // [key000001#5,1, key000002#72057594037927935,15] + // [key000002#6,1, key000004#72057594037927935,15] + auto begin_str = Key(0); + const rocksdb::Slice begin = begin_str; + dbfull()->TEST_CompactRange(1, &begin, &begin); + ASSERT_EQ(0, NumTableFilesAtLevel(1)); + ASSERT_EQ(3, NumTableFilesAtLevel(2)); + } + + db_->ReleaseSnapshot(snapshot); +} + TEST_F(DBRangeDelTest, UnorderedTombstones) { // Regression test for #2752. Range delete tombstones between // different snapshot stripes are not stored in order, so the first @@ -996,6 +1131,395 @@ TEST_F(DBRangeDelTest, UnorderedTombstones) { ASSERT_TRUE(s.IsNotFound()); } +class MockMergeOperator : public MergeOperator { + // Mock non-associative operator. Non-associativity is expressed by lack of + // implementation for any `PartialMerge*` functions. + public: + bool FullMergeV2(const MergeOperationInput& merge_in, + MergeOperationOutput* merge_out) const override { + assert(merge_out != nullptr); + merge_out->new_value = merge_in.operand_list.back().ToString(); + return true; + } + + const char* Name() const override { return "MockMergeOperator"; } +}; + +TEST_F(DBRangeDelTest, KeyAtOverlappingEndpointReappears) { + // This test uses a non-associative merge operator since that is a convenient + // way to get compaction to write out files with overlapping user-keys at the + // endpoints. Note, however, overlapping endpoints can also occur with other + // value types (Put, etc.), assuming the right snapshots are present. + const int kFileBytes = 1 << 20; + const int kValueBytes = 1 << 10; + const int kNumFiles = 4; + + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.disable_auto_compactions = true; + options.merge_operator.reset(new MockMergeOperator()); + options.target_file_size_base = kFileBytes; + Reopen(options); + + // Push dummy data to L3 so that our actual test files on L0-L2 + // will not be considered "bottommost" level, otherwise compaction + // may prevent us from creating overlapping user keys + // as on the bottommost layer MergeHelper + ASSERT_OK(db_->Merge(WriteOptions(), "key", "dummy")); + ASSERT_OK(db_->Flush(FlushOptions())); + MoveFilesToLevel(3); + + Random rnd(301); + const Snapshot* snapshot = nullptr; + for (int i = 0; i < kNumFiles; ++i) { + for (int j = 0; j < kFileBytes / kValueBytes; ++j) { + auto value = RandomString(&rnd, kValueBytes); + ASSERT_OK(db_->Merge(WriteOptions(), "key", value)); + } + if (i == kNumFiles - 1) { + // Take snapshot to prevent covered merge operands from being dropped by + // compaction. + snapshot = db_->GetSnapshot(); + // The DeleteRange is the last write so all merge operands are covered. + ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + "key", "key_")); + } + ASSERT_OK(db_->Flush(FlushOptions())); + } + ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); + std::string value; + ASSERT_TRUE(db_->Get(ReadOptions(), "key", &value).IsNotFound()); + + dbfull()->TEST_CompactRange(0 /* level */, nullptr /* begin */, + nullptr /* end */, nullptr /* column_family */, + true /* disallow_trivial_move */); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + // Now we have multiple files at L1 all containing a single user key, thus + // guaranteeing overlap in the file endpoints. + ASSERT_GT(NumTableFilesAtLevel(1), 1); + + // Verify no merge operands reappeared after the compaction. + ASSERT_TRUE(db_->Get(ReadOptions(), "key", &value).IsNotFound()); + + // Compact and verify again. It's worthwhile because now the files have + // tighter endpoints, so we can verify that doesn't mess anything up. + dbfull()->TEST_CompactRange(1 /* level */, nullptr /* begin */, + nullptr /* end */, nullptr /* column_family */, + true /* disallow_trivial_move */); + ASSERT_GT(NumTableFilesAtLevel(2), 1); + ASSERT_TRUE(db_->Get(ReadOptions(), "key", &value).IsNotFound()); + + db_->ReleaseSnapshot(snapshot); +} + +TEST_F(DBRangeDelTest, UntruncatedTombstoneDoesNotDeleteNewerKey) { + // Verify a key newer than a range tombstone cannot be deleted by being + // compacted to the bottom level (and thus having its seqnum zeroed) before + // the range tombstone. This used to happen when range tombstones were + // untruncated on reads such that they extended past their file boundaries. + // + // Test summary: + // + // - L1 is bottommost. + // - A couple snapshots are strategically taken to prevent seqnums from being + // zeroed, range tombstone from being dropped, merge operands from being + // dropped, and merge operands from being combined. + // - Left half of files in L1 all have same user key, ensuring their file + // boundaries overlap. In the past this would cause range tombstones to be + // untruncated. + // - Right half of L1 files all have different keys, ensuring no overlap. + // - A range tombstone spans all L1 keys, so it is stored in every L1 file. + // - Keys in the right side of the key-range are overwritten. These are + // compacted down to L1 after releasing snapshots such that their seqnums + // will be zeroed. + // - A full range scan is performed. If the tombstone in the left L1 files + // were untruncated, it would now cover keys newer than it (but with zeroed + // seqnums) in the right L1 files. + const int kFileBytes = 1 << 20; + const int kValueBytes = 1 << 10; + const int kNumFiles = 4; + const int kMaxKey = kNumFiles* kFileBytes / kValueBytes; + const int kKeysOverwritten = 10; + + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.disable_auto_compactions = true; + options.merge_operator.reset(new MockMergeOperator()); + options.num_levels = 2; + options.target_file_size_base = kFileBytes; + Reopen(options); + + Random rnd(301); + // - snapshots[0] prevents merge operands from being combined during + // compaction. + // - snapshots[1] prevents merge operands from being dropped due to the + // covering range tombstone. + const Snapshot* snapshots[] = {nullptr, nullptr}; + for (int i = 0; i < kNumFiles; ++i) { + for (int j = 0; j < kFileBytes / kValueBytes; ++j) { + auto value = RandomString(&rnd, kValueBytes); + std::string key; + if (i < kNumFiles / 2) { + key = Key(0); + } else { + key = Key(1 + i * kFileBytes / kValueBytes + j); + } + ASSERT_OK(db_->Merge(WriteOptions(), key, value)); + } + if (i == 0) { + snapshots[0] = db_->GetSnapshot(); + } + if (i == kNumFiles - 1) { + snapshots[1] = db_->GetSnapshot(); + // The DeleteRange is the last write so all merge operands are covered. + ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + Key(0), Key(kMaxKey + 1))); + } + ASSERT_OK(db_->Flush(FlushOptions())); + } + ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); + + auto get_key_count = [this]() -> int { + auto* iter = db_->NewIterator(ReadOptions()); + iter->SeekToFirst(); + int keys_found = 0; + for (; iter->Valid(); iter->Next()) { + ++keys_found; + } + delete iter; + return keys_found; + }; + + // All keys should be covered + ASSERT_EQ(0, get_key_count()); + + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr /* begin_key */, + nullptr /* end_key */)); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + // Roughly the left half of L1 files should have overlapping boundary keys, + // while the right half should not. + ASSERT_GE(NumTableFilesAtLevel(1), kNumFiles); + + // Now overwrite a few keys that are in L1 files that definitely don't have + // overlapping boundary keys. + for (int i = kMaxKey; i > kMaxKey - kKeysOverwritten; --i) { + auto value = RandomString(&rnd, kValueBytes); + ASSERT_OK(db_->Merge(WriteOptions(), Key(i), value)); + } + ASSERT_OK(db_->Flush(FlushOptions())); + + // The overwritten keys are in L0 now, so clearly aren't covered by the range + // tombstone in L1. + ASSERT_EQ(kKeysOverwritten, get_key_count()); + + // Release snapshots so seqnums can be zeroed when L0->L1 happens. + db_->ReleaseSnapshot(snapshots[0]); + db_->ReleaseSnapshot(snapshots[1]); + + auto begin_key_storage = Key(kMaxKey - kKeysOverwritten + 1); + auto end_key_storage = Key(kMaxKey); + Slice begin_key(begin_key_storage); + Slice end_key(end_key_storage); + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin_key, &end_key)); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GE(NumTableFilesAtLevel(1), kNumFiles); + + ASSERT_EQ(kKeysOverwritten, get_key_count()); +} + +TEST_F(DBRangeDelTest, DeletedMergeOperandReappearsIterPrev) { + // Exposes a bug where we were using + // `RangeDelPositioningMode::kBackwardTraversal` while scanning merge operands + // in the forward direction. Confusingly, this case happened during + // `DBIter::Prev`. It could cause assertion failure, or reappearing keys. + const int kFileBytes = 1 << 20; + const int kValueBytes = 1 << 10; + // Need multiple keys so we can get results when calling `Prev()` after + // `SeekToLast()`. + const int kNumKeys = 3; + const int kNumFiles = 4; + + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.disable_auto_compactions = true; + options.merge_operator.reset(new MockMergeOperator()); + options.target_file_size_base = kFileBytes; + Reopen(options); + + Random rnd(301); + const Snapshot* snapshot = nullptr; + for (int i = 0; i < kNumFiles; ++i) { + for (int j = 0; j < kFileBytes / kValueBytes; ++j) { + auto value = RandomString(&rnd, kValueBytes); + ASSERT_OK(db_->Merge(WriteOptions(), Key(j % kNumKeys), value)); + if (i == 0 && j == kNumKeys) { + // Take snapshot to prevent covered merge operands from being dropped or + // merged by compaction. + snapshot = db_->GetSnapshot(); + // Do a DeleteRange near the beginning so only the oldest merge operand + // for each key is covered. This ensures the sequence of events: + // + // - `DBIter::Prev()` is called + // - After several same versions of the same user key are encountered, + // it decides to seek using `DBIter::FindValueForCurrentKeyUsingSeek`. + // - Binary searches to the newest version of the key, which is in the + // leftmost file containing the user key. + // - Scans forwards to collect all merge operands. Eventually reaches + // the rightmost file containing the oldest merge operand, which + // should be covered by the `DeleteRange`. If `RangeDelAggregator` + // were not properly using `kForwardTraversal` here, that operand + // would reappear. + ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + Key(0), Key(kNumKeys + 1))); + } + } + ASSERT_OK(db_->Flush(FlushOptions())); + } + ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(0)); + + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr /* begin_key */, + nullptr /* end_key */)); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(1), 1); + + auto* iter = db_->NewIterator(ReadOptions()); + iter->SeekToLast(); + int keys_found = 0; + for (; iter->Valid(); iter->Prev()) { + ++keys_found; + } + delete iter; + ASSERT_EQ(kNumKeys, keys_found); + + db_->ReleaseSnapshot(snapshot); +} + +TEST_F(DBRangeDelTest, SnapshotPreventsDroppedKeys) { + const int kFileBytes = 1 << 20; + + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.disable_auto_compactions = true; + options.target_file_size_base = kFileBytes; + Reopen(options); + + ASSERT_OK(Put(Key(0), "a")); + const Snapshot* snapshot = db_->GetSnapshot(); + + ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(0), + Key(10))); + + db_->Flush(FlushOptions()); + + ReadOptions read_opts; + read_opts.snapshot = snapshot; + auto* iter = db_->NewIterator(read_opts); + + iter->SeekToFirst(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(Key(0), iter->key()); + + iter->Next(); + ASSERT_FALSE(iter->Valid()); + + delete iter; + db_->ReleaseSnapshot(snapshot); +} + +TEST_F(DBRangeDelTest, RangeTombstoneWrittenToMinimalSsts) { + // Adapted from + // https://github.com/cockroachdb/cockroach/blob/de8b3ea603dd1592d9dc26443c2cc92c356fbc2f/pkg/storage/engine/rocksdb_test.go#L1267-L1398. + // Regression test for issue where range tombstone was written to more files + // than necessary when it began exactly at the begin key in the next + // compaction output file. + const int kFileBytes = 1 << 20; + const int kValueBytes = 4 << 10; + Options options = CurrentOptions(); + options.compression = kNoCompression; + options.disable_auto_compactions = true; + // Have a bit of slack in the size limits but we enforce them more strictly + // when manually flushing/compacting. + options.max_compaction_bytes = 2 * kFileBytes; + options.target_file_size_base = 2 * kFileBytes; + options.write_buffer_size = 2 * kFileBytes; + Reopen(options); + + Random rnd(301); + for (char first_char : {'a', 'b', 'c'}) { + for (int i = 0; i < kFileBytes / kValueBytes; ++i) { + std::string key(1, first_char); + key.append(Key(i)); + std::string value = RandomString(&rnd, kValueBytes); + ASSERT_OK(Put(key, value)); + } + db_->Flush(FlushOptions()); + MoveFilesToLevel(2); + } + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_EQ(3, NumTableFilesAtLevel(2)); + + // Populate the memtable lightly while spanning the whole key-space. The + // setting of `max_compaction_bytes` will cause the L0->L1 to output multiple + // files to prevent a large L1->L2 compaction later. + ASSERT_OK(Put("a", "val")); + ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), + "c" + Key(1), "d")); + // Our compaction output file cutting logic currently only considers point + // keys. So, in order for the range tombstone to have a chance at landing at + // the start of a new file, we need a point key at the range tombstone's + // start. + // TODO(ajkr): remove this `Put` after file cutting accounts for range + // tombstones (#3977). + ASSERT_OK(Put("c" + Key(1), "value")); + db_->Flush(FlushOptions()); + + // Ensure manual L0->L1 compaction cuts the outputs before the range tombstone + // and the range tombstone is only placed in the second SST. + std::string begin_key_storage("c" + Key(1)); + Slice begin_key(begin_key_storage); + std::string end_key_storage("d"); + Slice end_key(end_key_storage); + dbfull()->TEST_CompactRange(0 /* level */, &begin_key /* begin */, + &end_key /* end */, nullptr /* column_family */, + true /* disallow_trivial_move */); + ASSERT_EQ(2, NumTableFilesAtLevel(1)); + + std::vector all_metadata; + std::vector l1_metadata; + db_->GetLiveFilesMetaData(&all_metadata); + for (const auto& metadata : all_metadata) { + if (metadata.level == 1) { + l1_metadata.push_back(metadata); + } + } + std::sort(l1_metadata.begin(), l1_metadata.end(), + [&](const LiveFileMetaData& a, const LiveFileMetaData& b) { + return options.comparator->Compare(a.smallestkey, b.smallestkey) < + 0; + }); + ASSERT_EQ("a", l1_metadata[0].smallestkey); + ASSERT_EQ("a", l1_metadata[0].largestkey); + ASSERT_EQ("c" + Key(1), l1_metadata[1].smallestkey); + ASSERT_EQ("d", l1_metadata[1].largestkey); + + TablePropertiesCollection all_table_props; + ASSERT_OK(db_->GetPropertiesOfAllTables(&all_table_props)); + int64_t num_range_deletions = 0; + for (const auto& name_and_table_props : all_table_props) { + const auto& name = name_and_table_props.first; + const auto& table_props = name_and_table_props.second; + // The range tombstone should only be output to the second L1 SST. + if (name.size() >= l1_metadata[1].name.size() && + name.substr(name.size() - l1_metadata[1].name.size()).compare(l1_metadata[1].name) == 0) { + ASSERT_EQ(1, table_props->num_range_deletions); + ++num_range_deletions; + } else { + ASSERT_EQ(0, table_props->num_range_deletions); + } + } + ASSERT_EQ(1, num_range_deletions); +} + #endif // ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/db_secondary_test.cc b/thirdparty/rocksdb/db/db_secondary_test.cc new file mode 100644 index 0000000000..478a7cec97 --- /dev/null +++ b/thirdparty/rocksdb/db/db_secondary_test.cc @@ -0,0 +1,480 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#include "db/db_impl_secondary.h" +#include "db/db_test_util.h" +#include "port/stack_trace.h" +#include "util/fault_injection_test_env.h" +#include "util/sync_point.h" + +namespace rocksdb { + +#ifndef ROCKSDB_LITE +class DBSecondaryTest : public DBTestBase { + public: + DBSecondaryTest() + : DBTestBase("/db_secondary_test"), + secondary_path_(), + handles_secondary_(), + db_secondary_(nullptr) { + secondary_path_ = + test::PerThreadDBPath(env_, "/db_secondary_test_secondary"); + } + + ~DBSecondaryTest() override { + CloseSecondary(); + if (getenv("KEEP_DB") != nullptr) { + fprintf(stdout, "Secondary DB is still at %s\n", secondary_path_.c_str()); + } else { + Options options; + options.env = env_; + EXPECT_OK(DestroyDB(secondary_path_, options)); + } + } + + protected: + Status ReopenAsSecondary(const Options& options) { + return DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_); + } + + void OpenSecondary(const Options& options); + + void OpenSecondaryWithColumnFamilies( + const std::vector& column_families, const Options& options); + + void CloseSecondary() { + for (auto h : handles_secondary_) { + db_secondary_->DestroyColumnFamilyHandle(h); + } + handles_secondary_.clear(); + delete db_secondary_; + db_secondary_ = nullptr; + } + + DBImplSecondary* db_secondary_full() { + return static_cast(db_secondary_); + } + + void CheckFileTypeCounts(const std::string& dir, int expected_log, + int expected_sst, int expected_manifest) const; + + std::string secondary_path_; + std::vector handles_secondary_; + DB* db_secondary_; +}; + +void DBSecondaryTest::OpenSecondary(const Options& options) { + Status s = + DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_secondary_); + ASSERT_OK(s); +} + +void DBSecondaryTest::OpenSecondaryWithColumnFamilies( + const std::vector& column_families, const Options& options) { + std::vector cf_descs; + cf_descs.emplace_back(kDefaultColumnFamilyName, options); + for (const auto& cf_name : column_families) { + cf_descs.emplace_back(cf_name, options); + } + Status s = DB::OpenAsSecondary(options, dbname_, secondary_path_, cf_descs, + &handles_secondary_, &db_secondary_); + ASSERT_OK(s); +} + +void DBSecondaryTest::CheckFileTypeCounts(const std::string& dir, + int expected_log, int expected_sst, + int expected_manifest) const { + std::vector filenames; + env_->GetChildren(dir, &filenames); + + int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0; + for (auto file : filenames) { + uint64_t number; + FileType type; + if (ParseFileName(file, &number, &type)) { + log_cnt += (type == kLogFile); + sst_cnt += (type == kTableFile); + manifest_cnt += (type == kDescriptorFile); + } + } + ASSERT_EQ(expected_log, log_cnt); + ASSERT_EQ(expected_sst, sst_cnt); + ASSERT_EQ(expected_manifest, manifest_cnt); +} + +TEST_F(DBSecondaryTest, ReopenAsSecondary) { + Options options; + options.env = env_; + Reopen(options); + ASSERT_OK(Put("foo", "foo_value")); + ASSERT_OK(Put("bar", "bar_value")); + ASSERT_OK(dbfull()->Flush(FlushOptions())); + Close(); + + ASSERT_OK(ReopenAsSecondary(options)); + ASSERT_EQ("foo_value", Get("foo")); + ASSERT_EQ("bar_value", Get("bar")); + ReadOptions ropts; + ropts.verify_checksums = true; + auto db1 = static_cast(db_); + ASSERT_NE(nullptr, db1); + Iterator* iter = db1->NewIterator(ropts); + ASSERT_NE(nullptr, iter); + size_t count = 0; + for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { + if (0 == count) { + ASSERT_EQ("bar", iter->key().ToString()); + ASSERT_EQ("bar_value", iter->value().ToString()); + } else if (1 == count) { + ASSERT_EQ("foo", iter->key().ToString()); + ASSERT_EQ("foo_value", iter->value().ToString()); + } + ++count; + } + delete iter; + ASSERT_EQ(2, count); +} + +TEST_F(DBSecondaryTest, OpenAsSecondary) { + Options options; + options.env = env_; + options.level0_file_num_compaction_trigger = 4; + Reopen(options); + for (int i = 0; i < 3; ++i) { + ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); + ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); + ASSERT_OK(Flush()); + } + Options options1; + options1.env = env_; + options1.max_open_files = -1; + OpenSecondary(options1); + ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + + ReadOptions ropts; + ropts.verify_checksums = true; + const auto verify_db_func = [&](const std::string& foo_val, + const std::string& bar_val) { + std::string value; + ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); + ASSERT_EQ(foo_val, value); + ASSERT_OK(db_secondary_->Get(ropts, "bar", &value)); + ASSERT_EQ(bar_val, value); + Iterator* iter = db_secondary_->NewIterator(ropts); + ASSERT_NE(nullptr, iter); + iter->Seek("foo"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("foo", iter->key().ToString()); + ASSERT_EQ(foo_val, iter->value().ToString()); + iter->Seek("bar"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("bar", iter->key().ToString()); + ASSERT_EQ(bar_val, iter->value().ToString()); + size_t count = 0; + for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { + ++count; + } + ASSERT_EQ(2, count); + delete iter; + }; + + verify_db_func("foo_value2", "bar_value2"); + + ASSERT_OK(Put("foo", "new_foo_value")); + ASSERT_OK(Put("bar", "new_bar_value")); + ASSERT_OK(Flush()); + + ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); + verify_db_func("new_foo_value", "new_bar_value"); +} + +TEST_F(DBSecondaryTest, OpenWithNonExistColumnFamily) { + Options options; + options.env = env_; + CreateAndReopenWithCF({"pikachu"}, options); + + Options options1; + options1.env = env_; + options1.max_open_files = -1; + std::vector cf_descs; + cf_descs.emplace_back(kDefaultColumnFamilyName, options1); + cf_descs.emplace_back("pikachu", options1); + cf_descs.emplace_back("eevee", options1); + Status s = DB::OpenAsSecondary(options1, dbname_, secondary_path_, cf_descs, + &handles_secondary_, &db_secondary_); + ASSERT_NOK(s); +} + +TEST_F(DBSecondaryTest, OpenWithSubsetOfColumnFamilies) { + Options options; + options.env = env_; + CreateAndReopenWithCF({"pikachu"}, options); + Options options1; + options1.env = env_; + options1.max_open_files = -1; + OpenSecondary(options1); + ASSERT_EQ(0, handles_secondary_.size()); + ASSERT_NE(nullptr, db_secondary_); + + ASSERT_OK(Put(0 /*cf*/, "foo", "foo_value")); + ASSERT_OK(Put(1 /*cf*/, "foo", "foo_value")); + ASSERT_OK(Flush(0 /*cf*/)); + ASSERT_OK(Flush(1 /*cf*/)); + ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); + ReadOptions ropts; + ropts.verify_checksums = true; + std::string value; + ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); + ASSERT_EQ("foo_value", value); +} + +TEST_F(DBSecondaryTest, SwitchToNewManifestDuringOpen) { + Options options; + options.env = env_; + Reopen(options); + Close(); + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->LoadDependency( + {{"ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:0", + "VersionSet::ProcessManifestWrites:BeforeNewManifest"}, + {"VersionSet::ProcessManifestWrites:AfterNewManifest", + "ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:" + "1"}}); + SyncPoint::GetInstance()->EnableProcessing(); + + // Make sure db calls RecoverLogFiles so as to trigger a manifest write, + // which causes the db to switch to a new MANIFEST upon start. + port::Thread ro_db_thread([&]() { + Options options1; + options1.env = env_; + options1.max_open_files = -1; + OpenSecondary(options1); + CloseSecondary(); + }); + Reopen(options); + ro_db_thread.join(); +} + +TEST_F(DBSecondaryTest, MissingTableFileDuringOpen) { + Options options; + options.env = env_; + options.level0_file_num_compaction_trigger = 4; + Reopen(options); + for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) { + ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); + ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); + ASSERT_OK(dbfull()->Flush(FlushOptions())); + } + ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + Options options1; + options1.env = env_; + options1.max_open_files = -1; + OpenSecondary(options1); + ReadOptions ropts; + ropts.verify_checksums = true; + std::string value; + ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); + ASSERT_EQ("foo_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + value); + ASSERT_OK(db_secondary_->Get(ropts, "bar", &value)); + ASSERT_EQ("bar_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + value); + Iterator* iter = db_secondary_->NewIterator(ropts); + ASSERT_NE(nullptr, iter); + iter->Seek("bar"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("bar", iter->key().ToString()); + ASSERT_EQ("bar_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + iter->value().ToString()); + iter->Seek("foo"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("foo", iter->key().ToString()); + ASSERT_EQ("foo_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + iter->value().ToString()); + size_t count = 0; + for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { + ++count; + } + ASSERT_EQ(2, count); + delete iter; +} + +TEST_F(DBSecondaryTest, MissingTableFile) { + int table_files_not_exist = 0; + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->SetCallBack( + "ReactiveVersionSet::ReadAndApply:AfterLoadTableHandlers", + [&](void* arg) { + Status s = *reinterpret_cast(arg); + if (s.IsPathNotFound()) { + ++table_files_not_exist; + } else if (!s.ok()) { + assert(false); // Should not reach here + } + }); + SyncPoint::GetInstance()->EnableProcessing(); + Options options; + options.env = env_; + options.level0_file_num_compaction_trigger = 4; + Reopen(options); + + Options options1; + options1.env = env_; + options1.max_open_files = -1; + OpenSecondary(options1); + + for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) { + ASSERT_OK(Put("foo", "foo_value" + std::to_string(i))); + ASSERT_OK(Put("bar", "bar_value" + std::to_string(i))); + ASSERT_OK(dbfull()->Flush(FlushOptions())); + } + ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + + ASSERT_NE(nullptr, db_secondary_full()); + ReadOptions ropts; + ropts.verify_checksums = true; + std::string value; + ASSERT_NOK(db_secondary_->Get(ropts, "foo", &value)); + ASSERT_NOK(db_secondary_->Get(ropts, "bar", &value)); + + ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); + ASSERT_EQ(options.level0_file_num_compaction_trigger, table_files_not_exist); + ASSERT_OK(db_secondary_->Get(ropts, "foo", &value)); + ASSERT_EQ("foo_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + value); + ASSERT_OK(db_secondary_->Get(ropts, "bar", &value)); + ASSERT_EQ("bar_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + value); + Iterator* iter = db_secondary_->NewIterator(ropts); + ASSERT_NE(nullptr, iter); + iter->Seek("bar"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("bar", iter->key().ToString()); + ASSERT_EQ("bar_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + iter->value().ToString()); + iter->Seek("foo"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("foo", iter->key().ToString()); + ASSERT_EQ("foo_value" + + std::to_string(options.level0_file_num_compaction_trigger - 1), + iter->value().ToString()); + size_t count = 0; + for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { + ++count; + } + ASSERT_EQ(2, count); + delete iter; +} + +TEST_F(DBSecondaryTest, PrimaryDropColumnFamily) { + Options options; + options.env = env_; + const std::string kCfName1 = "pikachu"; + CreateAndReopenWithCF({kCfName1}, options); + + Options options1; + options1.env = env_; + options1.max_open_files = -1; + OpenSecondaryWithColumnFamilies({kCfName1}, options1); + ASSERT_EQ(2, handles_secondary_.size()); + + ASSERT_OK(Put(1 /*cf*/, "foo", "foo_val_1")); + ASSERT_OK(Flush(1 /*cf*/)); + + ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); + ReadOptions ropts; + ropts.verify_checksums = true; + std::string value; + ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value)); + ASSERT_EQ("foo_val_1", value); + + ASSERT_OK(dbfull()->DropColumnFamily(handles_[1])); + Close(); + CheckFileTypeCounts(dbname_, 1, 0, 1); + ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); + value.clear(); + ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value)); + ASSERT_EQ("foo_val_1", value); +} + +TEST_F(DBSecondaryTest, SwitchManifest) { + Options options; + options.env = env_; + options.level0_file_num_compaction_trigger = 4; + Reopen(options); + + Options options1; + options1.env = env_; + options1.max_open_files = -1; + OpenSecondary(options1); + + const int kNumFiles = options.level0_file_num_compaction_trigger - 1; + // Keep it smaller than 10 so that key0, key1, ..., key9 are sorted as 0, 1, + // ..., 9. + const int kNumKeys = 10; + // Create two sst + for (int i = 0; i != kNumFiles; ++i) { + for (int j = 0; j != kNumKeys; ++j) { + ASSERT_OK(Put("key" + std::to_string(j), "value_" + std::to_string(i))); + } + ASSERT_OK(Flush()); + } + + ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); + const auto& range_scan_db = [&]() { + ReadOptions tmp_ropts; + tmp_ropts.total_order_seek = true; + tmp_ropts.verify_checksums = true; + std::unique_ptr iter(db_secondary_->NewIterator(tmp_ropts)); + int cnt = 0; + for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++cnt) { + ASSERT_EQ("key" + std::to_string(cnt), iter->key().ToString()); + ASSERT_EQ("value_" + std::to_string(kNumFiles - 1), + iter->value().ToString()); + } + }; + + range_scan_db(); + + // While secondary instance still keeps old MANIFEST open, we close primary, + // restart primary, performs full compaction, close again, restart again so + // that next time secondary tries to catch up with primary, the secondary + // will skip the MANIFEST in middle. + Reopen(options); + ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_OK(dbfull()->TEST_WaitForCompact()); + + Reopen(options); + ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}})); + + ASSERT_OK(db_secondary_->TryCatchUpWithPrimary()); + range_scan_db(); +} +#endif //! ROCKSDB_LITE + +} // namespace rocksdb + +int main(int argc, char** argv) { + rocksdb::port::InstallStackTraceHandler(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/thirdparty/rocksdb/db/db_sst_test.cc b/thirdparty/rocksdb/db/db_sst_test.cc index e01754c44e..dcd5847eb2 100644 --- a/thirdparty/rocksdb/db/db_sst_test.cc +++ b/thirdparty/rocksdb/db/db_sst_test.cc @@ -20,6 +20,37 @@ class DBSSTTest : public DBTestBase { DBSSTTest() : DBTestBase("/db_sst_test") {} }; +#ifndef ROCKSDB_LITE +// A class which remembers the name of each flushed file. +class FlushedFileCollector : public EventListener { + public: + FlushedFileCollector() {} + ~FlushedFileCollector() override {} + + void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override { + std::lock_guard lock(mutex_); + flushed_files_.push_back(info.file_path); + } + + std::vector GetFlushedFiles() { + std::lock_guard lock(mutex_); + std::vector result; + for (auto fname : flushed_files_) { + result.push_back(fname); + } + return result; + } + void ClearFlushedFiles() { + std::lock_guard lock(mutex_); + flushed_files_.clear(); + } + + private: + std::vector flushed_files_; + std::mutex mutex_; +}; +#endif // ROCKSDB_LITE + TEST_F(DBSSTTest, DontDeletePendingOutputs) { Options options; options.env = env_; @@ -72,7 +103,7 @@ TEST_F(DBSSTTest, SSTsWithLdbSuffixHandling) { ASSERT_GT(num_files, 0); std::vector filenames; - GetSstFiles(dbname_, &filenames); + GetSstFiles(env_, dbname_, &filenames); int num_ldb_files = 0; for (size_t i = 0; i < filenames.size(); ++i) { if (i & 1) { @@ -231,11 +262,11 @@ TEST_F(DBSSTTest, DBWithSstFileManager) { int files_deleted = 0; int files_moved = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnAddFile", [&](void* arg) { files_added++; }); + "SstFileManagerImpl::OnAddFile", [&](void* /*arg*/) { files_added++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnDeleteFile", [&](void* arg) { files_deleted++; }); + "SstFileManagerImpl::OnDeleteFile", [&](void* /*arg*/) { files_deleted++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "SstFileManagerImpl::OnMoveFile", [&](void* arg) { files_moved++; }); + "SstFileManagerImpl::OnMoveFile", [&](void* /*arg*/) { files_moved++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Options options = CurrentOptions(); @@ -325,26 +356,35 @@ TEST_F(DBSSTTest, RateLimitedDelete) { env_->time_elapse_only_sleep_ = true; Options options = CurrentOptions(); options.disable_auto_compactions = true; + // Need to disable stats dumping and persisting which also use + // RepeatableThread, one of whose member variables is of type + // InstrumentedCondVar. The callback for + // InstrumentedCondVar::TimedWaitInternal can be triggered by stats dumping + // and persisting threads and cause time_spent_deleting measurement to become + // incorrect. + options.stats_dump_period_sec = 0; + options.stats_persist_period_sec = 0; options.env = env_; - std::string trash_dir = test::TmpDir(env_) + "/trash"; int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec Status s; options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, trash_dir, 0, false, &s)); + NewSstFileManager(env_, nullptr, "", 0, false, &s, 0)); ASSERT_OK(s); options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec); auto sfm = static_cast(options.sst_file_manager.get()); - sfm->delete_scheduler()->TEST_SetMaxTrashDBRatio(1.1); + sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1); + WriteOptions wo; + wo.disableWAL = true; ASSERT_OK(TryReopen(options)); // Create 4 files in L0 for (char v = 'a'; v <= 'd'; v++) { - ASSERT_OK(Put("Key2", DummyString(1024, v))); - ASSERT_OK(Put("Key3", DummyString(1024, v))); - ASSERT_OK(Put("Key4", DummyString(1024, v))); - ASSERT_OK(Put("Key1", DummyString(1024, v))); - ASSERT_OK(Put("Key4", DummyString(1024, v))); + ASSERT_OK(Put("Key2", DummyString(1024, v), wo)); + ASSERT_OK(Put("Key3", DummyString(1024, v), wo)); + ASSERT_OK(Put("Key4", DummyString(1024, v), wo)); + ASSERT_OK(Put("Key1", DummyString(1024, v), wo)); + ASSERT_OK(Put("Key4", DummyString(1024, v), wo)); ASSERT_OK(Flush()); } // We created 4 sst files in L0 @@ -355,6 +395,7 @@ TEST_F(DBSSTTest, RateLimitedDelete) { // Compaction will move the 4 files in L0 to trash and create 1 L1 file ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); ASSERT_EQ("0,1", FilesPerLevel(0)); uint64_t delete_start_time = env_->NowMicros(); @@ -377,16 +418,93 @@ TEST_F(DBSSTTest, RateLimitedDelete) { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } +TEST_F(DBSSTTest, RateLimitedWALDelete) { + Destroy(last_options_); + + std::vector penalties; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DeleteScheduler::BackgroundEmptyTrash:Wait", + [&](void* arg) { penalties.push_back(*(static_cast(arg))); }); + + env_->no_slowdown_ = true; + env_->time_elapse_only_sleep_ = true; + Options options = CurrentOptions(); + options.disable_auto_compactions = true; + options.env = env_; + + int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec + Status s; + options.sst_file_manager.reset( + NewSstFileManager(env_, nullptr, "", 0, false, &s, 0)); + ASSERT_OK(s); + options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec); + auto sfm = static_cast(options.sst_file_manager.get()); + sfm->delete_scheduler()->SetMaxTrashDBRatio(2.1); + + ASSERT_OK(TryReopen(options)); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + // Create 4 files in L0 + for (char v = 'a'; v <= 'd'; v++) { + ASSERT_OK(Put("Key2", DummyString(1024, v))); + ASSERT_OK(Put("Key3", DummyString(1024, v))); + ASSERT_OK(Put("Key4", DummyString(1024, v))); + ASSERT_OK(Put("Key1", DummyString(1024, v))); + ASSERT_OK(Put("Key4", DummyString(1024, v))); + ASSERT_OK(Flush()); + } + // We created 4 sst files in L0 + ASSERT_EQ("4", FilesPerLevel(0)); + + // Compaction will move the 4 files in L0 to trash and create 1 L1 file + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); + ASSERT_EQ("0,1", FilesPerLevel(0)); + + sfm->WaitForEmptyTrash(); + ASSERT_EQ(penalties.size(), 8); + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBSSTTest, OpenDBWithExistingTrash) { + Options options = CurrentOptions(); + + options.sst_file_manager.reset( + NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */)); + auto sfm = static_cast(options.sst_file_manager.get()); + + Destroy(last_options_); + + // Add some trash files to the db directory so the DB can clean them up + env_->CreateDirIfMissing(dbname_); + ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "001.sst.trash")); + ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "002.sst.trash")); + ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash")); + + // Reopen the DB and verify that it deletes existing trash files + ASSERT_OK(TryReopen(options)); + sfm->WaitForEmptyTrash(); + ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash")); + ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash")); + ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash")); +} + + // Create a DB with 2 db_paths, and generate multiple files in the 2 // db_paths using CompactRangeOptions, make sure that files that were // deleted from first db_path were deleted using DeleteScheduler and // files in the second path were not. TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) { - int bg_delete_file = 0; + std::atomic bg_delete_file(0); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* arg) { bg_delete_file++; }); - rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + [&](void* /*arg*/) { bg_delete_file++; }); + // The deletion scheduler sometimes skips marking file as trash according to + // a heuristic. In that case the deletion will go through the below SyncPoint. + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DeleteScheduler::DeleteFile", + [&](void* /*arg*/) { bg_delete_file++; }); Options options = CurrentOptions(); options.disable_auto_compactions = true; @@ -394,20 +512,24 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) { options.db_paths.emplace_back(dbname_ + "_2", 1024 * 100); options.env = env_; - std::string trash_dir = test::TmpDir(env_) + "/trash"; int64_t rate_bytes_per_sec = 1024 * 1024; // 1 Mb / Sec Status s; - options.sst_file_manager.reset(NewSstFileManager( - env_, nullptr, trash_dir, rate_bytes_per_sec, false, &s)); + options.sst_file_manager.reset( + NewSstFileManager(env_, nullptr, "", rate_bytes_per_sec, false, &s, + /* max_trash_db_ratio= */ 1.1)); + ASSERT_OK(s); auto sfm = static_cast(options.sst_file_manager.get()); - sfm->delete_scheduler()->TEST_SetMaxTrashDBRatio(1.1); DestroyAndReopen(options); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + WriteOptions wo; + wo.disableWAL = true; // Create 4 files in L0 for (int i = 0; i < 4; i++) { - ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A'))); + ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A'), wo)); ASSERT_OK(Flush()); } // We created 4 sst files in L0 @@ -423,7 +545,7 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) { // Create 4 files in L0 for (int i = 4; i < 8; i++) { - ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'B'))); + ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'B'), wo)); ASSERT_OK(Flush()); } ASSERT_EQ("4,1", FilesPerLevel(0)); @@ -438,13 +560,15 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) { sfm->WaitForEmptyTrash(); ASSERT_EQ(bg_delete_file, 8); + // Compaction will delete both files and regenerate a file in L1 in second + // db path. The deleted files should still be cleaned up via delete scheduler. compact_options.bottommost_level_compaction = BottommostLevelCompaction::kForce; ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr)); ASSERT_EQ("0,1", FilesPerLevel(0)); sfm->WaitForEmptyTrash(); - ASSERT_EQ(bg_delete_file, 8); + ASSERT_EQ(bg_delete_file, 10); rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } @@ -453,16 +577,15 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) { int bg_delete_file = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DeleteScheduler::DeleteTrashFile:DeleteFile", - [&](void* arg) { bg_delete_file++; }); + [&](void* /*arg*/) { bg_delete_file++; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Status s; Options options = CurrentOptions(); options.disable_auto_compactions = true; options.env = env_; - std::string trash_dir = test::TmpDir(env_) + "/trash"; options.sst_file_manager.reset( - NewSstFileManager(env_, nullptr, trash_dir, 0, false, &s)); + NewSstFileManager(env_, nullptr, "", 0, false, &s, 0)); ASSERT_OK(s); DestroyAndReopen(options); @@ -477,14 +600,27 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) { // Close DB and destroy it using DeleteScheduler Close(); + int num_sst_files = 0; + int num_wal_files = 0; + std::vector db_files; + env_->GetChildren(dbname_, &db_files); + for (std::string f : db_files) { + if (f.substr(f.find_last_of(".") + 1) == "sst") { + num_sst_files++; + } else if (f.substr(f.find_last_of(".") + 1) == "log") { + num_wal_files++; + } + } + ASSERT_GT(num_sst_files, 0); + ASSERT_GT(num_wal_files, 0); + auto sfm = static_cast(options.sst_file_manager.get()); sfm->SetDeleteRateBytesPerSecond(1024 * 1024); - sfm->delete_scheduler()->TEST_SetMaxTrashDBRatio(1.1); + sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1); ASSERT_OK(DestroyDB(dbname_, options)); sfm->WaitForEmptyTrash(); - // We have deleted the 4 sst files in the delete_scheduler - ASSERT_EQ(bg_delete_file, 4); + ASSERT_EQ(bg_delete_file, num_sst_files + num_wal_files); } TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) { @@ -516,47 +652,163 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) { ASSERT_NOK(Flush()); } +TEST_F(DBSSTTest, CancellingCompactionsWorks) { + std::shared_ptr sst_file_manager(NewSstFileManager(env_)); + auto sfm = static_cast(sst_file_manager.get()); + + Options options = CurrentOptions(); + options.sst_file_manager = sst_file_manager; + options.level0_file_num_compaction_trigger = 2; + options.statistics = CreateDBStatistics(); + DestroyAndReopen(options); + + int completed_compactions = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::BackgroundCompaction():CancelledCompaction", [&](void* /*arg*/) { + sfm->SetMaxAllowedSpaceUsage(0); + ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); + }); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun", + [&](void* /*arg*/) { completed_compactions++; }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Random rnd(301); + + // Generate a file containing 10 keys. + for (int i = 0; i < 10; i++) { + ASSERT_OK(Put(Key(i), RandomString(&rnd, 50))); + } + ASSERT_OK(Flush()); + uint64_t total_file_size = 0; + auto files_in_db = GetAllSSTFiles(&total_file_size); + // Set the maximum allowed space usage to the current total size + sfm->SetMaxAllowedSpaceUsage(2 * total_file_size + 1); + + // Generate another file to trigger compaction. + for (int i = 0; i < 10; i++) { + ASSERT_OK(Put(Key(i), RandomString(&rnd, 50))); + } + ASSERT_OK(Flush()); + dbfull()->TEST_WaitForCompact(true); + + // Because we set a callback in CancelledCompaction, we actually + // let the compaction run + ASSERT_GT(completed_compactions, 0); + ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); + // Make sure the stat is bumped + ASSERT_GT(dbfull()->immutable_db_options().statistics.get()->getTickerCount(COMPACTION_CANCELLED), 0); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBSSTTest, CancellingManualCompactionsWorks) { + std::shared_ptr sst_file_manager(NewSstFileManager(env_)); + auto sfm = static_cast(sst_file_manager.get()); + + Options options = CurrentOptions(); + options.sst_file_manager = sst_file_manager; + options.statistics = CreateDBStatistics(); + + FlushedFileCollector* collector = new FlushedFileCollector(); + options.listeners.emplace_back(collector); + + DestroyAndReopen(options); + + Random rnd(301); + + // Generate a file containing 10 keys. + for (int i = 0; i < 10; i++) { + ASSERT_OK(Put(Key(i), RandomString(&rnd, 50))); + } + ASSERT_OK(Flush()); + uint64_t total_file_size = 0; + auto files_in_db = GetAllSSTFiles(&total_file_size); + // Set the maximum allowed space usage to the current total size + sfm->SetMaxAllowedSpaceUsage(2 * total_file_size + 1); + + // Generate another file to trigger compaction. + for (int i = 0; i < 10; i++) { + ASSERT_OK(Put(Key(i), RandomString(&rnd, 50))); + } + ASSERT_OK(Flush()); + + // OK, now trigger a manual compaction + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + + // Wait for manual compaction to get scheduled and finish + dbfull()->TEST_WaitForCompact(true); + + ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); + // Make sure the stat is bumped + ASSERT_EQ(dbfull()->immutable_db_options().statistics.get()->getTickerCount( + COMPACTION_CANCELLED), + 1); + + // Now make sure CompactFiles also gets cancelled + auto l0_files = collector->GetFlushedFiles(); + dbfull()->CompactFiles(rocksdb::CompactionOptions(), l0_files, 0); + + // Wait for manual compaction to get scheduled and finish + dbfull()->TEST_WaitForCompact(true); + + ASSERT_EQ(dbfull()->immutable_db_options().statistics.get()->getTickerCount( + COMPACTION_CANCELLED), + 2); + ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); + + // Now let the flush through and make sure GetCompactionsReservedSize + // returns to normal + sfm->SetMaxAllowedSpaceUsage(0); + int completed_compactions = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "CompactFilesImpl:End", [&](void* /*arg*/) { completed_compactions++; }); + + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + dbfull()->CompactFiles(rocksdb::CompactionOptions(), l0_files, 0); + dbfull()->TEST_WaitForCompact(true); + + ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0); + ASSERT_GT(completed_compactions, 0); + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) { // This test will set a maximum allowed space for the DB, then it will // keep filling the DB until the limit is reached and bg_error_ is set. // When bg_error_ is set we will verify that the DB size is greater // than the limit. - std::vector max_space_limits_mbs = {1, 2, 4, 8, 10}; - decltype(max_space_limits_mbs)::value_type limit_mb_cb; - bool bg_error_set = false; - uint64_t total_sst_files_size = 0; + std::vector max_space_limits_mbs = {1, 10}; + std::atomic bg_error_set(false); - std::atomic estimate_multiplier(1); - int reached_max_space_on_flush = 0; - int reached_max_space_on_compaction = 0; + std::atomic reached_max_space_on_flush(0); + std::atomic reached_max_space_on_compaction(0); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached", [&](void* arg) { Status* bg_error = static_cast(arg); bg_error_set = true; - GetAllSSTFiles(&total_sst_files_size); reached_max_space_on_flush++; - // low limit for size calculated using sst files - ASSERT_GE(total_sst_files_size, limit_mb_cb * 1024 * 1024); // clear error to ensure compaction callback is called *bg_error = Status::OK(); - estimate_multiplier++; // used in the main loop assert + }); + + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::BackgroundCompaction():CancelledCompaction", [&](void* arg) { + bool* enough_room = static_cast(arg); + *enough_room = true; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "CompactionJob::FinishCompactionOutputFile:MaxAllowedSpaceReached", - [&](void* arg) { + [&](void* /*arg*/) { bg_error_set = true; - GetAllSSTFiles(&total_sst_files_size); reached_max_space_on_compaction++; }); for (auto limit_mb : max_space_limits_mbs) { bg_error_set = false; - total_sst_files_size = 0; - estimate_multiplier = 1; - limit_mb_cb = limit_mb; rocksdb::SyncPoint::GetInstance()->ClearTrace(); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); std::shared_ptr sst_file_manager(NewSstFileManager(env_)); @@ -570,21 +822,17 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) { sfm->SetMaxAllowedSpaceUsage(limit_mb * 1024 * 1024); - int keys_written = 0; - uint64_t estimated_db_size = 0; + // It is easy to detect if the test is stuck in a loop. No need for + // complex termination logic. while (true) { auto s = Put(RandomString(&rnd, 10), RandomString(&rnd, 50)); if (!s.ok()) { break; } - keys_written++; - // Check the estimated db size vs the db limit just to make sure we - // dont run into an infinite loop - estimated_db_size = keys_written * 60; // ~60 bytes per key - ASSERT_LT(estimated_db_size, - estimate_multiplier * limit_mb * 1024 * 1024 * 2); } ASSERT_TRUE(bg_error_set); + uint64_t total_sst_files_size = 0; + GetAllSSTFiles(&total_sst_files_size); ASSERT_GE(total_sst_files_size, limit_mb * 1024 * 1024); rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } diff --git a/thirdparty/rocksdb/db/db_statistics_test.cc b/thirdparty/rocksdb/db/db_statistics_test.cc index 237a2c6814..31396a7bf4 100644 --- a/thirdparty/rocksdb/db/db_statistics_test.cc +++ b/thirdparty/rocksdb/db/db_statistics_test.cc @@ -46,7 +46,7 @@ TEST_F(DBStatisticsTest, CompressionStatsTest) { Options options = CurrentOptions(); options.compression = type; options.statistics = rocksdb::CreateDBStatistics(); - options.statistics->stats_level_ = StatsLevel::kExceptTimeForMutex; + options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex); DestroyAndReopen(options); int kNumKeysWritten = 100000; @@ -105,7 +105,7 @@ TEST_F(DBStatisticsTest, MutexWaitStats) { Options options = CurrentOptions(); options.create_if_missing = true; options.statistics = rocksdb::CreateDBStatistics(); - options.statistics->stats_level_ = StatsLevel::kAll; + options.statistics->set_stats_level(StatsLevel::kAll); CreateAndReopenWithCF({"pikachu"}, options); const uint64_t kMutexWaitDelay = 100; ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, diff --git a/thirdparty/rocksdb/db/db_table_properties_test.cc b/thirdparty/rocksdb/db/db_table_properties_test.cc index 265e9cb2e1..5a54fd81c0 100644 --- a/thirdparty/rocksdb/db/db_table_properties_test.cc +++ b/thirdparty/rocksdb/db/db_table_properties_test.cc @@ -13,6 +13,7 @@ #include "db/db_test_util.h" #include "port/stack_trace.h" #include "rocksdb/db.h" +#include "rocksdb/utilities/table_properties_collectors.h" #include "util/testharness.h" #include "util/testutil.h" @@ -250,6 +251,80 @@ TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) { } } +TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) { + int kNumKeys = 1000; + int kWindowSize = 100; + int kNumDelsTrigger = 90; + std::shared_ptr compact_on_del = + NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger); + + Options opts = CurrentOptions(); + opts.table_properties_collector_factories.emplace_back(compact_on_del); + Reopen(opts); + + // add an L1 file to prevent tombstones from dropping due to obsolescence + // during flush + Put(Key(0), "val"); + Flush(); + MoveFilesToLevel(1); + + for (int i = 0; i < kNumKeys; ++i) { + if (i >= kNumKeys - kWindowSize && + i < kNumKeys - kWindowSize + kNumDelsTrigger) { + Delete(Key(i)); + } else { + Put(Key(i), "val"); + } + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(1), 0); + + // Change the window size and deletion trigger and ensure new values take + // effect + kWindowSize = 50; + kNumDelsTrigger = 40; + static_cast + (compact_on_del.get())->SetWindowSize(kWindowSize); + static_cast + (compact_on_del.get())->SetDeletionTrigger(kNumDelsTrigger); + for (int i = 0; i < kNumKeys; ++i) { + if (i >= kNumKeys - kWindowSize && + i < kNumKeys - kWindowSize + kNumDelsTrigger) { + Delete(Key(i)); + } else { + Put(Key(i), "val"); + } + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(1), 0); + + // Change the window size to disable delete triggered compaction + kWindowSize = 0; + static_cast + (compact_on_del.get())->SetWindowSize(kWindowSize); + static_cast + (compact_on_del.get())->SetDeletionTrigger(kNumDelsTrigger); + for (int i = 0; i < kNumKeys; ++i) { + if (i >= kNumKeys - kWindowSize && + i < kNumKeys - kWindowSize + kNumDelsTrigger) { + Delete(Key(i)); + } else { + Put(Key(i), "val"); + } + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(1, NumTableFilesAtLevel(0)); + +} + } // namespace rocksdb #endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/db_tailing_iter_test.cc b/thirdparty/rocksdb/db/db_tailing_iter_test.cc index d217828db9..62e60758fd 100644 --- a/thirdparty/rocksdb/db/db_tailing_iter_test.cc +++ b/thirdparty/rocksdb/db/db_tailing_iter_test.cc @@ -157,10 +157,10 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) { }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "ForwardIterator::RenewIterators:Null", - [&](void* arg) { file_iters_renewed_null = true; }); + [&](void* /*arg*/) { file_iters_renewed_null = true; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "ForwardIterator::RenewIterators:Copy", - [&](void* arg) { file_iters_renewed_copy = true; }); + [&](void* /*arg*/) { file_iters_renewed_copy = true; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); const int num_records = 1000; for (int i = 1; i < num_records; ++i) { @@ -214,9 +214,9 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) { } ASSERT_TRUE(file_iters_renewed_null); ASSERT_TRUE(file_iters_renewed_copy); - iter = 0; - itern = 0; - iterh = 0; + iter = nullptr; + itern = nullptr; + iterh = nullptr; BlockBasedTableOptions table_options; table_options.no_block_cache = true; table_options.block_cache_compressed = nullptr; @@ -229,7 +229,7 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) { Slice target1(buf5, 20); iteri->Seek(target1); ASSERT_TRUE(iteri->status().IsIncomplete()); - iteri = 0; + iteri = nullptr; read_options.read_tier = kReadAllTier; options.table_factory.reset(NewBlockBasedTableFactory()); @@ -415,7 +415,7 @@ TEST_F(DBTestTailingIterator, TailingIteratorUpperBound) { int immutable_seeks = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "ForwardIterator::SeekInternal:Immutable", - [&](void* arg) { ++immutable_seeks; }); + [&](void* /*arg*/) { ++immutable_seeks; }); // Seek to 13. This should not require any immutable seeks. rocksdb::SyncPoint::GetInstance()->EnableProcessing(); @@ -479,275 +479,6 @@ TEST_F(DBTestTailingIterator, TailingIteratorGap) { ASSERT_EQ("40", it->key().ToString()); } -TEST_F(DBTestTailingIterator, ManagedTailingIteratorSingle) { - ReadOptions read_options; - read_options.tailing = true; - read_options.managed = true; - - std::unique_ptr 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()); -} - -TEST_F(DBTestTailingIterator, ManagedTailingIteratorKeepAdding) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ReadOptions read_options; - read_options.tailing = true; - read_options.managed = true; - - std::unique_ptr 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); - } -} - -TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToNext) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ReadOptions read_options; - read_options.tailing = true; - read_options.managed = true; - - std::unique_ptr 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); - } -} - -TEST_F(DBTestTailingIterator, ManagedTailingIteratorDeletes) { - CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); - ReadOptions read_options; - read_options.tailing = true; - read_options.managed = true; - - std::unique_ptr 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); -} - -TEST_F(DBTestTailingIterator, ManagedTailingIteratorPrefixSeek) { - ReadOptions read_options; - read_options.tailing = true; - read_options.managed = true; - - Options options = CurrentOptions(); - options.create_if_missing = true; - options.disable_auto_compactions = true; - options.prefix_extractor.reset(NewFixedPrefixTransform(2)); - options.memtable_factory.reset(NewHashSkipListRepFactory(16)); - options.allow_concurrent_memtable_write = false; - DestroyAndReopen(options); - CreateAndReopenWithCF({"pikachu"}, options); - - std::unique_ptr 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()); -} - -TEST_F(DBTestTailingIterator, ManagedTailingIteratorIncomplete) { - 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 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()); - - ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); - iter->SeekToFirst(); - // should still be true after compaction - ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete()); -} - -TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToSame) { - 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 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); - - // 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()); -} - -TEST_F(DBTestTailingIterator, ForwardIteratorVersionProperty) { - Options options = CurrentOptions(); - options.write_buffer_size = 1000; - - ReadOptions read_options; - read_options.tailing = true; - - Put("foo", "bar"); - - uint64_t v1, v2, v3, v4; - { - std::unique_ptr iter(db_->NewIterator(read_options)); - iter->Seek("foo"); - std::string prop_value; - ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number", - &prop_value)); - v1 = static_cast(std::atoi(prop_value.c_str())); - - Put("foo1", "bar1"); - Flush(); - - ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number", - &prop_value)); - v2 = static_cast(std::atoi(prop_value.c_str())); - - iter->Seek("f"); - - ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number", - &prop_value)); - v3 = static_cast(std::atoi(prop_value.c_str())); - - ASSERT_EQ(v1, v2); - ASSERT_GT(v3, v2); - } - - { - std::unique_ptr iter(db_->NewIterator(read_options)); - iter->Seek("foo"); - std::string prop_value; - ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number", - &prop_value)); - v4 = static_cast(std::atoi(prop_value.c_str())); - } - ASSERT_EQ(v3, v4); -} - TEST_F(DBTestTailingIterator, SeekWithUpperBoundBug) { ReadOptions read_options; read_options.tailing = true; @@ -809,6 +540,8 @@ int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); #else + (void) argc; + (void) argv; return 0; #endif } diff --git a/thirdparty/rocksdb/db/db_test.cc b/thirdparty/rocksdb/db/db_test.cc index 193101d460..60e66c6c33 100644 --- a/thirdparty/rocksdb/db/db_test.cc +++ b/thirdparty/rocksdb/db/db_test.cc @@ -60,7 +60,6 @@ #include "util/compression.h" #include "util/file_reader_writer.h" #include "util/filename.h" -#include "util/hash.h" #include "util/mutexlock.h" #include "util/rate_limiter.h" #include "util/string_util.h" @@ -94,7 +93,7 @@ class DBTestWithParam }; TEST_F(DBTest, MockEnvTest) { - unique_ptr env{new MockEnv(Env::Default())}; + std::unique_ptr env{new MockEnv(Env::Default())}; Options options; options.create_if_missing = true; options.env = env.get(); @@ -144,7 +143,7 @@ TEST_F(DBTest, MockEnvTest) { // defined. #ifndef ROCKSDB_LITE TEST_F(DBTest, MemEnvTest) { - unique_ptr env{NewMemEnv(Env::Default())}; + std::unique_ptr env{NewMemEnv(Env::Default())}; Options options; options.create_if_missing = true; options.env = env.get(); @@ -223,6 +222,10 @@ TEST_F(DBTest, SkipDelay) { for (bool sync : {true, false}) { for (bool disableWAL : {true, false}) { + if (sync && disableWAL) { + // sync and disableWAL is incompatible. + continue; + } // Use a small number to ensure a large delay that is still effective // when we do Put // TODO(myabandeh): this is time dependent and could potentially make @@ -231,11 +234,11 @@ TEST_F(DBTest, SkipDelay) { std::atomic sleep_count(0); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::DelayWrite:Sleep", - [&](void* arg) { sleep_count.fetch_add(1); }); + [&](void* /*arg*/) { sleep_count.fetch_add(1); }); std::atomic wait_count(0); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::DelayWrite:Wait", - [&](void* arg) { wait_count.fetch_add(1); }); + [&](void* /*arg*/) { wait_count.fetch_add(1); }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); WriteOptions wo; @@ -259,6 +262,196 @@ TEST_F(DBTest, SkipDelay) { } } +TEST_F(DBTest, MixedSlowdownOptions) { + Options options = CurrentOptions(); + options.env = env_; + options.write_buffer_size = 100000; + CreateAndReopenWithCF({"pikachu"}, options); + std::vector threads; + std::atomic thread_num(0); + + std::function write_slowdown_func = [&]() { + int a = thread_num.fetch_add(1); + std::string key = "foo" + std::to_string(a); + WriteOptions wo; + wo.no_slowdown = false; + ASSERT_OK(dbfull()->Put(wo, key, "bar")); + }; + std::function write_no_slowdown_func = [&]() { + int a = thread_num.fetch_add(1); + std::string key = "foo" + std::to_string(a); + WriteOptions wo; + wo.no_slowdown = true; + ASSERT_NOK(dbfull()->Put(wo, key, "bar")); + }; + // Use a small number to ensure a large delay that is still effective + // when we do Put + // TODO(myabandeh): this is time dependent and could potentially make + // the test flaky + auto token = dbfull()->TEST_write_controler().GetDelayToken(1); + std::atomic sleep_count(0); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::DelayWrite:BeginWriteStallDone", + [&](void* /*arg*/) { + sleep_count.fetch_add(1); + if (threads.empty()) { + for (int i = 0; i < 2; ++i) { + threads.emplace_back(write_slowdown_func); + } + for (int i = 0; i < 2; ++i) { + threads.emplace_back(write_no_slowdown_func); + } + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + WriteOptions wo; + wo.sync = false; + wo.disableWAL = false; + wo.no_slowdown = false; + dbfull()->Put(wo, "foo", "bar"); + // We need the 2nd write to trigger delay. This is because delay is + // estimated based on the last write size which is 0 for the first write. + ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2")); + token.reset(); + + for (auto& t : threads) { + t.join(); + } + ASSERT_GE(sleep_count.load(), 1); + + wo.no_slowdown = true; + ASSERT_OK(dbfull()->Put(wo, "foo3", "bar")); +} + +TEST_F(DBTest, MixedSlowdownOptionsInQueue) { + Options options = CurrentOptions(); + options.env = env_; + options.write_buffer_size = 100000; + CreateAndReopenWithCF({"pikachu"}, options); + std::vector threads; + std::atomic thread_num(0); + + std::function write_no_slowdown_func = [&]() { + int a = thread_num.fetch_add(1); + std::string key = "foo" + std::to_string(a); + WriteOptions wo; + wo.no_slowdown = true; + ASSERT_NOK(dbfull()->Put(wo, key, "bar")); + }; + // Use a small number to ensure a large delay that is still effective + // when we do Put + // TODO(myabandeh): this is time dependent and could potentially make + // the test flaky + auto token = dbfull()->TEST_write_controler().GetDelayToken(1); + std::atomic sleep_count(0); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::DelayWrite:Sleep", + [&](void* /*arg*/) { + sleep_count.fetch_add(1); + if (threads.empty()) { + for (int i = 0; i < 2; ++i) { + threads.emplace_back(write_no_slowdown_func); + } + // Sleep for 2s to allow the threads to insert themselves into the + // write queue + env_->SleepForMicroseconds(3000000ULL); + } + }); + std::atomic wait_count(0); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::DelayWrite:Wait", + [&](void* /*arg*/) { wait_count.fetch_add(1); }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + WriteOptions wo; + wo.sync = false; + wo.disableWAL = false; + wo.no_slowdown = false; + dbfull()->Put(wo, "foo", "bar"); + // We need the 2nd write to trigger delay. This is because delay is + // estimated based on the last write size which is 0 for the first write. + ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2")); + token.reset(); + + for (auto& t : threads) { + t.join(); + } + ASSERT_EQ(sleep_count.load(), 1); + ASSERT_GE(wait_count.load(), 0); +} + +TEST_F(DBTest, MixedSlowdownOptionsStop) { + Options options = CurrentOptions(); + options.env = env_; + options.write_buffer_size = 100000; + CreateAndReopenWithCF({"pikachu"}, options); + std::vector threads; + std::atomic thread_num(0); + + std::function write_slowdown_func = [&]() { + int a = thread_num.fetch_add(1); + std::string key = "foo" + std::to_string(a); + WriteOptions wo; + wo.no_slowdown = false; + ASSERT_OK(dbfull()->Put(wo, key, "bar")); + }; + std::function write_no_slowdown_func = [&]() { + int a = thread_num.fetch_add(1); + std::string key = "foo" + std::to_string(a); + WriteOptions wo; + wo.no_slowdown = true; + ASSERT_NOK(dbfull()->Put(wo, key, "bar")); + }; + std::function wakeup_writer = [&]() { + dbfull()->mutex_.Lock(); + dbfull()->bg_cv_.SignalAll(); + dbfull()->mutex_.Unlock(); + }; + // Use a small number to ensure a large delay that is still effective + // when we do Put + // TODO(myabandeh): this is time dependent and could potentially make + // the test flaky + auto token = dbfull()->TEST_write_controler().GetStopToken(); + std::atomic wait_count(0); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::DelayWrite:Wait", + [&](void* /*arg*/) { + wait_count.fetch_add(1); + if (threads.empty()) { + for (int i = 0; i < 2; ++i) { + threads.emplace_back(write_slowdown_func); + } + for (int i = 0; i < 2; ++i) { + threads.emplace_back(write_no_slowdown_func); + } + // Sleep for 2s to allow the threads to insert themselves into the + // write queue + env_->SleepForMicroseconds(3000000ULL); + } + token.reset(); + threads.emplace_back(wakeup_writer); + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + WriteOptions wo; + wo.sync = false; + wo.disableWAL = false; + wo.no_slowdown = false; + dbfull()->Put(wo, "foo", "bar"); + // We need the 2nd write to trigger delay. This is because delay is + // estimated based on the last write size which is 0 for the first write. + ASSERT_OK(dbfull()->Put(wo, "foo2", "bar2")); + token.reset(); + + for (auto& t : threads) { + t.join(); + } + ASSERT_GE(wait_count.load(), 1); + + wo.no_slowdown = true; + ASSERT_OK(dbfull()->Put(wo, "foo3", "bar")); +} #ifndef ROCKSDB_LITE TEST_F(DBTest, LevelLimitReopen) { @@ -294,11 +487,11 @@ TEST_F(DBTest, PutSingleDeleteGet) { ASSERT_EQ("v2", Get(1, "foo2")); ASSERT_OK(SingleDelete(1, "foo")); ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - // Skip HashCuckooRep as it does not support single delete. FIFO and - // universal compaction do not apply to the test case. Skip MergePut - // because single delete does not get removed when it encounters a merge. - } while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction | - kSkipUniversalCompaction | kSkipMergePut)); + // Skip FIFO and universal compaction beccause they do not apply to the test + // case. Skip MergePut because single delete does not get removed when it + // encounters a merge. + } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | + kSkipMergePut)); } TEST_F(DBTest, ReadFromPersistedTier) { @@ -411,7 +604,7 @@ TEST_F(DBTest, ReadFromPersistedTier) { DestroyAndReopen(options); } } - } while (ChangeOptions(kSkipHashCuckoo)); + } while (ChangeOptions()); } TEST_F(DBTest, SingleDeleteFlush) { @@ -447,11 +640,11 @@ TEST_F(DBTest, SingleDeleteFlush) { ASSERT_EQ("NOT_FOUND", Get(1, "bar")); ASSERT_EQ("NOT_FOUND", Get(1, "foo")); - // Skip HashCuckooRep as it does not support single delete. FIFO and - // universal compaction do not apply to the test case. Skip MergePut - // because merges cannot be combined with single deletions. - } while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction | - kSkipUniversalCompaction | kSkipMergePut)); + // Skip FIFO and universal compaction beccause they do not apply to the test + // case. Skip MergePut because single delete does not get removed when it + // encounters a merge. + } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | + kSkipMergePut)); } TEST_F(DBTest, SingleDeletePutFlush) { @@ -470,11 +663,41 @@ TEST_F(DBTest, SingleDeletePutFlush) { ASSERT_OK(Flush(1)); ASSERT_EQ("[ ]", AllEntriesFor("a", 1)); - // Skip HashCuckooRep as it does not support single delete. FIFO and - // universal compaction do not apply to the test case. Skip MergePut - // because merges cannot be combined with single deletions. - } while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction | - kSkipUniversalCompaction | kSkipMergePut)); + // Skip FIFO and universal compaction beccause they do not apply to the test + // case. Skip MergePut because single delete does not get removed when it + // encounters a merge. + } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | + kSkipMergePut)); +} + +// Disable because not all platform can run it. +// It requires more than 9GB memory to run it, With single allocation +// of more than 3GB. +TEST_F(DBTest, DISABLED_SanitizeVeryVeryLargeValue) { + const size_t kValueSize = 4 * size_t{1024 * 1024 * 1024}; // 4GB value + std::string raw(kValueSize, 'v'); + Options options = CurrentOptions(); + options.env = env_; + options.merge_operator = MergeOperators::CreatePutOperator(); + options.write_buffer_size = 100000; // Small write buffer + options.paranoid_checks = true; + DestroyAndReopen(options); + + ASSERT_OK(Put("boo", "v1")); + ASSERT_TRUE(Put("foo", raw).IsInvalidArgument()); + ASSERT_TRUE(Merge("foo", raw).IsInvalidArgument()); + + WriteBatch wb; + ASSERT_TRUE(wb.Put("foo", raw).IsInvalidArgument()); + ASSERT_TRUE(wb.Merge("foo", raw).IsInvalidArgument()); + + Slice value_slice = raw; + Slice key_slice = "foo"; + SliceParts sp_key(&key_slice, 1); + SliceParts sp_value(&value_slice, 1); + + ASSERT_TRUE(wb.Put(sp_key, sp_value).IsInvalidArgument()); + ASSERT_TRUE(wb.Merge(sp_key, sp_value).IsInvalidArgument()); } // Disable because not all platform can run it. @@ -500,7 +723,9 @@ TEST_F(DBTest, DISABLED_VeryLargeValue) { ASSERT_OK(Put(key2, raw)); dbfull()->TEST_WaitForFlushMemTable(); +#ifndef ROCKSDB_LITE ASSERT_EQ(1, NumTableFilesAtLevel(0)); +#endif // !ROCKSDB_LITE std::string value; Status s = db_->Get(ReadOptions(), key1, &value); @@ -715,13 +940,13 @@ TEST_F(DBTest, FlushSchedule) { namespace { 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 { + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { return false; } - virtual const char* Name() const override { return "KeepFilter"; } + const char* Name() const override { return "KeepFilter"; } }; class KeepFilterFactory : public CompactionFilterFactory { @@ -729,7 +954,7 @@ class KeepFilterFactory : public CompactionFilterFactory { explicit KeepFilterFactory(bool check_context = false) : check_context_(check_context) {} - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { if (check_context_) { EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction); @@ -738,7 +963,7 @@ class KeepFilterFactory : public CompactionFilterFactory { return std::unique_ptr(new KeepFilter()); } - virtual const char* Name() const override { return "KeepFilterFactory"; } + const char* Name() const override { return "KeepFilterFactory"; } bool check_context_; std::atomic_bool expect_full_compaction_; std::atomic_bool expect_manual_compaction_; @@ -747,14 +972,14 @@ class KeepFilterFactory : public CompactionFilterFactory { class DelayFilter : public CompactionFilter { public: explicit DelayFilter(DBTestBase* d) : db_test(d) {} - virtual bool Filter(int level, const Slice& key, const Slice& value, - std::string* new_value, - bool* value_changed) const override { + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { db_test->env_->addon_time_.fetch_add(1000); return true; } - virtual const char* Name() const override { return "DelayFilter"; } + const char* Name() const override { return "DelayFilter"; } private: DBTestBase* db_test; @@ -763,12 +988,12 @@ class DelayFilter : public CompactionFilter { class DelayFilterFactory : public CompactionFilterFactory { public: explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {} - virtual std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { + std::unique_ptr CreateCompactionFilter( + const CompactionFilter::Context& /*context*/) override { return std::unique_ptr(new DelayFilter(db_test)); } - virtual const char* Name() const override { return "DelayFilterFactory"; } + const char* Name() const override { return "DelayFilterFactory"; } private: DBTestBase* db_test; @@ -1344,7 +1569,7 @@ TEST_F(DBTest, Snapshot) { ASSERT_EQ(0U, GetNumSnapshots()); ASSERT_EQ("0v4", Get(0, "foo")); ASSERT_EQ("1v4", Get(1, "foo")); - } while (ChangeOptions(kSkipHashCuckoo)); + } while (ChangeOptions()); } TEST_F(DBTest, HiddenValuesAreRemoved) { @@ -1381,9 +1606,8 @@ TEST_F(DBTest, HiddenValuesAreRemoved) { ASSERT_TRUE(Between(Size("", "pastfoo", 1), 0, 1000)); // ApproximateOffsetOf() is not yet implemented in plain table format, // which is used by Size(). - // skip HashCuckooRep as it does not support snapshot } while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction | - kSkipPlainTable | kSkipHashCuckoo)); + kSkipPlainTable)); } #endif // ROCKSDB_LITE @@ -1429,11 +1653,11 @@ TEST_F(DBTest, UnremovableSingleDelete) { ASSERT_EQ("first", Get(1, "foo", snapshot)); ASSERT_EQ("NOT_FOUND", Get(1, "foo")); db_->ReleaseSnapshot(snapshot); - // Skip HashCuckooRep as it does not support single delete. FIFO and - // universal compaction do not apply to the test case. Skip MergePut - // because single delete does not get removed when it encounters a merge. - } while (ChangeOptions(kSkipHashCuckoo | kSkipFIFOCompaction | - kSkipUniversalCompaction | kSkipMergePut)); + // Skip FIFO and universal compaction beccause they do not apply to the test + // case. Skip MergePut because single delete does not get removed when it + // encounters a merge. + } while (ChangeOptions(kSkipFIFOCompaction | kSkipUniversalCompaction | + kSkipMergePut)); } #ifndef ROCKSDB_LITE @@ -1551,17 +1775,14 @@ TEST_F(DBTest, OverlapInLevel0) { TEST_F(DBTest, ComparatorCheck) { class NewComparator : public Comparator { public: - virtual const char* Name() const override { - return "rocksdb.NewComparator"; - } - virtual int Compare(const Slice& a, const Slice& b) const override { + const char* Name() const override { return "rocksdb.NewComparator"; } + int Compare(const Slice& a, const Slice& b) const override { return BytewiseComparator()->Compare(a, b); } - virtual void FindShortestSeparator(std::string* s, - const Slice& l) const override { + void FindShortestSeparator(std::string* s, const Slice& l) const override { BytewiseComparator()->FindShortestSeparator(s, l); } - virtual void FindShortSuccessor(std::string* key) const override { + void FindShortSuccessor(std::string* key) const override { BytewiseComparator()->FindShortSuccessor(key); } }; @@ -1584,18 +1805,15 @@ TEST_F(DBTest, ComparatorCheck) { TEST_F(DBTest, CustomComparator) { class NumberComparator : public Comparator { public: - virtual const char* Name() const override { - return "test.NumberComparator"; - } - virtual int Compare(const Slice& a, const Slice& b) const override { + const char* Name() const override { return "test.NumberComparator"; } + int Compare(const Slice& a, const Slice& b) const override { return ToNumber(a) - ToNumber(b); } - virtual void FindShortestSeparator(std::string* s, - const Slice& l) const override { + void FindShortestSeparator(std::string* s, const Slice& l) const override { ToNumber(*s); // Check format ToNumber(l); // Check format } - virtual void FindShortSuccessor(std::string* key) const override { + void FindShortSuccessor(std::string* key) const override { ToNumber(*key); // Check format } @@ -1647,7 +1865,7 @@ TEST_F(DBTest, CustomComparator) { TEST_F(DBTest, DBOpen_Options) { Options options = CurrentOptions(); - std::string dbname = test::TmpDir(env_) + "/db_options_test"; + std::string dbname = test::PerThreadDBPath("db_options_test"); ASSERT_OK(DestroyDB(dbname, options)); // Does not exist, and create_if_missing == false: error @@ -1705,7 +1923,7 @@ TEST_F(DBTest, DBOpen_Change_NumLevels) { } TEST_F(DBTest, DestroyDBMetaDatabase) { - std::string dbname = test::TmpDir(env_) + "/db_meta"; + std::string dbname = test::PerThreadDBPath("db_meta"); ASSERT_OK(env_->CreateDirIfMissing(dbname)); std::string metadbname = MetaDatabaseName(dbname, 0); ASSERT_OK(env_->CreateDirIfMissing(metadbname)); @@ -2029,15 +2247,12 @@ static void MTThreadBody(void* arg) { class MultiThreadedDBTest : public DBTest, public ::testing::WithParamInterface { public: - virtual void SetUp() override { option_config_ = GetParam(); } + void SetUp() override { option_config_ = GetParam(); } static std::vector GenerateOptionConfigs() { std::vector optionConfigs; for (int optionConfig = kDefault; optionConfig < kEnd; ++optionConfig) { - // skip as HashCuckooRep does not support snapshot - if (optionConfig != kHashCuckoo) { - optionConfigs.push_back(optionConfig); - } + optionConfigs.push_back(optionConfig); } return optionConfigs; } @@ -2088,6 +2303,9 @@ INSTANTIATE_TEST_CASE_P( #endif // ROCKSDB_LITE // Group commit test: +#if !defined(TRAVIS) && !defined(OS_WIN) +// Disable this test temporarily on Travis and appveyor as it fails +// intermittently. Github issue: #4151 namespace { static const int kGCNumThreads = 4; @@ -2118,10 +2336,16 @@ TEST_F(DBTest, GroupCommitTest) { do { Options options = CurrentOptions(); options.env = env_; - env_->log_write_slowdown_.store(100); options.statistics = rocksdb::CreateDBStatistics(); Reopen(options); + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"WriteThread::JoinBatchGroup:BeganWaiting", + "DBImpl::WriteImpl:BeforeLeaderEnters"}, + {"WriteThread::AwaitState:BlockingWaiting", + "WriteThread::EnterAsBatchGroupLeader:End"}}); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + // Start threads GCThread thread[kGCNumThreads]; for (int id = 0; id < kGCNumThreads; id++) { @@ -2130,13 +2354,7 @@ TEST_F(DBTest, GroupCommitTest) { thread[id].done = false; env_->StartThread(GCThreadBody, &thread[id]); } - - for (int id = 0; id < kGCNumThreads; id++) { - while (thread[id].done == false) { - env_->SleepForMicroseconds(100000); - } - } - env_->log_write_slowdown_.store(0); + env_->WaitForJoin(); ASSERT_GT(TestGetTickerCount(options, WRITE_DONE_BY_OTHER), 0); @@ -2162,6 +2380,7 @@ TEST_F(DBTest, GroupCommitTest) { ASSERT_GT(hist_data.average, 0.0); } while (ChangeOptions(kSkipNoSeekToLast)); } +#endif // TRAVIS namespace { typedef std::map KVMap; @@ -2173,7 +2392,7 @@ class ModelDB : public DB { public: KVMap map_; - virtual SequenceNumber GetSequenceNumber() const override { + SequenceNumber GetSequenceNumber() const override { // no need to call this assert(false); return 0; @@ -2182,45 +2401,47 @@ class ModelDB : public DB { explicit ModelDB(const Options& options) : options_(options) {} using DB::Put; - virtual Status Put(const WriteOptions& o, ColumnFamilyHandle* cf, - const Slice& k, const Slice& v) override { + Status Put(const WriteOptions& o, ColumnFamilyHandle* cf, const Slice& k, + const Slice& v) override { WriteBatch batch; batch.Put(cf, k, v); return Write(o, &batch); } + using DB::Close; + Status Close() override { return Status::OK(); } using DB::Delete; - virtual Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf, - const Slice& key) override { + Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf, + const Slice& key) override { WriteBatch batch; batch.Delete(cf, key); return Write(o, &batch); } using DB::SingleDelete; - virtual Status SingleDelete(const WriteOptions& o, ColumnFamilyHandle* cf, - const Slice& key) override { + Status SingleDelete(const WriteOptions& o, ColumnFamilyHandle* cf, + const Slice& key) override { WriteBatch batch; batch.SingleDelete(cf, key); return Write(o, &batch); } using DB::Merge; - virtual Status Merge(const WriteOptions& o, ColumnFamilyHandle* cf, - const Slice& k, const Slice& v) override { + Status Merge(const WriteOptions& o, ColumnFamilyHandle* cf, const Slice& k, + const Slice& v) override { WriteBatch batch; batch.Merge(cf, k, v); return Write(o, &batch); } using DB::Get; - virtual Status Get(const ReadOptions& options, ColumnFamilyHandle* cf, - const Slice& key, PinnableSlice* value) override { + Status Get(const ReadOptions& /*options*/, ColumnFamilyHandle* /*cf*/, + const Slice& key, PinnableSlice* /*value*/) override { return Status::NotSupported(key); } using DB::MultiGet; - virtual std::vector MultiGet( - const ReadOptions& options, - const std::vector& column_family, + std::vector MultiGet( + const ReadOptions& /*options*/, + const std::vector& /*column_family*/, const std::vector& keys, - std::vector* values) override { + std::vector* /*values*/) override { std::vector s(keys.size(), Status::NotSupported("Not implemented.")); return s; @@ -2228,44 +2449,50 @@ class ModelDB : public DB { #ifndef ROCKSDB_LITE using DB::IngestExternalFile; - virtual Status IngestExternalFile( - ColumnFamilyHandle* column_family, - const std::vector& external_files, - const IngestExternalFileOptions& options) override { + Status IngestExternalFile( + ColumnFamilyHandle* /*column_family*/, + const std::vector& /*external_files*/, + const IngestExternalFileOptions& /*options*/) override { return Status::NotSupported("Not implemented."); } - virtual Status VerifyChecksum() override { + using DB::IngestExternalFiles; + Status IngestExternalFiles( + const std::vector& /*args*/) override { + return Status::NotSupported("Not implemented"); + } + + Status VerifyChecksum() override { return Status::NotSupported("Not implemented."); } using DB::GetPropertiesOfAllTables; - virtual Status GetPropertiesOfAllTables( - ColumnFamilyHandle* column_family, - TablePropertiesCollection* props) override { + Status GetPropertiesOfAllTables( + ColumnFamilyHandle* /*column_family*/, + TablePropertiesCollection* /*props*/) override { return Status(); } - virtual Status GetPropertiesOfTablesInRange( - ColumnFamilyHandle* column_family, const Range* range, std::size_t n, - TablePropertiesCollection* props) override { + Status GetPropertiesOfTablesInRange( + ColumnFamilyHandle* /*column_family*/, const Range* /*range*/, + std::size_t /*n*/, TablePropertiesCollection* /*props*/) override { return Status(); } #endif // ROCKSDB_LITE using DB::KeyMayExist; - virtual bool KeyMayExist(const ReadOptions& options, - ColumnFamilyHandle* column_family, const Slice& key, - std::string* value, - bool* value_found = nullptr) override { + bool KeyMayExist(const ReadOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/, + std::string* /*value*/, + bool* value_found = nullptr) override { if (value_found != nullptr) { *value_found = false; } return true; // Not Supported directly } using DB::NewIterator; - virtual Iterator* NewIterator(const ReadOptions& options, - ColumnFamilyHandle* column_family) override { + Iterator* NewIterator(const ReadOptions& options, + ColumnFamilyHandle* /*column_family*/) override { if (options.snapshot == nullptr) { KVMap* saved = new KVMap; *saved = map_; @@ -2276,37 +2503,33 @@ class ModelDB : public DB { return new ModelIter(snapshot_state, false); } } - virtual Status NewIterators( - const ReadOptions& options, - const std::vector& column_family, - std::vector* iterators) override { + Status NewIterators(const ReadOptions& /*options*/, + const std::vector& /*column_family*/, + std::vector* /*iterators*/) override { return Status::NotSupported("Not supported yet"); } - virtual const Snapshot* GetSnapshot() override { + const Snapshot* GetSnapshot() override { ModelSnapshot* snapshot = new ModelSnapshot; snapshot->map_ = map_; return snapshot; } - virtual void ReleaseSnapshot(const Snapshot* snapshot) override { + void ReleaseSnapshot(const Snapshot* snapshot) override { delete reinterpret_cast(snapshot); } - virtual Status Write(const WriteOptions& options, - WriteBatch* batch) override { + Status Write(const WriteOptions& /*options*/, WriteBatch* batch) override { class Handler : public WriteBatch::Handler { public: KVMap* map_; - virtual void Put(const Slice& key, const Slice& value) override { + void Put(const Slice& key, const Slice& value) override { (*map_)[key.ToString()] = value.ToString(); } - virtual void Merge(const Slice& key, const Slice& value) override { + void Merge(const Slice& /*key*/, const Slice& /*value*/) override { // ignore merge for now // (*map_)[key.ToString()] = value.ToString(); } - virtual void Delete(const Slice& key) override { - map_->erase(key.ToString()); - } + void Delete(const Slice& key) override { map_->erase(key.ToString()); } }; Handler handler; handler.map_ = &map_; @@ -2314,62 +2537,64 @@ class ModelDB : public DB { } using DB::GetProperty; - virtual bool GetProperty(ColumnFamilyHandle* column_family, - const Slice& property, std::string* value) override { + bool GetProperty(ColumnFamilyHandle* /*column_family*/, + const Slice& /*property*/, std::string* /*value*/) override { return false; } using DB::GetIntProperty; - virtual bool GetIntProperty(ColumnFamilyHandle* column_family, - const Slice& property, uint64_t* value) override { + bool GetIntProperty(ColumnFamilyHandle* /*column_family*/, + const Slice& /*property*/, uint64_t* /*value*/) override { return false; } using DB::GetMapProperty; - virtual bool GetMapProperty(ColumnFamilyHandle* column_family, - const Slice& property, - std::map* value) override { + bool GetMapProperty(ColumnFamilyHandle* /*column_family*/, + const Slice& /*property*/, + std::map* /*value*/) override { return false; } using DB::GetAggregatedIntProperty; - virtual bool GetAggregatedIntProperty(const Slice& property, - uint64_t* value) override { + bool GetAggregatedIntProperty(const Slice& /*property*/, + uint64_t* /*value*/) override { return false; } using DB::GetApproximateSizes; - virtual void GetApproximateSizes(ColumnFamilyHandle* column_family, - const Range* range, int n, uint64_t* sizes, - uint8_t include_flags - = INCLUDE_FILES) override { + void GetApproximateSizes(ColumnFamilyHandle* /*column_family*/, + const Range* /*range*/, int n, uint64_t* sizes, + uint8_t /*include_flags*/ + = INCLUDE_FILES) override { for (int i = 0; i < n; i++) { sizes[i] = 0; } } using DB::GetApproximateMemTableStats; - virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family, - const Range& range, - uint64_t* const count, - uint64_t* const size) override { + void GetApproximateMemTableStats(ColumnFamilyHandle* /*column_family*/, + const Range& /*range*/, + uint64_t* const count, + uint64_t* const size) override { *count = 0; *size = 0; } using DB::CompactRange; - virtual Status CompactRange(const CompactRangeOptions& options, - ColumnFamilyHandle* column_family, - const Slice* start, const Slice* end) override { + Status CompactRange(const CompactRangeOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/, + const Slice* /*start*/, const Slice* /*end*/) override { return Status::NotSupported("Not supported operation."); } - virtual Status SetDBOptions( - const std::unordered_map& new_options) + Status SetDBOptions( + const std::unordered_map& /*new_options*/) override { return Status::NotSupported("Not supported operation."); } using DB::CompactFiles; - virtual Status CompactFiles(const CompactionOptions& compact_options, - ColumnFamilyHandle* column_family, - const std::vector& input_file_names, - const int output_level, - const int output_path_id = -1) override { + Status CompactFiles( + const CompactionOptions& /*compact_options*/, + ColumnFamilyHandle* /*column_family*/, + const std::vector& /*input_file_names*/, + const int /*output_level*/, const int /*output_path_id*/ = -1, + std::vector* const /*output_file_names*/ = nullptr, + CompactionJobInfo* /*compaction_job_info*/ = nullptr) override { return Status::NotSupported("Not supported operation."); } @@ -2382,113 +2607,115 @@ class ModelDB : public DB { } Status EnableAutoCompaction( - const std::vector& column_family_handles) override { + const std::vector& /*column_family_handles*/) + override { return Status::NotSupported("Not supported operation."); } using DB::NumberLevels; - virtual int NumberLevels(ColumnFamilyHandle* column_family) override { - return 1; - } + int NumberLevels(ColumnFamilyHandle* /*column_family*/) override { return 1; } using DB::MaxMemCompactionLevel; - virtual int MaxMemCompactionLevel( - ColumnFamilyHandle* column_family) override { + int MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) override { return 1; } using DB::Level0StopWriteTrigger; - virtual int Level0StopWriteTrigger( - ColumnFamilyHandle* column_family) override { + int Level0StopWriteTrigger(ColumnFamilyHandle* /*column_family*/) override { return -1; } - virtual const std::string& GetName() const override { return name_; } + const std::string& GetName() const override { return name_; } - virtual Env* GetEnv() const override { return nullptr; } + Env* GetEnv() const override { return nullptr; } using DB::GetOptions; - virtual Options GetOptions(ColumnFamilyHandle* column_family) const override { + Options GetOptions(ColumnFamilyHandle* /*column_family*/) const override { return options_; } using DB::GetDBOptions; - virtual DBOptions GetDBOptions() const override { return options_; } + DBOptions GetDBOptions() const override { return options_; } using DB::Flush; - virtual Status Flush(const rocksdb::FlushOptions& options, - ColumnFamilyHandle* column_family) override { + Status Flush(const rocksdb::FlushOptions& /*options*/, + ColumnFamilyHandle* /*column_family*/) override { Status ret; return ret; } + Status Flush( + const rocksdb::FlushOptions& /*options*/, + const std::vector& /*column_families*/) override { + return Status::OK(); + } - virtual Status SyncWAL() override { return Status::OK(); } + Status SyncWAL() override { return Status::OK(); } #ifndef ROCKSDB_LITE - virtual Status DisableFileDeletions() override { return Status::OK(); } + Status DisableFileDeletions() override { return Status::OK(); } - virtual Status EnableFileDeletions(bool force) override { - return Status::OK(); - } - virtual Status GetLiveFiles(std::vector&, uint64_t* size, - bool flush_memtable = true) override { + Status EnableFileDeletions(bool /*force*/) override { return Status::OK(); } + Status GetLiveFiles(std::vector&, uint64_t* /*size*/, + bool /*flush_memtable*/ = true) override { return Status::OK(); } - virtual Status GetSortedWalFiles(VectorLogPtr& files) override { + Status GetSortedWalFiles(VectorLogPtr& /*files*/) override { return Status::OK(); } - virtual Status DeleteFile(std::string name) override { return Status::OK(); } + Status DeleteFile(std::string /*name*/) override { return Status::OK(); } - virtual Status GetUpdatesSince( - rocksdb::SequenceNumber, unique_ptr*, - const TransactionLogIterator::ReadOptions& read_options = + Status GetUpdatesSince( + rocksdb::SequenceNumber, + std::unique_ptr*, + const TransactionLogIterator::ReadOptions& /*read_options*/ = TransactionLogIterator::ReadOptions()) override { return Status::NotSupported("Not supported in Model DB"); } - virtual void GetColumnFamilyMetaData( - ColumnFamilyHandle* column_family, - ColumnFamilyMetaData* metadata) override {} + void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/, + ColumnFamilyMetaData* /*metadata*/) override {} #endif // ROCKSDB_LITE - virtual Status GetDbIdentity(std::string& identity) const override { + Status GetDbIdentity(std::string& /*identity*/) const override { return Status::OK(); } - virtual SequenceNumber GetLatestSequenceNumber() const override { return 0; } + SequenceNumber GetLatestSequenceNumber() const override { return 0; } - virtual ColumnFamilyHandle* DefaultColumnFamily() const override { - return nullptr; + bool SetPreserveDeletesSequenceNumber(SequenceNumber /*seqnum*/) override { + return true; } + ColumnFamilyHandle* DefaultColumnFamily() const override { return nullptr; } + private: class ModelIter : public Iterator { public: ModelIter(const KVMap* map, bool owned) : map_(map), owned_(owned), iter_(map_->end()) {} - ~ModelIter() { + ~ModelIter() override { if (owned_) delete map_; } - virtual bool Valid() const override { return iter_ != map_->end(); } - virtual void SeekToFirst() override { iter_ = map_->begin(); } - virtual void SeekToLast() override { + bool Valid() const override { return iter_ != map_->end(); } + void SeekToFirst() override { iter_ = map_->begin(); } + void SeekToLast() override { if (map_->empty()) { iter_ = map_->end(); } else { iter_ = map_->find(map_->rbegin()->first); } } - virtual void Seek(const Slice& k) override { + void Seek(const Slice& k) override { iter_ = map_->lower_bound(k.ToString()); } - virtual void SeekForPrev(const Slice& k) override { + void SeekForPrev(const Slice& k) override { iter_ = map_->upper_bound(k.ToString()); Prev(); } - virtual void Next() override { ++iter_; } - virtual void Prev() override { + void Next() override { ++iter_; } + void Prev() override { if (iter_ == map_->begin()) { iter_ = map_->end(); return; @@ -2496,9 +2723,9 @@ class ModelDB : public DB { --iter_; } - virtual Slice key() const override { return iter_->first; } - virtual Slice value() const override { return iter_->second; } - virtual Status status() const override { return Status::OK(); } + Slice key() const override { return iter_->first; } + Slice value() const override { return iter_->second; } + Status status() const override { return Status::OK(); } private: const KVMap* const map_; @@ -2510,6 +2737,7 @@ class ModelDB : public DB { std::string name_ = ""; }; +#ifndef ROCKSDB_VALGRIND_RUN static std::string RandomKey(Random* rnd, int minimum = 0) { int len; do { @@ -2565,15 +2793,14 @@ static bool CompareIterators(int step, DB* model, DB* db, class DBTestRandomized : public DBTest, public ::testing::WithParamInterface { public: - virtual void SetUp() override { option_config_ = GetParam(); } + void SetUp() override { option_config_ = GetParam(); } static std::vector GenerateOptionConfigs() { std::vector option_configs; // skip cuckoo hash as it does not support snapshot. for (int option_config = kDefault; option_config < kEnd; ++option_config) { - if (!ShouldSkipOptions(option_config, kSkipDeletesFilterFirst | - kSkipNoSeekToLast | - kSkipHashCuckoo)) { + if (!ShouldSkipOptions(option_config, + kSkipDeletesFilterFirst | kSkipNoSeekToLast)) { option_configs.push_back(option_config); } } @@ -2603,7 +2830,6 @@ TEST_P(DBTestRandomized, Randomized) { int p = rnd.Uniform(100); int minimum = 0; if (option_config_ == kHashSkipList || option_config_ == kHashLinkList || - option_config_ == kHashCuckoo || option_config_ == kPlainTableFirstBytePrefix || option_config_ == kBlockBasedTableWithWholeKeyHashIndex || option_config_ == kBlockBasedTableWithPrefixHashIndex) { @@ -2666,6 +2892,7 @@ TEST_P(DBTestRandomized, Randomized) { if (model_snap != nullptr) model.ReleaseSnapshot(model_snap); if (db_snap != nullptr) db_->ReleaseSnapshot(db_snap); } +#endif // ROCKSDB_VALGRIND_RUN TEST_F(DBTest, BlockBasedTablePrefixIndexTest) { // create a DB with block prefix index @@ -2856,7 +3083,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLAndMaxOpenFilesTest) { Options options; options.compaction_style = kCompactionStyleFIFO; options.create_if_missing = true; - options.compaction_options_fifo.ttl = 600; // seconds + options.ttl = 600; // seconds // Check that it is not supported with max_open_files != -1. options.max_open_files = 100; @@ -2872,7 +3099,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLAndVariousTableFormatsTest) { Options options; options.compaction_style = kCompactionStyleFIFO; options.create_if_missing = true; - options.compaction_options_fifo.ttl = 600; // seconds + options.ttl = 600; // seconds options = CurrentOptions(options); options.table_factory.reset(NewBlockBasedTableFactory()); @@ -2882,10 +3109,6 @@ TEST_F(DBTest, FIFOCompactionWithTTLAndVariousTableFormatsTest) { options.table_factory.reset(NewPlainTableFactory()); ASSERT_TRUE(TryReopen(options).IsNotSupported()); - Destroy(options); - options.table_factory.reset(NewCuckooTableFactory()); - ASSERT_TRUE(TryReopen(options).IsNotSupported()); - Destroy(options); options.table_factory.reset(NewAdaptiveTableFactory()); ASSERT_TRUE(TryReopen(options).IsNotSupported()); @@ -2907,7 +3130,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) { env_->addon_time_.store(0); options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB options.compaction_options_fifo.allow_compaction = false; - options.compaction_options_fifo.ttl = 1 * 60 * 60 ; // 1 hour + options.ttl = 1 * 60 * 60 ; // 1 hour options = CurrentOptions(options); DestroyAndReopen(options); @@ -2942,7 +3165,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) { { options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB options.compaction_options_fifo.allow_compaction = false; - options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour + options.ttl = 1 * 60 * 60; // 1 hour options = CurrentOptions(options); DestroyAndReopen(options); @@ -2984,7 +3207,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) { options.write_buffer_size = 10 << 10; // 10KB options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB options.compaction_options_fifo.allow_compaction = false; - options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour + options.ttl = 1 * 60 * 60; // 1 hour options = CurrentOptions(options); DestroyAndReopen(options); @@ -3021,7 +3244,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) { { options.compaction_options_fifo.max_table_files_size = 150 << 10; // 150KB options.compaction_options_fifo.allow_compaction = true; - options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour + options.ttl = 1 * 60 * 60; // 1 hour options.level0_file_num_compaction_trigger = 6; options = CurrentOptions(options); DestroyAndReopen(options); @@ -3065,7 +3288,7 @@ TEST_F(DBTest, FIFOCompactionWithTTLTest) { options.write_buffer_size = 20 << 10; // 20K options.compaction_options_fifo.max_table_files_size = 1500 << 10; // 1.5MB options.compaction_options_fifo.allow_compaction = true; - options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour + options.ttl = 1 * 60 * 60; // 1 hour options.level0_file_num_compaction_trigger = 6; options = CurrentOptions(options); DestroyAndReopen(options); @@ -3257,8 +3480,14 @@ TEST_F(DBTest, SanitizeNumThreads) { (i < 4) ? Env::Priority::LOW : Env::Priority::HIGH); } - // Wait 100 milliseconds for they are scheduled. - env_->SleepForMicroseconds(100000); + // Wait until 10s for they are scheduled. + for (int i = 0; i < 10000; i++) { + if (options.env->GetThreadPoolQueueLen(Env::Priority::LOW) <= 1 && + options.env->GetThreadPoolQueueLen(Env::Priority::HIGH) <= 2) { + break; + } + env_->SleepForMicroseconds(1000); + } // pool size 3, total task 4. Queue size should be 1. ASSERT_EQ(1U, options.env->GetThreadPoolQueueLen(Env::Priority::LOW)); @@ -3295,6 +3524,56 @@ TEST_F(DBTest, WriteSingleThreadEntry) { } } +TEST_F(DBTest, ConcurrentFlushWAL) { + const size_t cnt = 100; + Options options; + WriteOptions wopt; + ReadOptions ropt; + for (bool two_write_queues : {false, true}) { + for (bool manual_wal_flush : {false, true}) { + options.two_write_queues = two_write_queues; + options.manual_wal_flush = manual_wal_flush; + options.create_if_missing = true; + DestroyAndReopen(options); + std::vector threads; + threads.emplace_back([&] { + for (size_t i = 0; i < cnt; i++) { + auto istr = ToString(i); + db_->Put(wopt, db_->DefaultColumnFamily(), "a" + istr, "b" + istr); + } + }); + if (two_write_queues) { + threads.emplace_back([&] { + for (size_t i = cnt; i < 2 * cnt; i++) { + auto istr = ToString(i); + WriteBatch batch; + batch.Put("a" + istr, "b" + istr); + dbfull()->WriteImpl(wopt, &batch, nullptr, nullptr, 0, true); + } + }); + } + threads.emplace_back([&] { + for (size_t i = 0; i < cnt * 100; i++) { // FlushWAL is faster than Put + db_->FlushWAL(false); + } + }); + for (auto& t : threads) { + t.join(); + } + options.create_if_missing = false; + // Recover from the wal and make sure that it is not corrupted + Reopen(options); + for (size_t i = 0; i < cnt; i++) { + PinnableSlice pval; + auto istr = ToString(i); + ASSERT_OK( + db_->Get(ropt, db_->DefaultColumnFamily(), "a" + istr, &pval)); + ASSERT_TRUE(pval == ("b" + istr)); + } + } + } +} + #ifndef ROCKSDB_LITE TEST_F(DBTest, DynamicMemtableOptions) { const uint64_t k64KB = 1 << 16; @@ -3388,7 +3667,7 @@ TEST_F(DBTest, DynamicMemtableOptions) { rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::DelayWrite:Wait", - [&](void* arg) { sleeping_task_low.WakeUp(); }); + [&](void* /*arg*/) { sleeping_task_low.WakeUp(); }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); while (!sleeping_task_low.WokenUp() && count < 256) { @@ -3481,12 +3760,16 @@ TEST_F(DBTest, GetThreadStatus) { const int kTestCount = 3; const unsigned int kHighPriCounts[kTestCount] = {3, 2, 5}; const unsigned int kLowPriCounts[kTestCount] = {10, 15, 3}; + const unsigned int kBottomPriCounts[kTestCount] = {2, 1, 4}; 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); + env_->SetBackgroundThreads(kBottomPriCounts[test], Env::BOTTOM); // Wait to ensure the all threads has been registered unsigned int thread_type_counts[ThreadStatus::NUM_THREAD_TYPES]; + // TODO(ajkr): it'd be better if SetBackgroundThreads returned only after + // all threads have been registered. // Try up to 60 seconds. for (int num_try = 0; num_try < 60000; num_try++) { env_->SleepForMicroseconds(1000); @@ -3501,20 +3784,21 @@ TEST_F(DBTest, GetThreadStatus) { if (thread_type_counts[ThreadStatus::HIGH_PRIORITY] == kHighPriCounts[test] && thread_type_counts[ThreadStatus::LOW_PRIORITY] == - kLowPriCounts[test]) { + kLowPriCounts[test] && + thread_type_counts[ThreadStatus::BOTTOM_PRIORITY] == + kBottomPriCounts[test]) { break; } } - // Verify the total number of threades - ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY] + - thread_type_counts[ThreadStatus::LOW_PRIORITY], - kHighPriCounts[test] + kLowPriCounts[test]); // Verify the number of high-priority threads ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY], kHighPriCounts[test]); // Verify the number of low-priority threads ASSERT_EQ(thread_type_counts[ThreadStatus::LOW_PRIORITY], kLowPriCounts[test]); + // Verify the number of bottom-priority threads + ASSERT_EQ(thread_type_counts[ThreadStatus::BOTTOM_PRIORITY], + kBottomPriCounts[test]); } if (i == 0) { // repeat the test with multiple column families @@ -4219,7 +4503,7 @@ TEST_F(DBTest, DynamicCompactionOptions) { // Clean up memtable and L0. Block compaction threads. If continue to write // and flush memtables. We should see put stop after 8 memtable flushes // since level0_stop_writes_trigger = 8 - dbfull()->TEST_FlushMemTable(true); + dbfull()->TEST_FlushMemTable(true, true); dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); // Block compaction test::SleepingBackgroundTask sleeping_task_low; @@ -4232,7 +4516,7 @@ TEST_F(DBTest, DynamicCompactionOptions) { WriteOptions wo; while (count < 64) { ASSERT_OK(Put(Key(count), RandomString(&rnd, 1024), wo)); - dbfull()->TEST_FlushMemTable(true); + dbfull()->TEST_FlushMemTable(true, true); count++; if (dbfull()->TEST_write_controler().IsStopped()) { sleeping_task_low.WakeUp(); @@ -4260,7 +4544,7 @@ TEST_F(DBTest, DynamicCompactionOptions) { count = 0; while (count < 64) { ASSERT_OK(Put(Key(count), RandomString(&rnd, 1024), wo)); - dbfull()->TEST_FlushMemTable(true); + dbfull()->TEST_FlushMemTable(true, true); count++; if (dbfull()->TEST_write_controler().IsStopped()) { sleeping_task_low.WakeUp(); @@ -4302,6 +4586,141 @@ TEST_F(DBTest, DynamicCompactionOptions) { dbfull()->TEST_WaitForCompact(); ASSERT_LT(NumTableFilesAtLevel(0), 4); } + +// Test dynamic FIFO compaction options. +// This test covers just option parsing and makes sure that the options are +// correctly assigned. Also look at DBOptionsTest.SetFIFOCompactionOptions +// test which makes sure that the FIFO compaction funcionality is working +// as expected on dynamically changing the options. +// Even more FIFOCompactionTests are at DBTest.FIFOCompaction* . +TEST_F(DBTest, DynamicFIFOCompactionOptions) { + Options options; + options.create_if_missing = true; + DestroyAndReopen(options); + + // Initial defaults + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 1024 * 1024 * 1024); + ASSERT_EQ(dbfull()->GetOptions().ttl, 0); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + false); + + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_fifo", "{max_table_files_size=23;}"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 23); + ASSERT_EQ(dbfull()->GetOptions().ttl, 0); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + false); + + ASSERT_OK(dbfull()->SetOptions({{"ttl", "97"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 23); + ASSERT_EQ(dbfull()->GetOptions().ttl, 97); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + false); + + ASSERT_OK(dbfull()->SetOptions({{"ttl", "203"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 23); + ASSERT_EQ(dbfull()->GetOptions().ttl, 203); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + false); + + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_fifo", "{allow_compaction=true;}"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 23); + ASSERT_EQ(dbfull()->GetOptions().ttl, 203); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + true); + + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_fifo", "{max_table_files_size=31;}"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 31); + ASSERT_EQ(dbfull()->GetOptions().ttl, 203); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + true); + + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_fifo", + "{max_table_files_size=51;allow_compaction=true;}"}})); + ASSERT_OK(dbfull()->SetOptions({{"ttl", "49"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size, + 51); + ASSERT_EQ(dbfull()->GetOptions().ttl, 49); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction, + true); +} + +TEST_F(DBTest, DynamicUniversalCompactionOptions) { + Options options; + options.create_if_missing = true; + DestroyAndReopen(options); + + // Initial defaults + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 1); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width, + 2); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width, + UINT_MAX); + ASSERT_EQ(dbfull() + ->GetOptions() + .compaction_options_universal.max_size_amplification_percent, + 200); + ASSERT_EQ(dbfull() + ->GetOptions() + .compaction_options_universal.compression_size_percent, + -1); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style, + kCompactionStopStyleTotalSize); + ASSERT_EQ( + dbfull()->GetOptions().compaction_options_universal.allow_trivial_move, + false); + + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_universal", "{size_ratio=7;}"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 7); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width, + 2); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width, + UINT_MAX); + ASSERT_EQ(dbfull() + ->GetOptions() + .compaction_options_universal.max_size_amplification_percent, + 200); + ASSERT_EQ(dbfull() + ->GetOptions() + .compaction_options_universal.compression_size_percent, + -1); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style, + kCompactionStopStyleTotalSize); + ASSERT_EQ( + dbfull()->GetOptions().compaction_options_universal.allow_trivial_move, + false); + + ASSERT_OK(dbfull()->SetOptions( + {{"compaction_options_universal", "{min_merge_width=11;}"}})); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 7); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width, + 11); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width, + UINT_MAX); + ASSERT_EQ(dbfull() + ->GetOptions() + .compaction_options_universal.max_size_amplification_percent, + 200); + ASSERT_EQ(dbfull() + ->GetOptions() + .compaction_options_universal.compression_size_percent, + -1); + ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style, + kCompactionStopStyleTotalSize); + ASSERT_EQ( + dbfull()->GetOptions().compaction_options_universal.allow_trivial_move, + false); +} #endif // ROCKSDB_LITE TEST_F(DBTest, FileCreationRandomFailure) { @@ -4365,6 +4784,7 @@ TEST_F(DBTest, FileCreationRandomFailure) { } #ifndef ROCKSDB_LITE + TEST_F(DBTest, DynamicMiscOptions) { // Test max_sequential_skip_in_iterations Options options; @@ -4515,7 +4935,7 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) { options.compression = comp; DestroyAndReopen(options); - int kNumKeysWritten = 100000; + int kNumKeysWritten = 1000; Random rnd(301); for (int i = 0; i < kNumKeysWritten; ++i) { @@ -4596,14 +5016,14 @@ class DelayedMergeOperator : public MergeOperator { public: explicit DelayedMergeOperator(DBTest* d) : db_test_(d) {} - virtual bool FullMergeV2(const MergeOperationInput& merge_in, - MergeOperationOutput* merge_out) const override { + bool FullMergeV2(const MergeOperationInput& /*merge_in*/, + MergeOperationOutput* merge_out) const override { db_test_->env_->addon_time_.fetch_add(1000); merge_out->new_value = ""; return true; } - virtual const char* Name() const override { return "DelayedMergeOperator"; } + const char* Name() const override { return "DelayedMergeOperator"; } }; TEST_F(DBTest, MergeTestTime) { @@ -4682,6 +5102,7 @@ TEST_P(DBTestWithParam, FilterCompactionTimeTest) { options.disable_auto_compactions = true; options.create_if_missing = true; options.statistics = rocksdb::CreateDBStatistics(); + options.statistics->set_stats_level(kExceptTimeForMutex); options.max_subcompactions = max_subcompactions_; DestroyAndReopen(options); @@ -4738,7 +5159,7 @@ TEST_F(DBTest, EmptyCompactedDB) { TEST_F(DBTest, SuggestCompactRangeTest) { class CompactionFilterFactoryGetContext : public CompactionFilterFactory { public: - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { saved_context = context; std::unique_ptr empty_filter; @@ -4908,56 +5329,148 @@ TEST_F(DBTest, PromoteL0Failure) { status = experimental::PromoteL0(db_, db_->DefaultColumnFamily()); ASSERT_TRUE(status.IsInvalidArgument()); } -#endif // ROCKSDB_LITE // Github issue #596 -TEST_F(DBTest, HugeNumberOfLevels) { +TEST_F(DBTest, CompactRangeWithEmptyBottomLevel) { + const int kNumLevels = 2; + const int kNumL0Files = 2; 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; + options.disable_auto_compactions = true; + options.num_levels = kNumLevels; DestroyAndReopen(options); Random rnd(301); - for (int i = 0; i < 300000; ++i) { - ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024))); + for (int i = 0; i < kNumL0Files; ++i) { + ASSERT_OK(Put(Key(0), RandomString(&rnd, 1024))); + Flush(); } + ASSERT_EQ(NumTableFilesAtLevel(0), kNumL0Files); + ASSERT_EQ(NumTableFilesAtLevel(1), 0); ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_EQ(NumTableFilesAtLevel(0), 0); + ASSERT_EQ(NumTableFilesAtLevel(1), kNumL0Files); } +#endif // ROCKSDB_LITE TEST_F(DBTest, AutomaticConflictsWithManualCompaction) { + const int kNumL0Files = 50; 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.level0_file_num_compaction_trigger = 4; + // never slowdown / stop + options.level0_slowdown_writes_trigger = 999999; + options.level0_stop_writes_trigger = 999999; 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))); - } - + // schedule automatic compactions after the manual one starts, but before it + // finishes to ensure conflict. + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::BackgroundCompaction:Start", + "DBTest::AutomaticConflictsWithManualCompaction:PrePuts"}, + {"DBTest::AutomaticConflictsWithManualCompaction:PostPuts", + "DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}}); std::atomic callback_count(0); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction()::Conflict", - [&](void* arg) { callback_count.fetch_add(1); }); + "DBImpl::MaybeScheduleFlushOrCompaction:Conflict", + [&](void* /*arg*/) { callback_count.fetch_add(1); }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); - CompactRangeOptions croptions; - croptions.exclusive_manual_compaction = false; - ASSERT_OK(db_->CompactRange(croptions, nullptr, nullptr)); + + Random rnd(301); + for (int i = 0; i < 2; ++i) { + // put two keys to ensure no trivial move + for (int j = 0; j < 2; ++j) { + ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024))); + } + ASSERT_OK(Flush()); + } + port::Thread manual_compaction_thread([this]() { + CompactRangeOptions croptions; + croptions.exclusive_manual_compaction = true; + ASSERT_OK(db_->CompactRange(croptions, nullptr, nullptr)); + }); + + TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PrePuts"); + for (int i = 0; i < kNumL0Files; ++i) { + // put two keys to ensure no trivial move + for (int j = 0; j < 2; ++j) { + ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024))); + } + ASSERT_OK(Flush()); + } + TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PostPuts"); + ASSERT_GE(callback_count.load(), 1); - rocksdb::SyncPoint::GetInstance()->DisableProcessing(); - for (int i = 0; i < 300000; ++i) { + for (int i = 0; i < 2; ++i) { ASSERT_NE("NOT_FOUND", Get(Key(i))); } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + manual_compaction_thread.join(); + dbfull()->TEST_WaitForCompact(); +} + +#ifndef ROCKSDB_LITE +TEST_F(DBTest, CompactFilesShouldTriggerAutoCompaction) { + Options options = CurrentOptions(); + options.max_background_compactions = 1; + options.level0_file_num_compaction_trigger = 4; + options.level0_slowdown_writes_trigger = 36; + options.level0_stop_writes_trigger = 36; + DestroyAndReopen(options); + + // generate files for manual compaction + Random rnd(301); + for (int i = 0; i < 2; ++i) { + // put two keys to ensure no trivial move + for (int j = 0; j < 2; ++j) { + ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024))); + } + ASSERT_OK(Flush()); + } + + rocksdb::ColumnFamilyMetaData cf_meta_data; + db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data); + + std::vector input_files; + input_files.push_back(cf_meta_data.levels[0].files[0].name); + + SyncPoint::GetInstance()->LoadDependency({ + {"CompactFilesImpl:0", + "DBTest::CompactFilesShouldTriggerAutoCompaction:Begin"}, + {"DBTest::CompactFilesShouldTriggerAutoCompaction:End", + "CompactFilesImpl:1"}, + }); + + SyncPoint::GetInstance()->EnableProcessing(); + + port::Thread manual_compaction_thread([&]() { + auto s = db_->CompactFiles(CompactionOptions(), + db_->DefaultColumnFamily(), input_files, 0); + }); + + TEST_SYNC_POINT( + "DBTest::CompactFilesShouldTriggerAutoCompaction:Begin"); + // generate enough files to trigger compaction + for (int i = 0; i < 20; ++i) { + for (int j = 0; j < 2; ++j) { + ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024))); + } + ASSERT_OK(Flush()); + } + db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data); + ASSERT_GT(cf_meta_data.levels[0].files.size(), + options.level0_file_num_compaction_trigger); + TEST_SYNC_POINT( + "DBTest::CompactFilesShouldTriggerAutoCompaction:End"); + + manual_compaction_thread.join(); + dbfull()->TEST_WaitForCompact(); + + db_->GetColumnFamilyMetaData(db_->DefaultColumnFamily(), &cf_meta_data); + ASSERT_LE(cf_meta_data.levels[0].files.size(), + options.level0_file_num_compaction_trigger); } +#endif // ROCKSDB_LITE // Github issue #595 // Large write batch with column families @@ -5146,7 +5659,7 @@ TEST_F(DBTest, HardLimit) { std::atomic callback_count(0); rocksdb::SyncPoint::GetInstance()->SetCallBack("DBImpl::DelayWrite:Wait", - [&](void* arg) { + [&](void* /*arg*/) { callback_count.fetch_add(1); sleeping_task_low.WakeUp(); }); @@ -5171,7 +5684,23 @@ TEST_F(DBTest, HardLimit) { sleeping_task_low.WaitUntilDone(); } -#ifndef ROCKSDB_LITE +#if !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION) +class WriteStallListener : public EventListener { + public: + WriteStallListener() : condition_(WriteStallCondition::kNormal) {} + void OnStallConditionsChanged(const WriteStallInfo& info) override { + MutexLock l(&mutex_); + condition_ = info.condition.cur; + } + bool CheckCondition(WriteStallCondition expected) { + MutexLock l(&mutex_); + return expected == condition_; + } + private: + port::Mutex mutex_; + WriteStallCondition condition_; +}; + TEST_F(DBTest, SoftLimit) { Options options = CurrentOptions(); options.env = env_; @@ -5187,6 +5716,43 @@ TEST_F(DBTest, SoftLimit) { options.max_bytes_for_level_multiplier = 10; options.max_background_compactions = 1; options.compression = kNoCompression; + WriteStallListener* listener = new WriteStallListener(); + options.listeners.emplace_back(listener); + + // FlushMemtable with opt.wait=true does not wait for + // `OnStallConditionsChanged` being called. The event listener is triggered + // on `JobContext::Clean`, which happens after flush result is installed. + // We use sync point to create a custom WaitForFlush that waits for + // context cleanup. + port::Mutex flush_mutex; + port::CondVar flush_cv(&flush_mutex); + bool flush_finished = false; + auto InstallFlushCallback = [&]() { + { + MutexLock l(&flush_mutex); + flush_finished = false; + } + SyncPoint::GetInstance()->SetCallBack( + "DBImpl::BackgroundCallFlush:ContextCleanedUp", [&](void*) { + { + MutexLock l(&flush_mutex); + flush_finished = true; + } + flush_cv.SignalAll(); + }); + }; + auto WaitForFlush = [&]() { + { + MutexLock l(&flush_mutex); + while (!flush_finished) { + flush_cv.Wait(); + } + } + SyncPoint::GetInstance()->ClearCallBack( + "DBImpl::BackgroundCallFlush:ContextCleanedUp"); + }; + + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); Reopen(options); @@ -5194,7 +5760,7 @@ TEST_F(DBTest, SoftLimit) { for (int i = 0; i < 72; i++) { Put(Key(i), std::string(5000, 'x')); if (i % 10 == 0) { - Flush(); + dbfull()->TEST_FlushMemTable(true, true); } } dbfull()->TEST_WaitForCompact(); @@ -5204,7 +5770,7 @@ TEST_F(DBTest, SoftLimit) { for (int i = 0; i < 72; i++) { Put(Key(i), std::string(5000, 'x')); if (i % 10 == 0) { - Flush(); + dbfull()->TEST_FlushMemTable(true, true); } } dbfull()->TEST_WaitForCompact(); @@ -5223,9 +5789,12 @@ TEST_F(DBTest, SoftLimit) { Put(Key(i), std::string(5000, 'x')); Put(Key(100 - i), std::string(5000, 'x')); // Flush the file. File size is around 30KB. - Flush(); + InstallFlushCallback(); + dbfull()->TEST_FlushMemTable(true, true); + WaitForFlush(); } ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); + ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed)); sleeping_task_low.WakeUp(); sleeping_task_low.WaitUntilDone(); @@ -5236,18 +5805,17 @@ TEST_F(DBTest, SoftLimit) { // The L1 file size is around 30KB. ASSERT_EQ(NumTableFilesAtLevel(1), 1); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); + ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal)); // Only allow one compactin going through. rocksdb::SyncPoint::GetInstance()->SetCallBack( - "BackgroundCallCompaction:0", [&](void* arg) { + "BackgroundCallCompaction:0", [&](void* /*arg*/) { // Schedule a sleeping task. sleeping_task_low.Reset(); env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, Env::Priority::LOW); }); - rocksdb::SyncPoint::GetInstance()->EnableProcessing(); - env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task_low, Env::Priority::LOW); sleeping_task_low.WaitUntilSleeping(); @@ -5256,7 +5824,9 @@ TEST_F(DBTest, SoftLimit) { Put(Key(10 + i), std::string(5000, 'x')); Put(Key(90 - i), std::string(5000, 'x')); // Flush the file. File size is around 30KB. - Flush(); + InstallFlushCallback(); + dbfull()->TEST_FlushMemTable(true, true); + WaitForFlush(); } // Wake up sleep task to enable compaction to run and waits @@ -5270,13 +5840,16 @@ TEST_F(DBTest, SoftLimit) { // doesn't trigger soft_pending_compaction_bytes_limit ASSERT_EQ(NumTableFilesAtLevel(1), 1); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); + ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal)); // Create 3 L0 files, making score of L0 to be 3, higher than L0. for (int i = 0; i < 3; i++) { Put(Key(20 + i), std::string(5000, 'x')); Put(Key(80 - i), std::string(5000, 'x')); // Flush the file. File size is around 30KB. - Flush(); + InstallFlushCallback(); + dbfull()->TEST_FlushMemTable(true, true); + WaitForFlush(); } // Wake up sleep task to enable compaction to run and waits // for it to go to sleep state again to make sure one compaction @@ -5290,11 +5863,13 @@ TEST_F(DBTest, SoftLimit) { // triggerring soft_pending_compaction_bytes_limit ASSERT_EQ(NumTableFilesAtLevel(1), 1); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); + ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed)); sleeping_task_low.WakeUp(); sleeping_task_low.WaitUntilSleeping(); ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay()); + ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal)); // shrink level base so L2 will hit soft limit easier. ASSERT_OK(dbfull()->SetOptions({ @@ -5304,6 +5879,7 @@ TEST_F(DBTest, SoftLimit) { Put("", ""); Flush(); ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay()); + ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed)); sleeping_task_low.WaitUntilSleeping(); rocksdb::SyncPoint::GetInstance()->DisableProcessing(); @@ -5345,7 +5921,7 @@ TEST_F(DBTest, LastWriteBufferDelay) { sleeping_task.WakeUp(); sleeping_task.WaitUntilDone(); } -#endif // ROCKSDB_LITE +#endif // !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION) TEST_F(DBTest, FailWhenCompressionNotSupportedTest) { CompressionType compressions[] = {kZlibCompression, kBZip2Compression, @@ -5489,6 +6065,50 @@ TEST_F(DBTest, PauseBackgroundWorkTest) { // now it's done ASSERT_TRUE(done.load()); } + +// Keep spawning short-living threads that create an iterator and quit. +// Meanwhile in another thread keep flushing memtables. +// This used to cause a deadlock. +TEST_F(DBTest, ThreadLocalPtrDeadlock) { + std::atomic flushes_done{0}; + std::atomic threads_destroyed{0}; + auto done = [&] { + return flushes_done.load() > 10; + }; + + port::Thread flushing_thread([&] { + for (int i = 0; !done(); ++i) { + ASSERT_OK(db_->Put(WriteOptions(), Slice("hi"), + Slice(std::to_string(i).c_str()))); + ASSERT_OK(db_->Flush(FlushOptions())); + int cnt = ++flushes_done; + fprintf(stderr, "Flushed %d times\n", cnt); + } + }); + + std::vector thread_spawning_threads(10); + for (auto& t: thread_spawning_threads) { + t = port::Thread([&] { + while (!done()) { + { + port::Thread tmp_thread([&] { + auto it = db_->NewIterator(ReadOptions()); + delete it; + }); + tmp_thread.join(); + } + ++threads_destroyed; + } + }); + } + + for (auto& t: thread_spawning_threads) { + t.join(); + } + flushing_thread.join(); + fprintf(stderr, "Done. Flushed %d times, destroyed %d threads\n", + flushes_done.load(), threads_destroyed.load()); +} } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_test2.cc b/thirdparty/rocksdb/db/db_test2.cc index 30afd5a690..6a00300eb5 100644 --- a/thirdparty/rocksdb/db/db_test2.cc +++ b/thirdparty/rocksdb/db/db_test2.cc @@ -11,6 +11,7 @@ #include #include "db/db_test_util.h" +#include "db/read_callback.h" #include "port/port.h" #include "port/stack_trace.h" #include "rocksdb/persistent_cache.h" @@ -29,7 +30,7 @@ class PrefixFullBloomWithReverseComparator public: PrefixFullBloomWithReverseComparator() : DBTestBase("/prefix_bloom_reverse") {} - virtual void SetUp() override { if_cache_filter_ = GetParam(); } + void SetUp() override { if_cache_filter_ = GetParam(); } bool if_cache_filter_; }; @@ -60,7 +61,7 @@ TEST_P(PrefixFullBloomWithReverseComparator, bbto.block_cache->EraseUnRefEntries(); } - unique_ptr iter(db_->NewIterator(ReadOptions())); + std::unique_ptr iter(db_->NewIterator(ReadOptions())); iter->Seek("bar345"); ASSERT_OK(iter->status()); ASSERT_TRUE(iter->Valid()); @@ -341,6 +342,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) { ASSERT_GE(cache->GetUsage(), 1024 * 1024); Close(); options.write_buffer_manager.reset(); + last_options_.write_buffer_manager.reset(); ASSERT_LT(cache->GetUsage(), 1024 * 1024); } rocksdb::SyncPoint::GetInstance()->DisableProcessing(); @@ -353,7 +355,7 @@ INSTANTIATE_TEST_CASE_P(DBTestSharedWriteBufferAcrossCFs, std::make_tuple(false, true))); TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) { - std::string dbname2 = test::TmpDir(env_) + "/db_shared_wb_db2"; + std::string dbname2 = test::PerThreadDBPath("db_shared_wb_db2"); Options options = CurrentOptions(); options.arena_block_size = 4096; // Avoid undeterministic value by malloc_usable_size(); @@ -453,6 +455,22 @@ TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } +TEST_F(DBTest2, TestWriteBufferNoLimitWithCache) { + Options options = CurrentOptions(); + options.arena_block_size = 4096; + std::shared_ptr cache = + NewLRUCache(LRUCacheOptions(10000000, 1, false, 0.0)); + options.write_buffer_size = 50000; // this is never hit + // Use a write buffer total size so that the soft limit is about + // 105000. + options.write_buffer_manager.reset(new WriteBufferManager(0, cache)); + Reopen(options); + + ASSERT_OK(Put("foo", "bar")); + // One dummy entry is 1MB. + ASSERT_GT(cache->GetUsage(), 500000); +} + namespace { void ValidateKeyExistence(DB* db, const std::vector& keys_must_exist, const std::vector& keys_must_not_exist) { @@ -497,9 +515,9 @@ TEST_F(DBTest2, WalFilterTest) { apply_option_at_record_index_(apply_option_for_record_index), current_record_index_(0) {} - virtual WalProcessingOption LogRecord(const WriteBatch& batch, - WriteBatch* new_batch, - bool* batch_changed) const override { + WalProcessingOption LogRecord(const WriteBatch& /*batch*/, + WriteBatch* /*new_batch*/, + bool* /*batch_changed*/) const override { WalFilter::WalProcessingOption option_to_return; if (current_record_index_ == apply_option_at_record_index_) { @@ -517,7 +535,7 @@ TEST_F(DBTest2, WalFilterTest) { return option_to_return; } - virtual const char* Name() const override { return "TestWalFilter"; } + const char* Name() const override { return "TestWalFilter"; } }; // Create 3 batches with two keys each @@ -669,7 +687,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) { : new_write_batch_(new_write_batch), num_keys_to_add_in_new_batch_(num_keys_to_add_in_new_batch), num_keys_added_(0) {} - virtual void Put(const Slice& key, const Slice& value) override { + void Put(const Slice& key, const Slice& value) override { if (num_keys_added_ < num_keys_to_add_in_new_batch_) { new_write_batch_->Put(key, value); ++num_keys_added_; @@ -693,9 +711,9 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) { num_keys_to_add_in_new_batch_(num_keys_to_add_in_new_batch), current_record_index_(0) {} - virtual WalProcessingOption LogRecord(const WriteBatch& batch, - WriteBatch* new_batch, - bool* batch_changed) const override { + WalProcessingOption LogRecord(const WriteBatch& batch, + WriteBatch* new_batch, + bool* batch_changed) const override { if (current_record_index_ >= change_records_from_index_) { ChangeBatchHandler handler(new_batch, num_keys_to_add_in_new_batch_); batch.Iterate(&handler); @@ -711,9 +729,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) { return WalProcessingOption::kContinueProcessing; } - virtual const char* Name() const override { - return "TestWalFilterWithChangeBatch"; - } + const char* Name() const override { return "TestWalFilterWithChangeBatch"; } }; std::vector> batch_keys(3); @@ -791,18 +807,17 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) { TEST_F(DBTest2, WalFilterTestWithChangeBatchExtraKeys) { class TestWalFilterWithChangeBatchAddExtraKeys : public WalFilter { public: - virtual WalProcessingOption LogRecord(const WriteBatch& batch, - WriteBatch* new_batch, - bool* batch_changed) const override { - *new_batch = batch; - new_batch->Put("key_extra", "value_extra"); - *batch_changed = true; - return WalProcessingOption::kContinueProcessing; - } - - virtual const char* Name() const override { - return "WalFilterTestWithChangeBatchExtraKeys"; - } + WalProcessingOption LogRecord(const WriteBatch& batch, WriteBatch* new_batch, + bool* batch_changed) const override { + *new_batch = batch; + new_batch->Put("key_extra", "value_extra"); + *batch_changed = true; + return WalProcessingOption::kContinueProcessing; + } + + const char* Name() const override { + return "WalFilterTestWithChangeBatchExtraKeys"; + } }; std::vector> batch_keys(3); @@ -866,19 +881,19 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) { // for verification against the keys we expect. std::map> cf_wal_keys_; public: - virtual void ColumnFamilyLogNumberMap( - const std::map& cf_lognumber_map, - const std::map& cf_name_id_map) override { - cf_log_number_map_ = cf_lognumber_map; - cf_name_id_map_ = cf_name_id_map; - } - - virtual WalProcessingOption LogRecordFound(unsigned long long log_number, - const std::string& log_file_name, - const WriteBatch& batch, - WriteBatch* new_batch, - bool* batch_changed) override { - class LogRecordBatchHandler : public WriteBatch::Handler { + void ColumnFamilyLogNumberMap( + const std::map& cf_lognumber_map, + const std::map& cf_name_id_map) override { + cf_log_number_map_ = cf_lognumber_map; + cf_name_id_map_ = cf_name_id_map; + } + + WalProcessingOption LogRecordFound(unsigned long long log_number, + const std::string& /*log_file_name*/, + const WriteBatch& batch, + WriteBatch* /*new_batch*/, + bool* /*batch_changed*/) override { + class LogRecordBatchHandler : public WriteBatch::Handler { private: const std::map & cf_log_number_map_; std::map> & cf_wal_keys_; @@ -891,8 +906,8 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) { cf_wal_keys_(cf_wal_keys), log_number_(current_log_number){} - virtual Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& /*value*/) override { + Status PutCF(uint32_t column_family_id, const Slice& key, + const Slice& /*value*/) override { auto it = cf_log_number_map_.find(column_family_id); assert(it != cf_log_number_map_.end()); unsigned long long log_number_for_cf = it->second; @@ -910,11 +925,11 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) { batch.Iterate(&handler); return WalProcessingOption::kContinueProcessing; - } + } - virtual const char* Name() const override { - return "WalFilterTestWithColumnFamilies"; - } + const char* Name() const override { + return "WalFilterTestWithColumnFamilies"; + } const std::map>& GetColumnFamilyKeys() { return cf_wal_keys_; @@ -1021,7 +1036,10 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) { ASSERT_TRUE(index == keys_cf.size()); } -TEST_F(DBTest2, PresetCompressionDict) { +// Temporarily disable it because the test is flaky. +TEST_F(DBTest2, DISABLED_PresetCompressionDict) { + // Verifies that compression ratio improves when dictionary is enabled, and + // improves even further when the dictionary is trained by ZSTD. const size_t kBlockSizeBytes = 4 << 10; const size_t kL0FileBytes = 128 << 10; const size_t kApproxPerBlockOverheadBytes = 50; @@ -1031,7 +1049,6 @@ TEST_F(DBTest2, PresetCompressionDict) { options.env = CurrentOptions().env; // Make sure to use any custom env that the test is configured with. options.allow_concurrent_memtable_write = false; options.arena_block_size = kBlockSizeBytes; - options.compaction_style = kCompactionStyleUniversal; options.create_if_missing = true; options.disable_auto_compactions = true; options.level0_file_num_compaction_trigger = kNumL0Files; @@ -1058,43 +1075,63 @@ TEST_F(DBTest2, PresetCompressionDict) { for (auto compression_type : compression_types) { options.compression = compression_type; size_t prev_out_bytes; - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < 3; ++i) { // First iteration: compress without preset dictionary // Second iteration: compress with preset dictionary - // To make sure the compression dictionary was actually used, we verify - // the compressed size is smaller in the second iteration. Also in the - // second iteration, verify the data we get out is the same data we put - // in. - if (i) { - options.compression_opts.max_dict_bytes = kBlockSizeBytes; - } else { - options.compression_opts.max_dict_bytes = 0; + // Third iteration (zstd only): compress with zstd-trained dictionary + // + // To make sure the compression dictionary has the intended effect, we + // verify the compressed size is smaller in successive iterations. Also in + // the non-first iterations, verify the data we get out is the same data + // we put in. + switch (i) { + case 0: + options.compression_opts.max_dict_bytes = 0; + options.compression_opts.zstd_max_train_bytes = 0; + break; + case 1: + options.compression_opts.max_dict_bytes = 4 * kBlockSizeBytes; + options.compression_opts.zstd_max_train_bytes = 0; + break; + case 2: + if (compression_type != kZSTD) { + continue; + } + options.compression_opts.max_dict_bytes = 4 * kBlockSizeBytes; + options.compression_opts.zstd_max_train_bytes = kL0FileBytes; + break; + default: + assert(false); } options.statistics = rocksdb::CreateDBStatistics(); options.table_factory.reset(NewBlockBasedTableFactory(table_options)); CreateAndReopenWithCF({"pikachu"}, options); Random rnd(301); - std::string seq_data = - RandomString(&rnd, kBlockSizeBytes - kApproxPerBlockOverheadBytes); + std::string seq_datas[10]; + for (int j = 0; j < 10; ++j) { + seq_datas[j] = + RandomString(&rnd, kBlockSizeBytes - kApproxPerBlockOverheadBytes); + } ASSERT_EQ(0, NumTableFilesAtLevel(0, 1)); for (int j = 0; j < kNumL0Files; ++j) { for (size_t k = 0; k < kL0FileBytes / kBlockSizeBytes + 1; ++k) { - ASSERT_OK(Put(1, Key(static_cast( - j * (kL0FileBytes / kBlockSizeBytes) + k)), - seq_data)); + auto key_num = j * (kL0FileBytes / kBlockSizeBytes) + k; + ASSERT_OK(Put(1, Key(static_cast(key_num)), + seq_datas[(key_num / 10) % 10])); } dbfull()->TEST_WaitForFlushMemTable(handles_[1]); ASSERT_EQ(j + 1, NumTableFilesAtLevel(0, 1)); } - db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr); + dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1], + true /* disallow_trivial_move */); ASSERT_EQ(0, NumTableFilesAtLevel(0, 1)); ASSERT_GT(NumTableFilesAtLevel(1, 1), 0); size_t out_bytes = 0; std::vector files; - GetSstFiles(dbname_, &files); + GetSstFiles(env_, dbname_, &files); for (const auto& file : files) { uint64_t curr_bytes; env_->GetFileSize(dbname_ + "/" + file, &curr_bytes); @@ -1103,7 +1140,7 @@ TEST_F(DBTest2, PresetCompressionDict) { for (size_t j = 0; j < kNumL0Files * (kL0FileBytes / kBlockSizeBytes); j++) { - ASSERT_EQ(seq_data, Get(1, Key(static_cast(j)))); + ASSERT_EQ(seq_datas[(j / 10) % 10], Get(1, Key(static_cast(j)))); } if (i) { ASSERT_GT(prev_out_bytes, out_bytes); @@ -1114,6 +1151,70 @@ TEST_F(DBTest2, PresetCompressionDict) { } } +TEST_F(DBTest2, PresetCompressionDictLocality) { + if (!ZSTD_Supported()) { + return; + } + // Verifies that compression dictionary is generated from local data. The + // verification simply checks all output SSTs have different compression + // dictionaries. We do not verify effectiveness as that'd likely be flaky in + // the future. + const int kNumEntriesPerFile = 1 << 10; // 1KB + const int kNumBytesPerEntry = 1 << 10; // 1KB + const int kNumFiles = 4; + Options options = CurrentOptions(); + options.compression = kZSTD; + options.compression_opts.max_dict_bytes = 1 << 14; // 16KB + options.compression_opts.zstd_max_train_bytes = 1 << 18; // 256KB + options.statistics = rocksdb::CreateDBStatistics(); + options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry; + BlockBasedTableOptions table_options; + table_options.cache_index_and_filter_blocks = true; + options.table_factory.reset(new BlockBasedTableFactory(table_options)); + Reopen(options); + + Random rnd(301); + for (int i = 0; i < kNumFiles; ++i) { + for (int j = 0; j < kNumEntriesPerFile; ++j) { + ASSERT_OK(Put(Key(i * kNumEntriesPerFile + j), + RandomString(&rnd, kNumBytesPerEntry))); + } + ASSERT_OK(Flush()); + MoveFilesToLevel(1); + ASSERT_EQ(NumTableFilesAtLevel(1), i + 1); + } + + // Store all the dictionaries generated during a full compaction. + std::vector compression_dicts; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "BlockBasedTableBuilder::WriteCompressionDictBlock:RawDict", + [&](void* arg) { + compression_dicts.emplace_back(static_cast(arg)->ToString()); + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + CompactRangeOptions compact_range_opts; + compact_range_opts.bottommost_level_compaction = + BottommostLevelCompaction::kForce; + ASSERT_OK(db_->CompactRange(compact_range_opts, nullptr, nullptr)); + + // Dictionary compression should not be so good as to compress four totally + // random files into one. If it does then there's probably something wrong + // with the test. + ASSERT_GT(NumTableFilesAtLevel(1), 1); + + // Furthermore, there should be one compression dictionary generated per file. + // And they should all be different from each other. + ASSERT_EQ(NumTableFilesAtLevel(1), + static_cast(compression_dicts.size())); + for (size_t i = 1; i < compression_dicts.size(); ++i) { + std::string& a = compression_dicts[i - 1]; + std::string& b = compression_dicts[i]; + size_t alen = a.size(); + size_t blen = b.size(); + ASSERT_TRUE(alen != blen || memcmp(a.data(), b.data(), alen) != 0); + } +} + class CompactionCompressionListener : public EventListener { public: explicit CompactionCompressionListener(Options* db_options) @@ -1133,7 +1234,7 @@ class CompactionCompressionListener : public EventListener { } if (db_options_->bottommost_compression != kDisableCompressionOption && - ci.output_level == bottommost_level && ci.output_level >= 2) { + ci.output_level == bottommost_level) { ASSERT_EQ(ci.compression, db_options_->bottommost_compression); } else if (db_options_->compression_per_level.size() != 0) { ASSERT_EQ(ci.compression, @@ -1210,14 +1311,23 @@ TEST_F(DBTest2, CompressionOptions) { class CompactionStallTestListener : public EventListener { public: - CompactionStallTestListener() : compacted_files_cnt_(0) {} + CompactionStallTestListener() : compacting_files_cnt_(0), compacted_files_cnt_(0) {} - void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override { + void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override { + ASSERT_EQ(ci.cf_name, "default"); + ASSERT_EQ(ci.base_input_level, 0); + ASSERT_EQ(ci.compaction_reason, CompactionReason::kLevelL0FilesNum); + compacting_files_cnt_ += ci.input_files.size(); + } + + void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override { ASSERT_EQ(ci.cf_name, "default"); ASSERT_EQ(ci.base_input_level, 0); ASSERT_EQ(ci.compaction_reason, CompactionReason::kLevelL0FilesNum); compacted_files_cnt_ += ci.input_files.size(); } + + std::atomic compacting_files_cnt_; std::atomic compacted_files_cnt_; }; @@ -1226,6 +1336,8 @@ TEST_F(DBTest2, CompactionStall) { {{"DBImpl::BGWorkCompaction", "DBTest2::CompactionStall:0"}, {"DBImpl::BGWorkCompaction", "DBTest2::CompactionStall:1"}, {"DBTest2::CompactionStall:2", + "DBImpl::NotifyOnCompactionBegin::UnlockMutex"}, + {"DBTest2::CompactionStall:3", "DBImpl::NotifyOnCompactionCompleted::UnlockMutex"}}); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); @@ -1267,14 +1379,18 @@ TEST_F(DBTest2, CompactionStall) { // Wait for another compaction to be triggered TEST_SYNC_POINT("DBTest2::CompactionStall:1"); - // Hold NotifyOnCompactionCompleted in the unlock mutex section + // Hold NotifyOnCompactionBegin in the unlock mutex section TEST_SYNC_POINT("DBTest2::CompactionStall:2"); + // Hold NotifyOnCompactionCompleted in the unlock mutex section + TEST_SYNC_POINT("DBTest2::CompactionStall:3"); + dbfull()->TEST_WaitForCompact(); ASSERT_LT(NumTableFilesAtLevel(0), options.level0_file_num_compaction_trigger); ASSERT_GT(listener->compacted_files_cnt_.load(), 10 - options.level0_file_num_compaction_trigger); + ASSERT_EQ(listener->compacting_files_cnt_.load(), listener->compacted_files_cnt_.load()); rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } @@ -1296,11 +1412,49 @@ TEST_F(DBTest2, FirstSnapshotTest) { db_->ReleaseSnapshot(s1); } -class PinL0IndexAndFilterBlocksTest : public DBTestBase, - public testing::WithParamInterface { +#ifndef ROCKSDB_LITE +TEST_F(DBTest2, DuplicateSnapshot) { + Options options; + options = CurrentOptions(options); + std::vector snapshots; + DBImpl* dbi = reinterpret_cast(db_); + SequenceNumber oldest_ww_snap, first_ww_snap; + + Put("k", "v"); // inc seq + snapshots.push_back(db_->GetSnapshot()); + snapshots.push_back(db_->GetSnapshot()); + Put("k", "v"); // inc seq + snapshots.push_back(db_->GetSnapshot()); + snapshots.push_back(dbi->GetSnapshotForWriteConflictBoundary()); + first_ww_snap = snapshots.back()->GetSequenceNumber(); + Put("k", "v"); // inc seq + snapshots.push_back(dbi->GetSnapshotForWriteConflictBoundary()); + snapshots.push_back(db_->GetSnapshot()); + Put("k", "v"); // inc seq + snapshots.push_back(db_->GetSnapshot()); + + { + InstrumentedMutexLock l(dbi->mutex()); + auto seqs = dbi->snapshots().GetAll(&oldest_ww_snap); + ASSERT_EQ(seqs.size(), 4); // duplicates are not counted + ASSERT_EQ(oldest_ww_snap, first_ww_snap); + } + + for (auto s : snapshots) { + db_->ReleaseSnapshot(s); + } +} +#endif // ROCKSDB_LITE + +class PinL0IndexAndFilterBlocksTest + : public DBTestBase, + public testing::WithParamInterface> { public: PinL0IndexAndFilterBlocksTest() : DBTestBase("/db_pin_l0_index_bloom_test") {} - virtual void SetUp() override { infinite_max_files_ = GetParam(); } + void SetUp() override { + infinite_max_files_ = std::get<0>(GetParam()); + disallow_preload_ = std::get<1>(GetParam()); + } void CreateTwoLevels(Options* options, bool close_afterwards) { if (infinite_max_files_) { @@ -1337,6 +1491,7 @@ class PinL0IndexAndFilterBlocksTest : public DBTestBase, } bool infinite_max_files_; + bool disallow_preload_; }; TEST_P(PinL0IndexAndFilterBlocksTest, @@ -1427,12 +1582,27 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) { uint64_t im = TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS); uint64_t ih = TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT); + if (disallow_preload_) { + // Now we have two files. We narrow the max open files to allow 3 entries + // so that preloading SST files won't happen. + options.max_open_files = 13; + // RocksDB sanitize max open files to at least 20. Modify it back. + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { + int* max_open_files = static_cast(arg); + *max_open_files = 13; + }); + } + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + // Reopen database. If max_open_files is set as -1, table readers will be // preloaded. This will trigger a BlockBasedTable::Open() and prefetch // L0 index and filter. Level 1's prefetching is disabled in DB::Open() TryReopenWithColumnFamilies({"default", "pikachu"}, options); - if (infinite_max_files_) { + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + + if (!disallow_preload_) { // After reopen, cache miss are increased by one because we read (and only // read) filter and index on L0 ASSERT_EQ(fm + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); @@ -1460,7 +1630,7 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) { // this should be read from L1 value = Get(1, "a"); - if (infinite_max_files_) { + if (!disallow_preload_) { // In inifinite max files case, there's a cache miss in executing Get() // because index and filter are not prefetched before. ASSERT_EQ(fm + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); @@ -1478,10 +1648,45 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) { ASSERT_EQ(im + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); ASSERT_EQ(ih + 1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); } + + // Force a full compaction to one single file. There will be a block + // cache read for both of index and filter. If prefetch doesn't explicitly + // happen, it will happen when verifying the file. + Compact(1, "a", "zzzzz"); + dbfull()->TEST_WaitForCompact(); + + if (!disallow_preload_) { + ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); + ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); + ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); + ASSERT_EQ(ih + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); + } else { + ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); + ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); + ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); + ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); + } + + // Bloom and index hit will happen when a Get() happens. + value = Get(1, "a"); + if (!disallow_preload_) { + ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); + ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); + ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); + ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); + } else { + ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS)); + ASSERT_EQ(fh + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT)); + ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS)); + ASSERT_EQ(ih + 4, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT)); + } } INSTANTIATE_TEST_CASE_P(PinL0IndexAndFilterBlocksTest, - PinL0IndexAndFilterBlocksTest, ::testing::Bool()); + PinL0IndexAndFilterBlocksTest, + ::testing::Values(std::make_tuple(true, false), + std::make_tuple(false, false), + std::make_tuple(false, true))); #ifndef ROCKSDB_LITE TEST_F(DBTest2, MaxCompactionBytesTest) { @@ -1549,7 +1754,7 @@ class MockPersistentCache : public PersistentCache { "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback); } - virtual ~MockPersistentCache() {} + ~MockPersistentCache() override {} PersistentCache::StatsType Stats() override { return PersistentCache::StatsType(); @@ -1597,6 +1802,127 @@ class MockPersistentCache : public PersistentCache { const size_t max_size_ = 10 * 1024; // 10KiB }; +#ifdef OS_LINUX +// Make sure that in CPU time perf context counters, Env::NowCPUNanos() +// is used, rather than Env::CPUNanos(); +TEST_F(DBTest2, TestPerfContextGetCpuTime) { + // force resizing table cache so table handle is not preloaded so that + // we can measure find_table_nanos during Get(). + dbfull()->TEST_table_cache()->SetCapacity(0); + ASSERT_OK(Put("foo", "bar")); + ASSERT_OK(Flush()); + env_->now_cpu_count_.store(0); + + // CPU timing is not enabled with kEnableTimeExceptForMutex + SetPerfLevel(PerfLevel::kEnableTimeExceptForMutex); + ASSERT_EQ("bar", Get("foo")); + ASSERT_EQ(0, get_perf_context()->get_cpu_nanos); + ASSERT_EQ(0, env_->now_cpu_count_.load()); + + uint64_t kDummyAddonTime = uint64_t{1000000000000}; + + // Add time to NowNanos() reading. + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "TableCache::FindTable:0", + [&](void* /*arg*/) { env_->addon_time_.fetch_add(kDummyAddonTime); }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); + ASSERT_EQ("bar", Get("foo")); + ASSERT_GT(env_->now_cpu_count_.load(), 2); + ASSERT_LT(get_perf_context()->get_cpu_nanos, kDummyAddonTime); + ASSERT_GT(get_perf_context()->find_table_nanos, kDummyAddonTime); + + SetPerfLevel(PerfLevel::kDisable); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBTest2, TestPerfContextIterCpuTime) { + DestroyAndReopen(CurrentOptions()); + // force resizing table cache so table handle is not preloaded so that + // we can measure find_table_nanos during iteration + dbfull()->TEST_table_cache()->SetCapacity(0); + + const size_t kNumEntries = 10; + for (size_t i = 0; i < kNumEntries; ++i) { + ASSERT_OK(Put("k" + ToString(i), "v" + ToString(i))); + } + ASSERT_OK(Flush()); + for (size_t i = 0; i < kNumEntries; ++i) { + ASSERT_EQ("v" + ToString(i), Get("k" + ToString(i))); + } + std::string last_key = "k" + ToString(kNumEntries - 1); + std::string last_value = "v" + ToString(kNumEntries - 1); + env_->now_cpu_count_.store(0); + + // CPU timing is not enabled with kEnableTimeExceptForMutex + SetPerfLevel(PerfLevel::kEnableTimeExceptForMutex); + Iterator* iter = db_->NewIterator(ReadOptions()); + iter->Seek("k0"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + iter->SeekForPrev(last_key); + ASSERT_TRUE(iter->Valid()); + iter->SeekToLast(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(last_value, iter->value().ToString()); + iter->SeekToFirst(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + ASSERT_EQ(0, get_perf_context()->iter_seek_cpu_nanos); + iter->Next(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v1", iter->value().ToString()); + ASSERT_EQ(0, get_perf_context()->iter_next_cpu_nanos); + iter->Prev(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + ASSERT_EQ(0, get_perf_context()->iter_prev_cpu_nanos); + ASSERT_EQ(0, env_->now_cpu_count_.load()); + delete iter; + + uint64_t kDummyAddonTime = uint64_t{1000000000000}; + + // Add time to NowNanos() reading. + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "TableCache::FindTable:0", + [&](void* /*arg*/) { env_->addon_time_.fetch_add(kDummyAddonTime); }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); + iter = db_->NewIterator(ReadOptions()); + iter->Seek("k0"); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + iter->SeekForPrev(last_key); + ASSERT_TRUE(iter->Valid()); + iter->SeekToLast(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(last_value, iter->value().ToString()); + iter->SeekToFirst(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + ASSERT_GT(get_perf_context()->iter_seek_cpu_nanos, 0); + ASSERT_LT(get_perf_context()->iter_seek_cpu_nanos, kDummyAddonTime); + iter->Next(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v1", iter->value().ToString()); + ASSERT_GT(get_perf_context()->iter_next_cpu_nanos, 0); + ASSERT_LT(get_perf_context()->iter_next_cpu_nanos, kDummyAddonTime); + iter->Prev(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + ASSERT_GT(get_perf_context()->iter_prev_cpu_nanos, 0); + ASSERT_LT(get_perf_context()->iter_prev_cpu_nanos, kDummyAddonTime); + ASSERT_GE(env_->now_cpu_count_.load(), 12); + ASSERT_GT(get_perf_context()->find_table_nanos, kDummyAddonTime); + + SetPerfLevel(PerfLevel::kDisable); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + delete iter; +} +#endif // OS_LINUX + #ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented TEST_F(DBTest2, PersistentCache) { int num_iter = 80; @@ -1673,7 +1999,7 @@ TEST_F(DBTest2, SyncPointMarker) { std::atomic sync_point_called(0); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBTest2::MarkedPoint", - [&](void* arg) { sync_point_called.fetch_add(1); }); + [&](void* /*arg*/) { sync_point_called.fetch_add(1); }); // The first dependency enforces Marker can be loaded before MarkedPoint. // The second checks that thread 1's MarkedPoint should be disabled here. @@ -1801,11 +2127,29 @@ TEST_F(DBTest2, ReadAmpBitmap) { #ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented TEST_F(DBTest2, ReadAmpBitmapLiveInCacheAfterDBClose) { - if (dbname_.find("dev/shm") != std::string::npos) { - // /dev/shm dont support getting a unique file id, this mean that - // running this test on /dev/shm will fail because lru_cache will load - // the blocks again regardless of them being already in the cache - return; + { + const int kIdBufLen = 100; + char id_buf[kIdBufLen]; +#ifndef OS_WIN + // You can't open a directory on windows using random access file + std::unique_ptr file; + ASSERT_OK(env_->NewRandomAccessFile(dbname_, &file, EnvOptions())); + if (file->GetUniqueId(id_buf, kIdBufLen) == 0) { + // fs holding db directory doesn't support getting a unique file id, + // this means that running this test will fail because lru_cache will load + // the blocks again regardless of them being already in the cache + return; + } +#else + std::unique_ptr dir; + ASSERT_OK(env_->NewDirectory(dbname_, &dir)); + if (dir->GetUniqueId(id_buf, kIdBufLen) == 0) { + // fs holding db directory doesn't support getting a unique file id, + // this means that running this test will fail because lru_cache will load + // the blocks again regardless of them being already in the cache + return; + } +#endif } uint32_t bytes_per_bit[2] = {1, 16}; for (size_t k = 0; k < 2; k++) { @@ -1918,19 +2262,19 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) { ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr)); auto get_stat = [](std::string level_str, LevelStatType type, - std::map props) { + std::map props) { auto prop_str = - level_str + "." + + "compaction." + level_str + "." + InternalStats::compaction_level_stats.at(type).property_name.c_str(); auto prop_item = props.find(prop_str); - return prop_item == props.end() ? 0 : prop_item->second; + return prop_item == props.end() ? 0 : std::stod(prop_item->second); }; // Trivial move 2 files to L2 ASSERT_EQ("0,0,2", FilesPerLevel()); // Also test that the stats GetMapProperty API reporting the same result { - std::map prop; + std::map prop; ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop)); ASSERT_EQ(0, get_stat("L0", LevelStatType::NUM_FILES, prop)); ASSERT_EQ(0, get_stat("L1", LevelStatType::NUM_FILES, prop)); @@ -1942,7 +2286,7 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) { // can fit in L2, these 2 files will be moved to L2 and overlap with // the running compaction and break the LSM consistency. rocksdb::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():Start", [&](void* arg) { + "CompactionJob::Run():Start", [&](void* /*arg*/) { ASSERT_OK( dbfull()->SetOptions({{"level0_file_num_compaction_trigger", "2"}, {"max_bytes_for_level_base", "1"}})); @@ -1966,7 +2310,7 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) { // Test that the stats GetMapProperty API reporting 1 file in L2 { - std::map prop; + std::map prop; ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop)); ASSERT_EQ(1, get_stat("L2", LevelStatType::NUM_FILES, prop)); } @@ -2008,7 +2352,7 @@ TEST_F(DBTest2, ManualCompactionOverlapManualCompaction) { // the running compaction and break the LSM consistency. std::atomic flag(false); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():Start", [&](void* arg) { + "CompactionJob::Run():Start", [&](void* /*arg*/) { if (flag.exchange(true)) { // We want to make sure to call this callback only once return; @@ -2260,7 +2604,8 @@ TEST_F(DBTest2, RateLimitedCompactionReads) { kBytesPerKey) /* rate_bytes_per_sec */, 10 * 1000 /* refill_period_us */, 10 /* fairness */, RateLimiter::Mode::kReadsOnly)); - options.use_direct_io_for_flush_and_compaction = use_direct_io; + options.use_direct_reads = options.use_direct_io_for_flush_and_compaction = + use_direct_io; BlockBasedTableOptions bbto; bbto.block_size = 16384; bbto.no_block_cache = true; @@ -2282,11 +2627,11 @@ TEST_F(DBTest2, RateLimitedCompactionReads) { // chose 1MB as the upper bound on the total bytes read. size_t rate_limited_bytes = options.rate_limiter->GetTotalBytesThrough(Env::IO_LOW); - // Include the explict prefetch of the footer in direct I/O case. + // Include the explicit prefetch of the footer in direct I/O case. size_t direct_io_extra = use_direct_io ? 512 * 1024 : 0; - ASSERT_GE(rate_limited_bytes, - static_cast(kNumKeysPerFile * kBytesPerKey * kNumL0Files + - direct_io_extra)); + ASSERT_GE( + rate_limited_bytes, + static_cast(kNumKeysPerFile * kBytesPerKey * kNumL0Files)); ASSERT_LT( rate_limited_bytes, static_cast(2 * kNumKeysPerFile * kBytesPerKey * kNumL0Files + @@ -2331,6 +2676,1033 @@ TEST_F(DBTest2, ReduceLevel) { ASSERT_EQ("0,1", FilesPerLevel()); #endif // !ROCKSDB_LITE } + +// Test that ReadCallback is actually used in both memtbale and sst tables +TEST_F(DBTest2, ReadCallbackTest) { + Options options; + options.disable_auto_compactions = true; + options.num_levels = 7; + Reopen(options); + std::vector snapshots; + // Try to create a db with multiple layers and a memtable + const std::string key = "foo"; + const std::string value = "bar"; + // This test assumes that the seq start with 1 and increased by 1 after each + // write batch of size 1. If that behavior changes, the test needs to be + // updated as well. + // TODO(myabandeh): update this test to use the seq number that is returned by + // the DB instead of assuming what seq the DB used. + int i = 1; + for (; i < 10; i++) { + Put(key, value + std::to_string(i)); + // Take a snapshot to avoid the value being removed during compaction + auto snapshot = dbfull()->GetSnapshot(); + snapshots.push_back(snapshot); + } + Flush(); + for (; i < 20; i++) { + Put(key, value + std::to_string(i)); + // Take a snapshot to avoid the value being removed during compaction + auto snapshot = dbfull()->GetSnapshot(); + snapshots.push_back(snapshot); + } + Flush(); + MoveFilesToLevel(6); +#ifndef ROCKSDB_LITE + ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel()); +#endif // !ROCKSDB_LITE + for (; i < 30; i++) { + Put(key, value + std::to_string(i)); + auto snapshot = dbfull()->GetSnapshot(); + snapshots.push_back(snapshot); + } + Flush(); +#ifndef ROCKSDB_LITE + ASSERT_EQ("1,0,0,0,0,0,2", FilesPerLevel()); +#endif // !ROCKSDB_LITE + // And also add some values to the memtable + for (; i < 40; i++) { + Put(key, value + std::to_string(i)); + auto snapshot = dbfull()->GetSnapshot(); + snapshots.push_back(snapshot); + } + + class TestReadCallback : public ReadCallback { + public: + explicit TestReadCallback(SequenceNumber snapshot) + : ReadCallback(snapshot), snapshot_(snapshot) {} + bool IsVisibleFullCheck(SequenceNumber seq) override { + return seq <= snapshot_; + } + + private: + SequenceNumber snapshot_; + }; + + for (int seq = 1; seq < i; seq++) { + PinnableSlice pinnable_val; + ReadOptions roptions; + TestReadCallback callback(seq); + bool dont_care = true; + Status s = dbfull()->GetImpl(roptions, dbfull()->DefaultColumnFamily(), key, + &pinnable_val, &dont_care, &callback); + ASSERT_TRUE(s.ok()); + // Assuming that after each Put the DB increased seq by one, the value and + // seq number must be equal since we also inc value by 1 after each Put. + ASSERT_EQ(value + std::to_string(seq), pinnable_val.ToString()); + } + + for (auto snapshot : snapshots) { + dbfull()->ReleaseSnapshot(snapshot); + } +} + +#ifndef ROCKSDB_LITE + +TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) { + // Regression test for race condition where an obsolete file is returned to + // user as a "live file" but then deleted, all while file deletions are + // disabled. + // + // It happened like this: + // + // 1. [flush thread] Log file "x.log" found by FindObsoleteFiles + // 2. [user thread] DisableFileDeletions, GetSortedWalFiles are called and the + // latter returned "x.log" + // 3. [flush thread] PurgeObsoleteFiles deleted "x.log" + // 4. [user thread] Reading "x.log" failed + // + // Unfortunately the only regression test I can come up with involves sleep. + // We cannot set SyncPoints to repro since, once the fix is applied, the + // SyncPoints would cause a deadlock as the repro's sequence of events is now + // prohibited. + // + // Instead, if we sleep for a second between Find and Purge, and ensure the + // read attempt happens after purge, then the sequence of events will almost + // certainly happen on the old code. + rocksdb::SyncPoint::GetInstance()->LoadDependency({ + {"DBImpl::BackgroundCallFlush:FilesFound", + "DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered"}, + {"DBImpl::PurgeObsoleteFiles:End", + "DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured"}, + }); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "DBImpl::PurgeObsoleteFiles:Begin", + [&](void* /*arg*/) { env_->SleepForMicroseconds(1000000); }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Put("key", "val"); + FlushOptions flush_opts; + flush_opts.wait = false; + db_->Flush(flush_opts); + TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered"); + + db_->DisableFileDeletions(); + VectorLogPtr log_files; + db_->GetSortedWalFiles(log_files); + TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured"); + for (const auto& log_file : log_files) { + ASSERT_OK(env_->FileExists(LogFileName(dbname_, log_file->LogNumber()))); + } + + db_->EnableFileDeletions(); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBTest2, TestNumPread) { + Options options = CurrentOptions(); + // disable block cache + BlockBasedTableOptions table_options; + table_options.no_block_cache = true; + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + Reopen(options); + env_->count_random_reads_ = true; + + env_->random_file_open_counter_.store(0); + ASSERT_OK(Put("bar", "foo")); + ASSERT_OK(Put("foo", "bar")); + ASSERT_OK(Flush()); + // After flush, we'll open the file and read footer, meta block, + // property block and index block. + ASSERT_EQ(4, env_->random_read_counter_.Read()); + ASSERT_EQ(1, env_->random_file_open_counter_.load()); + + // One pread per a normal data block read + env_->random_file_open_counter_.store(0); + env_->random_read_counter_.Reset(); + ASSERT_EQ("bar", Get("foo")); + ASSERT_EQ(1, env_->random_read_counter_.Read()); + // All files are already opened. + ASSERT_EQ(0, env_->random_file_open_counter_.load()); + + env_->random_file_open_counter_.store(0); + env_->random_read_counter_.Reset(); + ASSERT_OK(Put("bar2", "foo2")); + ASSERT_OK(Put("foo2", "bar2")); + ASSERT_OK(Flush()); + // After flush, we'll open the file and read footer, meta block, + // property block and index block. + ASSERT_EQ(4, env_->random_read_counter_.Read()); + ASSERT_EQ(1, env_->random_file_open_counter_.load()); + + // Compaction needs two input blocks, which requires 2 preads, and + // generate a new SST file which needs 4 preads (footer, meta block, + // property block and index block). In total 6. + env_->random_file_open_counter_.store(0); + env_->random_read_counter_.Reset(); + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_EQ(6, env_->random_read_counter_.Read()); + // All compactin input files should have already been opened. + ASSERT_EQ(1, env_->random_file_open_counter_.load()); + + // One pread per a normal data block read + env_->random_file_open_counter_.store(0); + env_->random_read_counter_.Reset(); + ASSERT_EQ("foo2", Get("bar2")); + ASSERT_EQ(1, env_->random_read_counter_.Read()); + // SST files are already opened. + ASSERT_EQ(0, env_->random_file_open_counter_.load()); +} + +TEST_F(DBTest2, TraceAndReplay) { + Options options = CurrentOptions(); + options.merge_operator = MergeOperators::CreatePutOperator(); + ReadOptions ro; + WriteOptions wo; + TraceOptions trace_opts; + EnvOptions env_opts; + CreateAndReopenWithCF({"pikachu"}, options); + Random rnd(301); + Iterator* single_iter = nullptr; + + ASSERT_TRUE(db_->EndTrace().IsIOError()); + + std::string trace_filename = dbname_ + "/rocksdb.trace"; + std::unique_ptr trace_writer; + ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer)); + ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer))); + + ASSERT_OK(Put(0, "a", "1")); + ASSERT_OK(Merge(0, "b", "2")); + ASSERT_OK(Delete(0, "c")); + ASSERT_OK(SingleDelete(0, "d")); + ASSERT_OK(db_->DeleteRange(wo, dbfull()->DefaultColumnFamily(), "e", "f")); + + WriteBatch batch; + ASSERT_OK(batch.Put("f", "11")); + ASSERT_OK(batch.Merge("g", "12")); + ASSERT_OK(batch.Delete("h")); + ASSERT_OK(batch.SingleDelete("i")); + ASSERT_OK(batch.DeleteRange("j", "k")); + ASSERT_OK(db_->Write(wo, &batch)); + + single_iter = db_->NewIterator(ro); + single_iter->Seek("f"); + single_iter->SeekForPrev("g"); + delete single_iter; + + ASSERT_EQ("1", Get(0, "a")); + ASSERT_EQ("12", Get(0, "g")); + + ASSERT_OK(Put(1, "foo", "bar")); + ASSERT_OK(Put(1, "rocksdb", "rocks")); + ASSERT_EQ("NOT_FOUND", Get(1, "leveldb")); + + ASSERT_OK(db_->EndTrace()); + // These should not get into the trace file as it is after EndTrace. + Put("hello", "world"); + Merge("foo", "bar"); + + // Open another db, replay, and verify the data + std::string value; + std::string dbname2 = test::TmpDir(env_) + "/db_replay"; + ASSERT_OK(DestroyDB(dbname2, options)); + + // Using a different name than db2, to pacify infer's use-after-lifetime + // warnings (http://fbinfer.com). + DB* db2_init = nullptr; + options.create_if_missing = true; + ASSERT_OK(DB::Open(options, dbname2, &db2_init)); + ColumnFamilyHandle* cf; + ASSERT_OK( + db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf)); + delete cf; + delete db2_init; + + DB* db2 = nullptr; + std::vector column_families; + ColumnFamilyOptions cf_options; + cf_options.merge_operator = MergeOperators::CreatePutOperator(); + column_families.push_back(ColumnFamilyDescriptor("default", cf_options)); + column_families.push_back( + ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions())); + std::vector handles; + ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2)); + + env_->SleepForMicroseconds(100); + // Verify that the keys don't already exist + ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound()); + + std::unique_ptr trace_reader; + ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader)); + Replayer replayer(db2, handles_, std::move(trace_reader)); + ASSERT_OK(replayer.Replay()); + + ASSERT_OK(db2->Get(ro, handles[0], "a", &value)); + ASSERT_EQ("1", value); + ASSERT_OK(db2->Get(ro, handles[0], "g", &value)); + ASSERT_EQ("12", value); + ASSERT_TRUE(db2->Get(ro, handles[0], "hello", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "world", &value).IsNotFound()); + + ASSERT_OK(db2->Get(ro, handles[1], "foo", &value)); + ASSERT_EQ("bar", value); + ASSERT_OK(db2->Get(ro, handles[1], "rocksdb", &value)); + ASSERT_EQ("rocks", value); + + for (auto handle : handles) { + delete handle; + } + delete db2; + ASSERT_OK(DestroyDB(dbname2, options)); +} + +TEST_F(DBTest2, TraceWithLimit) { + Options options = CurrentOptions(); + options.merge_operator = MergeOperators::CreatePutOperator(); + ReadOptions ro; + WriteOptions wo; + TraceOptions trace_opts; + EnvOptions env_opts; + CreateAndReopenWithCF({"pikachu"}, options); + Random rnd(301); + + // test the max trace file size options + trace_opts.max_trace_file_size = 5; + std::string trace_filename = dbname_ + "/rocksdb.trace1"; + std::unique_ptr trace_writer; + ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer)); + ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer))); + ASSERT_OK(Put(0, "a", "1")); + ASSERT_OK(Put(0, "b", "1")); + ASSERT_OK(Put(0, "c", "1")); + ASSERT_OK(db_->EndTrace()); + + std::string dbname2 = test::TmpDir(env_) + "/db_replay2"; + std::string value; + ASSERT_OK(DestroyDB(dbname2, options)); + + // Using a different name than db2, to pacify infer's use-after-lifetime + // warnings (http://fbinfer.com). + DB* db2_init = nullptr; + options.create_if_missing = true; + ASSERT_OK(DB::Open(options, dbname2, &db2_init)); + ColumnFamilyHandle* cf; + ASSERT_OK( + db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf)); + delete cf; + delete db2_init; + + DB* db2 = nullptr; + std::vector column_families; + ColumnFamilyOptions cf_options; + cf_options.merge_operator = MergeOperators::CreatePutOperator(); + column_families.push_back(ColumnFamilyDescriptor("default", cf_options)); + column_families.push_back( + ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions())); + std::vector handles; + ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2)); + + env_->SleepForMicroseconds(100); + // Verify that the keys don't already exist + ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "b", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound()); + + std::unique_ptr trace_reader; + ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader)); + Replayer replayer(db2, handles_, std::move(trace_reader)); + ASSERT_OK(replayer.Replay()); + + ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "b", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound()); + + for (auto handle : handles) { + delete handle; + } + delete db2; + ASSERT_OK(DestroyDB(dbname2, options)); +} + +TEST_F(DBTest2, TraceWithSampling) { + Options options = CurrentOptions(); + ReadOptions ro; + WriteOptions wo; + TraceOptions trace_opts; + EnvOptions env_opts; + CreateAndReopenWithCF({"pikachu"}, options); + Random rnd(301); + + // test the trace file sampling options + trace_opts.sampling_frequency = 2; + std::string trace_filename = dbname_ + "/rocksdb.trace_sampling"; + std::unique_ptr trace_writer; + ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer)); + ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer))); + ASSERT_OK(Put(0, "a", "1")); + ASSERT_OK(Put(0, "b", "2")); + ASSERT_OK(Put(0, "c", "3")); + ASSERT_OK(Put(0, "d", "4")); + ASSERT_OK(Put(0, "e", "5")); + ASSERT_OK(db_->EndTrace()); + + std::string dbname2 = test::TmpDir(env_) + "/db_replay_sampling"; + std::string value; + ASSERT_OK(DestroyDB(dbname2, options)); + + // Using a different name than db2, to pacify infer's use-after-lifetime + // warnings (http://fbinfer.com). + DB* db2_init = nullptr; + options.create_if_missing = true; + ASSERT_OK(DB::Open(options, dbname2, &db2_init)); + ColumnFamilyHandle* cf; + ASSERT_OK( + db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf)); + delete cf; + delete db2_init; + + DB* db2 = nullptr; + std::vector column_families; + ColumnFamilyOptions cf_options; + column_families.push_back(ColumnFamilyDescriptor("default", cf_options)); + column_families.push_back( + ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions())); + std::vector handles; + ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2)); + + env_->SleepForMicroseconds(100); + ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "b", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "d", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "e", &value).IsNotFound()); + + std::unique_ptr trace_reader; + ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader)); + Replayer replayer(db2, handles_, std::move(trace_reader)); + ASSERT_OK(replayer.Replay()); + + ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_FALSE(db2->Get(ro, handles[0], "b", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "c", &value).IsNotFound()); + ASSERT_FALSE(db2->Get(ro, handles[0], "d", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "e", &value).IsNotFound()); + + for (auto handle : handles) { + delete handle; + } + delete db2; + ASSERT_OK(DestroyDB(dbname2, options)); +} + +TEST_F(DBTest2, TraceWithFilter) { + Options options = CurrentOptions(); + options.merge_operator = MergeOperators::CreatePutOperator(); + ReadOptions ro; + WriteOptions wo; + TraceOptions trace_opts; + EnvOptions env_opts; + CreateAndReopenWithCF({"pikachu"}, options); + Random rnd(301); + Iterator* single_iter = nullptr; + + trace_opts.filter = TraceFilterType::kTraceFilterWrite; + + std::string trace_filename = dbname_ + "/rocksdb.trace"; + std::unique_ptr trace_writer; + ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer)); + ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer))); + + ASSERT_OK(Put(0, "a", "1")); + ASSERT_OK(Merge(0, "b", "2")); + ASSERT_OK(Delete(0, "c")); + ASSERT_OK(SingleDelete(0, "d")); + ASSERT_OK(db_->DeleteRange(wo, dbfull()->DefaultColumnFamily(), "e", "f")); + + WriteBatch batch; + ASSERT_OK(batch.Put("f", "11")); + ASSERT_OK(batch.Merge("g", "12")); + ASSERT_OK(batch.Delete("h")); + ASSERT_OK(batch.SingleDelete("i")); + ASSERT_OK(batch.DeleteRange("j", "k")); + ASSERT_OK(db_->Write(wo, &batch)); + + single_iter = db_->NewIterator(ro); + single_iter->Seek("f"); + single_iter->SeekForPrev("g"); + delete single_iter; + + ASSERT_EQ("1", Get(0, "a")); + ASSERT_EQ("12", Get(0, "g")); + + ASSERT_OK(Put(1, "foo", "bar")); + ASSERT_OK(Put(1, "rocksdb", "rocks")); + ASSERT_EQ("NOT_FOUND", Get(1, "leveldb")); + + ASSERT_OK(db_->EndTrace()); + // These should not get into the trace file as it is after EndTrace. + Put("hello", "world"); + Merge("foo", "bar"); + + // Open another db, replay, and verify the data + std::string value; + std::string dbname2 = test::TmpDir(env_) + "/db_replay"; + ASSERT_OK(DestroyDB(dbname2, options)); + + // Using a different name than db2, to pacify infer's use-after-lifetime + // warnings (http://fbinfer.com). + DB* db2_init = nullptr; + options.create_if_missing = true; + ASSERT_OK(DB::Open(options, dbname2, &db2_init)); + ColumnFamilyHandle* cf; + ASSERT_OK( + db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf)); + delete cf; + delete db2_init; + + DB* db2 = nullptr; + std::vector column_families; + ColumnFamilyOptions cf_options; + cf_options.merge_operator = MergeOperators::CreatePutOperator(); + column_families.push_back(ColumnFamilyDescriptor("default", cf_options)); + column_families.push_back( + ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions())); + std::vector handles; + ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2)); + + env_->SleepForMicroseconds(100); + // Verify that the keys don't already exist + ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound()); + + std::unique_ptr trace_reader; + ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader)); + Replayer replayer(db2, handles_, std::move(trace_reader)); + ASSERT_OK(replayer.Replay()); + + // All the key-values should not present since we filter out the WRITE ops. + ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "hello", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "world", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "foo", &value).IsNotFound()); + ASSERT_TRUE(db2->Get(ro, handles[0], "rocksdb", &value).IsNotFound()); + + for (auto handle : handles) { + delete handle; + } + delete db2; + ASSERT_OK(DestroyDB(dbname2, options)); + + // Set up a new db. + std::string dbname3 = test::TmpDir(env_) + "/db_not_trace_read"; + ASSERT_OK(DestroyDB(dbname3, options)); + + DB* db3_init = nullptr; + options.create_if_missing = true; + ColumnFamilyHandle* cf3; + ASSERT_OK(DB::Open(options, dbname3, &db3_init)); + ASSERT_OK( + db3_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf3)); + delete cf3; + delete db3_init; + + column_families.clear(); + column_families.push_back(ColumnFamilyDescriptor("default", cf_options)); + column_families.push_back( + ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions())); + handles.clear(); + + DB* db3 = nullptr; + ASSERT_OK(DB::Open(DBOptions(), dbname3, column_families, &handles, &db3)); + + env_->SleepForMicroseconds(100); + // Verify that the keys don't already exist + ASSERT_TRUE(db3->Get(ro, handles[0], "a", &value).IsNotFound()); + ASSERT_TRUE(db3->Get(ro, handles[0], "g", &value).IsNotFound()); + + //The tracer will not record the READ ops. + trace_opts.filter = TraceFilterType::kTraceFilterGet; + std::string trace_filename3 = dbname_ + "/rocksdb.trace_3"; + std::unique_ptr trace_writer3; + ASSERT_OK( + NewFileTraceWriter(env_, env_opts, trace_filename3, &trace_writer3)); + ASSERT_OK(db3->StartTrace(trace_opts, std::move(trace_writer3))); + + ASSERT_OK(db3->Put(wo, handles[0], "a", "1")); + ASSERT_OK(db3->Merge(wo, handles[0], "b", "2")); + ASSERT_OK(db3->Delete(wo, handles[0], "c")); + ASSERT_OK(db3->SingleDelete(wo, handles[0], "d")); + + ASSERT_OK(db3->Get(ro, handles[0], "a", &value)); + ASSERT_EQ(value, "1"); + ASSERT_TRUE(db3->Get(ro, handles[0], "c", &value).IsNotFound()); + + ASSERT_OK(db3->EndTrace()); + + for (auto handle : handles) { + delete handle; + } + delete db3; + ASSERT_OK(DestroyDB(dbname3, options)); + + std::unique_ptr trace_reader3; + ASSERT_OK( + NewFileTraceReader(env_, env_opts, trace_filename3, &trace_reader3)); + + // Count the number of records in the trace file; + int count = 0; + std::string data; + Status s; + while (true) { + s = trace_reader3->Read(&data); + if (!s.ok()) { + break; + } + count += 1; + } + // We also need to count the header and footer + // 4 WRITE + HEADER + FOOTER = 6 + ASSERT_EQ(count, 6); +} + +#endif // ROCKSDB_LITE + +TEST_F(DBTest2, PinnableSliceAndMmapReads) { + Options options = CurrentOptions(); + options.allow_mmap_reads = true; + options.max_open_files = 100; + options.compression = kNoCompression; + Reopen(options); + + ASSERT_OK(Put("foo", "bar")); + ASSERT_OK(Flush()); + + PinnableSlice pinned_value; + ASSERT_EQ(Get("foo", &pinned_value), Status::OK()); + // It is not safe to pin mmap files as they might disappear by compaction + ASSERT_FALSE(pinned_value.IsPinned()); + ASSERT_EQ(pinned_value.ToString(), "bar"); + + dbfull()->TEST_CompactRange(0 /* level */, nullptr /* begin */, + nullptr /* end */, nullptr /* column_family */, + true /* disallow_trivial_move */); + + // Ensure pinned_value doesn't rely on memory munmap'd by the above + // compaction. It crashes if it does. + ASSERT_EQ(pinned_value.ToString(), "bar"); + +#ifndef ROCKSDB_LITE + pinned_value.Reset(); + // Unsafe to pin mmap files when they could be kicked out of table cache + Close(); + ASSERT_OK(ReadOnlyReopen(options)); + ASSERT_EQ(Get("foo", &pinned_value), Status::OK()); + ASSERT_FALSE(pinned_value.IsPinned()); + ASSERT_EQ(pinned_value.ToString(), "bar"); + + pinned_value.Reset(); + // In read-only mode with infinite capacity on table cache it should pin the + // value and avoid the memcpy + Close(); + options.max_open_files = -1; + ASSERT_OK(ReadOnlyReopen(options)); + ASSERT_EQ(Get("foo", &pinned_value), Status::OK()); + ASSERT_TRUE(pinned_value.IsPinned()); + ASSERT_EQ(pinned_value.ToString(), "bar"); +#endif +} + +TEST_F(DBTest2, DISABLED_IteratorPinnedMemory) { + Options options = CurrentOptions(); + options.create_if_missing = true; + options.statistics = rocksdb::CreateDBStatistics(); + BlockBasedTableOptions bbto; + bbto.no_block_cache = false; + bbto.cache_index_and_filter_blocks = false; + bbto.block_cache = NewLRUCache(100000); + bbto.block_size = 400; // small block size + options.table_factory.reset(new BlockBasedTableFactory(bbto)); + Reopen(options); + + Random rnd(301); + std::string v = RandomString(&rnd, 400); + + // Since v is the size of a block, each key should take a block + // of 400+ bytes. + Put("1", v); + Put("3", v); + Put("5", v); + Put("7", v); + ASSERT_OK(Flush()); + + ASSERT_EQ(0, bbto.block_cache->GetPinnedUsage()); + + // Verify that iterators don't pin more than one data block in block cache + // at each time. + { + std::unique_ptr iter(db_->NewIterator(ReadOptions())); + iter->SeekToFirst(); + + for (int i = 0; i < 4; i++) { + ASSERT_TRUE(iter->Valid()); + // Block cache should contain exactly one block. + ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0); + ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800); + iter->Next(); + } + ASSERT_FALSE(iter->Valid()); + + iter->Seek("4"); + ASSERT_TRUE(iter->Valid()); + + ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0); + ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800); + + iter->Seek("3"); + ASSERT_TRUE(iter->Valid()); + + ASSERT_GT(bbto.block_cache->GetPinnedUsage(), 0); + ASSERT_LT(bbto.block_cache->GetPinnedUsage(), 800); + } + ASSERT_EQ(0, bbto.block_cache->GetPinnedUsage()); + + // Test compaction case + Put("2", v); + Put("5", v); + Put("6", v); + Put("8", v); + ASSERT_OK(Flush()); + + // Clear existing data in block cache + bbto.block_cache->SetCapacity(0); + bbto.block_cache->SetCapacity(100000); + + // Verify compaction input iterators don't hold more than one data blocks at + // one time. + std::atomic finished(false); + std::atomic block_newed(0); + std::atomic block_destroyed(0); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "Block::Block:0", [&](void* /*arg*/) { + if (finished) { + return; + } + // Two iterators. At most 2 outstanding blocks. + EXPECT_GE(block_newed.load(), block_destroyed.load()); + EXPECT_LE(block_newed.load(), block_destroyed.load() + 1); + block_newed.fetch_add(1); + }); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "Block::~Block", [&](void* /*arg*/) { + if (finished) { + return; + } + // Two iterators. At most 2 outstanding blocks. + EXPECT_GE(block_newed.load(), block_destroyed.load() + 1); + EXPECT_LE(block_newed.load(), block_destroyed.load() + 2); + block_destroyed.fetch_add(1); + }); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "CompactionJob::Run:BeforeVerify", + [&](void* /*arg*/) { finished = true; }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + + // Two input files. Each of them has 4 data blocks. + ASSERT_EQ(8, block_newed.load()); + ASSERT_EQ(8, block_destroyed.load()); + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(DBTest2, TestBBTTailPrefetch) { + std::atomic called(false); + size_t expected_lower_bound = 512 * 1024; + size_t expected_higher_bound = 512 * 1024; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "BlockBasedTable::Open::TailPrefetchLen", [&](void* arg) { + size_t* prefetch_size = static_cast(arg); + EXPECT_LE(expected_lower_bound, *prefetch_size); + EXPECT_GE(expected_higher_bound, *prefetch_size); + called = true; + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Put("1", "1"); + Put("9", "1"); + Flush(); + + expected_lower_bound = 0; + expected_higher_bound = 8 * 1024; + + Put("1", "1"); + Put("9", "1"); + Flush(); + + Put("1", "1"); + Put("9", "1"); + Flush(); + + // Full compaction to make sure there is no L0 file after the open. + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + + ASSERT_TRUE(called.load()); + called = false; + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); + + std::atomic first_call(true); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "BlockBasedTable::Open::TailPrefetchLen", [&](void* arg) { + size_t* prefetch_size = static_cast(arg); + if (first_call) { + EXPECT_EQ(4 * 1024, *prefetch_size); + first_call = false; + } else { + EXPECT_GE(4 * 1024, *prefetch_size); + } + called = true; + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Options options = CurrentOptions(); + options.max_file_opening_threads = 1; // one thread + BlockBasedTableOptions table_options; + table_options.cache_index_and_filter_blocks = true; + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + options.max_open_files = -1; + Reopen(options); + + Put("1", "1"); + Put("9", "1"); + Flush(); + + Put("1", "1"); + Put("9", "1"); + Flush(); + + ASSERT_TRUE(called.load()); + called = false; + + // Parallel loading SST files + options.max_file_opening_threads = 16; + Reopen(options); + + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + + ASSERT_TRUE(called.load()); + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); +} + +TEST_F(DBTest2, TestGetColumnFamilyHandleUnlocked) { + // Setup sync point dependency to reproduce the race condition of + // DBImpl::GetColumnFamilyHandleUnlocked + rocksdb::SyncPoint::GetInstance()->LoadDependency( + { {"TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked1", + "TestGetColumnFamilyHandleUnlocked::PreGetColumnFamilyHandleUnlocked2"}, + {"TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked2", + "TestGetColumnFamilyHandleUnlocked::ReadColumnFamilyHandle1"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + + CreateColumnFamilies({"test1", "test2"}, Options()); + ASSERT_EQ(handles_.size(), 2); + + DBImpl* dbi = reinterpret_cast(db_); + port::Thread user_thread1([&]() { + auto cfh = dbi->GetColumnFamilyHandleUnlocked(handles_[0]->GetID()); + ASSERT_EQ(cfh->GetID(), handles_[0]->GetID()); + TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked1"); + TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::ReadColumnFamilyHandle1"); + ASSERT_EQ(cfh->GetID(), handles_[0]->GetID()); + }); + + port::Thread user_thread2([&]() { + TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::PreGetColumnFamilyHandleUnlocked2"); + auto cfh = dbi->GetColumnFamilyHandleUnlocked(handles_[1]->GetID()); + ASSERT_EQ(cfh->GetID(), handles_[1]->GetID()); + TEST_SYNC_POINT("TestGetColumnFamilyHandleUnlocked::GetColumnFamilyHandleUnlocked2"); + ASSERT_EQ(cfh->GetID(), handles_[1]->GetID()); + }); + + user_thread1.join(); + user_thread2.join(); + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); +} + +#ifndef ROCKSDB_LITE +TEST_F(DBTest2, TestCompactFiles) { + // Setup sync point dependency to reproduce the race condition of + // DBImpl::GetColumnFamilyHandleUnlocked + rocksdb::SyncPoint::GetInstance()->LoadDependency({ + {"TestCompactFiles::IngestExternalFile1", + "TestCompactFiles::IngestExternalFile2"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + + Options options; + options.num_levels = 2; + options.disable_auto_compactions = true; + Reopen(options); + auto* handle = db_->DefaultColumnFamily(); + ASSERT_EQ(db_->NumberLevels(handle), 2); + + rocksdb::SstFileWriter sst_file_writer{rocksdb::EnvOptions(), options}; + std::string external_file1 = dbname_ + "/test_compact_files1.sst_t"; + std::string external_file2 = dbname_ + "/test_compact_files2.sst_t"; + std::string external_file3 = dbname_ + "/test_compact_files3.sst_t"; + + ASSERT_OK(sst_file_writer.Open(external_file1)); + ASSERT_OK(sst_file_writer.Put("1", "1")); + ASSERT_OK(sst_file_writer.Put("2", "2")); + ASSERT_OK(sst_file_writer.Finish()); + + ASSERT_OK(sst_file_writer.Open(external_file2)); + ASSERT_OK(sst_file_writer.Put("3", "3")); + ASSERT_OK(sst_file_writer.Put("4", "4")); + ASSERT_OK(sst_file_writer.Finish()); + + ASSERT_OK(sst_file_writer.Open(external_file3)); + ASSERT_OK(sst_file_writer.Put("5", "5")); + ASSERT_OK(sst_file_writer.Put("6", "6")); + ASSERT_OK(sst_file_writer.Finish()); + + ASSERT_OK(db_->IngestExternalFile(handle, {external_file1, external_file3}, + IngestExternalFileOptions())); + ASSERT_EQ(NumTableFilesAtLevel(1, 0), 2); + std::vector files; + GetSstFiles(env_, dbname_, &files); + ASSERT_EQ(files.size(), 2); + + port::Thread user_thread1( + [&]() { db_->CompactFiles(CompactionOptions(), handle, files, 1); }); + + port::Thread user_thread2([&]() { + ASSERT_OK(db_->IngestExternalFile(handle, {external_file2}, + IngestExternalFileOptions())); + TEST_SYNC_POINT("TestCompactFiles::IngestExternalFile1"); + }); + + user_thread1.join(); + user_thread2.join(); + + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); +} +#endif // ROCKSDB_LITE + +// TODO: figure out why this test fails in appveyor +#ifndef OS_WIN +TEST_F(DBTest2, MultiDBParallelOpenTest) { + const int kNumDbs = 2; + Options options = CurrentOptions(); + std::vector dbnames; + for (int i = 0; i < kNumDbs; ++i) { + dbnames.emplace_back(test::TmpDir(env_) + "/db" + ToString(i)); + ASSERT_OK(DestroyDB(dbnames.back(), options)); + } + + // Verify empty DBs can be created in parallel + std::vector open_threads; + std::vector dbs{static_cast(kNumDbs), nullptr}; + options.create_if_missing = true; + for (int i = 0; i < kNumDbs; ++i) { + open_threads.emplace_back( + [&](int dbnum) { + ASSERT_OK(DB::Open(options, dbnames[dbnum], &dbs[dbnum])); + }, + i); + } + + // Now add some data and close, so next we can verify non-empty DBs can be + // recovered in parallel + for (int i = 0; i < kNumDbs; ++i) { + open_threads[i].join(); + ASSERT_OK(dbs[i]->Put(WriteOptions(), "xi", "gua")); + delete dbs[i]; + } + + // Verify non-empty DBs can be recovered in parallel + dbs.clear(); + open_threads.clear(); + for (int i = 0; i < kNumDbs; ++i) { + open_threads.emplace_back( + [&](int dbnum) { + ASSERT_OK(DB::Open(options, dbnames[dbnum], &dbs[dbnum])); + }, + i); + } + + // Wait and cleanup + for (int i = 0; i < kNumDbs; ++i) { + open_threads[i].join(); + delete dbs[i]; + ASSERT_OK(DestroyDB(dbnames[i], options)); + } +} +#endif // OS_WIN + +namespace { +class DummyOldStats : public Statistics { + public: + uint64_t getTickerCount(uint32_t /*ticker_type*/) const override { return 0; } + void recordTick(uint32_t /* ticker_type */, uint64_t /* count */) override { + num_rt++; + } + void setTickerCount(uint32_t /*ticker_type*/, uint64_t /*count*/) override {} + uint64_t getAndResetTickerCount(uint32_t /*ticker_type*/) override { + return 0; + } + void measureTime(uint32_t /*histogram_type*/, uint64_t /*count*/) override { + num_mt++; + } + void histogramData(uint32_t /*histogram_type*/, + rocksdb::HistogramData* const /*data*/) const override {} + std::string getHistogramString(uint32_t /*type*/) const override { + return ""; + } + bool HistEnabledForType(uint32_t /*type*/) const override { return false; } + std::string ToString() const override { return ""; } + int num_rt = 0; + int num_mt = 0; +}; +} // namespace + +TEST_F(DBTest2, OldStatsInterface) { + DummyOldStats* dos = new DummyOldStats(); + std::shared_ptr stats(dos); + Options options = CurrentOptions(); + options.create_if_missing = true; + options.statistics = stats; + Reopen(options); + + Put("foo", "bar"); + ASSERT_EQ("bar", Get("foo")); + ASSERT_OK(Flush()); + ASSERT_EQ("bar", Get("foo")); + + ASSERT_GT(dos->num_rt, 0); + ASSERT_GT(dos->num_mt, 0); +} } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/db_test_util.cc b/thirdparty/rocksdb/db/db_test_util.cc index c4d465ba11..9ef82fd2e1 100644 --- a/thirdparty/rocksdb/db/db_test_util.cc +++ b/thirdparty/rocksdb/db/db_test_util.cc @@ -63,7 +63,7 @@ DBTestBase::DBTestBase(const std::string path) option_config_(kDefault) { env_->SetBackgroundThreads(1, Env::LOW); env_->SetBackgroundThreads(1, Env::HIGH); - dbname_ = test::TmpDir(env_) + path; + dbname_ = test::PerThreadDBPath(env_, path); alternative_wal_dir_ = dbname_ + "/wal"; alternative_db_log_dir_ = dbname_ + "/db_log_dir"; auto options = CurrentOptions(); @@ -101,24 +101,25 @@ DBTestBase::~DBTestBase() { bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) { #ifdef ROCKSDB_LITE // These options are not supported in ROCKSDB_LITE - if (option_config == kHashSkipList || - option_config == kPlainTableFirstBytePrefix || - option_config == kPlainTableCappedPrefix || - option_config == kPlainTableCappedPrefixNonMmap || - option_config == kPlainTableAllBytesPrefix || - option_config == kVectorRep || option_config == kHashLinkList || - option_config == kHashCuckoo || option_config == kUniversalCompaction || - option_config == kUniversalCompactionMultiLevel || - option_config == kUniversalSubcompactions || - option_config == kFIFOCompaction || - option_config == kConcurrentSkipList) { - return true; + if (option_config == kHashSkipList || + option_config == kPlainTableFirstBytePrefix || + option_config == kPlainTableCappedPrefix || + option_config == kPlainTableCappedPrefixNonMmap || + option_config == kPlainTableAllBytesPrefix || + option_config == kVectorRep || option_config == kHashLinkList || + option_config == kUniversalCompaction || + option_config == kUniversalCompactionMultiLevel || + option_config == kUniversalSubcompactions || + option_config == kFIFOCompaction || + option_config == kConcurrentSkipList) { + return true; } #endif if ((skip_mask & kSkipUniversalCompaction) && (option_config == kUniversalCompaction || - option_config == kUniversalCompactionMultiLevel)) { + option_config == kUniversalCompactionMultiLevel || + option_config == kUniversalSubcompactions)) { return true; } if ((skip_mask & kSkipMergePut) && option_config == kMergePut) { @@ -140,9 +141,6 @@ bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) { option_config == kBlockBasedTableWithWholeKeyHashIndex)) { return true; } - if ((skip_mask & kSkipHashCuckoo) && (option_config == kHashCuckoo)) { - return true; - } if ((skip_mask & kSkipFIFOCompaction) && option_config == kFIFOCompaction) { return true; } @@ -258,6 +256,47 @@ bool DBTestBase::ChangeFilterOptions() { return true; } +// Switch between different DB options for file ingestion tests. +bool DBTestBase::ChangeOptionsForFileIngestionTest() { + if (option_config_ == kDefault) { + option_config_ = kUniversalCompaction; + Destroy(last_options_); + auto options = CurrentOptions(); + options.create_if_missing = true; + TryReopen(options); + return true; + } else if (option_config_ == kUniversalCompaction) { + option_config_ = kUniversalCompactionMultiLevel; + Destroy(last_options_); + auto options = CurrentOptions(); + options.create_if_missing = true; + TryReopen(options); + return true; + } else if (option_config_ == kUniversalCompactionMultiLevel) { + option_config_ = kLevelSubcompactions; + Destroy(last_options_); + auto options = CurrentOptions(); + assert(options.max_subcompactions > 1); + TryReopen(options); + return true; + } else if (option_config_ == kLevelSubcompactions) { + option_config_ = kUniversalSubcompactions; + Destroy(last_options_); + auto options = CurrentOptions(); + assert(options.max_subcompactions > 1); + TryReopen(options); + return true; + } else if (option_config_ == kUniversalSubcompactions) { + option_config_ = kDirectIO; + Destroy(last_options_); + auto options = CurrentOptions(); + TryReopen(options); + return true; + } else { + return false; + } +} + // Return the current option configuration. Options DBTestBase::CurrentOptions( const anon::OptionsOverride& options_override) const { @@ -288,12 +327,11 @@ Options DBTestBase::GetOptions( Options options = default_options; BlockBasedTableOptions table_options; bool set_block_based_table_factory = true; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) +#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ + !defined(OS_AIX) rocksdb::SyncPoint::GetInstance()->ClearCallBack( "NewRandomAccessFile:O_DIRECT"); - rocksdb::SyncPoint::GetInstance()->ClearCallBack( - "NewWritableFile:O_DIRECT"); + rocksdb::SyncPoint::GetInstance()->ClearCallBack("NewWritableFile:O_DIRECT"); #endif bool can_allow_mmap = IsMemoryMappedAccessSupported(); @@ -342,11 +380,26 @@ Options DBTestBase::GetOptions( NewHashLinkListRepFactory(4, 0, 3, true, 4)); options.allow_concurrent_memtable_write = false; break; - case kHashCuckoo: - options.memtable_factory.reset( - NewHashCuckooRepFactory(options.write_buffer_size)); - options.allow_concurrent_memtable_write = false; - break; + case kDirectIO: { + options.use_direct_reads = true; + options.use_direct_io_for_flush_and_compaction = true; + options.compaction_readahead_size = 2 * 1024 * 1024; + #if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ + !defined(OS_AIX) && !defined(OS_OPENBSD) + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "NewWritableFile:O_DIRECT", [&](void* arg) { + int* val = static_cast(arg); + *val &= ~O_DIRECT; + }); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "NewRandomAccessFile:O_DIRECT", [&](void* arg) { + int* val = static_cast(arg); + *val &= ~O_DIRECT; + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + #endif + break; + } #endif // ROCKSDB_LITE case kMergePut: options.merge_operator = MergeOperators::CreatePutOperator(); @@ -410,6 +463,10 @@ Options DBTestBase::GetOptions( table_options.checksum = kxxHash; break; } + case kxxHash64Checksum: { + table_options.checksum = kxxHash64; + break; + } case kFIFOCompaction: { options.compaction_style = kCompactionStyleFIFO; break; @@ -429,6 +486,18 @@ Options DBTestBase::GetOptions( options.prefix_extractor.reset(NewNoopTransform()); break; } + case kBlockBasedTableWithPartitionedIndexFormat4: { + table_options.format_version = 4; + // Format 4 changes the binary index format. Since partitioned index is a + // super-set of simple indexes, we are also using kTwoLevelIndexSearch to + // test this format. + table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch; + // The top-level index in partition filters are also affected by format 4. + table_options.filter_policy.reset(NewBloomFilterPolicy(10, false)); + table_options.partition_filters = true; + table_options.index_block_restart_interval = 8; + break; + } case kBlockBasedTableWithIndexRestartInterval: { table_options.index_block_restart_interval = 8; break; @@ -461,33 +530,13 @@ Options DBTestBase::GetOptions( options.enable_write_thread_adaptive_yield = true; break; } - case kDirectIO: { - options.use_direct_reads = true; - options.use_direct_io_for_flush_and_compaction = true; - options.compaction_readahead_size = 2 * 1024 * 1024; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \ - !defined(OS_AIX) - rocksdb::SyncPoint::GetInstance()->SetCallBack( - "NewWritableFile:O_DIRECT", [&](void* arg) { - int* val = static_cast(arg); - *val &= ~O_DIRECT; - }); - rocksdb::SyncPoint::GetInstance()->SetCallBack( - "NewRandomAccessFile:O_DIRECT", [&](void* arg) { - int* val = static_cast(arg); - *val &= ~O_DIRECT; - }); - rocksdb::SyncPoint::GetInstance()->EnableProcessing(); -#endif - break; - } case kPipelinedWrite: { options.enable_pipelined_write = true; break; } case kConcurrentWALWrites: { // This options optimize 2PC commit path - options.concurrent_prepare = true; + options.two_write_queues = true; options.manual_wal_flush = true; break; } @@ -547,6 +596,7 @@ Status DBTestBase::TryReopenWithColumnFamilies( column_families.push_back(ColumnFamilyDescriptor(cfs[i], options[i])); } DBOptions db_opts = DBOptions(options[0]); + last_options_ = options[0]; return DB::Open(db_opts, dbname_, column_families, &handles_, &db_); } @@ -576,9 +626,17 @@ void DBTestBase::DestroyAndReopen(const Options& options) { ASSERT_OK(TryReopen(options)); } -void DBTestBase::Destroy(const Options& options) { +void DBTestBase::Destroy(const Options& options, bool delete_cf_paths) { + std::vector column_families; + if (delete_cf_paths) { + for (size_t i = 0; i < handles_.size(); ++i) { + ColumnFamilyDescriptor cfdescriptor; + handles_[i]->GetDescriptor(&cfdescriptor); + column_families.push_back(cfdescriptor); + } + } Close(); - ASSERT_OK(DestroyDB(dbname_, options)); + ASSERT_OK(DestroyDB(dbname_, options, column_families)); } Status DBTestBase::ReadOnlyReopen(const Options& options) { @@ -588,30 +646,19 @@ Status DBTestBase::ReadOnlyReopen(const Options& options) { Status DBTestBase::TryReopen(const Options& options) { Close(); last_options_.table_factory.reset(); - // Note: operator= is an unsafe approach here since it destructs shared_ptr in - // the same order of their creation, in contrast to destructors which - // destructs them in the opposite order of creation. One particular problme is - // that the cache destructor might invoke callback functions that use Option - // members such as statistics. To work around this problem, we manually call - // destructor of table_facotry which eventually clears the block cache. + // Note: operator= is an unsafe approach here since it destructs + // std::shared_ptr in the same order of their creation, in contrast to + // destructors which destructs them in the opposite order of creation. One + // particular problme is that the cache destructor might invoke callback + // functions that use Option members such as statistics. To work around this + // problem, we manually call destructor of table_facotry which eventually + // clears the block cache. last_options_ = options; return DB::Open(options, dbname_, &db_); } bool DBTestBase::IsDirectIOSupported() { - EnvOptions env_options; - env_options.use_mmap_writes = false; - env_options.use_direct_writes = true; - std::string tmp = TempFileName(dbname_, 999); - Status s; - { - unique_ptr file; - s = env_->NewWritableFile(tmp, &file, env_options); - } - if (s.ok()) { - s = env_->DeleteFile(tmp); - } - return s.ok(); + return test::IsDirectIOSupported(env_, dbname_); } bool DBTestBase::IsMemoryMappedAccessSupported() const { @@ -626,6 +673,13 @@ Status DBTestBase::Flush(int cf) { } } +Status DBTestBase::Flush(const std::vector& cf_ids) { + std::vector cfhs; + std::for_each(cf_ids.begin(), cf_ids.end(), + [&cfhs, this](int id) { cfhs.emplace_back(handles_[id]); }); + return db_->Flush(FlushOptions(), cfhs); +} + Status DBTestBase::Put(const Slice& k, const Slice& v, WriteOptions wo) { if (kMergePut == option_config_) { return db_->Merge(wo, k, v); @@ -668,6 +722,10 @@ Status DBTestBase::SingleDelete(int cf, const std::string& k) { return db_->SingleDelete(WriteOptions(), handles_[cf], k); } +bool DBTestBase::SetPreserveDeletesSequenceNumber(SequenceNumber sn) { + return db_->SetPreserveDeletesSequenceNumber(sn); +} + std::string DBTestBase::Get(const std::string& k, const Snapshot* snapshot) { ReadOptions options; options.verify_checksums = true; @@ -697,6 +755,31 @@ std::string DBTestBase::Get(int cf, const std::string& k, return result; } +std::vector DBTestBase::MultiGet(std::vector cfs, + const std::vector& k, + const Snapshot* snapshot) { + ReadOptions options; + options.verify_checksums = true; + options.snapshot = snapshot; + std::vector handles; + std::vector keys; + std::vector result; + + for (unsigned int i = 0; i < cfs.size(); ++i) { + handles.push_back(handles_[cfs[i]]); + keys.push_back(k[i]); + } + std::vector s = db_->MultiGet(options, handles, keys, &result); + for (unsigned int i = 0; i < s.size(); ++i) { + if (s[i].IsNotFound()) { + result[i] = "NOT_FOUND"; + } else if (!s[i].ok()) { + result[i] = s[i].ToString(); + } + } + return result; +} + Status DBTestBase::Get(const std::string& k, PinnableSlice* v) { ReadOptions options; options.verify_checksums = true; @@ -749,13 +832,15 @@ std::string DBTestBase::AllEntriesFor(const Slice& user_key, int cf) { Arena arena; auto options = CurrentOptions(); InternalKeyComparator icmp(options.comparator); - RangeDelAggregator range_del_agg(icmp, {} /* snapshots */); + ReadRangeDelAggregator range_del_agg(&icmp, + kMaxSequenceNumber /* upper_bound */); ScopedArenaIterator iter; if (cf == 0) { - iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg)); + iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg, + kMaxSequenceNumber)); } else { - iter.set( - dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[cf])); + iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg, + kMaxSequenceNumber, handles_[cf])); } InternalKey target(user_key, kMaxSequenceNumber, kTypeValue); iter->Seek(target.Encode()); @@ -864,7 +949,6 @@ size_t DBTestBase::CountLiveFiles() { db_->GetLiveFilesMetaData(&metadata); return metadata.size(); } -#endif // ROCKSDB_LITE int DBTestBase::NumTableFilesAtLevel(int level, int cf) { std::string property; @@ -925,6 +1009,7 @@ std::string DBTestBase::FilesPerLevel(int cf) { result.resize(last_non_zero_offset); return result; } +#endif // !ROCKSDB_LITE size_t DBTestBase::CountFiles() { std::vector files; @@ -994,6 +1079,7 @@ void DBTestBase::MoveFilesToLevel(int level, int cf) { } } +#ifndef ROCKSDB_LITE void DBTestBase::DumpFileCounts(const char* label) { fprintf(stderr, "---\n%s:\n", label); fprintf(stderr, "maxoverlap: %" PRIu64 "\n", @@ -1005,6 +1091,7 @@ void DBTestBase::DumpFileCounts(const char* label) { } } } +#endif // !ROCKSDB_LITE std::string DBTestBase::DumpSSTableList() { std::string property; @@ -1012,9 +1099,9 @@ std::string DBTestBase::DumpSSTableList() { return property; } -void DBTestBase::GetSstFiles(std::string path, +void DBTestBase::GetSstFiles(Env* env, std::string path, std::vector* files) { - env_->GetChildren(path, files); + env->GetChildren(path, files); files->erase( std::remove_if(files->begin(), files->end(), [](std::string name) { @@ -1026,7 +1113,7 @@ void DBTestBase::GetSstFiles(std::string path, int DBTestBase::GetSstFileCount(std::string path) { std::vector files; - GetSstFiles(path, &files); + DBTestBase::GetSstFiles(env_, path, &files); return static_cast(files.size()); } @@ -1138,39 +1225,44 @@ UpdateStatus DBTestBase::updateInPlaceSmallerVarintSize(char* prevValue, } } -UpdateStatus DBTestBase::updateInPlaceLargerSize(char* prevValue, - uint32_t* prevSize, +UpdateStatus DBTestBase::updateInPlaceLargerSize(char* /*prevValue*/, + uint32_t* /*prevSize*/, Slice delta, std::string* newValue) { *newValue = std::string(delta.size(), 'c'); return UpdateStatus::UPDATED; } -UpdateStatus DBTestBase::updateInPlaceNoAction(char* prevValue, - uint32_t* prevSize, Slice delta, - std::string* newValue) { +UpdateStatus DBTestBase::updateInPlaceNoAction(char* /*prevValue*/, + uint32_t* /*prevSize*/, + Slice /*delta*/, + std::string* /*newValue*/) { return UpdateStatus::UPDATE_FAILED; } // Utility method to test InplaceUpdate void DBTestBase::validateNumberOfEntries(int numValues, int cf) { - ScopedArenaIterator iter; Arena arena; auto options = CurrentOptions(); InternalKeyComparator icmp(options.comparator); - RangeDelAggregator range_del_agg(icmp, {} /* snapshots */); + ReadRangeDelAggregator range_del_agg(&icmp, + kMaxSequenceNumber /* upper_bound */); + // This should be defined after range_del_agg so that it destructs the + // assigned iterator before it range_del_agg is already destructed. + ScopedArenaIterator iter; if (cf != 0) { - iter.set( - dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[cf])); + iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg, + kMaxSequenceNumber, handles_[cf])); } else { - iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg)); + iter.set(dbfull()->NewInternalIterator(&arena, &range_del_agg, + kMaxSequenceNumber)); } iter->SeekToFirst(); ASSERT_EQ(iter->status().ok(), true); int seq = numValues; while (iter->Valid()) { ParsedInternalKey ikey; - ikey.sequence = -1; + ikey.clear(); ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true); // checks sequence number for updates @@ -1183,9 +1275,9 @@ void DBTestBase::validateNumberOfEntries(int numValues, int cf) { void DBTestBase::CopyFile(const std::string& source, const std::string& destination, uint64_t size) { const EnvOptions soptions; - unique_ptr srcfile; + std::unique_ptr srcfile; ASSERT_OK(env_->NewSequentialFile(source, &srcfile, soptions)); - unique_ptr destfile; + std::unique_ptr destfile; ASSERT_OK(env_->NewWritableFile(destination, &destfile, soptions)); if (size == 0) { @@ -1363,8 +1455,10 @@ void DBTestBase::VerifyDBInternal( std::vector> true_data) { Arena arena; InternalKeyComparator icmp(last_options_.comparator); - RangeDelAggregator range_del_agg(icmp, {}); - auto iter = dbfull()->NewInternalIterator(&arena, &range_del_agg); + ReadRangeDelAggregator range_del_agg(&icmp, + kMaxSequenceNumber /* upper_bound */); + auto iter = + dbfull()->NewInternalIterator(&arena, &range_del_agg, kMaxSequenceNumber); iter->SeekToFirst(); for (auto p : true_data) { ASSERT_TRUE(iter->Valid()); diff --git a/thirdparty/rocksdb/db/db_test_util.h b/thirdparty/rocksdb/db/db_test_util.h index f2caa46ca2..1ba1f0a964 100644 --- a/thirdparty/rocksdb/db/db_test_util.h +++ b/thirdparty/rocksdb/db/db_test_util.h @@ -46,6 +46,7 @@ #include "table/scoped_arena_iterator.h" #include "util/compression.h" #include "util/filename.h" +#include "util/mock_time_env.h" #include "util/mutexlock.h" #include "util/string_util.h" @@ -109,8 +110,6 @@ struct OptionsOverride { // These will be used only if filter_policy is set bool partition_filters = false; uint64_t metadata_block_size = 1024; - BlockBasedTableOptions::IndexType index_type = - BlockBasedTableOptions::IndexType::kBinarySearch; // Used as a bit mask of individual enums in which to skip an XF test point int skip_policy = 0; @@ -137,8 +136,8 @@ class SpecialMemTableRep : public MemTableRep { // Insert key into the list. // REQUIRES: nothing that compares equal to key is currently in the list. virtual void Insert(KeyHandle handle) override { - memtable_->Insert(handle); num_entries_++; + memtable_->Insert(handle); } // Returns true iff an entry that compares equal to key is in the list. @@ -170,7 +169,7 @@ class SpecialMemTableRep : public MemTableRep { virtual ~SpecialMemTableRep() override {} private: - unique_ptr memtable_; + std::unique_ptr memtable_; int num_entries_flush_; int num_entries_; }; @@ -187,7 +186,7 @@ class SpecialSkipListFactory : public MemTableRepFactory { using MemTableRepFactory::CreateMemTableRep; virtual MemTableRep* CreateMemTableRep( const MemTableRep::KeyComparator& compare, Allocator* allocator, - const SliceTransform* transform, Logger* logger) override { + const SliceTransform* transform, Logger* /*logger*/) override { return new SpecialMemTableRep( allocator, factory_.CreateMemTableRep(compare, allocator, transform, 0), num_entries_flush_); @@ -208,15 +207,15 @@ class SpecialEnv : public EnvWrapper { public: explicit SpecialEnv(Env* base); - Status NewWritableFile(const std::string& f, unique_ptr* r, + Status NewWritableFile(const std::string& f, std::unique_ptr* r, const EnvOptions& soptions) override { class SSTableFile : public WritableFile { private: SpecialEnv* env_; - unique_ptr base_; + std::unique_ptr base_; public: - SSTableFile(SpecialEnv* env, unique_ptr&& base) + SSTableFile(SpecialEnv* env, std::unique_ptr&& base) : env_(env), base_(std::move(base)) {} Status Append(const Slice& data) override { if (env_->table_write_callback_) { @@ -296,7 +295,7 @@ class SpecialEnv : public EnvWrapper { }; class ManifestFile : public WritableFile { public: - ManifestFile(SpecialEnv* env, unique_ptr&& b) + ManifestFile(SpecialEnv* env, std::unique_ptr&& b) : env_(env), base_(std::move(b)) {} Status Append(const Slice& data) override { if (env_->manifest_write_error_.load(std::memory_order_acquire)) { @@ -317,14 +316,17 @@ class SpecialEnv : public EnvWrapper { } } uint64_t GetFileSize() override { return base_->GetFileSize(); } + Status Allocate(uint64_t offset, uint64_t len) override { + return base_->Allocate(offset, len); + } private: SpecialEnv* env_; - unique_ptr base_; + std::unique_ptr base_; }; class WalFile : public WritableFile { public: - WalFile(SpecialEnv* env, unique_ptr&& b) + WalFile(SpecialEnv* env, std::unique_ptr&& b) : env_(env), base_(std::move(b)) { env_->num_open_wal_file_.fetch_add(1); } @@ -370,10 +372,13 @@ class SpecialEnv : public EnvWrapper { bool IsSyncThreadSafe() const override { return env_->is_wal_sync_thread_safe_.load(); } + Status Allocate(uint64_t offset, uint64_t len) override { + return base_->Allocate(offset, len); + } private: SpecialEnv* env_; - unique_ptr base_; + std::unique_ptr base_; }; if (non_writeable_rate_.load(std::memory_order_acquire) > 0) { @@ -415,11 +420,11 @@ class SpecialEnv : public EnvWrapper { } Status NewRandomAccessFile(const std::string& f, - unique_ptr* r, + std::unique_ptr* r, const EnvOptions& soptions) override { class CountingFile : public RandomAccessFile { public: - CountingFile(unique_ptr&& target, + CountingFile(std::unique_ptr&& target, anon::AtomicCounter* counter, std::atomic* bytes_read) : target_(std::move(target)), @@ -434,7 +439,7 @@ class SpecialEnv : public EnvWrapper { } private: - unique_ptr target_; + std::unique_ptr target_; anon::AtomicCounter* counter_; std::atomic* bytes_read_; }; @@ -445,14 +450,18 @@ class SpecialEnv : public EnvWrapper { r->reset(new CountingFile(std::move(*r), &random_read_counter_, &random_read_bytes_counter_)); } + if (s.ok() && soptions.compaction_readahead_size > 0) { + compaction_readahead_size_ = soptions.compaction_readahead_size; + } return s; } - Status NewSequentialFile(const std::string& f, unique_ptr* r, - const EnvOptions& soptions) override { + virtual Status NewSequentialFile(const std::string& f, + std::unique_ptr* r, + const EnvOptions& soptions) override { class CountingFile : public SequentialFile { public: - CountingFile(unique_ptr&& target, + CountingFile(std::unique_ptr&& target, anon::AtomicCounter* counter) : target_(std::move(target)), counter_(counter) {} virtual Status Read(size_t n, Slice* result, char* scratch) override { @@ -462,7 +471,7 @@ class SpecialEnv : public EnvWrapper { virtual Status Skip(uint64_t n) override { return target_->Skip(n); } private: - unique_ptr target_; + std::unique_ptr target_; anon::AtomicCounter* counter_; }; @@ -494,6 +503,11 @@ class SpecialEnv : public EnvWrapper { return s; } + virtual uint64_t NowCPUNanos() override { + now_cpu_count_.fetch_add(1); + return target()->NowCPUNanos(); + } + virtual uint64_t NowNanos() override { return (time_elapse_only_sleep_ ? 0 : target()->NowNanos()) + addon_time_.load() * 1000; @@ -563,44 +577,17 @@ class SpecialEnv : public EnvWrapper { std::atomic addon_time_; + std::atomic now_cpu_count_; + std::atomic delete_count_; - bool time_elapse_only_sleep_; + std::atomic time_elapse_only_sleep_; bool no_slowdown_; std::atomic is_wal_sync_thread_safe_{true}; -}; - -class MockTimeEnv : public EnvWrapper { - public: - explicit MockTimeEnv(Env* base) : EnvWrapper(base) {} - - virtual Status GetCurrentTime(int64_t* time) override { - assert(time != nullptr); - assert(current_time_ <= - static_cast(std::numeric_limits::max())); - *time = static_cast(current_time_); - return Status::OK(); - } - - virtual uint64_t NowMicros() override { - assert(current_time_ <= std::numeric_limits::max() / 1000000); - return current_time_ * 1000000; - } - - virtual uint64_t NowNanos() override { - assert(current_time_ <= std::numeric_limits::max() / 1000000000); - return current_time_ * 1000000000; - } - void set_current_time(uint64_t time) { - assert(time >= current_time_); - current_time_ = time; - } - - private: - uint64_t current_time_ = 0; + std::atomic compaction_readahead_size_{}; }; #ifndef ROCKSDB_LITE @@ -665,36 +652,38 @@ class DBTestBase : public testing::Test { kPlainTableAllBytesPrefix = 6, kVectorRep = 7, kHashLinkList = 8, - kHashCuckoo = 9, - kMergePut = 10, - kFilter = 11, - kFullFilterWithNewTableReaderForCompactions = 12, - kUncompressed = 13, - kNumLevel_3 = 14, - kDBLogDir = 15, - kWalDirAndMmapReads = 16, - kManifestFileSize = 17, - kPerfOptions = 18, - kHashSkipList = 19, - kUniversalCompaction = 20, - kUniversalCompactionMultiLevel = 21, - kCompressedBlockCache = 22, - kInfiniteMaxOpenFiles = 23, - kxxHashChecksum = 24, - kFIFOCompaction = 25, - kOptimizeFiltersForHits = 26, - kRowCache = 27, - kRecycleLogFiles = 28, - kConcurrentSkipList = 29, - kPipelinedWrite = 30, - kConcurrentWALWrites = 31, - kEnd = 32, - kDirectIO = 33, - kLevelSubcompactions = 34, - kUniversalSubcompactions = 35, - kBlockBasedTableWithIndexRestartInterval = 36, - kBlockBasedTableWithPartitionedIndex = 37, - kPartitionedFilterWithNewTableReaderForCompactions = 38, + kMergePut = 9, + kFilter = 10, + kFullFilterWithNewTableReaderForCompactions = 11, + kUncompressed = 12, + kNumLevel_3 = 13, + kDBLogDir = 14, + kWalDirAndMmapReads = 15, + kManifestFileSize = 16, + kPerfOptions = 17, + kHashSkipList = 18, + kUniversalCompaction = 19, + kUniversalCompactionMultiLevel = 20, + kCompressedBlockCache = 21, + kInfiniteMaxOpenFiles = 22, + kxxHashChecksum = 23, + kFIFOCompaction = 24, + kOptimizeFiltersForHits = 25, + kRowCache = 26, + kRecycleLogFiles = 27, + kConcurrentSkipList = 28, + kPipelinedWrite = 29, + kConcurrentWALWrites = 30, + kDirectIO, + kLevelSubcompactions, + kBlockBasedTableWithIndexRestartInterval, + kBlockBasedTableWithPartitionedIndex, + kBlockBasedTableWithPartitionedIndexFormat4, + kPartitionedFilterWithNewTableReaderForCompactions, + kUniversalSubcompactions, + kxxHash64Checksum, + // This must be the last line + kEnd, }; public: @@ -720,11 +709,17 @@ class DBTestBase : public testing::Test { kSkipPlainTable = 8, kSkipHashIndex = 16, kSkipNoSeekToLast = 32, - kSkipHashCuckoo = 64, kSkipFIFOCompaction = 128, kSkipMmapReads = 256, }; + const int kRangeDelSkipConfigs = + // Plain tables do not support range deletions. + kSkipPlainTable | + // MmapReads disables the iterator pinning that RangeDelAggregator + // requires. + kSkipMmapReads; + explicit DBTestBase(const std::string path); ~DBTestBase(); @@ -757,6 +752,9 @@ class DBTestBase : public testing::Test { // Jump from kDefault to kFilter to kFullFilter bool ChangeFilterOptions(); + // Switch between different DB options for file ingestion tests. + bool ChangeOptionsForFileIngestionTest(); + // Return the current option configuration. Options CurrentOptions(const anon::OptionsOverride& options_override = anon::OptionsOverride()) const; @@ -798,7 +796,7 @@ class DBTestBase : public testing::Test { void DestroyAndReopen(const Options& options); - void Destroy(const Options& options); + void Destroy(const Options& options, bool delete_cf_paths = false); Status ReadOnlyReopen(const Options& options); @@ -810,6 +808,8 @@ class DBTestBase : public testing::Test { Status Flush(int cf = 0); + Status Flush(const std::vector& cf_ids); + Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions()); Status Put(int cf, const Slice& k, const Slice& v, @@ -829,6 +829,8 @@ class DBTestBase : public testing::Test { Status SingleDelete(int cf, const std::string& k); + bool SetPreserveDeletesSequenceNumber(SequenceNumber sn); + std::string Get(const std::string& k, const Snapshot* snapshot = nullptr); std::string Get(int cf, const std::string& k, @@ -836,6 +838,10 @@ class DBTestBase : public testing::Test { Status Get(const std::string& k, PinnableSlice* v); + std::vector MultiGet(std::vector cfs, + const std::vector& k, + const Snapshot* snapshot = nullptr); + uint64_t GetNumSnapshots(); uint64_t GetTimeOldestSnapshots(); @@ -856,13 +862,13 @@ class DBTestBase : public testing::Test { size_t TotalLiveFiles(int cf = 0); size_t CountLiveFiles(); -#endif // ROCKSDB_LITE int NumTableFilesAtLevel(int level, int cf = 0); double CompressionRatioAtLevel(int level, int cf = 0); int TotalTableFiles(int cf = 0, int levels = -1); +#endif // ROCKSDB_LITE // Return spread of files per level std::string FilesPerLevel(int cf = 0); @@ -890,11 +896,14 @@ class DBTestBase : public testing::Test { void MoveFilesToLevel(int level, int cf = 0); +#ifndef ROCKSDB_LITE void DumpFileCounts(const char* label); +#endif // ROCKSDB_LITE std::string DumpSSTableList(); - void GetSstFiles(std::string path, std::vector* files); + static void GetSstFiles(Env* env, std::string path, + std::vector* files); int GetSstFileCount(std::string path); diff --git a/thirdparty/rocksdb/db/db_universal_compaction_test.cc b/thirdparty/rocksdb/db/db_universal_compaction_test.cc index 58fda80d54..2bd8af684e 100644 --- a/thirdparty/rocksdb/db/db_universal_compaction_test.cc +++ b/thirdparty/rocksdb/db/db_universal_compaction_test.cc @@ -10,6 +10,7 @@ #include "db/db_test_util.h" #include "port/stack_trace.h" #if !defined(ROCKSDB_LITE) +#include "rocksdb/utilities/table_properties_collectors.h" #include "util/sync_point.h" namespace rocksdb { @@ -26,7 +27,7 @@ class DBTestUniversalCompactionBase public: explicit DBTestUniversalCompactionBase( const std::string& path) : DBTestBase(path) {} - virtual void SetUp() override { + void SetUp() override { num_levels_ = std::get<0>(GetParam()); exclusive_manual_compaction_ = std::get<1>(GetParam()); } @@ -40,6 +41,12 @@ class DBTestUniversalCompaction : public DBTestUniversalCompactionBase { DBTestUniversalCompactionBase("/db_universal_compaction_test") {} }; +class DBTestUniversalDeleteTrigCompaction : public DBTestBase { + public: + DBTestUniversalDeleteTrigCompaction() + : DBTestBase("/db_universal_compaction_test") {} +}; + namespace { void VerifyCompactionResult( const ColumnFamilyMetaData& cf_meta, @@ -56,13 +63,13 @@ void VerifyCompactionResult( 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 { + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { return false; } - virtual const char* Name() const override { return "KeepFilter"; } + const char* Name() const override { return "KeepFilter"; } }; class KeepFilterFactory : public CompactionFilterFactory { @@ -70,7 +77,7 @@ class KeepFilterFactory : public CompactionFilterFactory { explicit KeepFilterFactory(bool check_context = false) : check_context_(check_context) {} - virtual std::unique_ptr CreateCompactionFilter( + std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) override { if (check_context_) { EXPECT_EQ(expect_full_compaction_.load(), context.is_full_compaction); @@ -79,7 +86,7 @@ class KeepFilterFactory : public CompactionFilterFactory { return std::unique_ptr(new KeepFilter()); } - virtual const char* Name() const override { return "KeepFilterFactory"; } + const char* Name() const override { return "KeepFilterFactory"; } bool check_context_; std::atomic_bool expect_full_compaction_; std::atomic_bool expect_manual_compaction_; @@ -88,14 +95,14 @@ class KeepFilterFactory : public CompactionFilterFactory { class DelayFilter : public CompactionFilter { public: explicit DelayFilter(DBTestBase* d) : db_test(d) {} - virtual bool Filter(int level, const Slice& key, const Slice& value, - std::string* new_value, - bool* value_changed) const override { + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { db_test->env_->addon_time_.fetch_add(1000); return true; } - virtual const char* Name() const override { return "DelayFilter"; } + const char* Name() const override { return "DelayFilter"; } private: DBTestBase* db_test; @@ -104,12 +111,12 @@ class DelayFilter : public CompactionFilter { class DelayFilterFactory : public CompactionFilterFactory { public: explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {} - virtual std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override { + std::unique_ptr CreateCompactionFilter( + const CompactionFilter::Context& /*context*/) override { return std::unique_ptr(new DelayFilter(db_test)); } - virtual const char* Name() const override { return "DelayFilterFactory"; } + const char* Name() const override { return "DelayFilterFactory"; } private: DBTestBase* db_test; @@ -374,6 +381,181 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSizeAmplification) { ASSERT_EQ(NumSortedRuns(1), 1); } +TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionSizeAmplification) { + Options options = CurrentOptions(); + options.compaction_style = kCompactionStyleUniversal; + options.num_levels = 1; + options.write_buffer_size = 100 << 10; // 100KB + options.target_file_size_base = 32 << 10; // 32KB + options.level0_file_num_compaction_trigger = 3; + // Initial setup of compaction_options_universal will prevent universal + // compaction from happening + options.compaction_options_universal.size_ratio = 100; + options.compaction_options_universal.min_merge_width = 100; + DestroyAndReopen(options); + + int total_picked_compactions = 0; + int total_size_amp_compactions = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "UniversalCompactionPicker::PickCompaction:Return", [&](void* arg) { + if (arg) { + total_picked_compactions++; + Compaction* c = static_cast(arg); + if (c->compaction_reason() == + CompactionReason::kUniversalSizeAmplification) { + total_size_amp_compactions++; + } + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + MutableCFOptions mutable_cf_options; + CreateAndReopenWithCF({"pikachu"}, options); + + Random rnd(301); + int key_idx = 0; + + // Generate two files in Level 0. Both files are approx the same size. + for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; + num++) { + // Write 110KB (11 values, each 10K) + for (int i = 0; i < 11; i++) { + ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000))); + key_idx++; + } + dbfull()->TEST_WaitForFlushMemTable(handles_[1]); + ASSERT_EQ(NumSortedRuns(1), num + 1); + } + ASSERT_EQ(NumSortedRuns(1), 2); + + // Flush whatever is remaining in memtable. This is typically + // small, which should not trigger size ratio based compaction + // but could instead trigger size amplification if it's set + // to 110. + ASSERT_OK(Flush(1)); + dbfull()->TEST_WaitForCompact(); + // Verify compaction did not happen + ASSERT_EQ(NumSortedRuns(1), 3); + + // Trigger compaction if size amplification exceeds 110% without reopening DB + ASSERT_EQ(dbfull() + ->GetOptions(handles_[1]) + .compaction_options_universal.max_size_amplification_percent, + 200); + ASSERT_OK(dbfull()->SetOptions(handles_[1], + {{"compaction_options_universal", + "{max_size_amplification_percent=110;}"}})); + ASSERT_EQ(dbfull() + ->GetOptions(handles_[1]) + .compaction_options_universal.max_size_amplification_percent, + 110); + ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], + &mutable_cf_options)); + ASSERT_EQ(110, mutable_cf_options.compaction_options_universal + .max_size_amplification_percent); + + dbfull()->TEST_WaitForCompact(); + // Verify that size amplification did happen + ASSERT_EQ(NumSortedRuns(1), 1); + ASSERT_EQ(total_picked_compactions, 1); + ASSERT_EQ(total_size_amp_compactions, 1); +} + +TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionReadAmplification) { + Options options = CurrentOptions(); + options.compaction_style = kCompactionStyleUniversal; + options.num_levels = 1; + options.write_buffer_size = 100 << 10; // 100KB + options.target_file_size_base = 32 << 10; // 32KB + options.level0_file_num_compaction_trigger = 3; + // Initial setup of compaction_options_universal will prevent universal + // compaction from happening + options.compaction_options_universal.max_size_amplification_percent = 2000; + options.compaction_options_universal.size_ratio = 0; + options.compaction_options_universal.min_merge_width = 100; + DestroyAndReopen(options); + + int total_picked_compactions = 0; + int total_size_ratio_compactions = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "UniversalCompactionPicker::PickCompaction:Return", [&](void* arg) { + if (arg) { + total_picked_compactions++; + Compaction* c = static_cast(arg); + if (c->compaction_reason() == CompactionReason::kUniversalSizeRatio) { + total_size_ratio_compactions++; + } + } + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + MutableCFOptions mutable_cf_options; + CreateAndReopenWithCF({"pikachu"}, options); + + Random rnd(301); + int key_idx = 0; + + // Generate three files in Level 0. All files are approx the same size. + for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) { + // Write 110KB (11 values, each 10K) + for (int i = 0; i < 11; i++) { + ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000))); + key_idx++; + } + dbfull()->TEST_WaitForFlushMemTable(handles_[1]); + ASSERT_EQ(NumSortedRuns(1), num + 1); + } + ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger); + + // Flush whatever is remaining in memtable. This is typically small, about + // 30KB. + ASSERT_OK(Flush(1)); + dbfull()->TEST_WaitForCompact(); + // Verify compaction did not happen + ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger + 1); + ASSERT_EQ(total_picked_compactions, 0); + + ASSERT_OK(dbfull()->SetOptions( + handles_[1], + {{"compaction_options_universal", + "{min_merge_width=2;max_merge_width=2;size_ratio=100;}"}})); + ASSERT_EQ(dbfull() + ->GetOptions(handles_[1]) + .compaction_options_universal.min_merge_width, + 2); + ASSERT_EQ(dbfull() + ->GetOptions(handles_[1]) + .compaction_options_universal.max_merge_width, + 2); + ASSERT_EQ( + dbfull()->GetOptions(handles_[1]).compaction_options_universal.size_ratio, + 100); + + ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1], + &mutable_cf_options)); + ASSERT_EQ(mutable_cf_options.compaction_options_universal.size_ratio, 100); + ASSERT_EQ(mutable_cf_options.compaction_options_universal.min_merge_width, 2); + ASSERT_EQ(mutable_cf_options.compaction_options_universal.max_merge_width, 2); + + dbfull()->TEST_WaitForCompact(); + + // Files in L0 are approx: 0.3 (30KB), 1, 1, 1. + // On compaction: the files are below the size amp threshold, so we + // fallthrough to checking read amp conditions. The configured size ratio is + // not big enough to take 0.3 into consideration. So the next files 1 and 1 + // are compacted together first as they satisfy size ratio condition and + // (min_merge_width, max_merge_width) condition, to give out a file size of 2. + // Next, the newly generated 2 and the last file 1 are compacted together. So + // at the end: #sortedRuns = 2, #picked_compactions = 2, and all the picked + // ones are size ratio based compactions. + ASSERT_EQ(NumSortedRuns(1), 2); + // If max_merge_width had not been changed dynamically above, and if it + // continued to be the default value of UINIT_MAX, total_picked_compactions + // would have been 1. + ASSERT_EQ(total_picked_compactions, 2); + ASSERT_EQ(total_size_ratio_compactions, 2); +} + TEST_P(DBTestUniversalCompaction, CompactFilesOnUniversalCompaction) { const int kTestKeySize = 16; const int kTestValueSize = 984; @@ -480,7 +662,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTargetLevel) { ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0)); } - +#ifndef ROCKSDB_VALGRIND_RUN class DBTestUniversalCompactionMultiLevels : public DBTestUniversalCompactionBase { public: @@ -516,13 +698,14 @@ TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionMultiLevels) { ASSERT_EQ(Get(1, Key(i % num_keys)), Key(i)); } } + // Tests universal compaction with trivial move enabled TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionTrivialMove) { int32_t trivial_move = 0; int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", [&](void* arg) { non_trivial_move++; @@ -594,7 +777,7 @@ TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) { std::atomic num_compactions_running(0); std::atomic has_parallel(false); rocksdb::SyncPoint::GetInstance()->SetCallBack("CompactionJob::Run():Start", - [&](void* arg) { + [&](void* /*arg*/) { if (num_compactions_running.fetch_add(1) > 0) { has_parallel.store(true); return; @@ -609,7 +792,7 @@ TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) { }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "CompactionJob::Run():End", - [&](void* arg) { num_compactions_running.fetch_add(-1); }); + [&](void* /*arg*/) { num_compactions_running.fetch_add(-1); }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); options = CurrentOptions(options); @@ -758,6 +941,7 @@ INSTANTIATE_TEST_CASE_P(DBTestUniversalCompactionParallel, DBTestUniversalCompactionParallel, ::testing::Combine(::testing::Values(1, 10), ::testing::Values(false))); +#endif // ROCKSDB_VALGRIND_RUN TEST_P(DBTestUniversalCompaction, UniversalCompactionOptions) { Options options = CurrentOptions(); @@ -970,16 +1154,17 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio2) { dbfull()->TEST_WaitForFlushMemTable(); dbfull()->TEST_WaitForCompact(); } - ASSERT_LT(TotalSize(), 120000U * 12 * 0.8 + 120000 * 2); + ASSERT_LT(TotalSize(), 120000U * 12 * 0.82 + 120000 * 2); } +#ifndef ROCKSDB_VALGRIND_RUN // Test that checks trivial move in universal compaction TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest1) { int32_t trivial_move = 0; int32_t non_trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", [&](void* arg) { non_trivial_move++; @@ -1025,7 +1210,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) { int32_t trivial_move = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:TrivialMove", - [&](void* arg) { trivial_move++; }); + [&](void* /*arg*/) { trivial_move++; }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:NonTrivial", [&](void* arg) { ASSERT_TRUE(arg != nullptr); @@ -1065,6 +1250,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } +#endif // ROCKSDB_VALGRIND_RUN TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) { Options options = CurrentOptions(); @@ -1168,6 +1354,146 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) { Destroy(options); } +TEST_P(DBTestUniversalCompaction, UniversalCompactionCFPathUse) { + Options options = CurrentOptions(); + 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.memtable_factory.reset( + new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); + options.compaction_style = kCompactionStyleUniversal; + options.compaction_options_universal.size_ratio = 10; + options.write_buffer_size = 111 << 10; // 114KB + options.arena_block_size = 4 << 10; + options.level0_file_num_compaction_trigger = 2; + options.num_levels = 1; + + std::vector option_vector; + option_vector.emplace_back(options); + ColumnFamilyOptions cf_opt1(options), cf_opt2(options); + // Configure CF1 specific paths. + cf_opt1.cf_paths.emplace_back(dbname_ + "cf1", 300 * 1024); + cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_2", 300 * 1024); + cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_3", 500 * 1024); + cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_4", 1024 * 1024 * 1024); + option_vector.emplace_back(DBOptions(options), cf_opt1); + CreateColumnFamilies({"one"},option_vector[1]); + + // Configura CF2 specific paths. + cf_opt2.cf_paths.emplace_back(dbname_ + "cf2", 300 * 1024); + cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_2", 300 * 1024); + cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_3", 500 * 1024); + cf_opt2.cf_paths.emplace_back(dbname_ + "cf2_4", 1024 * 1024 * 1024); + option_vector.emplace_back(DBOptions(options), cf_opt2); + CreateColumnFamilies({"two"},option_vector[2]); + + ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); + + Random rnd(301); + int key_idx = 0; + int key_idx1 = 0; + int key_idx2 = 0; + + auto generate_file = [&]() { + GenerateNewFile(0, &rnd, &key_idx); + GenerateNewFile(1, &rnd, &key_idx1); + GenerateNewFile(2, &rnd, &key_idx2); + }; + + auto check_sstfilecount = [&](int path_id, int expected) { + ASSERT_EQ(expected, GetSstFileCount(options.db_paths[path_id].path)); + ASSERT_EQ(expected, GetSstFileCount(cf_opt1.cf_paths[path_id].path)); + ASSERT_EQ(expected, GetSstFileCount(cf_opt2.cf_paths[path_id].path)); + }; + + auto check_getvalues = [&]() { + for (int i = 0; i < key_idx; i++) { + auto v = Get(0, Key(i)); + ASSERT_NE(v, "NOT_FOUND"); + ASSERT_TRUE(v.size() == 1 || v.size() == 990); + } + + for (int i = 0; i < key_idx1; i++) { + auto v = Get(1, Key(i)); + ASSERT_NE(v, "NOT_FOUND"); + ASSERT_TRUE(v.size() == 1 || v.size() == 990); + } + + for (int i = 0; i < key_idx2; i++) { + auto v = Get(2, Key(i)); + ASSERT_NE(v, "NOT_FOUND"); + ASSERT_TRUE(v.size() == 1 || v.size() == 990); + } + }; + + // First three 110KB files are not going to second path. + // After that, (100K, 200K) + for (int num = 0; num < 3; num++) { + generate_file(); + } + + // Another 110KB triggers a compaction to 400K file to second path + generate_file(); + check_sstfilecount(2, 1); + + // (1, 4) + generate_file(); + check_sstfilecount(2, 1); + check_sstfilecount(0, 1); + + // (1,1,4) -> (2, 4) + generate_file(); + check_sstfilecount(2, 1); + check_sstfilecount(1, 1); + check_sstfilecount(0, 0); + + // (1, 2, 4) -> (3, 4) + generate_file(); + check_sstfilecount(2, 1); + check_sstfilecount(1, 1); + check_sstfilecount(0, 0); + + // (1, 3, 4) -> (8) + generate_file(); + check_sstfilecount(3, 1); + + // (1, 8) + generate_file(); + check_sstfilecount(3, 1); + check_sstfilecount(0, 1); + + // (1, 1, 8) -> (2, 8) + generate_file(); + check_sstfilecount(3, 1); + check_sstfilecount(1, 1); + + // (1, 2, 8) -> (3, 8) + generate_file(); + check_sstfilecount(3, 1); + check_sstfilecount(1, 1); + check_sstfilecount(0, 0); + + // (1, 3, 8) -> (4, 8) + generate_file(); + check_sstfilecount(2, 1); + check_sstfilecount(3, 1); + + // (1, 4, 8) -> (5, 8) + generate_file(); + check_sstfilecount(3, 1); + check_sstfilecount(2, 1); + check_sstfilecount(0, 0); + + check_getvalues(); + + ReopenWithColumnFamilies({"default", "one", "two"}, option_vector); + + check_getvalues(); + + Destroy(options, true); +} + TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) { std::function verify_func = [&](int num_keys_in_db) { std::string keys_in_db; @@ -1365,50 +1691,6 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) { Destroy(options); } -TEST_P(DBTestUniversalCompaction, FullCompactionInBottomPriThreadPool) { - const int kNumFilesTrigger = 3; - Env::Default()->SetBackgroundThreads(1, Env::Priority::BOTTOM); - for (bool allow_ingest_behind : {false, true}) { - Options options = CurrentOptions(); - options.allow_ingest_behind = allow_ingest_behind; - options.compaction_style = kCompactionStyleUniversal; - options.num_levels = num_levels_; - options.write_buffer_size = 100 << 10; // 100KB - options.target_file_size_base = 32 << 10; // 32KB - options.level0_file_num_compaction_trigger = kNumFilesTrigger; - // Trigger compaction if size amplification exceeds 110% - options.compaction_options_universal.max_size_amplification_percent = 110; - DestroyAndReopen(options); - - int num_bottom_pri_compactions = 0; - SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BGWorkBottomCompaction", - [&](void* arg) { ++num_bottom_pri_compactions; }); - SyncPoint::GetInstance()->EnableProcessing(); - - Random rnd(301); - for (int num = 0; num < kNumFilesTrigger; num++) { - ASSERT_EQ(NumSortedRuns(), num); - int key_idx = 0; - GenerateNewFile(&rnd, &key_idx); - } - dbfull()->TEST_WaitForCompact(); - - if (allow_ingest_behind || num_levels_ > 1) { - // allow_ingest_behind increases number of levels while sanitizing. - ASSERT_EQ(1, num_bottom_pri_compactions); - } else { - // for single-level universal, everything's bottom level so nothing should - // be executed in bottom-pri thread pool. - ASSERT_EQ(0, num_bottom_pri_compactions); - } - // Verify that size amplification did occur - ASSERT_EQ(NumSortedRuns(), 1); - rocksdb::SyncPoint::GetInstance()->DisableProcessing(); - } - Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM); -} - TEST_P(DBTestUniversalCompaction, ConcurrentBottomPriLowPriCompactions) { if (num_levels_ == 1) { // for single-level universal, everything's bottom level so nothing should @@ -1481,7 +1763,7 @@ TEST_P(DBTestUniversalCompaction, RecalculateScoreAfterPicking) { std::atomic num_compactions_attempted(0); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::BackgroundCompaction:Start", [&](void* arg) { + "DBImpl::BackgroundCompaction:Start", [&](void* /*arg*/) { ++num_compactions_attempted; }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); @@ -1499,6 +1781,63 @@ TEST_P(DBTestUniversalCompaction, RecalculateScoreAfterPicking) { ASSERT_EQ(NumSortedRuns(), 5); } +TEST_P(DBTestUniversalCompaction, FinalSortedRunCompactFilesConflict) { + // Regression test for conflict between: + // (1) Running CompactFiles including file in the final sorted run; and + // (2) Picking universal size-amp-triggered compaction, which always includes + // the final sorted run. + if (exclusive_manual_compaction_) { + return; + } + + Options opts = CurrentOptions(); + opts.compaction_style = kCompactionStyleUniversal; + opts.compaction_options_universal.max_size_amplification_percent = 50; + opts.compaction_options_universal.min_merge_width = 2; + opts.compression = kNoCompression; + opts.level0_file_num_compaction_trigger = 2; + opts.max_background_compactions = 2; + opts.num_levels = num_levels_; + Reopen(opts); + + // make sure compaction jobs can be parallelized + auto stop_token = + dbfull()->TEST_write_controler().GetCompactionPressureToken(); + + Put("key", "val"); + Flush(); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + ASSERT_EQ(NumTableFilesAtLevel(num_levels_ - 1), 1); + ColumnFamilyMetaData cf_meta; + ColumnFamilyHandle* default_cfh = db_->DefaultColumnFamily(); + dbfull()->GetColumnFamilyMetaData(default_cfh, &cf_meta); + ASSERT_EQ(1, cf_meta.levels[num_levels_ - 1].files.size()); + std::string first_sst_filename = + cf_meta.levels[num_levels_ - 1].files[0].name; + + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"CompactFilesImpl:0", + "DBTestUniversalCompaction:FinalSortedRunCompactFilesConflict:0"}, + {"DBImpl::BackgroundCompaction():AfterPickCompaction", + "CompactFilesImpl:1"}}); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + port::Thread compact_files_thread([&]() { + ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), default_cfh, + {first_sst_filename}, num_levels_ - 1)); + }); + + TEST_SYNC_POINT( + "DBTestUniversalCompaction:FinalSortedRunCompactFilesConflict:0"); + for (int i = 0; i < 2; ++i) { + Put("key", "val"); + Flush(); + } + dbfull()->TEST_WaitForCompact(); + + compact_files_thread.join(); +} + INSTANTIATE_TEST_CASE_P(UniversalCompactionNumLevels, DBTestUniversalCompaction, ::testing::Combine(::testing::Values(1, 3, 5), ::testing::Bool())); @@ -1574,6 +1913,241 @@ INSTANTIATE_TEST_CASE_P(DBTestUniversalManualCompactionOutputPathId, ::testing::Combine(::testing::Values(1, 8), ::testing::Bool())); +TEST_F(DBTestUniversalDeleteTrigCompaction, BasicL0toL1) { + const int kNumKeys = 3000; + const int kWindowSize = 100; + const int kNumDelsTrigger = 90; + + Options opts = CurrentOptions(); + opts.table_properties_collector_factories.emplace_back( + NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); + opts.compaction_style = kCompactionStyleUniversal; + opts.level0_file_num_compaction_trigger = 2; + opts.compression = kNoCompression; + opts.compaction_options_universal.size_ratio = 10; + opts.compaction_options_universal.min_merge_width = 2; + opts.compaction_options_universal.max_size_amplification_percent = 200; + Reopen(opts); + + // add an L1 file to prevent tombstones from dropping due to obsolescence + // during flush + int i; + for (i = 0; i < 2000; ++i) { + Put(Key(i), "val"); + } + Flush(); + // MoveFilesToLevel(6); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + + for (i = 1999; i < kNumKeys; ++i) { + if (i >= kNumKeys - kWindowSize && + i < kNumKeys - kWindowSize + kNumDelsTrigger) { + Delete(Key(i)); + } else { + Put(Key(i), "val"); + } + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(6), 0); +} + +TEST_F(DBTestUniversalDeleteTrigCompaction, SingleLevel) { + const int kNumKeys = 3000; + const int kWindowSize = 100; + const int kNumDelsTrigger = 90; + + Options opts = CurrentOptions(); + opts.table_properties_collector_factories.emplace_back( + NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); + opts.compaction_style = kCompactionStyleUniversal; + opts.level0_file_num_compaction_trigger = 2; + opts.compression = kNoCompression; + opts.num_levels = 1; + opts.compaction_options_universal.size_ratio = 10; + opts.compaction_options_universal.min_merge_width = 2; + opts.compaction_options_universal.max_size_amplification_percent = 200; + Reopen(opts); + + // add an L1 file to prevent tombstones from dropping due to obsolescence + // during flush + int i; + for (i = 0; i < 2000; ++i) { + Put(Key(i), "val"); + } + Flush(); + + for (i = 1999; i < kNumKeys; ++i) { + if (i >= kNumKeys - kWindowSize && + i < kNumKeys - kWindowSize + kNumDelsTrigger) { + Delete(Key(i)); + } else { + Put(Key(i), "val"); + } + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(1, NumTableFilesAtLevel(0)); +} + +TEST_F(DBTestUniversalDeleteTrigCompaction, MultipleLevels) { + const int kWindowSize = 100; + const int kNumDelsTrigger = 90; + + Options opts = CurrentOptions(); + opts.table_properties_collector_factories.emplace_back( + NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); + opts.compaction_style = kCompactionStyleUniversal; + opts.level0_file_num_compaction_trigger = 4; + opts.compression = kNoCompression; + opts.compaction_options_universal.size_ratio = 10; + opts.compaction_options_universal.min_merge_width = 2; + opts.compaction_options_universal.max_size_amplification_percent = 200; + Reopen(opts); + + // add an L1 file to prevent tombstones from dropping due to obsolescence + // during flush + int i; + for (i = 0; i < 500; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 500; i < 1000; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 1000; i < 1500; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 1500; i < 2000; ++i) { + Put(Key(i), "val"); + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(6), 0); + + for (i = 1999; i < 2333; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 2333; i < 2666; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 2666; i < 2999; ++i) { + Put(Key(i), "val"); + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(6), 0); + ASSERT_GT(NumTableFilesAtLevel(5), 0); + + for (i = 1900; i < 2100; ++i) { + Delete(Key(i)); + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_EQ(0, NumTableFilesAtLevel(1)); + ASSERT_EQ(0, NumTableFilesAtLevel(2)); + ASSERT_EQ(0, NumTableFilesAtLevel(3)); + ASSERT_EQ(0, NumTableFilesAtLevel(4)); + ASSERT_EQ(0, NumTableFilesAtLevel(5)); + ASSERT_GT(NumTableFilesAtLevel(6), 0); +} + +TEST_F(DBTestUniversalDeleteTrigCompaction, OverlappingL0) { + const int kWindowSize = 100; + const int kNumDelsTrigger = 90; + + Options opts = CurrentOptions(); + opts.table_properties_collector_factories.emplace_back( + NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); + opts.compaction_style = kCompactionStyleUniversal; + opts.level0_file_num_compaction_trigger = 5; + opts.compression = kNoCompression; + opts.compaction_options_universal.size_ratio = 10; + opts.compaction_options_universal.min_merge_width = 2; + opts.compaction_options_universal.max_size_amplification_percent = 200; + Reopen(opts); + + // add an L1 file to prevent tombstones from dropping due to obsolescence + // during flush + int i; + for (i = 0; i < 2000; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 2000; i < 3000; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 3500; i < 4000; ++i) { + Put(Key(i), "val"); + } + Flush(); + for (i = 2900; i < 3100; ++i) { + Delete(Key(i)); + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(2, NumTableFilesAtLevel(0)); + ASSERT_GT(NumTableFilesAtLevel(6), 0); +} + +TEST_F(DBTestUniversalDeleteTrigCompaction, IngestBehind) { + const int kNumKeys = 3000; + const int kWindowSize = 100; + const int kNumDelsTrigger = 90; + + Options opts = CurrentOptions(); + opts.table_properties_collector_factories.emplace_back( + NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger)); + opts.compaction_style = kCompactionStyleUniversal; + opts.level0_file_num_compaction_trigger = 2; + opts.compression = kNoCompression; + opts.allow_ingest_behind = true; + opts.compaction_options_universal.size_ratio = 10; + opts.compaction_options_universal.min_merge_width = 2; + opts.compaction_options_universal.max_size_amplification_percent = 200; + Reopen(opts); + + // add an L1 file to prevent tombstones from dropping due to obsolescence + // during flush + int i; + for (i = 0; i < 2000; ++i) { + Put(Key(i), "val"); + } + Flush(); + // MoveFilesToLevel(6); + dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr); + + for (i = 1999; i < kNumKeys; ++i) { + if (i >= kNumKeys - kWindowSize && + i < kNumKeys - kWindowSize + kNumDelsTrigger) { + Delete(Key(i)); + } else { + Put(Key(i), "val"); + } + } + Flush(); + + dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_EQ(0, NumTableFilesAtLevel(6)); + ASSERT_GT(NumTableFilesAtLevel(5), 0); +} + } // namespace rocksdb #endif // !defined(ROCKSDB_LITE) @@ -1584,6 +2158,8 @@ int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); #else + (void) argc; + (void) argv; return 0; #endif } diff --git a/thirdparty/rocksdb/db/db_wal_test.cc b/thirdparty/rocksdb/db/db_wal_test.cc index 461fe46739..78f72b4a0e 100644 --- a/thirdparty/rocksdb/db/db_wal_test.cc +++ b/thirdparty/rocksdb/db/db_wal_test.cc @@ -18,8 +18,119 @@ namespace rocksdb { class DBWALTest : public DBTestBase { public: DBWALTest() : DBTestBase("/db_wal_test") {} + +#if defined(ROCKSDB_PLATFORM_POSIX) + uint64_t GetAllocatedFileSize(std::string file_name) { + struct stat sbuf; + int err = stat(file_name.c_str(), &sbuf); + assert(err == 0); + return sbuf.st_blocks * 512; + } +#endif +}; + +// A SpecialEnv enriched to give more insight about deleted files +class EnrichedSpecialEnv : public SpecialEnv { + public: + explicit EnrichedSpecialEnv(Env* base) : SpecialEnv(base) {} + Status NewSequentialFile(const std::string& f, + std::unique_ptr* r, + const EnvOptions& soptions) override { + InstrumentedMutexLock l(&env_mutex_); + if (f == skipped_wal) { + deleted_wal_reopened = true; + if (IsWAL(f) && largetest_deleted_wal.size() != 0 && + f.compare(largetest_deleted_wal) <= 0) { + gap_in_wals = true; + } + } + return SpecialEnv::NewSequentialFile(f, r, soptions); + } + Status DeleteFile(const std::string& fname) override { + if (IsWAL(fname)) { + deleted_wal_cnt++; + InstrumentedMutexLock l(&env_mutex_); + // If this is the first WAL, remember its name and skip deleting it. We + // remember its name partly because the application might attempt to + // delete the file again. + if (skipped_wal.size() != 0 && skipped_wal != fname) { + if (largetest_deleted_wal.size() == 0 || + largetest_deleted_wal.compare(fname) < 0) { + largetest_deleted_wal = fname; + } + } else { + skipped_wal = fname; + return Status::OK(); + } + } + return SpecialEnv::DeleteFile(fname); + } + bool IsWAL(const std::string& fname) { + // printf("iswal %s\n", fname.c_str()); + return fname.compare(fname.size() - 3, 3, "log") == 0; + } + + InstrumentedMutex env_mutex_; + // the wal whose actual delete was skipped by the env + std::string skipped_wal = ""; + // the largest WAL that was requested to be deleted + std::string largetest_deleted_wal = ""; + // number of WALs that were successfully deleted + std::atomic deleted_wal_cnt = {0}; + // the WAL whose delete from fs was skipped is reopened during recovery + std::atomic deleted_wal_reopened = {false}; + // whether a gap in the WALs was detected during recovery + std::atomic gap_in_wals = {false}; +}; + +class DBWALTestWithEnrichedEnv : public DBTestBase { + public: + DBWALTestWithEnrichedEnv() : DBTestBase("/db_wal_test") { + enriched_env_ = new EnrichedSpecialEnv(env_->target()); + auto options = CurrentOptions(); + options.env = enriched_env_; + options.allow_2pc = true; + Reopen(options); + delete env_; + // to be deleted by the parent class + env_ = enriched_env_; + } + + protected: + EnrichedSpecialEnv* enriched_env_; }; +// Test that the recovery would successfully avoid the gaps between the logs. +// One known scenario that could cause this is that the application issue the +// WAL deletion out of order. For the sake of simplicity in the test, here we +// create the gap by manipulating the env to skip deletion of the first WAL but +// not the ones after it. +TEST_F(DBWALTestWithEnrichedEnv, SkipDeletedWALs) { + auto options = last_options_; + // To cause frequent WAL deletion + options.write_buffer_size = 128; + Reopen(options); + + WriteOptions writeOpt = WriteOptions(); + for (int i = 0; i < 128 * 5; i++) { + ASSERT_OK(dbfull()->Put(writeOpt, "foo", "v1")); + } + FlushOptions fo; + fo.wait = true; + ASSERT_OK(db_->Flush(fo)); + + // some wals are deleted + ASSERT_NE(0, enriched_env_->deleted_wal_cnt); + // but not the first one + ASSERT_NE(0, enriched_env_->skipped_wal.size()); + + // Test that the WAL that was not deleted will be skipped during recovery + options = last_options_; + Reopen(options); + ASSERT_FALSE(enriched_env_->deleted_wal_reopened); + ASSERT_FALSE(enriched_env_->gap_in_wals); +} + TEST_F(DBWALTest, WAL) { do { CreateAndReopenWithCF({"pikachu"}, CurrentOptions()); @@ -172,7 +283,31 @@ TEST_F(DBWALTest, RecoverWithTableHandle) { ASSERT_OK(Put(1, "bar", "v4")); ASSERT_OK(Flush(1)); ASSERT_OK(Put(1, "big", std::string(100, 'a'))); - ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions()); + + options = CurrentOptions(); + const int kSmallMaxOpenFiles = 13; + if (option_config_ == kDBLogDir) { + // Use this option to check not preloading files + // Set the max open files to be small enough so no preload will + // happen. + options.max_open_files = kSmallMaxOpenFiles; + // RocksDB sanitize max open files to at least 20. Modify it back. + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) { + int* max_open_files = static_cast(arg); + *max_open_files = kSmallMaxOpenFiles; + }); + + } else if (option_config_ == kWalDirAndMmapReads) { + // Use this option to check always loading all files. + options.max_open_files = 100; + } else { + options.max_open_files = -1; + } + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + ReopenWithColumnFamilies({"default", "pikachu"}, options); + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); std::vector> files; dbfull()->TEST_GetFilesMetaData(handles_[1], &files); @@ -183,10 +318,10 @@ TEST_F(DBWALTest, RecoverWithTableHandle) { 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 { + if (options.max_open_files == kSmallMaxOpenFiles) { ASSERT_TRUE(file.table_reader_handle == nullptr); + } else { + ASSERT_TRUE(file.table_reader_handle != nullptr); } } } @@ -693,12 +828,12 @@ class RecoveryTestHelper { *count = 0; - shared_ptr table_cache = NewLRUCache(50, 0); + std::shared_ptr table_cache = NewLRUCache(50, 0); EnvOptions env_options; WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size); - unique_ptr versions; - unique_ptr wal_manager; + std::unique_ptr versions; + std::unique_ptr wal_manager; WriteController write_controller; versions.reset(new VersionSet(test->dbname_, &db_options, env_options, @@ -712,10 +847,10 @@ class RecoveryTestHelper { for (size_t j = kWALFileOffset; j < wal_count + kWALFileOffset; j++) { uint64_t current_log_number = j; std::string fname = LogFileName(test->dbname_, current_log_number); - unique_ptr file; + std::unique_ptr file; ASSERT_OK(db_options.env->NewWritableFile(fname, &file, env_options)); - unique_ptr file_writer( - new WritableFileWriter(std::move(file), env_options)); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(file), fname, env_options)); current_log_writer.reset( new log::Writer(std::move(file_writer), current_log_number, db_options.recycle_log_file_num > 0)); @@ -730,7 +865,8 @@ class RecoveryTestHelper { batch.Put(key, value); WriteBatchInternal::SetSequence(&batch, seq); current_log_writer->AddRecord(WriteBatchInternal::Contents(&batch)); - versions->SetLastToBeWrittenSequence(seq); + versions->SetLastAllocatedSequence(seq); + versions->SetLastPublishedSequence(seq); versions->SetLastSequence(seq); } } @@ -876,6 +1012,39 @@ TEST_F(DBWALTest, kAbsoluteConsistency) { } } +// Test scope: +// We don't expect the data store to be opened if there is any inconsistency +// between WAL and SST files +TEST_F(DBWALTest, kPointInTimeRecoveryCFConsistency) { + Options options = CurrentOptions(); + options.avoid_flush_during_recovery = true; + + // Create DB with multiple column families. + CreateAndReopenWithCF({"one", "two"}, options); + ASSERT_OK(Put(1, "key1", "val1")); + ASSERT_OK(Put(2, "key2", "val2")); + + // Record the offset at this point + Env* env = options.env; + uint64_t wal_file_id = dbfull()->TEST_LogfileNumber(); + std::string fname = LogFileName(dbname_, wal_file_id); + uint64_t offset_to_corrupt; + ASSERT_OK(env->GetFileSize(fname, &offset_to_corrupt)); + ASSERT_GT(offset_to_corrupt, 0); + + ASSERT_OK(Put(1, "key3", "val3")); + // Corrupt WAL at location of key3 + RecoveryTestHelper::InduceCorruption( + fname, static_cast(offset_to_corrupt), static_cast(4)); + ASSERT_OK(Put(2, "key4", "val4")); + ASSERT_OK(Put(1, "key5", "val5")); + Flush(2); + + // PIT recovery & verify + options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery; + ASSERT_NOK(TryReopenWithColumnFamilies({"default", "one", "two"}, options)); +} + // Test scope: // - We expect to open data store under all circumstances // - We expect only data upto the point where the first error was encountered @@ -1007,7 +1176,7 @@ TEST_F(DBWALTest, AvoidFlushDuringRecovery) { Reopen(options); ASSERT_EQ("v11", Get("foo")); ASSERT_EQ("v12", Get("bar")); - ASSERT_EQ(2, TotalTableFiles()); + ASSERT_EQ(3, TotalTableFiles()); } TEST_F(DBWALTest, WalCleanupAfterAvoidFlushDuringRecovery) { @@ -1195,6 +1364,99 @@ TEST_F(DBWALTest, RecoverFromCorruptedWALWithoutFlush) { } } +// Tests that total log size is recovered if we set +// avoid_flush_during_recovery=true. +// Flush should trigger if max_total_wal_size is reached. +TEST_F(DBWALTest, RestoreTotalLogSizeAfterRecoverWithoutFlush) { + class TestFlushListener : public EventListener { + public: + std::atomic count{0}; + + TestFlushListener() = default; + + void OnFlushBegin(DB* /*db*/, const FlushJobInfo& flush_job_info) override { + count++; + assert(FlushReason::kWriteBufferManager == flush_job_info.flush_reason); + } + }; + std::shared_ptr test_listener = + std::make_shared(); + + constexpr size_t kKB = 1024; + constexpr size_t kMB = 1024 * 1024; + Options options = CurrentOptions(); + options.avoid_flush_during_recovery = true; + options.max_total_wal_size = 1 * kMB; + options.listeners.push_back(test_listener); + // Have to open DB in multi-CF mode to trigger flush when + // max_total_wal_size is reached. + CreateAndReopenWithCF({"one"}, options); + // Write some keys and we will end up with one log file which is slightly + // smaller than 1MB. + std::string value_100k(100 * kKB, 'v'); + std::string value_300k(300 * kKB, 'v'); + ASSERT_OK(Put(0, "foo", "v1")); + for (int i = 0; i < 9; i++) { + ASSERT_OK(Put(1, "key" + ToString(i), value_100k)); + } + // Get log files before reopen. + VectorLogPtr log_files_before; + ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before)); + ASSERT_EQ(1, log_files_before.size()); + uint64_t log_size_before = log_files_before[0]->SizeFileBytes(); + ASSERT_GT(log_size_before, 900 * kKB); + ASSERT_LT(log_size_before, 1 * kMB); + ReopenWithColumnFamilies({"default", "one"}, options); + // Write one more value to make log larger than 1MB. + ASSERT_OK(Put(1, "bar", value_300k)); + // Get log files again. A new log file will be opened. + VectorLogPtr log_files_after_reopen; + ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after_reopen)); + ASSERT_EQ(2, log_files_after_reopen.size()); + ASSERT_EQ(log_files_before[0]->LogNumber(), + log_files_after_reopen[0]->LogNumber()); + ASSERT_GT(log_files_after_reopen[0]->SizeFileBytes() + + log_files_after_reopen[1]->SizeFileBytes(), + 1 * kMB); + // Write one more key to trigger flush. + ASSERT_OK(Put(0, "foo", "v2")); + dbfull()->TEST_WaitForFlushMemTable(); + // Flushed two column families. + ASSERT_EQ(2, test_listener->count.load()); +} + +#if defined(ROCKSDB_PLATFORM_POSIX) +#if defined(ROCKSDB_FALLOCATE_PRESENT) +// Tests that we will truncate the preallocated space of the last log from +// previous. +TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithoutFlush) { + constexpr size_t kKB = 1024; + Options options = CurrentOptions(); + options.avoid_flush_during_recovery = true; + DestroyAndReopen(options); + size_t preallocated_size = + dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size); + ASSERT_OK(Put("foo", "v1")); + VectorLogPtr log_files_before; + ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_before)); + ASSERT_EQ(1, log_files_before.size()); + auto& file_before = log_files_before[0]; + ASSERT_LT(file_before->SizeFileBytes(), 1 * kKB); + // The log file has preallocated space. + ASSERT_GE(GetAllocatedFileSize(dbname_ + file_before->PathName()), + preallocated_size); + Reopen(options); + VectorLogPtr log_files_after; + ASSERT_OK(dbfull()->GetSortedWalFiles(log_files_after)); + ASSERT_EQ(1, log_files_after.size()); + ASSERT_LT(log_files_after[0]->SizeFileBytes(), 1 * kKB); + // The preallocated space should be truncated. + ASSERT_LT(GetAllocatedFileSize(dbname_ + file_before->PathName()), + preallocated_size); +} +#endif // ROCKSDB_FALLOCATE_PRESENT +#endif // ROCKSDB_PLATFORM_POSIX + #endif // ROCKSDB_LITE TEST_F(DBWALTest, WalTermTest) { diff --git a/thirdparty/rocksdb/db/db_write_test.cc b/thirdparty/rocksdb/db/db_write_test.cc index 726f444fa1..e6bab87511 100644 --- a/thirdparty/rocksdb/db/db_write_test.cc +++ b/thirdparty/rocksdb/db/db_write_test.cc @@ -3,12 +3,17 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). +#include #include #include #include #include "db/db_test_util.h" #include "db/write_batch_internal.h" +#include "db/write_thread.h" +#include "port/port.h" #include "port/stack_trace.h" +#include "util/fault_injection_test_env.h" +#include "util/string_util.h" #include "util/sync_point.h" namespace rocksdb { @@ -18,53 +23,166 @@ class DBWriteTest : public DBTestBase, public testing::WithParamInterface { public: DBWriteTest() : DBTestBase("/db_write_test") {} - void Open() { DBTestBase::Reopen(GetOptions(GetParam())); } + Options GetOptions() { return DBTestBase::GetOptions(GetParam()); } + + void Open() { DBTestBase::Reopen(GetOptions()); } }; -// Sequence number should be return through input write batch. -TEST_P(DBWriteTest, ReturnSeuqneceNumber) { - Random rnd(4422); - Open(); - for (int i = 0; i < 100; i++) { - WriteBatch batch; - batch.Put("key" + ToString(i), test::RandomHumanReadableString(&rnd, 10)); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), - WriteBatchInternal::Sequence(&batch)); - } +// It is invalid to do sync write while disabling WAL. +TEST_P(DBWriteTest, SyncAndDisableWAL) { + WriteOptions write_options; + write_options.sync = true; + write_options.disableWAL = true; + ASSERT_TRUE(dbfull()->Put(write_options, "foo", "bar").IsInvalidArgument()); + WriteBatch batch; + ASSERT_OK(batch.Put("foo", "bar")); + ASSERT_TRUE(dbfull()->Write(write_options, &batch).IsInvalidArgument()); } -TEST_P(DBWriteTest, ReturnSeuqneceNumberMultiThreaded) { - constexpr size_t kThreads = 16; - constexpr size_t kNumKeys = 1000; - Open(); - ASSERT_EQ(0, dbfull()->GetLatestSequenceNumber()); - // Check each sequence is used once and only once. - std::vector flags(kNumKeys * kThreads + 1); - for (size_t i = 0; i < flags.size(); i++) { - flags[i].clear(); - } - auto writer = [&](size_t id) { - Random rnd(4422 + static_cast(id)); - for (size_t k = 0; k < kNumKeys; k++) { - WriteBatch batch; - batch.Put("key" + ToString(id) + "-" + ToString(k), - test::RandomHumanReadableString(&rnd, 10)); - ASSERT_OK(dbfull()->Write(WriteOptions(), &batch)); - SequenceNumber sequence = WriteBatchInternal::Sequence(&batch); - ASSERT_GT(sequence, 0); - ASSERT_LE(sequence, kNumKeys * kThreads); - // The sequence isn't consumed by someone else. - ASSERT_FALSE(flags[sequence].test_and_set()); - } - }; +TEST_P(DBWriteTest, IOErrorOnWALWritePropagateToWriteThreadFollower) { + constexpr int kNumThreads = 5; + std::unique_ptr mock_env( + new FaultInjectionTestEnv(Env::Default())); + Options options = GetOptions(); + options.env = mock_env.get(); + Reopen(options); + std::atomic ready_count{0}; + std::atomic leader_count{0}; std::vector threads; - for (size_t i = 0; i < kThreads; i++) { - threads.emplace_back(writer, i); + mock_env->SetFilesystemActive(false); + + // Wait until all threads linked to write threads, to make sure + // all threads join the same batch group. + SyncPoint::GetInstance()->SetCallBack( + "WriteThread::JoinBatchGroup:Wait", [&](void* arg) { + ready_count++; + auto* w = reinterpret_cast(arg); + if (w->state == WriteThread::STATE_GROUP_LEADER) { + leader_count++; + while (ready_count < kNumThreads) { + // busy waiting + } + } + }); + SyncPoint::GetInstance()->EnableProcessing(); + for (int i = 0; i < kNumThreads; i++) { + threads.push_back(port::Thread( + [&](int index) { + // All threads should fail. + auto res = Put("key" + ToString(index), "value"); + if (options.manual_wal_flush) { + ASSERT_TRUE(res.ok()); + // we should see fs error when we do the flush + + // TSAN reports a false alarm for lock-order-inversion but Open and + // FlushWAL are not run concurrently. Disabling this until TSAN is + // fixed. + // res = dbfull()->FlushWAL(false); + // ASSERT_FALSE(res.ok()); + } else { + ASSERT_FALSE(res.ok()); + } + }, + i)); } - for (size_t i = 0; i < kThreads; i++) { + for (int i = 0; i < kNumThreads; i++) { threads[i].join(); } + ASSERT_EQ(1, leader_count); + // Close before mock_env destruct. + Close(); +} + +TEST_P(DBWriteTest, ManualWalFlushInEffect) { + Options options = GetOptions(); + Reopen(options); + // try the 1st WAL created during open + ASSERT_TRUE(Put("key" + ToString(0), "value").ok()); + ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty()); + ASSERT_TRUE(dbfull()->FlushWAL(false).ok()); + ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty()); + // try the 2nd wal created during SwitchWAL + dbfull()->TEST_SwitchWAL(); + ASSERT_TRUE(Put("key" + ToString(0), "value").ok()); + ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty()); + ASSERT_TRUE(dbfull()->FlushWAL(false).ok()); + ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty()); +} + +TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) { + std::unique_ptr mock_env( + new FaultInjectionTestEnv(Env::Default())); + Options options = GetOptions(); + options.env = mock_env.get(); + Reopen(options); + for (int i = 0; i < 2; i++) { + // Forcibly fail WAL write for the first Put only. Subsequent Puts should + // fail due to read-only mode + mock_env->SetFilesystemActive(i != 0); + auto res = Put("key" + ToString(i), "value"); + // TSAN reports a false alarm for lock-order-inversion but Open and + // FlushWAL are not run concurrently. Disabling this until TSAN is + // fixed. + /* + if (options.manual_wal_flush && i == 0) { + // even with manual_wal_flush the 2nd Put should return error because of + // the read-only mode + ASSERT_TRUE(res.ok()); + // we should see fs error when we do the flush + res = dbfull()->FlushWAL(false); + } + */ + if (!options.manual_wal_flush) { + ASSERT_FALSE(res.ok()); + } + } + // Close before mock_env destruct. + Close(); +} + +TEST_P(DBWriteTest, IOErrorOnSwitchMemtable) { + Random rnd(301); + std::unique_ptr mock_env( + new FaultInjectionTestEnv(Env::Default())); + Options options = GetOptions(); + options.env = mock_env.get(); + options.writable_file_max_buffer_size = 4 * 1024 * 1024; + options.write_buffer_size = 3 * 512 * 1024; + options.wal_bytes_per_sync = 256 * 1024; + options.manual_wal_flush = true; + Reopen(options); + mock_env->SetFilesystemActive(false, Status::IOError("Not active")); + Status s; + for (int i = 0; i < 4 * 512; ++i) { + s = Put(Key(i), RandomString(&rnd, 1024)); + if (!s.ok()) { + break; + } + } + ASSERT_EQ(s.severity(), Status::Severity::kFatalError); + + mock_env->SetFilesystemActive(true); + // Close before mock_env destruct. + Close(); +} + +// Test that db->LockWAL() flushes the WAL after locking. +TEST_P(DBWriteTest, LockWalInEffect) { + Options options = GetOptions(); + Reopen(options); + // try the 1st WAL created during open + ASSERT_OK(Put("key" + ToString(0), "value")); + ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty()); + ASSERT_OK(dbfull()->LockWAL()); + ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false)); + ASSERT_OK(dbfull()->UnlockWAL()); + // try the 2nd wal created during SwitchWAL + dbfull()->TEST_SwitchWAL(); + ASSERT_OK(Put("key" + ToString(0), "value")); + ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty()); + ASSERT_OK(dbfull()->LockWAL()); + ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false)); + ASSERT_OK(dbfull()->UnlockWAL()); } INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest, diff --git a/thirdparty/rocksdb/db/dbformat.cc b/thirdparty/rocksdb/db/dbformat.cc index f287ae9f4e..cd2878198c 100644 --- a/thirdparty/rocksdb/db/dbformat.cc +++ b/thirdparty/rocksdb/db/dbformat.cc @@ -36,6 +36,36 @@ uint64_t PackSequenceAndType(uint64_t seq, ValueType t) { return (seq << 8) | t; } +EntryType GetEntryType(ValueType value_type) { + switch (value_type) { + case kTypeValue: + return kEntryPut; + case kTypeDeletion: + return kEntryDelete; + case kTypeSingleDeletion: + return kEntrySingleDelete; + case kTypeMerge: + return kEntryMerge; + case kTypeRangeDeletion: + return kEntryRangeDeletion; + case kTypeBlobIndex: + return kEntryBlobIndex; + default: + return kEntryOther; + } +} + +bool ParseFullKey(const Slice& internal_key, FullKey* fkey) { + ParsedInternalKey ikey; + if (!ParseInternalKey(internal_key, &ikey)) { + return false; + } + fkey->user_key = ikey.user_key; + fkey->sequence = ikey.sequence; + fkey->type = GetEntryType(ikey.type); + return true; +} + void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t) { *seq = packed >> 8; *t = static_cast(packed & 0xff); @@ -76,28 +106,7 @@ std::string InternalKey::DebugString(bool hex) const { return result; } -const char* InternalKeyComparator::Name() const { - return name_.c_str(); -} - -int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const { - // Order by: - // increasing user key (according to user-supplied comparator) - // decreasing sequence number - // decreasing type (though sequence# should be enough to disambiguate) - int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey)); - PERF_COUNTER_ADD(user_key_comparison_count, 1); - if (r == 0) { - const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8); - const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8); - if (anum > bnum) { - r = -1; - } else if (anum < bnum) { - r = +1; - } - } - return r; -} +const char* InternalKeyComparator::Name() const { return name_.c_str(); } int InternalKeyComparator::Compare(const ParsedInternalKey& a, const ParsedInternalKey& b) const { @@ -105,8 +114,7 @@ int InternalKeyComparator::Compare(const ParsedInternalKey& a, // increasing user key (according to user-supplied comparator) // decreasing sequence number // decreasing type (though sequence# should be enough to disambiguate) - int r = user_comparator_->Compare(a.user_key, b.user_key); - PERF_COUNTER_ADD(user_key_comparison_count, 1); + int r = user_comparator_.Compare(a.user_key, b.user_key); if (r == 0) { if (a.sequence > b.sequence) { r = -1; @@ -121,19 +129,19 @@ int InternalKeyComparator::Compare(const ParsedInternalKey& a, return r; } -void InternalKeyComparator::FindShortestSeparator( - std::string* start, - const Slice& limit) const { +void InternalKeyComparator::FindShortestSeparator(std::string* start, + const Slice& limit) const { // Attempt to shorten the user portion of the key Slice user_start = ExtractUserKey(*start); Slice user_limit = ExtractUserKey(limit); std::string tmp(user_start.data(), user_start.size()); - user_comparator_->FindShortestSeparator(&tmp, user_limit); + user_comparator_.FindShortestSeparator(&tmp, user_limit); if (tmp.size() <= user_start.size() && - user_comparator_->Compare(user_start, tmp) < 0) { + user_comparator_.Compare(user_start, tmp) < 0) { // User key has become shorter physically, but larger logically. // Tack on the earliest possible number to the shortened user key. - PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek)); + PutFixed64(&tmp, + PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek)); assert(this->Compare(*start, tmp) < 0); assert(this->Compare(tmp, limit) < 0); start->swap(tmp); @@ -143,12 +151,13 @@ void InternalKeyComparator::FindShortestSeparator( void InternalKeyComparator::FindShortSuccessor(std::string* key) const { Slice user_key = ExtractUserKey(*key); std::string tmp(user_key.data(), user_key.size()); - user_comparator_->FindShortSuccessor(&tmp); + user_comparator_.FindShortSuccessor(&tmp); if (tmp.size() <= user_key.size() && - user_comparator_->Compare(user_key, tmp) < 0) { + user_comparator_.Compare(user_key, tmp) < 0) { // User key has become shorter physically, but larger logically. // Tack on the earliest possible number to the shortened user key. - PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek)); + PutFixed64(&tmp, + PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek)); assert(this->Compare(*key, tmp) < 0); key->swap(tmp); } @@ -174,4 +183,13 @@ LookupKey::LookupKey(const Slice& _user_key, SequenceNumber s) { end_ = dst; } +void IterKey::EnlargeBuffer(size_t key_size) { + // If size is smaller than buffer size, continue using current buffer, + // or the static allocated one, as default + assert(key_size > buf_size_); + // Need to enlarge the buffer. + ResetBuffer(); + buf_ = new char[key_size]; + buf_size_ = key_size; +} } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/dbformat.h b/thirdparty/rocksdb/db/dbformat.h index c58b8363ab..7a5ddc1ad0 100644 --- a/thirdparty/rocksdb/db/dbformat.h +++ b/thirdparty/rocksdb/db/dbformat.h @@ -11,6 +11,7 @@ #include #include #include +#include "monitoring/perf_context_imp.h" #include "rocksdb/comparator.h" #include "rocksdb/db.h" #include "rocksdb/filter_policy.h" @@ -20,6 +21,7 @@ #include "rocksdb/types.h" #include "util/coding.h" #include "util/logging.h" +#include "util/user_comparator_wrapper.h" namespace rocksdb { @@ -49,7 +51,16 @@ enum ValueType : unsigned char { kTypeRangeDeletion = 0xF, // meta block kTypeColumnFamilyBlobIndex = 0x10, // Blob DB only kTypeBlobIndex = 0x11, // Blob DB only - kMaxValue = 0x7F // Not used for storing records. + // When the prepared record is also persisted in db, we use a different + // record. This is to ensure that the WAL that is generated by a WritePolicy + // is not mistakenly read by another, which would result into data + // inconsistency. + kTypeBeginPersistedPrepareXID = 0x12, // WAL only. + // Similar to kTypeBeginPersistedPrepareXID, this is to ensure that WAL + // generated by WriteUnprepared write policy is not mistakenly read by + // another. + kTypeBeginUnprepareXID = 0x13, // WAL only. + kMaxValue = 0x7F // Not used for storing records. }; // Defined in dbformat.cc @@ -70,8 +81,7 @@ inline bool IsExtendedValueType(ValueType t) { // We leave eight bits empty at the bottom so a type and sequence# // can be packed together into 64-bits. -static const SequenceNumber kMaxSequenceNumber = - ((0x1ull << 56) - 1); +static const SequenceNumber kMaxSequenceNumber = ((0x1ull << 56) - 1); static const SequenceNumber kDisableGlobalSequenceNumber = port::kMaxUint64; @@ -84,7 +94,7 @@ struct ParsedInternalKey { : sequence(kMaxSequenceNumber) // Make code analyzer happy {} // Intentionally left uninitialized (for speed) ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t) - : user_key(u), sequence(seq), type(t) { } + : user_key(u), sequence(seq), type(t) {} std::string DebugString(bool hex = false) const; void clear() { @@ -106,6 +116,8 @@ extern uint64_t PackSequenceAndType(uint64_t seq, ValueType t); // and the ValueType in *t. extern void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t); +EntryType GetEntryType(ValueType value_type); + // Append the serialization of "key" to *result. extern void AppendInternalKey(std::string* result, const ParsedInternalKey& key); @@ -128,39 +140,52 @@ inline Slice ExtractUserKey(const Slice& internal_key) { return Slice(internal_key.data(), internal_key.size() - 8); } -inline ValueType ExtractValueType(const Slice& internal_key) { +inline uint64_t ExtractInternalKeyFooter(const Slice& internal_key) { assert(internal_key.size() >= 8); const size_t n = internal_key.size(); - uint64_t num = DecodeFixed64(internal_key.data() + n - 8); + return DecodeFixed64(internal_key.data() + n - 8); +} + +inline ValueType ExtractValueType(const Slice& internal_key) { + uint64_t num = ExtractInternalKeyFooter(internal_key); unsigned char c = num & 0xff; return static_cast(c); } // A comparator for internal keys that uses a specified comparator for // the user key portion and breaks ties by decreasing sequence number. -class InternalKeyComparator : public Comparator { +class InternalKeyComparator +#ifdef NDEBUG + final +#endif + : public Comparator { private: - const Comparator* user_comparator_; + UserComparatorWrapper user_comparator_; std::string name_; + public: - explicit InternalKeyComparator(const Comparator* c) : user_comparator_(c), - name_("rocksdb.InternalKeyComparator:" + - std::string(user_comparator_->Name())) { - } + explicit InternalKeyComparator(const Comparator* c) + : user_comparator_(c), + name_("rocksdb.InternalKeyComparator:" + + std::string(user_comparator_.Name())) {} virtual ~InternalKeyComparator() {} virtual const char* Name() const override; virtual int Compare(const Slice& a, const Slice& b) const override; + // Same as Compare except that it excludes the value type from comparison + virtual int CompareKeySeq(const Slice& a, const Slice& b) const; virtual void FindShortestSeparator(std::string* start, const Slice& limit) const override; virtual void FindShortSuccessor(std::string* key) const override; - const Comparator* user_comparator() const { return user_comparator_; } + const Comparator* user_comparator() const { + return user_comparator_.user_comparator(); + } int Compare(const InternalKey& a, const InternalKey& b) const; int Compare(const ParsedInternalKey& a, const ParsedInternalKey& b) const; virtual const Comparator* GetRootComparator() const override { - return user_comparator_->GetRootComparator(); + return user_comparator_.GetRootComparator(); } }; @@ -170,8 +195,9 @@ class InternalKeyComparator : public Comparator { class InternalKey { private: std::string rep_; + public: - InternalKey() { } // Leave rep_ as empty to indicate it is invalid + InternalKey() {} // Leave rep_ as empty to indicate it is invalid InternalKey(const Slice& _user_key, SequenceNumber s, ValueType t) { AppendInternalKey(&rep_, ParsedInternalKey(_user_key, s, t)); } @@ -179,15 +205,15 @@ class InternalKey { // sets the internal key to be bigger or equal to all internal keys with this // user key void SetMaxPossibleForUserKey(const Slice& _user_key) { - AppendInternalKey(&rep_, ParsedInternalKey(_user_key, kMaxSequenceNumber, - kValueTypeForSeek)); + AppendInternalKey( + &rep_, ParsedInternalKey(_user_key, 0, static_cast(0))); } // sets the internal key to be smaller or equal to all internal keys with this // user key void SetMinPossibleForUserKey(const Slice& _user_key) { - AppendInternalKey( - &rep_, ParsedInternalKey(_user_key, 0, static_cast(0))); + AppendInternalKey(&rep_, ParsedInternalKey(_user_key, kMaxSequenceNumber, + kValueTypeForSeek)); } bool Valid() const { @@ -228,8 +254,8 @@ class InternalKey { std::string DebugString(bool hex = false) const; }; -inline int InternalKeyComparator::Compare( - const InternalKey& a, const InternalKey& b) const { +inline int InternalKeyComparator::Compare(const InternalKey& a, + const InternalKey& b) const { return Compare(a.Encode(), b.Encode()); } @@ -266,7 +292,6 @@ inline uint64_t GetInternalKeySeqno(const Slice& internal_key) { return num >> 8; } - // A helper class useful for DBImpl::Get() class LookupKey { public: @@ -302,7 +327,7 @@ class LookupKey { const char* start_; const char* kstart_; const char* end_; - char space_[200]; // Avoid allocation for short keys + char space_[200]; // Avoid allocation for short keys // No copying allowed LookupKey(const LookupKey&); @@ -317,13 +342,19 @@ class IterKey { public: IterKey() : buf_(space_), - buf_size_(sizeof(space_)), key_(buf_), key_size_(0), + buf_size_(sizeof(space_)), is_user_key_(true) {} ~IterKey() { ResetBuffer(); } + // The bool will be picked up by the next calls to SetKey + void SetIsUserKey(bool is_user_key) { is_user_key_ = is_user_key; } + + // Returns the key in whichever format that was provided to KeyIter + Slice GetKey() const { return Slice(key_, key_size_); } + Slice GetInternalKey() const { assert(!IsUserKey()); return Slice(key_, key_size_); @@ -373,6 +404,11 @@ class IterKey { key_size_ = total_size; } + Slice SetKey(const Slice& key, bool copy = true) { + // is_user_key_ expected to be set already via SetIsUserKey + return SetKeyImpl(key, copy); + } + Slice SetUserKey(const Slice& key, bool copy = true) { is_user_key_ = true; return SetKeyImpl(key, copy); @@ -463,9 +499,9 @@ class IterKey { private: char* buf_; - size_t buf_size_; const char* key_; size_t key_size_; + size_t buf_size_; char space_[32]; // Avoid allocation for short keys bool is_user_key_; @@ -502,13 +538,12 @@ class IterKey { // If size is smaller than buffer size, continue using current buffer, // or the static allocated one, as default if (key_size > buf_size_) { - // Need to enlarge the buffer. - ResetBuffer(); - buf_ = new char[key_size]; - buf_size_ = key_size; + EnlargeBuffer(key_size); } } + void EnlargeBuffer(size_t key_size); + // No copying allowed IterKey(const IterKey&) = delete; void operator=(const IterKey&) = delete; @@ -589,10 +624,66 @@ struct RangeTombstone { return InternalKey(start_key_, seq_, kTypeRangeDeletion); } + // The tombstone end-key is exclusive, so we generate an internal-key here + // which has a similar property. Using kMaxSequenceNumber guarantees that + // the returned internal-key will compare less than any other internal-key + // with the same user-key. This in turn guarantees that the serialized + // end-key for a tombstone such as [a-b] will compare less than the key "b". + // // be careful to use SerializeEndKey(), allocates new memory InternalKey SerializeEndKey() const { - return InternalKey(end_key_, seq_, kTypeRangeDeletion); + return InternalKey(end_key_, kMaxSequenceNumber, kTypeRangeDeletion); } }; +inline int InternalKeyComparator::Compare(const Slice& akey, + const Slice& bkey) const { + // Order by: + // increasing user key (according to user-supplied comparator) + // decreasing sequence number + // decreasing type (though sequence# should be enough to disambiguate) + int r = user_comparator_.Compare(ExtractUserKey(akey), ExtractUserKey(bkey)); + if (r == 0) { + const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8); + const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8); + if (anum > bnum) { + r = -1; + } else if (anum < bnum) { + r = +1; + } + } + return r; +} + +inline int InternalKeyComparator::CompareKeySeq(const Slice& akey, + const Slice& bkey) const { + // Order by: + // increasing user key (according to user-supplied comparator) + // decreasing sequence number + int r = user_comparator_.Compare(ExtractUserKey(akey), ExtractUserKey(bkey)); + if (r == 0) { + // Shift the number to exclude the last byte which contains the value type + const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8) >> 8; + const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8) >> 8; + if (anum > bnum) { + r = -1; + } else if (anum < bnum) { + r = +1; + } + } + return r; +} + +struct ParsedInternalKeyComparator { + explicit ParsedInternalKeyComparator(const InternalKeyComparator* c) + : cmp(c) {} + + bool operator()(const ParsedInternalKey& a, + const ParsedInternalKey& b) const { + return cmp->Compare(a, b) < 0; + } + + const InternalKeyComparator* cmp; +}; + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/dbformat_test.cc b/thirdparty/rocksdb/db/dbformat_test.cc index d96b5757af..0b16c13f57 100644 --- a/thirdparty/rocksdb/db/dbformat_test.cc +++ b/thirdparty/rocksdb/db/dbformat_test.cc @@ -192,6 +192,13 @@ TEST_F(FormatTest, UpdateInternalKey) { ASSERT_EQ(new_val_type, decoded.type); } +TEST_F(FormatTest, RangeTombstoneSerializeEndKey) { + RangeTombstone t("a", "b", 2); + InternalKey k("b", 3, kTypeValue); + const InternalKeyComparator cmp(BytewiseComparator()); + ASSERT_LT(cmp.Compare(t.SerializeEndKey(), k), 0); +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/deletefile_test.cc b/thirdparty/rocksdb/db/deletefile_test.cc index 989c0c4118..3ae464c584 100644 --- a/thirdparty/rocksdb/db/deletefile_test.cc +++ b/thirdparty/rocksdb/db/deletefile_test.cc @@ -45,7 +45,7 @@ class DeleteFileTest : public testing::Test { options_.max_bytes_for_level_base = 1024*1024*1000; options_.WAL_ttl_seconds = 300; // Used to test log files options_.WAL_size_limit_MB = 1024; // Used to test log files - dbname_ = test::TmpDir() + "/deletefile_test"; + dbname_ = test::PerThreadDBPath("deletefile_test"); options_.wal_dir = dbname_ + "/wal_files"; // clean up all the files that might have been there before @@ -71,7 +71,9 @@ class DeleteFileTest : public testing::Test { } db_ = nullptr; options_.create_if_missing = create; - return DB::Open(options_, dbname_, &db_); + Status s = DB::Open(options_, dbname_, &db_); + assert(db_); + return s; } void CloseDB() { @@ -159,7 +161,7 @@ class DeleteFileTest : public testing::Test { } // An empty job to guard all jobs are processed - static void GuardFinish(void* arg) { + static void GuardFinish(void* /*arg*/) { TEST_SYNC_POINT("DeleteFileTest::GuardFinish"); } }; @@ -228,7 +230,7 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) { // this time, we keep an iterator alive ReopenDB(true); - Iterator *itr = 0; + Iterator *itr = nullptr; CreateTwoLevels(); itr = db_->NewIterator(ReadOptions()); db_->CompactRange(compact_options, &first_slice, &last_slice); @@ -241,7 +243,7 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) { CloseDB(); } -TEST_F(DeleteFileTest, BackgroundPurgeTest) { +TEST_F(DeleteFileTest, BackgroundPurgeIteratorTest) { std::string first("0"), last("999999"); CompactRangeOptions compact_options; compact_options.change_level = true; @@ -249,7 +251,7 @@ TEST_F(DeleteFileTest, BackgroundPurgeTest) { Slice first_slice(first), last_slice(last); // We keep an iterator alive - Iterator* itr = 0; + Iterator* itr = nullptr; CreateTwoLevels(); ReadOptions options; options.background_purge_on_iterator_cleanup = true; @@ -279,6 +281,53 @@ TEST_F(DeleteFileTest, BackgroundPurgeTest) { CloseDB(); } +TEST_F(DeleteFileTest, BackgroundPurgeCFDropTest) { + auto do_test = [&](bool bg_purge) { + ColumnFamilyOptions co; + WriteOptions wo; + FlushOptions fo; + ColumnFamilyHandle* cfh = nullptr; + + ASSERT_OK(db_->CreateColumnFamily(co, "dropme", &cfh)); + + ASSERT_OK(db_->Put(wo, cfh, "pika", "chu")); + ASSERT_OK(db_->Flush(fo, cfh)); + // Expect 1 sst file. + CheckFileTypeCounts(dbname_, 0, 1, 1); + + ASSERT_OK(db_->DropColumnFamily(cfh)); + // Still 1 file, it won't be deleted while ColumnFamilyHandle is alive. + CheckFileTypeCounts(dbname_, 0, 1, 1); + + delete cfh; + test::SleepingBackgroundTask sleeping_task_after; + env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, + &sleeping_task_after, Env::Priority::HIGH); + // If background purge is enabled, the file should still be there. + CheckFileTypeCounts(dbname_, 0, bg_purge ? 1 : 0, 1); + + // Execute background purges. + sleeping_task_after.WakeUp(); + sleeping_task_after.WaitUntilDone(); + // The file should have been deleted. + CheckFileTypeCounts(dbname_, 0, 0, 1); + }; + + { + SCOPED_TRACE("avoid_unnecessary_blocking_io = false"); + do_test(false); + } + + options_.avoid_unnecessary_blocking_io = true; + ASSERT_OK(ReopenDB(false)); + { + SCOPED_TRACE("avoid_unnecessary_blocking_io = true"); + do_test(true); + } + + CloseDB(); +} + // This test is to reproduce a bug that read invalid ReadOption in iterator // cleanup function TEST_F(DeleteFileTest, BackgroundPurgeCopyOptions) { @@ -289,7 +338,7 @@ TEST_F(DeleteFileTest, BackgroundPurgeCopyOptions) { Slice first_slice(first), last_slice(last); // We keep an iterator alive - Iterator* itr = 0; + Iterator* itr = nullptr; CreateTwoLevels(); ReadOptions* options = new ReadOptions(); options->background_purge_on_iterator_cleanup = true; @@ -500,7 +549,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as DBImpl::DeleteFile is not supported in ROCKSDB_LITE\n"); return 0; diff --git a/thirdparty/rocksdb/db/error_handler.cc b/thirdparty/rocksdb/db/error_handler.cc new file mode 100644 index 0000000000..afec14edcb --- /dev/null +++ b/thirdparty/rocksdb/db/error_handler.cc @@ -0,0 +1,345 @@ +// Copyright (c) 2018-present, Facebook, 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). +// +#include "db/error_handler.h" +#include "db/db_impl.h" +#include "db/event_helpers.h" +#include "util/sst_file_manager_impl.h" + +namespace rocksdb { + +// Maps to help decide the severity of an error based on the +// BackgroundErrorReason, Code, SubCode and whether db_options.paranoid_checks +// is set or not. There are 3 maps, going from most specific to least specific +// (i.e from all 4 fields in a tuple to only the BackgroundErrorReason and +// paranoid_checks). The less specific map serves as a catch all in case we miss +// a specific error code or subcode. +std::map, + Status::Severity> + ErrorSeverityMap = { + // Errors during BG compaction + {std::make_tuple(BackgroundErrorReason::kCompaction, + Status::Code::kIOError, Status::SubCode::kNoSpace, + true), + Status::Severity::kSoftError}, + {std::make_tuple(BackgroundErrorReason::kCompaction, + Status::Code::kIOError, Status::SubCode::kNoSpace, + false), + Status::Severity::kNoError}, + {std::make_tuple(BackgroundErrorReason::kCompaction, + Status::Code::kIOError, Status::SubCode::kSpaceLimit, + true), + Status::Severity::kHardError}, + // Errors during BG flush + {std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError, + Status::SubCode::kNoSpace, true), + Status::Severity::kHardError}, + {std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError, + Status::SubCode::kNoSpace, false), + Status::Severity::kNoError}, + {std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError, + Status::SubCode::kSpaceLimit, true), + Status::Severity::kHardError}, + // Errors during Write + {std::make_tuple(BackgroundErrorReason::kWriteCallback, + Status::Code::kIOError, Status::SubCode::kNoSpace, + true), + Status::Severity::kHardError}, + {std::make_tuple(BackgroundErrorReason::kWriteCallback, + Status::Code::kIOError, Status::SubCode::kNoSpace, + false), + Status::Severity::kHardError}, +}; + +std::map, Status::Severity> + DefaultErrorSeverityMap = { + // Errors during BG compaction + {std::make_tuple(BackgroundErrorReason::kCompaction, + Status::Code::kCorruption, true), + Status::Severity::kUnrecoverableError}, + {std::make_tuple(BackgroundErrorReason::kCompaction, + Status::Code::kCorruption, false), + Status::Severity::kNoError}, + {std::make_tuple(BackgroundErrorReason::kCompaction, + Status::Code::kIOError, true), + Status::Severity::kFatalError}, + {std::make_tuple(BackgroundErrorReason::kCompaction, + Status::Code::kIOError, false), + Status::Severity::kNoError}, + // Errors during BG flush + {std::make_tuple(BackgroundErrorReason::kFlush, + Status::Code::kCorruption, true), + Status::Severity::kUnrecoverableError}, + {std::make_tuple(BackgroundErrorReason::kFlush, + Status::Code::kCorruption, false), + Status::Severity::kNoError}, + {std::make_tuple(BackgroundErrorReason::kFlush, + Status::Code::kIOError, true), + Status::Severity::kFatalError}, + {std::make_tuple(BackgroundErrorReason::kFlush, + Status::Code::kIOError, false), + Status::Severity::kNoError}, + // Errors during Write + {std::make_tuple(BackgroundErrorReason::kWriteCallback, + Status::Code::kCorruption, true), + Status::Severity::kUnrecoverableError}, + {std::make_tuple(BackgroundErrorReason::kWriteCallback, + Status::Code::kCorruption, false), + Status::Severity::kNoError}, + {std::make_tuple(BackgroundErrorReason::kWriteCallback, + Status::Code::kIOError, true), + Status::Severity::kFatalError}, + {std::make_tuple(BackgroundErrorReason::kWriteCallback, + Status::Code::kIOError, false), + Status::Severity::kNoError}, +}; + +std::map, Status::Severity> + DefaultReasonMap = { + // Errors during BG compaction + {std::make_tuple(BackgroundErrorReason::kCompaction, true), + Status::Severity::kFatalError}, + {std::make_tuple(BackgroundErrorReason::kCompaction, false), + Status::Severity::kNoError}, + // Errors during BG flush + {std::make_tuple(BackgroundErrorReason::kFlush, true), + Status::Severity::kFatalError}, + {std::make_tuple(BackgroundErrorReason::kFlush, false), + Status::Severity::kNoError}, + // Errors during Write + {std::make_tuple(BackgroundErrorReason::kWriteCallback, true), + Status::Severity::kFatalError}, + {std::make_tuple(BackgroundErrorReason::kWriteCallback, false), + Status::Severity::kFatalError}, + // Errors during Memtable update + {std::make_tuple(BackgroundErrorReason::kMemTable, true), + Status::Severity::kFatalError}, + {std::make_tuple(BackgroundErrorReason::kMemTable, false), + Status::Severity::kFatalError}, +}; + +void ErrorHandler::CancelErrorRecovery() { +#ifndef ROCKSDB_LITE + db_mutex_->AssertHeld(); + + // We'll release the lock before calling sfm, so make sure no new + // recovery gets scheduled at that point + auto_recovery_ = false; + SstFileManagerImpl* sfm = reinterpret_cast( + db_options_.sst_file_manager.get()); + if (sfm) { + // This may or may not cancel a pending recovery + db_mutex_->Unlock(); + bool cancelled = sfm->CancelErrorRecovery(this); + db_mutex_->Lock(); + if (cancelled) { + recovery_in_prog_ = false; + } + } +#endif +} + +// This is the main function for looking at an error during a background +// operation and deciding the severity, and error recovery strategy. The high +// level algorithm is as follows - +// 1. Classify the severity of the error based on the ErrorSeverityMap, +// DefaultErrorSeverityMap and DefaultReasonMap defined earlier +// 2. Call a Status code specific override function to adjust the severity +// if needed. The reason for this is our ability to recover may depend on +// the exact options enabled in DBOptions +// 3. Determine if auto recovery is possible. A listener notification callback +// is called, which can disable the auto recovery even if we decide its +// feasible +// 4. For Status::NoSpace() errors, rely on SstFileManagerImpl to control +// the actual recovery. If no sst file manager is specified in DBOptions, +// a default one is allocated during DB::Open(), so there will always be +// one. +// This can also get called as part of a recovery operation. In that case, we +// also track the error separately in recovery_error_ so we can tell in the +// end whether recovery succeeded or not +Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reason) { + db_mutex_->AssertHeld(); + + if (bg_err.ok()) { + return Status::OK(); + } + + // Check if recovery is currently in progress. If it is, we will save this + // error so we can check it at the end to see if recovery succeeded or not + if (recovery_in_prog_ && recovery_error_.ok()) { + recovery_error_ = bg_err; + } + + bool paranoid = db_options_.paranoid_checks; + Status::Severity sev = Status::Severity::kFatalError; + Status new_bg_err; + bool found = false; + + { + auto entry = ErrorSeverityMap.find(std::make_tuple(reason, bg_err.code(), + bg_err.subcode(), paranoid)); + if (entry != ErrorSeverityMap.end()) { + sev = entry->second; + found = true; + } + } + + if (!found) { + auto entry = DefaultErrorSeverityMap.find(std::make_tuple(reason, + bg_err.code(), paranoid)); + if (entry != DefaultErrorSeverityMap.end()) { + sev = entry->second; + found = true; + } + } + + if (!found) { + auto entry = DefaultReasonMap.find(std::make_tuple(reason, paranoid)); + if (entry != DefaultReasonMap.end()) { + sev = entry->second; + } + } + + new_bg_err = Status(bg_err, sev); + + bool auto_recovery = auto_recovery_; + if (new_bg_err.severity() >= Status::Severity::kFatalError && auto_recovery) { + auto_recovery = false; + ; + } + + // Allow some error specific overrides + if (new_bg_err == Status::NoSpace()) { + new_bg_err = OverrideNoSpaceError(new_bg_err, &auto_recovery); + } + + if (!new_bg_err.ok()) { + Status s = new_bg_err; + EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s, + db_mutex_, &auto_recovery); + if (!s.ok() && (s.severity() > bg_error_.severity())) { + bg_error_ = s; + } else { + // This error is less severe than previously encountered error. Don't + // take any further action + return bg_error_; + } + } + + if (auto_recovery) { + recovery_in_prog_ = true; + + // Kick-off error specific recovery + if (bg_error_ == Status::NoSpace()) { + RecoverFromNoSpace(); + } + } + return bg_error_; +} + +Status ErrorHandler::OverrideNoSpaceError(Status bg_error, + bool* auto_recovery) { +#ifndef ROCKSDB_LITE + if (bg_error.severity() >= Status::Severity::kFatalError) { + return bg_error; + } + + if (db_options_.sst_file_manager.get() == nullptr) { + // We rely on SFM to poll for enough disk space and recover + *auto_recovery = false; + return bg_error; + } + + if (db_options_.allow_2pc && + (bg_error.severity() <= Status::Severity::kSoftError)) { + // Don't know how to recover, as the contents of the current WAL file may + // be inconsistent, and it may be needed for 2PC. If 2PC is not enabled, + // we can just flush the memtable and discard the log + *auto_recovery = false; + return Status(bg_error, Status::Severity::kFatalError); + } + + { + uint64_t free_space; + if (db_options_.env->GetFreeSpace(db_options_.db_paths[0].path, + &free_space) == Status::NotSupported()) { + *auto_recovery = false; + } + } + + return bg_error; +#else + (void)auto_recovery; + return Status(bg_error, Status::Severity::kFatalError); +#endif +} + +void ErrorHandler::RecoverFromNoSpace() { +#ifndef ROCKSDB_LITE + SstFileManagerImpl* sfm = + reinterpret_cast(db_options_.sst_file_manager.get()); + + // Inform SFM of the error, so it can kick-off the recovery + if (sfm) { + sfm->StartErrorRecovery(this, bg_error_); + } +#endif +} + +Status ErrorHandler::ClearBGError() { +#ifndef ROCKSDB_LITE + db_mutex_->AssertHeld(); + + // Signal that recovery succeeded + if (recovery_error_.ok()) { + Status old_bg_error = bg_error_; + bg_error_ = Status::OK(); + recovery_in_prog_ = false; + EventHelpers::NotifyOnErrorRecoveryCompleted(db_options_.listeners, + old_bg_error, db_mutex_); + } + return recovery_error_; +#else + return bg_error_; +#endif +} + +Status ErrorHandler::RecoverFromBGError(bool is_manual) { +#ifndef ROCKSDB_LITE + InstrumentedMutexLock l(db_mutex_); + if (is_manual) { + // If its a manual recovery and there's a background recovery in progress + // return busy status + if (recovery_in_prog_) { + return Status::Busy(); + } + recovery_in_prog_ = true; + } + + if (bg_error_.severity() == Status::Severity::kSoftError) { + // Simply clear the background error and return + recovery_error_ = Status::OK(); + return ClearBGError(); + } + + // Reset recovery_error_. We will use this to record any errors that happen + // during the recovery process. While recovering, the only operations that + // can generate background errors should be the flush operations + recovery_error_ = Status::OK(); + Status s = db_->ResumeImpl(); + // For manual recover, shutdown, and fatal error cases, set + // recovery_in_prog_ to false. For automatic background recovery, leave it + // as is regardless of success or failure as it will be retried + if (is_manual || s.IsShutdownInProgress() || + bg_error_.severity() >= Status::Severity::kFatalError) { + recovery_in_prog_ = false; + } + return s; +#else + (void)is_manual; + return bg_error_; +#endif +} +} diff --git a/thirdparty/rocksdb/db/error_handler.h b/thirdparty/rocksdb/db/error_handler.h new file mode 100644 index 0000000000..c2af809fc6 --- /dev/null +++ b/thirdparty/rocksdb/db/error_handler.h @@ -0,0 +1,75 @@ +// Copyright (c) 2018-present, Facebook, 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). +#pragma once + +#include "monitoring/instrumented_mutex.h" +#include "options/db_options.h" +#include "rocksdb/listener.h" +#include "rocksdb/status.h" + +namespace rocksdb { + +class DBImpl; + +class ErrorHandler { + public: + ErrorHandler(DBImpl* db, const ImmutableDBOptions& db_options, + InstrumentedMutex* db_mutex) + : db_(db), + db_options_(db_options), + bg_error_(Status::OK()), + recovery_error_(Status::OK()), + db_mutex_(db_mutex), + auto_recovery_(false), + recovery_in_prog_(false) {} + ~ErrorHandler() {} + + void EnableAutoRecovery() { auto_recovery_ = true; } + + Status::Severity GetErrorSeverity(BackgroundErrorReason reason, + Status::Code code, + Status::SubCode subcode); + + Status SetBGError(const Status& bg_err, BackgroundErrorReason reason); + + Status GetBGError() { return bg_error_; } + + Status GetRecoveryError() { return recovery_error_; } + + Status ClearBGError(); + + bool IsDBStopped() { + return !bg_error_.ok() && + bg_error_.severity() >= Status::Severity::kHardError; + } + + bool IsBGWorkStopped() { + return !bg_error_.ok() && + (bg_error_.severity() >= Status::Severity::kHardError || + !auto_recovery_); + } + + bool IsRecoveryInProgress() { return recovery_in_prog_; } + + Status RecoverFromBGError(bool is_manual = false); + void CancelErrorRecovery(); + + private: + DBImpl* db_; + const ImmutableDBOptions& db_options_; + Status bg_error_; + // A separate Status variable used to record any errors during the + // recovery process from hard errors + Status recovery_error_; + InstrumentedMutex* db_mutex_; + // A flag indicating whether automatic recovery from errors is enabled + bool auto_recovery_; + bool recovery_in_prog_; + + Status OverrideNoSpaceError(Status bg_error, bool* auto_recovery); + void RecoverFromNoSpace(); +}; + +} diff --git a/thirdparty/rocksdb/db/error_handler_test.cc b/thirdparty/rocksdb/db/error_handler_test.cc new file mode 100644 index 0000000000..d33e19df5d --- /dev/null +++ b/thirdparty/rocksdb/db/error_handler_test.cc @@ -0,0 +1,691 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. +#ifndef ROCKSDB_LITE + +#include "db/db_test_util.h" +#include "port/stack_trace.h" +#include "rocksdb/perf_context.h" +#include "rocksdb/sst_file_manager.h" +#include "util/fault_injection_test_env.h" +#if !defined(ROCKSDB_LITE) +#include "util/sync_point.h" +#endif + +namespace rocksdb { + +class DBErrorHandlingTest : public DBTestBase { + public: + DBErrorHandlingTest() : DBTestBase("/db_error_handling_test") {} +}; + +class DBErrorHandlingEnv : public EnvWrapper { + public: + DBErrorHandlingEnv() : EnvWrapper(Env::Default()), + trig_no_space(false), trig_io_error(false) {} + + void SetTrigNoSpace() {trig_no_space = true;} + void SetTrigIoError() {trig_io_error = true;} + private: + bool trig_no_space; + bool trig_io_error; +}; + +class ErrorHandlerListener : public EventListener { + public: + ErrorHandlerListener() + : mutex_(), + cv_(&mutex_), + no_auto_recovery_(false), + recovery_complete_(false), + file_creation_started_(false), + override_bg_error_(false), + file_count_(0), + fault_env_(nullptr) {} + + void OnTableFileCreationStarted( + const TableFileCreationBriefInfo& /*ti*/) override { + InstrumentedMutexLock l(&mutex_); + file_creation_started_ = true; + if (file_count_ > 0) { + if (--file_count_ == 0) { + fault_env_->SetFilesystemActive(false, file_creation_error_); + file_creation_error_ = Status::OK(); + } + } + cv_.SignalAll(); + } + + void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/, + Status /*bg_error*/, + bool* auto_recovery) override { + if (*auto_recovery && no_auto_recovery_) { + *auto_recovery = false; + } + } + + void OnErrorRecoveryCompleted(Status /*old_bg_error*/) override { + InstrumentedMutexLock l(&mutex_); + recovery_complete_ = true; + cv_.SignalAll(); + } + + bool WaitForRecovery(uint64_t /*abs_time_us*/) { + InstrumentedMutexLock l(&mutex_); + while (!recovery_complete_) { + cv_.Wait(/*abs_time_us*/); + } + if (recovery_complete_) { + recovery_complete_ = false; + return true; + } + return false; + } + + void WaitForTableFileCreationStarted(uint64_t /*abs_time_us*/) { + InstrumentedMutexLock l(&mutex_); + while (!file_creation_started_) { + cv_.Wait(/*abs_time_us*/); + } + file_creation_started_ = false; + } + + void OnBackgroundError(BackgroundErrorReason /*reason*/, + Status* bg_error) override { + if (override_bg_error_) { + *bg_error = bg_error_; + override_bg_error_ = false; + } + } + + void EnableAutoRecovery(bool enable = true) { no_auto_recovery_ = !enable; } + + void OverrideBGError(Status bg_err) { + bg_error_ = bg_err; + override_bg_error_ = true; + } + + void InjectFileCreationError(FaultInjectionTestEnv* env, int file_count, + Status s) { + fault_env_ = env; + file_count_ = file_count; + file_creation_error_ = s; + } + + private: + InstrumentedMutex mutex_; + InstrumentedCondVar cv_; + bool no_auto_recovery_; + bool recovery_complete_; + bool file_creation_started_; + bool override_bg_error_; + int file_count_; + Status file_creation_error_; + Status bg_error_; + FaultInjectionTestEnv* fault_env_; +}; + +TEST_F(DBErrorHandlingTest, FLushWriteError) { + std::unique_ptr fault_env( + new FaultInjectionTestEnv(Env::Default())); + std::shared_ptr listener(new ErrorHandlerListener()); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.env = fault_env.get(); + options.listeners.emplace_back(listener); + Status s; + + listener->EnableAutoRecovery(false); + DestroyAndReopen(options); + + Put(Key(0), "val"); + SyncPoint::GetInstance()->SetCallBack( + "FlushJob::Start", [&](void *) { + fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); + }); + SyncPoint::GetInstance()->EnableProcessing(); + s = Flush(); + ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError); + SyncPoint::GetInstance()->DisableProcessing(); + fault_env->SetFilesystemActive(true); + s = dbfull()->Resume(); + ASSERT_EQ(s, Status::OK()); + + Reopen(options); + ASSERT_EQ("val", Get(Key(0))); + Destroy(options); +} + +TEST_F(DBErrorHandlingTest, CompactionWriteError) { + std::unique_ptr fault_env( + new FaultInjectionTestEnv(Env::Default())); + std::shared_ptr listener(new ErrorHandlerListener()); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.level0_file_num_compaction_trigger = 2; + options.listeners.emplace_back(listener); + options.env = fault_env.get(); + Status s; + DestroyAndReopen(options); + + Put(Key(0), "va;"); + Put(Key(2), "va;"); + s = Flush(); + ASSERT_EQ(s, Status::OK()); + + listener->OverrideBGError( + Status(Status::NoSpace(), Status::Severity::kHardError) + ); + listener->EnableAutoRecovery(false); + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"FlushMemTableFinished", "BackgroundCallCompaction:0"}}); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "BackgroundCallCompaction:0", [&](void *) { + fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Put(Key(1), "val"); + s = Flush(); + ASSERT_EQ(s, Status::OK()); + + s = dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError); + + fault_env->SetFilesystemActive(true); + s = dbfull()->Resume(); + ASSERT_EQ(s, Status::OK()); + Destroy(options); +} + +TEST_F(DBErrorHandlingTest, CorruptionError) { + std::unique_ptr fault_env( + new FaultInjectionTestEnv(Env::Default())); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.level0_file_num_compaction_trigger = 2; + options.env = fault_env.get(); + Status s; + DestroyAndReopen(options); + + Put(Key(0), "va;"); + Put(Key(2), "va;"); + s = Flush(); + ASSERT_EQ(s, Status::OK()); + + rocksdb::SyncPoint::GetInstance()->LoadDependency( + {{"FlushMemTableFinished", "BackgroundCallCompaction:0"}}); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "BackgroundCallCompaction:0", [&](void *) { + fault_env->SetFilesystemActive(false, Status::Corruption("Corruption")); + }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Put(Key(1), "val"); + s = Flush(); + ASSERT_EQ(s, Status::OK()); + + s = dbfull()->TEST_WaitForCompact(); + ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kUnrecoverableError); + + fault_env->SetFilesystemActive(true); + s = dbfull()->Resume(); + ASSERT_NE(s, Status::OK()); + Destroy(options); +} + +TEST_F(DBErrorHandlingTest, AutoRecoverFlushError) { + std::unique_ptr fault_env( + new FaultInjectionTestEnv(Env::Default())); + std::shared_ptr listener(new ErrorHandlerListener()); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.env = fault_env.get(); + options.listeners.emplace_back(listener); + Status s; + + listener->EnableAutoRecovery(); + DestroyAndReopen(options); + + Put(Key(0), "val"); + SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { + fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); + }); + SyncPoint::GetInstance()->EnableProcessing(); + s = Flush(); + ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError); + SyncPoint::GetInstance()->DisableProcessing(); + fault_env->SetFilesystemActive(true); + ASSERT_EQ(listener->WaitForRecovery(5000000), true); + + s = Put(Key(1), "val"); + ASSERT_EQ(s, Status::OK()); + + Reopen(options); + ASSERT_EQ("val", Get(Key(0))); + ASSERT_EQ("val", Get(Key(1))); + Destroy(options); +} + +TEST_F(DBErrorHandlingTest, FailRecoverFlushError) { + std::unique_ptr fault_env( + new FaultInjectionTestEnv(Env::Default())); + std::shared_ptr listener(new ErrorHandlerListener()); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.env = fault_env.get(); + options.listeners.emplace_back(listener); + Status s; + + listener->EnableAutoRecovery(); + DestroyAndReopen(options); + + Put(Key(0), "val"); + SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) { + fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); + }); + SyncPoint::GetInstance()->EnableProcessing(); + s = Flush(); + ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError); + // We should be able to shutdown the database while auto recovery is going + // on in the background + Close(); + DestroyDB(dbname_, options); +} + +TEST_F(DBErrorHandlingTest, WALWriteError) { + std::unique_ptr fault_env( + new FaultInjectionTestEnv(Env::Default())); + std::shared_ptr listener(new ErrorHandlerListener()); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.writable_file_max_buffer_size = 32768; + options.env = fault_env.get(); + options.listeners.emplace_back(listener); + Status s; + Random rnd(301); + + listener->EnableAutoRecovery(); + DestroyAndReopen(options); + + { + WriteBatch batch; + + for (auto i = 0; i<100; ++i) { + batch.Put(Key(i), RandomString(&rnd, 1024)); + } + + WriteOptions wopts; + wopts.sync = true; + ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK()); + }; + + { + WriteBatch batch; + int write_error = 0; + + for (auto i = 100; i<199; ++i) { + batch.Put(Key(i), RandomString(&rnd, 1024)); + } + + SyncPoint::GetInstance()->SetCallBack("WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { + write_error++; + if (write_error > 2) { + fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); + } + }); + SyncPoint::GetInstance()->EnableProcessing(); + WriteOptions wopts; + wopts.sync = true; + s = dbfull()->Write(wopts, &batch); + ASSERT_EQ(s, s.NoSpace()); + } + SyncPoint::GetInstance()->DisableProcessing(); + fault_env->SetFilesystemActive(true); + ASSERT_EQ(listener->WaitForRecovery(5000000), true); + for (auto i=0; i<199; ++i) { + if (i < 100) { + ASSERT_NE(Get(Key(i)), "NOT_FOUND"); + } else { + ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); + } + } + Reopen(options); + for (auto i=0; i<199; ++i) { + if (i < 100) { + ASSERT_NE(Get(Key(i)), "NOT_FOUND"); + } else { + ASSERT_EQ(Get(Key(i)), "NOT_FOUND"); + } + } + Close(); +} + +TEST_F(DBErrorHandlingTest, MultiCFWALWriteError) { + std::unique_ptr fault_env( + new FaultInjectionTestEnv(Env::Default())); + std::shared_ptr listener(new ErrorHandlerListener()); + Options options = GetDefaultOptions(); + options.create_if_missing = true; + options.writable_file_max_buffer_size = 32768; + options.env = fault_env.get(); + options.listeners.emplace_back(listener); + Status s; + Random rnd(301); + + listener->EnableAutoRecovery(); + CreateAndReopenWithCF({"one", "two", "three"}, options); + + { + WriteBatch batch; + + for (auto i = 1; i < 4; ++i) { + for (auto j = 0; j < 100; ++j) { + batch.Put(handles_[i], Key(j), RandomString(&rnd, 1024)); + } + } + + WriteOptions wopts; + wopts.sync = true; + ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK()); + }; + + { + WriteBatch batch; + int write_error = 0; + + // Write to one CF + for (auto i = 100; i < 199; ++i) { + batch.Put(handles_[2], Key(i), RandomString(&rnd, 1024)); + } + + SyncPoint::GetInstance()->SetCallBack( + "WritableFileWriter::Append:BeforePrepareWrite", [&](void*) { + write_error++; + if (write_error > 2) { + fault_env->SetFilesystemActive(false, + Status::NoSpace("Out of space")); + } + }); + SyncPoint::GetInstance()->EnableProcessing(); + WriteOptions wopts; + wopts.sync = true; + s = dbfull()->Write(wopts, &batch); + ASSERT_EQ(s, s.NoSpace()); + } + SyncPoint::GetInstance()->DisableProcessing(); + fault_env->SetFilesystemActive(true); + ASSERT_EQ(listener->WaitForRecovery(5000000), true); + + for (auto i = 1; i < 4; ++i) { + // Every CF should have been flushed + ASSERT_EQ(NumTableFilesAtLevel(0, i), 1); + } + + for (auto i = 1; i < 4; ++i) { + for (auto j = 0; j < 199; ++j) { + if (j < 100) { + ASSERT_NE(Get(i, Key(j)), "NOT_FOUND"); + } else { + ASSERT_EQ(Get(i, Key(j)), "NOT_FOUND"); + } + } + } + ReopenWithColumnFamilies({"default", "one", "two", "three"}, options); + for (auto i = 1; i < 4; ++i) { + for (auto j = 0; j < 199; ++j) { + if (j < 100) { + ASSERT_NE(Get(i, Key(j)), "NOT_FOUND"); + } else { + ASSERT_EQ(Get(i, Key(j)), "NOT_FOUND"); + } + } + } + Close(); +} + +TEST_F(DBErrorHandlingTest, MultiDBCompactionError) { + FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default()); + std::vector> fault_env; + std::vector options; + std::vector> listener; + std::vector db; + std::shared_ptr sfm(NewSstFileManager(def_env)); + int kNumDbInstances = 3; + Random rnd(301); + + for (auto i = 0; i < kNumDbInstances; ++i) { + listener.emplace_back(new ErrorHandlerListener()); + options.emplace_back(GetDefaultOptions()); + fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default())); + options[i].create_if_missing = true; + options[i].level0_file_num_compaction_trigger = 2; + options[i].writable_file_max_buffer_size = 32768; + options[i].env = fault_env[i].get(); + options[i].listeners.emplace_back(listener[i]); + options[i].sst_file_manager = sfm; + DB* dbptr; + char buf[16]; + + listener[i]->EnableAutoRecovery(); + // Setup for returning error for the 3rd SST, which would be level 1 + listener[i]->InjectFileCreationError(fault_env[i].get(), 3, + Status::NoSpace("Out of space")); + snprintf(buf, sizeof(buf), "_%d", i); + DestroyDB(dbname_ + std::string(buf), options[i]); + ASSERT_EQ(DB::Open(options[i], dbname_ + std::string(buf), &dbptr), + Status::OK()); + db.emplace_back(dbptr); + } + + for (auto i = 0; i < kNumDbInstances; ++i) { + WriteBatch batch; + + for (auto j = 0; j <= 100; ++j) { + batch.Put(Key(j), RandomString(&rnd, 1024)); + } + + WriteOptions wopts; + wopts.sync = true; + ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK()); + ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK()); + } + + def_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); + for (auto i = 0; i < kNumDbInstances; ++i) { + WriteBatch batch; + + // Write to one CF + for (auto j = 100; j < 199; ++j) { + batch.Put(Key(j), RandomString(&rnd, 1024)); + } + + WriteOptions wopts; + wopts.sync = true; + ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK()); + ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK()); + } + + for (auto i = 0; i < kNumDbInstances; ++i) { + Status s = static_cast(db[i])->TEST_WaitForCompact(true); + ASSERT_EQ(s.severity(), Status::Severity::kSoftError); + fault_env[i]->SetFilesystemActive(true); + } + + def_env->SetFilesystemActive(true); + for (auto i = 0; i < kNumDbInstances; ++i) { + std::string prop; + ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true); + EXPECT_TRUE(db[i]->GetProperty( + "rocksdb.num-files-at-level" + NumberToString(0), &prop)); + EXPECT_EQ(atoi(prop.c_str()), 0); + EXPECT_TRUE(db[i]->GetProperty( + "rocksdb.num-files-at-level" + NumberToString(1), &prop)); + EXPECT_EQ(atoi(prop.c_str()), 1); + } + + for (auto i = 0; i < kNumDbInstances; ++i) { + char buf[16]; + snprintf(buf, sizeof(buf), "_%d", i); + delete db[i]; + fault_env[i]->SetFilesystemActive(true); + if (getenv("KEEP_DB")) { + printf("DB is still at %s%s\n", dbname_.c_str(), buf); + } else { + Status s = DestroyDB(dbname_ + std::string(buf), options[i]); + } + } + options.clear(); + sfm.reset(); + delete def_env; +} + +TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) { + FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default()); + std::vector> fault_env; + std::vector options; + std::vector> listener; + std::vector db; + std::shared_ptr sfm(NewSstFileManager(def_env)); + int kNumDbInstances = 3; + Random rnd(301); + + for (auto i = 0; i < kNumDbInstances; ++i) { + listener.emplace_back(new ErrorHandlerListener()); + options.emplace_back(GetDefaultOptions()); + fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default())); + options[i].create_if_missing = true; + options[i].level0_file_num_compaction_trigger = 2; + options[i].writable_file_max_buffer_size = 32768; + options[i].env = fault_env[i].get(); + options[i].listeners.emplace_back(listener[i]); + options[i].sst_file_manager = sfm; + DB* dbptr; + char buf[16]; + + listener[i]->EnableAutoRecovery(); + switch (i) { + case 0: + // Setup for returning error for the 3rd SST, which would be level 1 + listener[i]->InjectFileCreationError(fault_env[i].get(), 3, + Status::NoSpace("Out of space")); + break; + case 1: + // Setup for returning error after the 1st SST, which would result + // in a hard error + listener[i]->InjectFileCreationError(fault_env[i].get(), 2, + Status::NoSpace("Out of space")); + break; + default: + break; + } + snprintf(buf, sizeof(buf), "_%d", i); + DestroyDB(dbname_ + std::string(buf), options[i]); + ASSERT_EQ(DB::Open(options[i], dbname_ + std::string(buf), &dbptr), + Status::OK()); + db.emplace_back(dbptr); + } + + for (auto i = 0; i < kNumDbInstances; ++i) { + WriteBatch batch; + + for (auto j = 0; j <= 100; ++j) { + batch.Put(Key(j), RandomString(&rnd, 1024)); + } + + WriteOptions wopts; + wopts.sync = true; + ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK()); + ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK()); + } + + def_env->SetFilesystemActive(false, Status::NoSpace("Out of space")); + for (auto i = 0; i < kNumDbInstances; ++i) { + WriteBatch batch; + + // Write to one CF + for (auto j = 100; j < 199; ++j) { + batch.Put(Key(j), RandomString(&rnd, 1024)); + } + + WriteOptions wopts; + wopts.sync = true; + ASSERT_EQ(db[i]->Write(wopts, &batch), Status::OK()); + if (i != 1) { + ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::OK()); + } else { + ASSERT_EQ(db[i]->Flush(FlushOptions()), Status::NoSpace()); + } + } + + for (auto i = 0; i < kNumDbInstances; ++i) { + Status s = static_cast(db[i])->TEST_WaitForCompact(true); + switch (i) { + case 0: + ASSERT_EQ(s.severity(), Status::Severity::kSoftError); + break; + case 1: + ASSERT_EQ(s.severity(), Status::Severity::kHardError); + break; + case 2: + ASSERT_EQ(s, Status::OK()); + break; + } + fault_env[i]->SetFilesystemActive(true); + } + + def_env->SetFilesystemActive(true); + for (auto i = 0; i < kNumDbInstances; ++i) { + std::string prop; + if (i < 2) { + ASSERT_EQ(listener[i]->WaitForRecovery(5000000), true); + } + if (i == 1) { + ASSERT_EQ(static_cast(db[i])->TEST_WaitForCompact(true), + Status::OK()); + } + EXPECT_TRUE(db[i]->GetProperty( + "rocksdb.num-files-at-level" + NumberToString(0), &prop)); + EXPECT_EQ(atoi(prop.c_str()), 0); + EXPECT_TRUE(db[i]->GetProperty( + "rocksdb.num-files-at-level" + NumberToString(1), &prop)); + EXPECT_EQ(atoi(prop.c_str()), 1); + } + + for (auto i = 0; i < kNumDbInstances; ++i) { + char buf[16]; + snprintf(buf, sizeof(buf), "_%d", i); + fault_env[i]->SetFilesystemActive(true); + delete db[i]; + if (getenv("KEEP_DB")) { + printf("DB is still at %s%s\n", dbname_.c_str(), buf); + } else { + DestroyDB(dbname_ + std::string(buf), options[i]); + } + } + options.clear(); + delete def_env; +} + +} // namespace rocksdb + +int main(int argc, char** argv) { + rocksdb::port::InstallStackTraceHandler(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +#else +#include + +int main(int /*argc*/, char** /*argv*/) { + fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n"); + return 0; +} + +#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/event_helpers.cc b/thirdparty/rocksdb/db/event_helpers.cc index 1b79acb0f2..c80c5aefb7 100644 --- a/thirdparty/rocksdb/db/event_helpers.cc +++ b/thirdparty/rocksdb/db/event_helpers.cc @@ -8,7 +8,7 @@ namespace rocksdb { namespace { -template +template inline T SafeDivide(T a, T b) { return b == 0 ? 0 : a / b; } @@ -17,7 +17,8 @@ inline T SafeDivide(T a, T b) { void EventHelpers::AppendCurrentTime(JSONWriter* jwriter) { *jwriter << "time_micros" << std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); } #ifndef ROCKSDB_LITE @@ -39,8 +40,8 @@ void EventHelpers::NotifyTableFileCreationStarted( void EventHelpers::NotifyOnBackgroundError( const std::vector>& listeners, - BackgroundErrorReason reason, Status* bg_error, - InstrumentedMutex* db_mutex) { + BackgroundErrorReason reason, Status* bg_error, InstrumentedMutex* db_mutex, + bool* auto_recovery) { #ifndef ROCKSDB_LITE if (listeners.size() == 0U) { return; @@ -50,8 +51,17 @@ void EventHelpers::NotifyOnBackgroundError( db_mutex->Unlock(); for (auto& listener : listeners) { listener->OnBackgroundError(reason, bg_error); + if (*auto_recovery) { + listener->OnErrorRecoveryBegin(reason, *bg_error, auto_recovery); + } } db_mutex->Lock(); +#else + (void)listeners; + (void)reason; + (void)bg_error; + (void)db_mutex; + (void)auto_recovery; #endif // ROCKSDB_LITE } @@ -117,20 +127,25 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished( for (auto& listener : listeners) { listener->OnTableFileCreated(info); } +#else + (void)listeners; + (void)db_name; + (void)cf_name; + (void)file_path; + (void)reason; #endif // !ROCKSDB_LITE } void EventHelpers::LogAndNotifyTableFileDeletion( - EventLogger* event_logger, int job_id, - uint64_t file_number, const std::string& file_path, - const Status& status, const std::string& dbname, + EventLogger* event_logger, int job_id, uint64_t file_number, + const std::string& file_path, const Status& status, + const std::string& dbname, const std::vector>& listeners) { - JSONWriter jwriter; AppendCurrentTime(&jwriter); - jwriter << "job" << job_id - << "event" << "table_file_deletion" + jwriter << "job" << job_id << "event" + << "table_file_deletion" << "file_number" << file_number; if (!status.ok()) { jwriter << "status" << status.ToString(); @@ -149,7 +164,32 @@ void EventHelpers::LogAndNotifyTableFileDeletion( for (auto& listener : listeners) { listener->OnTableFileDeleted(info); } +#else + (void)file_path; + (void)dbname; + (void)listeners; #endif // !ROCKSDB_LITE } +void EventHelpers::NotifyOnErrorRecoveryCompleted( + const std::vector>& listeners, + Status old_bg_error, InstrumentedMutex* db_mutex) { +#ifndef ROCKSDB_LITE + if (listeners.size() == 0U) { + return; + } + db_mutex->AssertHeld(); + // release lock while notifying events + db_mutex->Unlock(); + for (auto& listener : listeners) { + listener->OnErrorRecoveryCompleted(old_bg_error); + } + db_mutex->Lock(); +#else + (void)listeners; + (void)old_bg_error; + (void)db_mutex; +#endif // ROCKSDB_LITE +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/event_helpers.h b/thirdparty/rocksdb/db/event_helpers.h index 674e6c5f6f..ea35b4b5b1 100644 --- a/thirdparty/rocksdb/db/event_helpers.h +++ b/thirdparty/rocksdb/db/event_helpers.h @@ -28,7 +28,7 @@ class EventHelpers { static void NotifyOnBackgroundError( const std::vector>& listeners, BackgroundErrorReason reason, Status* bg_error, - InstrumentedMutex* db_mutex); + InstrumentedMutex* db_mutex, bool* auto_recovery); static void LogAndNotifyTableFileCreationFinished( EventLogger* event_logger, const std::vector>& listeners, @@ -41,6 +41,9 @@ class EventHelpers { uint64_t file_number, const std::string& file_path, const Status& status, const std::string& db_name, const std::vector>& listeners); + static void NotifyOnErrorRecoveryCompleted( + const std::vector>& listeners, + Status bg_error, InstrumentedMutex* db_mutex); private: static void LogAndNotifyTableFileCreation( diff --git a/thirdparty/rocksdb/db/experimental.cc b/thirdparty/rocksdb/db/experimental.cc index effe9d7c35..d509a37bf2 100644 --- a/thirdparty/rocksdb/db/experimental.cc +++ b/thirdparty/rocksdb/db/experimental.cc @@ -30,12 +30,13 @@ Status PromoteL0(DB* db, ColumnFamilyHandle* column_family, int target_level) { #else // ROCKSDB_LITE -Status SuggestCompactRange(DB* db, ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end) { +Status SuggestCompactRange(DB* /*db*/, ColumnFamilyHandle* /*column_family*/, + const Slice* /*begin*/, const Slice* /*end*/) { return Status::NotSupported("Not supported in RocksDB LITE"); } -Status PromoteL0(DB* db, ColumnFamilyHandle* column_family, int target_level) { +Status PromoteL0(DB* /*db*/, ColumnFamilyHandle* /*column_family*/, + int /*target_level*/) { return Status::NotSupported("Not supported in RocksDB LITE"); } diff --git a/thirdparty/rocksdb/db/external_sst_file_basic_test.cc b/thirdparty/rocksdb/db/external_sst_file_basic_test.cc index 534e8a0bf7..256db0728b 100644 --- a/thirdparty/rocksdb/db/external_sst_file_basic_test.cc +++ b/thirdparty/rocksdb/db/external_sst_file_basic_test.cc @@ -14,9 +14,11 @@ namespace rocksdb { #ifndef ROCKSDB_LITE -class ExternalSSTFileBasicTest : public DBTestBase { +class ExternalSSTFileBasicTest + : public DBTestBase, + public ::testing::WithParamInterface> { public: - ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_test") { + ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_basic_test") { sst_files_dir_ = dbname_ + "/sst_files/"; DestroyAndRecreateExternalSSTFilesDir(); } @@ -39,7 +41,9 @@ class ExternalSSTFileBasicTest : public DBTestBase { Status GenerateAndAddExternalFile( const Options options, std::vector keys, - const std::vector& value_types, int file_id, + const std::vector& value_types, + std::vector> range_deletions, int file_id, + bool write_global_seqno, bool verify_checksums_before_ingest, std::map* true_data) { assert(value_types.size() == 1 || keys.size() == value_types.size()); std::string file_path = sst_files_dir_ + ToString(file_id); @@ -49,6 +53,29 @@ class ExternalSSTFileBasicTest : public DBTestBase { if (!s.ok()) { return s; } + for (size_t i = 0; i < range_deletions.size(); i++) { + // Account for the effect of range deletions on true_data before + // all point operators, even though sst_file_writer.DeleteRange + // must be called before other sst_file_writer methods. This is + // because point writes take precedence over range deletions + // in the same ingested sst. + std::string start_key = Key(range_deletions[i].first); + std::string end_key = Key(range_deletions[i].second); + s = sst_file_writer.DeleteRange(start_key, end_key); + if (!s.ok()) { + sst_file_writer.Finish(); + return s; + } + auto start_key_it = true_data->find(start_key); + if (start_key_it == true_data->end()) { + start_key_it = true_data->upper_bound(start_key); + } + auto end_key_it = true_data->find(end_key); + if (end_key_it == true_data->end()) { + end_key_it = true_data->upper_bound(end_key); + } + true_data->erase(start_key_it, end_key_it); + } for (size_t i = 0; i < keys.size(); i++) { std::string key = Key(keys[i]); std::string value = Key(keys[i]) + ToString(file_id); @@ -81,20 +108,35 @@ class ExternalSSTFileBasicTest : public DBTestBase { if (s.ok()) { IngestExternalFileOptions ifo; ifo.allow_global_seqno = true; + ifo.write_global_seqno = write_global_seqno; + ifo.verify_checksums_before_ingest = verify_checksums_before_ingest; s = db_->IngestExternalFile({file_path}, ifo); } return s; } + Status GenerateAndAddExternalFile( + const Options options, std::vector keys, + const std::vector& value_types, int file_id, + bool write_global_seqno, bool verify_checksums_before_ingest, + std::map* true_data) { + return GenerateAndAddExternalFile( + options, keys, value_types, {}, file_id, write_global_seqno, + verify_checksums_before_ingest, true_data); + } + Status GenerateAndAddExternalFile( const Options options, std::vector keys, const ValueType value_type, - int file_id, std::map* true_data) { - return GenerateAndAddExternalFile(options, keys, - std::vector(1, value_type), - file_id, true_data); + int file_id, bool write_global_seqno, bool verify_checksums_before_ingest, + std::map* true_data) { + return GenerateAndAddExternalFile( + options, keys, std::vector(1, value_type), file_id, + write_global_seqno, verify_checksums_before_ingest, true_data); } - ~ExternalSSTFileBasicTest() { test::DestroyDir(env_, sst_files_dir_); } + ~ExternalSSTFileBasicTest() override { + test::DestroyDir(env_, sst_files_dir_); + } protected: std::string sst_files_dir_; @@ -126,9 +168,14 @@ TEST_F(ExternalSSTFileBasicTest, Basic) { ASSERT_EQ(file1_info.num_entries, 100); ASSERT_EQ(file1_info.smallest_key, Key(0)); ASSERT_EQ(file1_info.largest_key, Key(99)); + ASSERT_EQ(file1_info.num_range_del_entries, 0); + ASSERT_EQ(file1_info.smallest_range_del_key, ""); + ASSERT_EQ(file1_info.largest_range_del_key, ""); // sst_file_writer already finished, cannot add this value s = sst_file_writer.Put(Key(100), "bad_val"); ASSERT_FALSE(s.ok()) << s.ToString(); + s = sst_file_writer.DeleteRange(Key(100), Key(200)); + ASSERT_FALSE(s.ok()) << s.ToString(); DestroyAndReopen(options); // Add file using file path @@ -189,6 +236,7 @@ TEST_F(ExternalSSTFileBasicTest, NoCopy) { ASSERT_EQ(file3_info.num_entries, 15); ASSERT_EQ(file3_info.smallest_key, Key(110)); ASSERT_EQ(file3_info.largest_key, Key(124)); + s = DeprecatedAddFile({file1}, true /* move file */); ASSERT_TRUE(s.ok()) << s.ToString(); ASSERT_EQ(Status::NotFound(), env_->FileExists(file1)); @@ -197,8 +245,8 @@ TEST_F(ExternalSSTFileBasicTest, NoCopy) { ASSERT_TRUE(s.ok()) << s.ToString(); ASSERT_OK(env_->FileExists(file2)); - // This file have overlapping values with the existing data - s = DeprecatedAddFile({file2}, true /* move file */); + // This file has overlapping values with the existing data + s = DeprecatedAddFile({file3}, true /* move file */); ASSERT_FALSE(s.ok()) << s.ToString(); ASSERT_OK(env_->FileExists(file3)); @@ -207,7 +255,9 @@ TEST_F(ExternalSSTFileBasicTest, NoCopy) { } } -TEST_F(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) { +TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) { + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); do { Options options = CurrentOptions(); DestroyAndReopen(options); @@ -215,37 +265,40 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) { int file_id = 1; - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2, 3, 4, 5, 6}, - ValueType::kTypeValue, file_id++, - &true_data)); - // File dont overwrite any keys, No seqno needed + ASSERT_OK(GenerateAndAddExternalFile( + options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - ASSERT_OK(GenerateAndAddExternalFile(options, {10, 11, 12, 13}, - ValueType::kTypeValue, file_id++, - - &true_data)); - // File dont overwrite any keys, No seqno needed + ASSERT_OK(GenerateAndAddExternalFile( + options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 4, 6}, ValueType::kTypeValue, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {1, 4, 6}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); ASSERT_OK(GenerateAndAddExternalFile( - options, {11, 15, 19}, ValueType::kTypeValue, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {11, 15, 19}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); ASSERT_OK(GenerateAndAddExternalFile( - options, {120, 130}, ValueType::kTypeValue, file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + options, {120, 130}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 130}, ValueType::kTypeValue, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {1, 130}, ValueType::kTypeValue, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); // Write some keys through normal write path @@ -256,18 +309,21 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) { SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); ASSERT_OK(GenerateAndAddExternalFile( - options, {60, 61, 62}, ValueType::kTypeValue, file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + options, {60, 61, 62}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); ASSERT_OK(GenerateAndAddExternalFile( - options, {40, 41, 42}, ValueType::kTypeValue, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {40, 41, 42}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); ASSERT_OK(GenerateAndAddExternalFile( - options, {20, 30, 40}, ValueType::kTypeValue, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {20, 30, 40}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); const Snapshot* snapshot = db_->GetSnapshot(); @@ -275,34 +331,39 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) { // We will need a seqno for the file regardless if the file overwrite // keys in the DB or not because we have a snapshot ASSERT_OK(GenerateAndAddExternalFile( - options, {1000, 1002}, ValueType::kTypeValue, file_id++, &true_data)); + options, {1000, 1002}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); ASSERT_OK(GenerateAndAddExternalFile( - options, {2000, 3002}, ValueType::kTypeValue, file_id++, &true_data)); + options, {2000, 3002}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 20, 40, 100, 150}, - ValueType::kTypeValue, file_id++, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, {1, 20, 40, 100, 150}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); db_->ReleaseSnapshot(snapshot); ASSERT_OK(GenerateAndAddExternalFile( - options, {5000, 5001}, ValueType::kTypeValue, file_id++, &true_data)); + options, {5000, 5001}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // No snapshot anymore, no need to assign a seqno ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); size_t kcnt = 0; VerifyDBFromMap(true_data, &kcnt, false); - } while (ChangeCompactOptions()); + } while (ChangeOptionsForFileIngestionTest()); } -TEST_F(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) { +TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) { + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); do { Options options = CurrentOptions(); options.merge_operator.reset(new TestPutOperator()); @@ -311,40 +372,62 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) { int file_id = 1; - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2, 3, 4, 5, 6}, - ValueType::kTypeValue, file_id++, - &true_data)); - // File dont overwrite any keys, No seqno needed + ASSERT_OK(GenerateAndAddExternalFile( + options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); - ASSERT_OK(GenerateAndAddExternalFile(options, {10, 11, 12, 13}, - ValueType::kTypeValue, file_id++, - - &true_data)); - // File dont overwrite any keys, No seqno needed + ASSERT_OK(GenerateAndAddExternalFile( + options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 4, 6}, ValueType::kTypeMerge, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {1, 4, 6}, ValueType::kTypeMerge, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); - ASSERT_OK(GenerateAndAddExternalFile(options, {11, 15, 19}, - ValueType::kTypeDeletion, file_id++, - &true_data)); - // File overwrite some keys, a seqno will be assigned + ASSERT_OK(GenerateAndAddExternalFile( + options, {11, 15, 19}, ValueType::kTypeDeletion, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); ASSERT_OK(GenerateAndAddExternalFile( - options, {120, 130}, ValueType::kTypeMerge, file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + options, {120, 130}, ValueType::kTypeMerge, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 130}, ValueType::kTypeDeletion, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {1, 130}, ValueType::kTypeDeletion, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); + ASSERT_OK(GenerateAndAddExternalFile( + options, {120}, {ValueType::kTypeValue}, {{120, 135}}, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4); + + ASSERT_OK(GenerateAndAddExternalFile( + options, {}, {}, {{110, 120}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); + // The range deletion ends on a key, but it doesn't actually delete + // this key because the largest key in the range is exclusive. Still, + // it counts as an overlap so a new seqno will be assigned. + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); + + ASSERT_OK(GenerateAndAddExternalFile( + options, {}, {}, {{100, 109}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); + // Write some keys through normal write path for (int i = 0; i < 50; i++) { ASSERT_OK(Put(Key(i), "memtable")); @@ -353,19 +436,21 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) { SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); ASSERT_OK(GenerateAndAddExternalFile( - options, {60, 61, 62}, ValueType::kTypeValue, file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + options, {60, 61, 62}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); ASSERT_OK(GenerateAndAddExternalFile( - options, {40, 41, 42}, ValueType::kTypeMerge, file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {40, 41, 42}, ValueType::kTypeMerge, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); - ASSERT_OK(GenerateAndAddExternalFile(options, {20, 30, 40}, - ValueType::kTypeDeletion, file_id++, - &true_data)); - // File overwrite some keys, a seqno will be assigned + ASSERT_OK(GenerateAndAddExternalFile( + options, {20, 30, 40}, ValueType::kTypeDeletion, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); const Snapshot* snapshot = db_->GetSnapshot(); @@ -373,34 +458,39 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) { // We will need a seqno for the file regardless if the file overwrite // keys in the DB or not because we have a snapshot ASSERT_OK(GenerateAndAddExternalFile( - options, {1000, 1002}, ValueType::kTypeMerge, file_id++, &true_data)); + options, {1000, 1002}, ValueType::kTypeMerge, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); ASSERT_OK(GenerateAndAddExternalFile( - options, {2000, 3002}, ValueType::kTypeMerge, file_id++, &true_data)); + options, {2000, 3002}, ValueType::kTypeMerge, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 20, 40, 100, 150}, - ValueType::kTypeMerge, file_id++, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, {1, 20, 40, 100, 150}, ValueType::kTypeMerge, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); db_->ReleaseSnapshot(snapshot); ASSERT_OK(GenerateAndAddExternalFile( - options, {5000, 5001}, ValueType::kTypeValue, file_id++, &true_data)); + options, {5000, 5001}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data)); // No snapshot anymore, no need to assign a seqno ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); size_t kcnt = 0; VerifyDBFromMap(true_data, &kcnt, false); - } while (ChangeCompactOptions()); + } while (ChangeOptionsForFileIngestionTest()); } -TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { +TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); do { Options options = CurrentOptions(); options.merge_operator.reset(new TestPutOperator()); @@ -413,44 +503,78 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { options, {1, 2, 3, 4, 5, 6}, {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); ASSERT_OK(GenerateAndAddExternalFile( options, {10, 11, 12, 13}, {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); ASSERT_OK(GenerateAndAddExternalFile( - options, {1, 4, 6}, {ValueType::kTypeDeletion, ValueType::kTypeValue, - ValueType::kTypeMerge}, - file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {1, 4, 6}, + {ValueType::kTypeDeletion, ValueType::kTypeValue, + ValueType::kTypeMerge}, + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); ASSERT_OK(GenerateAndAddExternalFile( - options, {11, 15, 19}, {ValueType::kTypeDeletion, ValueType::kTypeMerge, - ValueType::kTypeValue}, - file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {11, 15, 19}, + {ValueType::kTypeDeletion, ValueType::kTypeMerge, + ValueType::kTypeValue}, + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); ASSERT_OK(GenerateAndAddExternalFile( options, {120, 130}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); ASSERT_OK(GenerateAndAddExternalFile( options, {1, 130}, {ValueType::kTypeMerge, ValueType::kTypeDeletion}, - file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); + ASSERT_OK(GenerateAndAddExternalFile( + options, {150, 151, 152}, + {ValueType::kTypeValue, ValueType::kTypeMerge, + ValueType::kTypeDeletion}, + {{150, 160}, {180, 190}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); + // File doesn't overwrite any keys, no seqno needed + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); + + ASSERT_OK(GenerateAndAddExternalFile( + options, {150, 151, 152}, + {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue}, + {{200, 250}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4); + + ASSERT_OK(GenerateAndAddExternalFile( + options, {300, 301, 302}, + {ValueType::kTypeValue, ValueType::kTypeMerge, + ValueType::kTypeDeletion}, + {{1, 2}, {152, 154}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); + // File overwrites some keys, a seqno will be assigned + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); + // Write some keys through normal write path for (int i = 0; i < 50; i++) { ASSERT_OK(Put(Key(i), "memtable")); @@ -461,23 +585,27 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { ASSERT_OK(GenerateAndAddExternalFile( options, {60, 61, 62}, {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue}, - file_id++, &true_data)); - // File dont overwrite any keys, No seqno needed + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File doesn't overwrite any keys, no seqno needed ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); ASSERT_OK(GenerateAndAddExternalFile( - options, {40, 41, 42}, {ValueType::kTypeValue, ValueType::kTypeDeletion, - ValueType::kTypeDeletion}, - file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + options, {40, 41, 42}, + {ValueType::kTypeValue, ValueType::kTypeDeletion, + ValueType::kTypeDeletion}, + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); ASSERT_OK(GenerateAndAddExternalFile( options, {20, 30, 40}, {ValueType::kTypeDeletion, ValueType::kTypeDeletion, ValueType::kTypeDeletion}, - file_id++, &true_data)); - // File overwrite some keys, a seqno will be assigned + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + // File overwrites some keys, a seqno will be assigned ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); const Snapshot* snapshot = db_->GetSnapshot(); @@ -486,13 +614,15 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { // keys in the DB or not because we have a snapshot ASSERT_OK(GenerateAndAddExternalFile( options, {1000, 1002}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, &true_data)); + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); ASSERT_OK(GenerateAndAddExternalFile( options, {2000, 3002}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, &true_data)); + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); @@ -500,7 +630,8 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { options, {1, 20, 40, 100, 150}, {ValueType::kTypeDeletion, ValueType::kTypeDeletion, ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeMerge}, - file_id++, &true_data)); + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); // A global seqno will be assigned anyway because of the snapshot ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); @@ -508,13 +639,14 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { ASSERT_OK(GenerateAndAddExternalFile( options, {5000, 5001}, {ValueType::kTypeValue, ValueType::kTypeMerge}, - file_id++, &true_data)); + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); // No snapshot anymore, no need to assign a seqno ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); size_t kcnt = 0; VerifyDBFromMap(true_data, &kcnt, false); - } while (ChangeCompactOptions()); + } while (ChangeOptionsForFileIngestionTest()); } TEST_F(ExternalSSTFileBasicTest, FadviseTrigger) { @@ -557,9 +689,11 @@ TEST_F(ExternalSSTFileBasicTest, FadviseTrigger) { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } -TEST_F(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) { +TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) { + int kNumLevels = 7; Options options = CurrentOptions(); options.disable_auto_compactions = true; + options.num_levels = kNumLevels; Reopen(options); std::map true_data; @@ -567,45 +701,232 @@ TEST_F(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) { // prevent range deletions from being dropped due to becoming obsolete. const Snapshot* snapshot = db_->GetSnapshot(); - // range del [0, 50) in L0 file, [50, 100) in memtable - for (int i = 0; i < 2; i++) { - if (i == 1) { + // range del [0, 50) in L6 file, [50, 100) in L0 file, [100, 150) in memtable + for (int i = 0; i < 3; i++) { + if (i != 0) { db_->Flush(FlushOptions()); + if (i == 1) { + MoveFilesToLevel(kNumLevels - 1); + } } ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), Key(50 * i), Key(50 * (i + 1)))); } ASSERT_EQ(1, NumTableFilesAtLevel(0)); + ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2)); + ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 1)); - // overlaps with L0 file but not memtable, so flush is skipped + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); + // overlaps with L0 file but not memtable, so flush is skipped and file is + // ingested into L0 SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); + ASSERT_OK(GenerateAndAddExternalFile( + options, {60, 90}, {ValueType::kTypeValue, ValueType::kTypeValue}, + {{65, 70}, {70, 85}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); + ASSERT_EQ(2, NumTableFilesAtLevel(0)); + ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2)); + ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); + + // overlaps with L6 file but not memtable or L0 file, so flush is skipped and + // file is ingested into L5 ASSERT_OK(GenerateAndAddExternalFile( options, {10, 40}, {ValueType::kTypeValue, ValueType::kTypeValue}, - file_id++, &true_data)); + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); + ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); + ASSERT_EQ(2, NumTableFilesAtLevel(0)); + ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); + ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); + + // overlaps with L5 file but not memtable or L0 file, so flush is skipped and + // file is ingested into L4 + ASSERT_OK(GenerateAndAddExternalFile( + options, {}, {}, {{5, 15}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); ASSERT_EQ(2, NumTableFilesAtLevel(0)); + ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); + ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 2)); + ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); - // overlaps with memtable, so flush is triggered (thus file count increases by - // two at this step). + // ingested file overlaps with memtable, so flush is triggered before the file + // is ingested such that the ingested data is considered newest. So L0 file + // count increases by two. ASSERT_OK(GenerateAndAddExternalFile( - options, {50, 90}, {ValueType::kTypeValue, ValueType::kTypeValue}, - file_id++, &true_data)); + options, {100, 140}, {ValueType::kTypeValue, ValueType::kTypeValue}, + file_id++, write_global_seqno, verify_checksums_before_ingest, + &true_data)); ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); ASSERT_EQ(4, NumTableFilesAtLevel(0)); + ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); + ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); - // snapshot unneeded now that both range deletions are persisted + // snapshot unneeded now that all range deletions are persisted db_->ReleaseSnapshot(snapshot); // overlaps with nothing, so places at bottom level and skips incrementing // seqnum. ASSERT_OK(GenerateAndAddExternalFile( - options, {101, 125}, {ValueType::kTypeValue, ValueType::kTypeValue}, - file_id++, &true_data)); + options, {151, 175}, {ValueType::kTypeValue, ValueType::kTypeValue}, + {{160, 200}}, file_id++, write_global_seqno, + verify_checksums_before_ingest, &true_data)); ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); ASSERT_EQ(4, NumTableFilesAtLevel(0)); - ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); + ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); + ASSERT_EQ(2, NumTableFilesAtLevel(options.num_levels - 1)); +} + +TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) { + bool change_checksum_called = false; + const auto& change_checksum = [&](void* arg) { + if (!change_checksum_called) { + char* buf = reinterpret_cast(arg); + assert(nullptr != buf); + buf[0] ^= 0x1; + change_checksum_called = true; + } + }; + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->SetCallBack( + "BlockBasedTableBuilder::WriteRawBlock:TamperWithChecksum", + change_checksum); + SyncPoint::GetInstance()->EnableProcessing(); + int file_id = 0; + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); + do { + Options options = CurrentOptions(); + DestroyAndReopen(options); + std::map true_data; + Status s = GenerateAndAddExternalFile( + options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, + write_global_seqno, verify_checksums_before_ingest, &true_data); + if (verify_checksums_before_ingest) { + ASSERT_NOK(s); + } else { + ASSERT_OK(s); + } + change_checksum_called = false; + } while (ChangeOptionsForFileIngestionTest()); +} + +TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) { + SyncPoint::GetInstance()->DisableProcessing(); + int file_id = 0; + EnvOptions env_options; + do { + Options options = CurrentOptions(); + std::string file_path = sst_files_dir_ + ToString(file_id++); + SstFileWriter sst_file_writer(env_options, options); + Status s = sst_file_writer.Open(file_path); + ASSERT_OK(s); + for (int i = 0; i != 100; ++i) { + std::string key = Key(i); + std::string value = Key(i) + ToString(0); + ASSERT_OK(sst_file_writer.Put(key, value)); + } + ASSERT_OK(sst_file_writer.Finish()); + { + // Get file size + uint64_t file_size = 0; + ASSERT_OK(env_->GetFileSize(file_path, &file_size)); + ASSERT_GT(file_size, 8); + std::unique_ptr rwfile; + ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions())); + // Manually corrupt the file + // We deterministically corrupt the first byte because we currently + // cannot choose a random offset. The reason for this limitation is that + // we do not checksum property block at present. + const uint64_t offset = 0; + char scratch[8] = {0}; + Slice buf; + ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch)); + scratch[0] ^= 0xff; // flip one bit + ASSERT_OK(rwfile->Write(offset, buf)); + } + // Ingest file. + IngestExternalFileOptions ifo; + ifo.write_global_seqno = std::get<0>(GetParam()); + ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); + s = db_->IngestExternalFile({file_path}, ifo); + if (ifo.verify_checksums_before_ingest) { + ASSERT_NOK(s); + } else { + ASSERT_OK(s); + } + } while (ChangeOptionsForFileIngestionTest()); } +TEST_P(ExternalSSTFileBasicTest, IngestExternalFileWithCorruptedPropsBlock) { + bool verify_checksums_before_ingest = std::get<1>(GetParam()); + if (!verify_checksums_before_ingest) { + return; + } + uint64_t props_block_offset = 0; + size_t props_block_size = 0; + const auto& get_props_block_offset = [&](void* arg) { + props_block_offset = *reinterpret_cast(arg); + }; + const auto& get_props_block_size = [&](void* arg) { + props_block_size = *reinterpret_cast(arg); + }; + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->SetCallBack( + "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockOffset", + get_props_block_offset); + SyncPoint::GetInstance()->SetCallBack( + "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockSize", + get_props_block_size); + SyncPoint::GetInstance()->EnableProcessing(); + int file_id = 0; + Random64 rand(time(nullptr)); + do { + std::string file_path = sst_files_dir_ + ToString(file_id++); + Options options = CurrentOptions(); + SstFileWriter sst_file_writer(EnvOptions(), options); + Status s = sst_file_writer.Open(file_path); + ASSERT_OK(s); + for (int i = 0; i != 100; ++i) { + std::string key = Key(i); + std::string value = Key(i) + ToString(0); + ASSERT_OK(sst_file_writer.Put(key, value)); + } + ASSERT_OK(sst_file_writer.Finish()); + + { + std::unique_ptr rwfile; + ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions())); + // Manually corrupt the file + ASSERT_GT(props_block_size, 8); + uint64_t offset = + props_block_offset + rand.Next() % (props_block_size - 8); + char scratch[8] = {0}; + Slice buf; + ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch)); + scratch[0] ^= 0xff; // flip one bit + ASSERT_OK(rwfile->Write(offset, buf)); + } + + // Ingest file. + IngestExternalFileOptions ifo; + ifo.write_global_seqno = std::get<0>(GetParam()); + ifo.verify_checksums_before_ingest = true; + s = db_->IngestExternalFile({file_path}, ifo); + ASSERT_NOK(s); + } while (ChangeOptionsForFileIngestionTest()); +} + +INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest, + testing::Values(std::make_tuple(true, true), + std::make_tuple(true, false), + std::make_tuple(false, true), + std::make_tuple(false, false))); + #endif // ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/external_sst_file_ingestion_job.cc b/thirdparty/rocksdb/db/external_sst_file_ingestion_job.cc index 58fa354463..28b481678a 100644 --- a/thirdparty/rocksdb/db/external_sst_file_ingestion_job.cc +++ b/thirdparty/rocksdb/db/external_sst_file_ingestion_job.cc @@ -29,13 +29,14 @@ namespace rocksdb { Status ExternalSstFileIngestionJob::Prepare( - const std::vector& external_files_paths) { + const std::vector& external_files_paths, + uint64_t next_file_number, SuperVersion* sv) { Status status; // Read the information of files we are ingesting for (const std::string& file_path : external_files_paths) { IngestedFileInfo file_to_ingest; - status = GetIngestedFileInfo(file_path, &file_to_ingest); + status = GetIngestedFileInfo(file_path, &file_to_ingest, sv); if (!status.ok()) { return status; } @@ -78,7 +79,7 @@ Status ExternalSstFileIngestionJob::Prepare( } for (IngestedFileInfo& f : files_to_ingest_) { - if (f.num_entries == 0) { + if (f.num_entries == 0 && f.num_range_deletions == 0) { return Status::InvalidArgument("File contain no entries"); } @@ -90,11 +91,12 @@ Status ExternalSstFileIngestionJob::Prepare( // Copy/Move external files into DB for (IngestedFileInfo& f : files_to_ingest_) { - f.fd = FileDescriptor(versions_->NewFileNumber(), 0, f.file_size); + f.fd = FileDescriptor(next_file_number++, 0, f.file_size); const std::string path_outside_db = f.external_file_path; const std::string path_inside_db = - TableFileName(db_options_.db_paths, f.fd.GetNumber(), f.fd.GetPathId()); + TableFileName(cfd_->ioptions()->cf_paths, f.fd.GetNumber(), + f.fd.GetPathId()); if (ingestion_options_.move_files) { status = env_->LinkFile(path_outside_db, path_inside_db); @@ -102,12 +104,16 @@ Status ExternalSstFileIngestionJob::Prepare( // Original file is on a different FS, use copy instead of hard linking status = CopyFile(env_, path_outside_db, path_inside_db, 0, db_options_.use_fsync); + f.copy_file = true; + } else { + f.copy_file = false; } } else { status = CopyFile(env_, path_outside_db, path_inside_db, 0, db_options_.use_fsync); + f.copy_file = true; } - TEST_SYNC_POINT("DBImpl::AddFile:FileCopied"); + TEST_SYNC_POINT("ExternalSstFileIngestionJob::Prepare:FileAdded"); if (!status.ok()) { break; } @@ -117,7 +123,7 @@ Status ExternalSstFileIngestionJob::Prepare( if (!status.ok()) { // We failed, remove all files that we copied into the db for (IngestedFileInfo& f : files_to_ingest_) { - if (f.internal_file_path == "") { + if (f.internal_file_path.empty()) { break; } Status s = env_->DeleteFile(f.internal_file_path); @@ -132,11 +138,15 @@ Status ExternalSstFileIngestionJob::Prepare( return status; } -Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed) { - SuperVersion* super_version = cfd_->GetSuperVersion(); +Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed, + SuperVersion* super_version) { + autovector ranges; + for (const IngestedFileInfo& file_to_ingest : files_to_ingest_) { + ranges.emplace_back(file_to_ingest.smallest_user_key, + file_to_ingest.largest_user_key); + } Status status = - IngestedFilesOverlapWithMemtables(super_version, flush_needed); - + cfd_->RangesOverlapWithMemtables(ranges, super_version, flush_needed); if (status.ok() && *flush_needed && !ingestion_options_.allow_blocking_flush) { status = Status::InvalidArgument("External file requires flush"); @@ -148,15 +158,15 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed) { // nonmem_write_thread_ Status ExternalSstFileIngestionJob::Run() { Status status; + SuperVersion* super_version = cfd_->GetSuperVersion(); #ifndef NDEBUG // We should never run the job with a memtable that is overlapping // with the files we are ingesting bool need_flush = false; - status = NeedsFlush(&need_flush); + status = NeedsFlush(&need_flush, super_version); assert(status.ok() && need_flush == false); #endif - bool consumed_seqno = false; bool force_global_seqno = false; if (ingestion_options_.snapshot_consistency && !db_snapshots_->empty()) { @@ -164,10 +174,9 @@ Status ExternalSstFileIngestionJob::Run() { // if the dont overlap with any ranges since we have snapshots force_global_seqno = true; } - // It is safe to use this instead of LastToBeWrittenSequence since we are + // It is safe to use this instead of LastAllocatedSequence since we are // the only active writer, and hence they are equal const SequenceNumber last_seqno = versions_->LastSequence(); - SuperVersion* super_version = cfd_->GetSuperVersion(); edit_.SetColumnFamily(cfd_->GetID()); // The levels that the files will be ingested into @@ -187,7 +196,7 @@ Status ExternalSstFileIngestionJob::Run() { TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run", &assigned_seqno); if (assigned_seqno == last_seqno + 1) { - consumed_seqno = true; + consumed_seqno_ = true; } if (!status.ok()) { return status; @@ -197,12 +206,6 @@ Status ExternalSstFileIngestionJob::Run() { f.largest_internal_key(), f.assigned_seqno, f.assigned_seqno, false); } - - if (consumed_seqno) { - versions_->SetLastToBeWrittenSequence(last_seqno + 1); - versions_->SetLastSequence(last_seqno + 1); - } - return status; } @@ -212,11 +215,20 @@ void ExternalSstFileIngestionJob::UpdateStats() { uint64_t total_l0_files = 0; uint64_t total_time = env_->NowMicros() - job_start_time_; for (IngestedFileInfo& f : files_to_ingest_) { - InternalStats::CompactionStats stats(1); + InternalStats::CompactionStats stats(CompactionReason::kExternalSstIngestion, 1); stats.micros = total_time; - stats.bytes_written = f.fd.GetFileSize(); + // If actual copy occurred for this file, then we need to count the file + // size as the actual bytes written. If the file was linked, then we ignore + // the bytes written for file metadata. + // TODO (yanqin) maybe account for file metadata bytes for exact accuracy? + if (f.copy_file) { + stats.bytes_written = f.fd.GetFileSize(); + } else { + stats.bytes_moved = f.fd.GetFileSize(); + } stats.num_output_files = 1; - cfd_->internal_stats()->AddCompactionStats(f.picked_level, stats); + cfd_->internal_stats()->AddCompactionStats(f.picked_level, + Env::Priority::USER, stats); cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_INGESTED_ADD_FILE, f.fd.GetFileSize()); total_keys += f.num_entries; @@ -250,6 +262,7 @@ void ExternalSstFileIngestionJob::Cleanup(const Status& status) { f.internal_file_path.c_str(), s.ToString().c_str()); } } + consumed_seqno_ = false; } else if (status.ok() && ingestion_options_.move_files) { // The files were moved and added successfully, remove original file links for (IngestedFileInfo& f : files_to_ingest_) { @@ -266,7 +279,8 @@ void ExternalSstFileIngestionJob::Cleanup(const Status& status) { } Status ExternalSstFileIngestionJob::GetIngestedFileInfo( - const std::string& external_file, IngestedFileInfo* file_to_ingest) { + const std::string& external_file, IngestedFileInfo* file_to_ingest, + SuperVersion* sv) { file_to_ingest->external_file_path = external_file; // Get external file size @@ -288,13 +302,21 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo( external_file)); status = cfd_->ioptions()->table_factory->NewTableReader( - TableReaderOptions(*cfd_->ioptions(), env_options_, - cfd_->internal_comparator()), + TableReaderOptions(*cfd_->ioptions(), + sv->mutable_cf_options.prefix_extractor.get(), + env_options_, cfd_->internal_comparator()), std::move(sst_file_reader), file_to_ingest->file_size, &table_reader); if (!status.ok()) { return status; } + if (ingestion_options_.verify_checksums_before_ingest) { + status = table_reader->VerifyChecksum(); + } + if (!status.ok()) { + return status; + } + // Get the external file properties auto props = table_reader->GetTableProperties(); const auto& uprops = props->user_collected_properties; @@ -316,12 +338,14 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo( // Set the global sequence number file_to_ingest->original_seqno = DecodeFixed64(seqno_iter->second.c_str()); - file_to_ingest->global_seqno_offset = props->properties_offsets.at( + auto offsets_iter = props->properties_offsets.find( ExternalSstFilePropertyNames::kGlobalSeqno); - - if (file_to_ingest->global_seqno_offset == 0) { + if (offsets_iter == props->properties_offsets.end() || + offsets_iter->second == 0) { + file_to_ingest->global_seqno_offset = 0; return Status::Corruption("Was not able to find file global seqno field"); } + file_to_ingest->global_seqno_offset = static_cast(offsets_iter->second); } else if (file_to_ingest->version == 1) { // SST file V1 should not have global seqno field assert(seqno_iter == uprops.end()); @@ -336,6 +360,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo( } // Get number of entries in table file_to_ingest->num_entries = props->num_entries; + file_to_ingest->num_range_deletions = props->num_range_deletions; ParsedInternalKey key; ReadOptions ro; @@ -345,27 +370,57 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo( // We need to disable fill_cache so that we read from the file without // updating the block cache. ro.fill_cache = false; - std::unique_ptr iter(table_reader->NewIterator(ro)); + std::unique_ptr iter(table_reader->NewIterator( + ro, sv->mutable_cf_options.prefix_extractor.get())); + std::unique_ptr range_del_iter( + table_reader->NewRangeTombstoneIterator(ro)); - // Get first (smallest) key from file + // Get first (smallest) and last (largest) key from file. + bool bounds_set = false; iter->SeekToFirst(); - if (!ParseInternalKey(iter->key(), &key)) { - return Status::Corruption("external file have corrupted keys"); - } - if (key.sequence != 0) { - return Status::Corruption("external file have non zero sequence number"); - } - file_to_ingest->smallest_user_key = key.user_key.ToString(); + if (iter->Valid()) { + if (!ParseInternalKey(iter->key(), &key)) { + return Status::Corruption("external file have corrupted keys"); + } + if (key.sequence != 0) { + return Status::Corruption("external file have non zero sequence number"); + } + file_to_ingest->smallest_user_key = key.user_key.ToString(); + + iter->SeekToLast(); + if (!ParseInternalKey(iter->key(), &key)) { + return Status::Corruption("external file have corrupted keys"); + } + if (key.sequence != 0) { + return Status::Corruption("external file have non zero sequence number"); + } + file_to_ingest->largest_user_key = key.user_key.ToString(); - // Get last (largest) key from file - iter->SeekToLast(); - if (!ParseInternalKey(iter->key(), &key)) { - return Status::Corruption("external file have corrupted keys"); + bounds_set = true; } - if (key.sequence != 0) { - return Status::Corruption("external file have non zero sequence number"); + + // We may need to adjust these key bounds, depending on whether any range + // deletion tombstones extend past them. + const Comparator* ucmp = cfd_->internal_comparator().user_comparator(); + if (range_del_iter != nullptr) { + for (range_del_iter->SeekToFirst(); range_del_iter->Valid(); + range_del_iter->Next()) { + if (!ParseInternalKey(range_del_iter->key(), &key)) { + return Status::Corruption("external file have corrupted keys"); + } + RangeTombstone tombstone(key, range_del_iter->value()); + + if (!bounds_set || ucmp->Compare(tombstone.start_key_, + file_to_ingest->smallest_user_key) < 0) { + file_to_ingest->smallest_user_key = tombstone.start_key_.ToString(); + } + if (!bounds_set || ucmp->Compare(tombstone.end_key_, + file_to_ingest->largest_user_key) > 0) { + file_to_ingest->largest_user_key = tombstone.end_key_.ToString(); + } + bounds_set = true; + } } - file_to_ingest->largest_user_key = key.user_key.ToString(); file_to_ingest->cf_id = static_cast(props->column_family_id); @@ -374,46 +429,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo( return status; } -Status ExternalSstFileIngestionJob::IngestedFilesOverlapWithMemtables( - SuperVersion* sv, bool* overlap) { - // Create an InternalIterator over all memtables - Arena arena; - ReadOptions ro; - ro.total_order_seek = true; - MergeIteratorBuilder merge_iter_builder(&cfd_->internal_comparator(), &arena); - merge_iter_builder.AddIterator(sv->mem->NewIterator(ro, &arena)); - sv->imm->AddIterators(ro, &merge_iter_builder); - ScopedArenaIterator memtable_iter(merge_iter_builder.Finish()); - - std::vector memtable_range_del_iters; - auto* active_range_del_iter = sv->mem->NewRangeTombstoneIterator(ro); - if (active_range_del_iter != nullptr) { - memtable_range_del_iters.push_back(active_range_del_iter); - } - sv->imm->AddRangeTombstoneIterators(ro, &memtable_range_del_iters); - std::unique_ptr memtable_range_del_iter(NewMergingIterator( - &cfd_->internal_comparator(), - memtable_range_del_iters.empty() ? nullptr : &memtable_range_del_iters[0], - static_cast(memtable_range_del_iters.size()))); - - Status status; - *overlap = false; - for (IngestedFileInfo& f : files_to_ingest_) { - status = - IngestedFileOverlapWithIteratorRange(&f, memtable_iter.get(), overlap); - if (!status.ok() || *overlap == true) { - break; - } - status = IngestedFileOverlapWithRangeDeletions( - &f, memtable_range_del_iter.get(), overlap); - if (!status.ok() || *overlap == true) { - break; - } - } - - return status; -} - Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile( SuperVersion* sv, bool force_global_seqno, CompactionStyle compaction_style, IngestedFileInfo* file_to_ingest, SequenceNumber* assigned_seqno) { @@ -442,8 +457,9 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile( if (vstorage->NumLevelFiles(lvl) > 0) { bool overlap_with_level = false; - status = IngestedFileOverlapWithLevel(sv, file_to_ingest, lvl, - &overlap_with_level); + status = sv->current->OverlapWithLevelIterator(ro, env_options_, + file_to_ingest->smallest_user_key, file_to_ingest->largest_user_key, + lvl, &overlap_with_level); if (!status.ok()) { return status; } @@ -461,10 +477,13 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile( const SequenceNumber level_largest_seqno = (*max_element(level_files.begin(), level_files.end(), [](FileMetaData* f1, FileMetaData* f2) { - return f1->largest_seqno < f2->largest_seqno; + return f1->fd.largest_seqno < f2->fd.largest_seqno; })) - ->largest_seqno; - if (level_largest_seqno != 0) { + ->fd.largest_seqno; + // should only assign seqno to current level's largest seqno when + // the file fits + if (level_largest_seqno != 0 && + IngestedFileFitInLevel(file_to_ingest, lvl)) { *assigned_seqno = level_largest_seqno; } else { continue; @@ -505,7 +524,7 @@ Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile( // at some upper level for (int lvl = 0; lvl < cfd_->NumberLevels() - 1; lvl++) { for (auto file : vstorage->LevelFiles(lvl)) { - if (file->smallest_seqno == 0) { + if (file->fd.smallest_seqno == 0) { return Status::InvalidArgument( "Can't ingest_behind file as despite allow_ingest_behind=true " "there are files with 0 seqno in database at upper levels!"); @@ -530,76 +549,26 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile( "field"); } - std::unique_ptr rwfile; - Status status = env_->NewRandomRWFile(file_to_ingest->internal_file_path, - &rwfile, env_options_); - if (!status.ok()) { - return status; - } - - // Write the new seqno in the global sequence number field in the file - std::string seqno_val; - PutFixed64(&seqno_val, seqno); - status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val); - if (status.ok()) { - file_to_ingest->assigned_seqno = seqno; - } - return status; -} - -Status ExternalSstFileIngestionJob::IngestedFileOverlapWithIteratorRange( - const IngestedFileInfo* file_to_ingest, InternalIterator* iter, - bool* overlap) { - auto* vstorage = cfd_->current()->storage_info(); - auto* ucmp = vstorage->InternalComparator()->user_comparator(); - InternalKey range_start(file_to_ingest->smallest_user_key, kMaxSequenceNumber, - kValueTypeForSeek); - iter->Seek(range_start.Encode()); - if (!iter->status().ok()) { - return iter->status(); - } - - *overlap = false; - if (iter->Valid()) { - ParsedInternalKey seek_result; - if (!ParseInternalKey(iter->key(), &seek_result)) { - return Status::Corruption("DB have corrupted keys"); - } - - if (ucmp->Compare(seek_result.user_key, file_to_ingest->largest_user_key) <= - 0) { - *overlap = true; - } - } - - return iter->status(); -} - -Status ExternalSstFileIngestionJob::IngestedFileOverlapWithRangeDeletions( - const IngestedFileInfo* file_to_ingest, InternalIterator* range_del_iter, - bool* overlap) { - auto* vstorage = cfd_->current()->storage_info(); - auto* ucmp = vstorage->InternalComparator()->user_comparator(); - - *overlap = false; - if (range_del_iter != nullptr) { - for (range_del_iter->SeekToFirst(); range_del_iter->Valid(); - range_del_iter->Next()) { - ParsedInternalKey parsed_key; - if (!ParseInternalKey(range_del_iter->key(), &parsed_key)) { - return Status::Corruption("corrupted range deletion key: " + - range_del_iter->key().ToString()); - } - RangeTombstone range_del(parsed_key, range_del_iter->value()); - if (ucmp->Compare(range_del.start_key_, - file_to_ingest->largest_user_key) <= 0 && - ucmp->Compare(file_to_ingest->smallest_user_key, - range_del.end_key_) <= 0) { - *overlap = true; - break; + if (ingestion_options_.write_global_seqno) { + // Determine if we can write global_seqno to a given offset of file. + // If the file system does not support random write, then we should not. + // Otherwise we should. + std::unique_ptr rwfile; + Status status = env_->NewRandomRWFile(file_to_ingest->internal_file_path, + &rwfile, env_options_); + if (status.ok()) { + std::string seqno_val; + PutFixed64(&seqno_val, seqno); + status = rwfile->Write(file_to_ingest->global_seqno_offset, seqno_val); + if (!status.ok()) { + return status; } + } else if (!status.IsNotSupported()) { + return status; } } + + file_to_ingest->assigned_seqno = seqno; return Status::OK(); } @@ -631,35 +600,6 @@ bool ExternalSstFileIngestionJob::IngestedFileFitInLevel( return true; } -Status ExternalSstFileIngestionJob::IngestedFileOverlapWithLevel( - SuperVersion* sv, IngestedFileInfo* file_to_ingest, int lvl, - bool* overlap_with_level) { - Arena arena; - ReadOptions ro; - ro.total_order_seek = true; - MergeIteratorBuilder merge_iter_builder(&cfd_->internal_comparator(), - &arena); - sv->current->AddIteratorsForLevel(ro, env_options_, &merge_iter_builder, lvl, - nullptr /* range_del_agg */); - ScopedArenaIterator level_iter(merge_iter_builder.Finish()); - - std::vector level_range_del_iters; - sv->current->AddRangeDelIteratorsForLevel(ro, env_options_, lvl, - &level_range_del_iters); - std::unique_ptr level_range_del_iter(NewMergingIterator( - &cfd_->internal_comparator(), - level_range_del_iters.empty() ? nullptr : &level_range_del_iters[0], - static_cast(level_range_del_iters.size()))); - - Status status = IngestedFileOverlapWithIteratorRange( - file_to_ingest, level_iter.get(), overlap_with_level); - if (status.ok() && *overlap_with_level == false) { - status = IngestedFileOverlapWithRangeDeletions( - file_to_ingest, level_range_del_iter.get(), overlap_with_level); - } - return status; -} - } // namespace rocksdb #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/external_sst_file_ingestion_job.h b/thirdparty/rocksdb/db/external_sst_file_ingestion_job.h index 2d0fadeed7..baa8e9f0f6 100644 --- a/thirdparty/rocksdb/db/external_sst_file_ingestion_job.h +++ b/thirdparty/rocksdb/db/external_sst_file_ingestion_job.h @@ -36,6 +36,8 @@ struct IngestedFileInfo { uint64_t file_size; // total number of keys in external file uint64_t num_entries; + // total number of range deletions in external file + uint64_t num_range_deletions; // Id of column family this file shoule be ingested into uint32_t cf_id; // TableProperties read from external file @@ -46,11 +48,18 @@ struct IngestedFileInfo { // FileDescriptor for the file inside the DB FileDescriptor fd; // file path that we picked for file inside the DB - std::string internal_file_path = ""; + std::string internal_file_path; // Global sequence number that we picked for the file inside the DB SequenceNumber assigned_seqno = 0; // Level inside the DB we picked for the external file. int picked_level = 0; + // Whether to copy or link the external sst file. copy_file will be set to + // false if ingestion_options.move_files is true and underlying FS + // supports link operation. Need to provide a default value to make the + // undefined-behavior sanity check of llvm happy. Since + // ingestion_options.move_files is false by default, thus copy_file is true + // by default. + bool copy_file = true; InternalKey smallest_internal_key() const { return InternalKey(smallest_user_key, assigned_seqno, @@ -76,16 +85,22 @@ class ExternalSstFileIngestionJob { env_options_(env_options), db_snapshots_(db_snapshots), ingestion_options_(ingestion_options), - job_start_time_(env_->NowMicros()) {} + job_start_time_(env_->NowMicros()), + consumed_seqno_(false) {} // Prepare the job by copying external files into the DB. - Status Prepare(const std::vector& external_files_paths); + Status Prepare(const std::vector& external_files_paths, + uint64_t next_file_number, SuperVersion* sv); // Check if we need to flush the memtable before running the ingestion job // This will be true if the files we are ingesting are overlapping with any // key range in the memtable. - // REQUIRES: Mutex held - Status NeedsFlush(bool* flush_needed); + // + // @param super_version A referenced SuperVersion that will be held for the + // duration of this function. + // + // Thread-safe + Status NeedsFlush(bool* flush_needed, SuperVersion* super_version); // Will execute the ingestion job and prepare edit() to be applied. // REQUIRES: Mutex held @@ -104,15 +119,15 @@ class ExternalSstFileIngestionJob { return files_to_ingest_; } + // Whether to increment VersionSet's seqno after this job runs + bool ShouldIncrementLastSequence() const { return consumed_seqno_; } + private: // Open the external file and populate `file_to_ingest` with all the // external information we need to ingest this file. Status GetIngestedFileInfo(const std::string& external_file, - IngestedFileInfo* file_to_ingest); - - // Check if the files we are ingesting overlap with any memtable. - // REQUIRES: Mutex held - Status IngestedFilesOverlapWithMemtables(SuperVersion* sv, bool* overlap); + IngestedFileInfo* file_to_ingest, + SuperVersion* sv); // Assign `file_to_ingest` the appropriate sequence number and the lowest // possible level that it can be ingested to according to compaction_style. @@ -133,24 +148,6 @@ class ExternalSstFileIngestionJob { Status AssignGlobalSeqnoForIngestedFile(IngestedFileInfo* file_to_ingest, SequenceNumber seqno); - // Check if `file_to_ingest` key range overlap with the range `iter` represent - // REQUIRES: Mutex held - Status IngestedFileOverlapWithIteratorRange( - const IngestedFileInfo* file_to_ingest, InternalIterator* iter, - bool* overlap); - - // Check if `file_to_ingest` key range overlaps with any range deletions - // specified by `iter`. - // REQUIRES: Mutex held - Status IngestedFileOverlapWithRangeDeletions( - const IngestedFileInfo* file_to_ingest, InternalIterator* range_del_iter, - bool* overlap); - - // Check if `file_to_ingest` key range overlap with level - // REQUIRES: Mutex held - Status IngestedFileOverlapWithLevel(SuperVersion* sv, - IngestedFileInfo* file_to_ingest, int lvl, bool* overlap_with_level); - // Check if `file_to_ingest` can fit in level `level` // REQUIRES: Mutex held bool IngestedFileFitInLevel(const IngestedFileInfo* file_to_ingest, @@ -166,6 +163,7 @@ class ExternalSstFileIngestionJob { const IngestExternalFileOptions& ingestion_options_; VersionEdit edit_; uint64_t job_start_time_; + bool consumed_seqno_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/external_sst_file_test.cc b/thirdparty/rocksdb/db/external_sst_file_test.cc index 4a4e82e792..cbbb2fa262 100644 --- a/thirdparty/rocksdb/db/external_sst_file_test.cc +++ b/thirdparty/rocksdb/db/external_sst_file_test.cc @@ -10,11 +10,15 @@ #include "port/port.h" #include "port/stack_trace.h" #include "rocksdb/sst_file_writer.h" +#include "util/fault_injection_test_env.h" +#include "util/filename.h" #include "util/testutil.h" namespace rocksdb { -class ExternalSSTFileTest : public DBTestBase { +class ExternalSSTFileTest + : public DBTestBase, + public ::testing::WithParamInterface> { public: ExternalSSTFileTest() : DBTestBase("/external_sst_file_test") { sst_files_dir_ = dbname_ + "/sst_files/"; @@ -26,18 +30,15 @@ class ExternalSSTFileTest : public DBTestBase { env_->CreateDir(sst_files_dir_); } - Status GenerateAndAddExternalFile( - const Options options, - std::vector> data, int file_id = -1, - bool allow_global_seqno = false, bool sort_data = false, - std::map* true_data = nullptr, - ColumnFamilyHandle* cfh = nullptr) { + Status GenerateOneExternalFile( + const Options& options, ColumnFamilyHandle* cfh, + std::vector>& data, int file_id, + bool sort_data, std::string* external_file_path, + std::map* true_data) { // Generate a file id if not provided - if (file_id == -1) { - file_id = last_file_id_ + 1; - last_file_id_++; + if (-1 == file_id) { + file_id = (++last_file_id_); } - // Sort data if asked to do so if (sort_data) { std::sort(data.begin(), data.end(), @@ -55,12 +56,11 @@ class ExternalSSTFileTest : public DBTestBase { } std::string file_path = sst_files_dir_ + ToString(file_id); SstFileWriter sst_file_writer(EnvOptions(), options, cfh); - Status s = sst_file_writer.Open(file_path); if (!s.ok()) { return s; } - for (auto& entry : data) { + for (const auto& entry : data) { s = sst_file_writer.Put(entry.first, entry.second); if (!s.ok()) { sst_file_writer.Finish(); @@ -68,29 +68,22 @@ class ExternalSSTFileTest : public DBTestBase { } } s = sst_file_writer.Finish(); - - if (s.ok()) { - IngestExternalFileOptions ifo; - ifo.allow_global_seqno = allow_global_seqno; - if (cfh) { - s = db_->IngestExternalFile(cfh, {file_path}, ifo); - } else { - s = db_->IngestExternalFile({file_path}, ifo); - } + if (s.ok() && external_file_path != nullptr) { + *external_file_path = file_path; } - - if (s.ok() && true_data) { - for (auto& entry : data) { - (*true_data)[entry.first] = entry.second; + if (s.ok() && nullptr != true_data) { + for (const auto& entry : data) { + true_data->insert({entry.first, entry.second}); } } - return s; } - Status GenerateAndAddExternalFileIngestBehind( - const Options options, const IngestExternalFileOptions ifo, + Status GenerateAndAddExternalFile( + const Options options, std::vector> data, int file_id = -1, + bool allow_global_seqno = false, bool write_global_seqno = false, + bool verify_checksums_before_ingest = true, bool ingest_behind = false, bool sort_data = false, std::map* true_data = nullptr, ColumnFamilyHandle* cfh = nullptr) { @@ -132,6 +125,11 @@ class ExternalSSTFileTest : public DBTestBase { s = sst_file_writer.Finish(); if (s.ok()) { + IngestExternalFileOptions ifo; + ifo.allow_global_seqno = allow_global_seqno; + ifo.write_global_seqno = allow_global_seqno ? write_global_seqno : false; + ifo.verify_checksums_before_ingest = verify_checksums_before_ingest; + ifo.ingest_behind = ingest_behind; if (cfh) { s = db_->IngestExternalFile(cfh, {file_path}, ifo); } else { @@ -148,11 +146,47 @@ class ExternalSSTFileTest : public DBTestBase { return s; } + Status GenerateAndAddExternalFiles( + const Options& options, + const std::vector& column_families, + const std::vector& ifos, + std::vector>>& data, + int file_id, bool sort_data, + std::vector>& true_data) { + if (-1 == file_id) { + file_id = (++last_file_id_); + } + // Generate external SST files, one for each column family + size_t num_cfs = column_families.size(); + assert(ifos.size() == num_cfs); + assert(data.size() == num_cfs); + Status s; + std::vector args(num_cfs); + for (size_t i = 0; i != num_cfs; ++i) { + std::string external_file_path; + s = GenerateOneExternalFile( + options, column_families[i], data[i], file_id, sort_data, + &external_file_path, + true_data.size() == num_cfs ? &true_data[i] : nullptr); + if (!s.ok()) { + return s; + } + ++file_id; + args[i].column_family = column_families[i]; + args[i].external_files.push_back(external_file_path); + args[i].options = ifos[i]; + } + s = db_->IngestExternalFiles(args); + return s; + } Status GenerateAndAddExternalFile( const Options options, std::vector> data, - int file_id = -1, bool allow_global_seqno = false, bool sort_data = false, + int file_id = -1, bool allow_global_seqno = false, + bool write_global_seqno = false, + bool verify_checksums_before_ingest = true, bool ingest_behind = false, + bool sort_data = false, std::map* true_data = nullptr, ColumnFamilyHandle* cfh = nullptr) { std::vector> file_data; @@ -160,13 +194,16 @@ class ExternalSSTFileTest : public DBTestBase { file_data.emplace_back(Key(entry.first), entry.second); } return GenerateAndAddExternalFile(options, file_data, file_id, - allow_global_seqno, sort_data, true_data, - cfh); + allow_global_seqno, write_global_seqno, + verify_checksums_before_ingest, + ingest_behind, sort_data, true_data, cfh); } Status GenerateAndAddExternalFile( const Options options, std::vector keys, int file_id = -1, - bool allow_global_seqno = false, bool sort_data = false, + bool allow_global_seqno = false, bool write_global_seqno = false, + bool verify_checksums_before_ingest = true, bool ingest_behind = false, + bool sort_data = false, std::map* true_data = nullptr, ColumnFamilyHandle* cfh = nullptr) { std::vector> file_data; @@ -174,22 +211,25 @@ class ExternalSSTFileTest : public DBTestBase { file_data.emplace_back(Key(k), Key(k) + ToString(file_id)); } return GenerateAndAddExternalFile(options, file_data, file_id, - allow_global_seqno, sort_data, true_data, - cfh); + allow_global_seqno, write_global_seqno, + verify_checksums_before_ingest, + ingest_behind, sort_data, true_data, cfh); } Status DeprecatedAddFile(const std::vector& files, bool move_files = false, - bool skip_snapshot_check = false) { + bool skip_snapshot_check = false, + bool skip_write_global_seqno = false) { IngestExternalFileOptions opts; opts.move_files = move_files; opts.snapshot_consistency = !skip_snapshot_check; opts.allow_global_seqno = false; opts.allow_blocking_flush = false; + opts.write_global_seqno = !skip_write_global_seqno; return db_->IngestExternalFile(files, opts); } - ~ExternalSSTFileTest() { test::DestroyDir(env_, sst_files_dir_); } + ~ExternalSSTFileTest() override { test::DestroyDir(env_, sst_files_dir_); } protected: int last_file_id_ = 0; @@ -222,6 +262,9 @@ TEST_F(ExternalSSTFileTest, Basic) { ASSERT_EQ(file1_info.num_entries, 100); ASSERT_EQ(file1_info.smallest_key, Key(0)); ASSERT_EQ(file1_info.largest_key, Key(99)); + ASSERT_EQ(file1_info.num_range_del_entries, 0); + ASSERT_EQ(file1_info.smallest_range_del_key, ""); + ASSERT_EQ(file1_info.largest_range_del_key, ""); // sst_file_writer already finished, cannot add this value s = sst_file_writer.Put(Key(100), "bad_val"); ASSERT_FALSE(s.ok()) << s.ToString(); @@ -290,6 +333,58 @@ TEST_F(ExternalSSTFileTest, Basic) { ASSERT_EQ(file5_info.smallest_key, Key(400)); ASSERT_EQ(file5_info.largest_key, Key(499)); + // file6.sst (delete 400 => 500) + std::string file6 = sst_files_dir_ + "file6.sst"; + ASSERT_OK(sst_file_writer.Open(file6)); + sst_file_writer.DeleteRange(Key(400), Key(500)); + ExternalSstFileInfo file6_info; + s = sst_file_writer.Finish(&file6_info); + ASSERT_TRUE(s.ok()) << s.ToString(); + ASSERT_EQ(file6_info.file_path, file6); + ASSERT_EQ(file6_info.num_entries, 0); + ASSERT_EQ(file6_info.smallest_key, ""); + ASSERT_EQ(file6_info.largest_key, ""); + ASSERT_EQ(file6_info.num_range_del_entries, 1); + ASSERT_EQ(file6_info.smallest_range_del_key, Key(400)); + ASSERT_EQ(file6_info.largest_range_del_key, Key(500)); + + // file7.sst (delete 500 => 570, put 520 => 599 divisible by 2) + std::string file7 = sst_files_dir_ + "file7.sst"; + ASSERT_OK(sst_file_writer.Open(file7)); + sst_file_writer.DeleteRange(Key(500), Key(550)); + for (int k = 520; k < 560; k += 2) { + ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); + } + sst_file_writer.DeleteRange(Key(525), Key(575)); + for (int k = 560; k < 600; k += 2) { + ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); + } + ExternalSstFileInfo file7_info; + s = sst_file_writer.Finish(&file7_info); + ASSERT_TRUE(s.ok()) << s.ToString(); + ASSERT_EQ(file7_info.file_path, file7); + ASSERT_EQ(file7_info.num_entries, 40); + ASSERT_EQ(file7_info.smallest_key, Key(520)); + ASSERT_EQ(file7_info.largest_key, Key(598)); + ASSERT_EQ(file7_info.num_range_del_entries, 2); + ASSERT_EQ(file7_info.smallest_range_del_key, Key(500)); + ASSERT_EQ(file7_info.largest_range_del_key, Key(575)); + + // file8.sst (delete 600 => 700) + std::string file8 = sst_files_dir_ + "file8.sst"; + ASSERT_OK(sst_file_writer.Open(file8)); + sst_file_writer.DeleteRange(Key(600), Key(700)); + ExternalSstFileInfo file8_info; + s = sst_file_writer.Finish(&file8_info); + ASSERT_TRUE(s.ok()) << s.ToString(); + ASSERT_EQ(file8_info.file_path, file8); + ASSERT_EQ(file8_info.num_entries, 0); + ASSERT_EQ(file8_info.smallest_key, ""); + ASSERT_EQ(file8_info.largest_key, ""); + ASSERT_EQ(file8_info.num_range_del_entries, 1); + ASSERT_EQ(file8_info.smallest_range_del_key, Key(600)); + ASSERT_EQ(file8_info.largest_range_del_key, Key(700)); + // Cannot create an empty sst file std::string file_empty = sst_files_dir_ + "file_empty.sst"; ExternalSstFileInfo file_empty_info; @@ -336,6 +431,16 @@ TEST_F(ExternalSSTFileTest, Basic) { // Key range of file5 (400 => 499) dont overlap with any keys in DB ASSERT_OK(DeprecatedAddFile({file5})); + // This file has overlapping values with the existing data + s = DeprecatedAddFile({file6}); + ASSERT_FALSE(s.ok()) << s.ToString(); + + // Key range of file7 (500 => 598) dont overlap with any keys in DB + ASSERT_OK(DeprecatedAddFile({file7})); + + // Key range of file7 (600 => 700) dont overlap with any keys in DB + ASSERT_OK(DeprecatedAddFile({file8})); + // Make sure values are correct before and after flush/compaction for (int i = 0; i < 2; i++) { for (int k = 0; k < 200; k++) { @@ -349,6 +454,13 @@ TEST_F(ExternalSSTFileTest, Basic) { std::string value = Key(k) + "_val"; ASSERT_EQ(Get(Key(k)), value); } + for (int k = 500; k < 600; k++) { + std::string value = Key(k) + "_val"; + if (k < 520 || k % 2 == 1) { + value = "NOT_FOUND"; + } + ASSERT_EQ(Get(Key(k)), value); + } ASSERT_OK(Flush()); ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); } @@ -377,8 +489,10 @@ TEST_F(ExternalSSTFileTest, Basic) { ASSERT_EQ(Get(Key(k)), value); } DestroyAndRecreateExternalSSTFilesDir(); - } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction)); + } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction | + kRangeDelSkipConfigs)); } + class SstFileWriterCollector : public TablePropertiesCollector { public: explicit SstFileWriterCollector(const std::string prefix) : prefix_(prefix) { @@ -388,20 +502,22 @@ class SstFileWriterCollector : public TablePropertiesCollector { const char* Name() const override { return name_.c_str(); } Status Finish(UserCollectedProperties* properties) override { + std::string count = std::to_string(count_); *properties = UserCollectedProperties{ {prefix_ + "_SstFileWriterCollector", "YES"}, - {prefix_ + "_Count", std::to_string(count_)}, + {prefix_ + "_Count", count}, }; return Status::OK(); } - Status AddUserKey(const Slice& user_key, const Slice& value, EntryType type, - SequenceNumber seq, uint64_t file_size) override { + 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 { + UserCollectedProperties GetReadableProperties() const override { return UserCollectedProperties{}; } @@ -415,8 +531,8 @@ class SstFileWriterCollectorFactory : public TablePropertiesCollectorFactory { public: explicit SstFileWriterCollectorFactory(std::string prefix) : prefix_(prefix), num_created_(0) {} - virtual TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context context) override { + TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollectorFactory::Context /*context*/) override { num_created_++; return new SstFileWriterCollector(prefix_); } @@ -516,17 +632,57 @@ TEST_F(ExternalSSTFileTest, AddList) { ASSERT_EQ(file5_info.smallest_key, Key(200)); ASSERT_EQ(file5_info.largest_key, Key(299)); + // file6.sst (delete 0 => 100) + std::string file6 = sst_files_dir_ + "file6.sst"; + ASSERT_OK(sst_file_writer.Open(file6)); + ASSERT_OK(sst_file_writer.DeleteRange(Key(0), Key(75))); + ASSERT_OK(sst_file_writer.DeleteRange(Key(25), Key(100))); + ExternalSstFileInfo file6_info; + s = sst_file_writer.Finish(&file6_info); + ASSERT_TRUE(s.ok()) << s.ToString(); + ASSERT_EQ(file6_info.file_path, file6); + ASSERT_EQ(file6_info.num_entries, 0); + ASSERT_EQ(file6_info.smallest_key, ""); + ASSERT_EQ(file6_info.largest_key, ""); + ASSERT_EQ(file6_info.num_range_del_entries, 2); + ASSERT_EQ(file6_info.smallest_range_del_key, Key(0)); + ASSERT_EQ(file6_info.largest_range_del_key, Key(100)); + + // file7.sst (delete 100 => 200) + std::string file7 = sst_files_dir_ + "file7.sst"; + ASSERT_OK(sst_file_writer.Open(file7)); + ASSERT_OK(sst_file_writer.DeleteRange(Key(100), Key(200))); + ExternalSstFileInfo file7_info; + s = sst_file_writer.Finish(&file7_info); + ASSERT_TRUE(s.ok()) << s.ToString(); + ASSERT_EQ(file7_info.file_path, file7); + ASSERT_EQ(file7_info.num_entries, 0); + ASSERT_EQ(file7_info.smallest_key, ""); + ASSERT_EQ(file7_info.largest_key, ""); + ASSERT_EQ(file7_info.num_range_del_entries, 1); + ASSERT_EQ(file7_info.smallest_range_del_key, Key(100)); + ASSERT_EQ(file7_info.largest_range_del_key, Key(200)); + // list 1 has internal key range conflict std::vector file_list0({file1, file2}); std::vector file_list1({file3, file2, file1}); std::vector file_list2({file5}); std::vector file_list3({file3, file4}); + std::vector file_list4({file5, file7}); + std::vector file_list5({file6, file7}); DestroyAndReopen(options); - // This list of files have key ranges are overlapping with each other + // These lists of files have key ranges that overlap with each other s = DeprecatedAddFile(file_list1); ASSERT_FALSE(s.ok()) << s.ToString(); + // Both of the following overlap on the end key of a range deletion + // tombstone. This is a limitation because these tombstones have exclusive + // end keys that should not count as overlapping with other keys. + s = DeprecatedAddFile(file_list4); + ASSERT_FALSE(s.ok()) << s.ToString(); + s = DeprecatedAddFile(file_list5); + ASSERT_FALSE(s.ok()) << s.ToString(); // Add files using file path list s = DeprecatedAddFile(file_list0); @@ -617,7 +773,8 @@ TEST_F(ExternalSSTFileTest, AddList) { ASSERT_EQ(Get(Key(k)), value); } DestroyAndRecreateExternalSSTFilesDir(); - } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction)); + } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction | + kRangeDelSkipConfigs)); } TEST_F(ExternalSSTFileTest, AddListAtomicity) { @@ -687,7 +844,7 @@ TEST_F(ExternalSSTFileTest, PurgeObsoleteFilesBug) { DestroyAndReopen(options); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::AddFile:FileCopied", [&](void* arg) { + "ExternalSstFileIngestionJob::Prepare:FileAdded", [&](void* /* arg */) { ASSERT_OK(Put("aaa", "bbb")); ASSERT_OK(Flush()); ASSERT_OK(Put("aaa", "xxx")); @@ -895,11 +1052,11 @@ TEST_F(ExternalSSTFileTest, MultiThreaded) { TEST_F(ExternalSSTFileTest, OverlappingRanges) { Random rnd(301); - int picked_level = 0; + SequenceNumber assigned_seqno = 0; rocksdb::SyncPoint::GetInstance()->SetCallBack( - "ExternalSstFileIngestionJob::Run", [&picked_level](void* arg) { + "ExternalSstFileIngestionJob::Run", [&assigned_seqno](void* arg) { ASSERT_TRUE(arg != nullptr); - picked_level = *(static_cast(arg)); + assigned_seqno = *(static_cast(arg)); }); bool need_flush = false; rocksdb::SyncPoint::GetInstance()->SetCallBack( @@ -923,7 +1080,7 @@ TEST_F(ExternalSSTFileTest, OverlappingRanges) { printf("Option config = %d\n", option_config_); std::vector> key_ranges; - for (int i = 0; i < 500; i++) { + for (int i = 0; i < 100; i++) { int range_start = rnd.Uniform(20000); int keys_per_range = 10 + rnd.Uniform(41); @@ -969,7 +1126,8 @@ TEST_F(ExternalSSTFileTest, OverlappingRanges) { s = DeprecatedAddFile({file_name}); auto it = true_data.lower_bound(Key(range_start)); if (option_config_ != kUniversalCompaction && - option_config_ != kUniversalCompactionMultiLevel) { + option_config_ != kUniversalCompactionMultiLevel && + option_config_ != kUniversalSubcompactions) { if (it != true_data.end() && it->first <= Key(range_end)) { // This range overlap with data already exist in DB ASSERT_NOK(s); @@ -980,7 +1138,7 @@ TEST_F(ExternalSSTFileTest, OverlappingRanges) { } } else { if ((it != true_data.end() && it->first <= Key(range_end)) || - need_flush || picked_level > 0 || overlap_with_db) { + need_flush || assigned_seqno > 0 || overlap_with_db) { // This range overlap with data already exist in DB ASSERT_NOK(s); failed_add_file++; @@ -1023,7 +1181,7 @@ TEST_F(ExternalSSTFileTest, OverlappingRanges) { } while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction)); } -TEST_F(ExternalSSTFileTest, PickedLevel) { +TEST_P(ExternalSSTFileTest, PickedLevel) { Options options = CurrentOptions(); options.disable_auto_compactions = false; options.level0_file_num_compaction_trigger = 4; @@ -1033,13 +1191,13 @@ TEST_F(ExternalSSTFileTest, PickedLevel) { std::map true_data; // File 0 will go to last level (L3) - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, false, true, + false, false, &true_data)); EXPECT_EQ(FilesPerLevel(), "0,0,0,1"); // File 1 will go to level L2 (since it overlap with file 0 in L3) - ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, false, true, + false, false, &true_data)); EXPECT_EQ(FilesPerLevel(), "0,0,1,1"); rocksdb::SyncPoint::GetInstance()->LoadDependency({ @@ -1068,13 +1226,13 @@ TEST_F(ExternalSSTFileTest, PickedLevel) { // This file overlaps with file 0 (L3), file 1 (L2) and the // output of compaction going to L1 - ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, -1, false, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, -1, false, false, true, + false, false, &true_data)); EXPECT_EQ(FilesPerLevel(), "5,0,1,1"); // This file does not overlap with any file or with the running compaction ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false, - &true_data)); + false, false, false, &true_data)); EXPECT_EQ(FilesPerLevel(), "5,0,1,2"); // Hold compaction from finishing @@ -1126,7 +1284,7 @@ TEST_F(ExternalSSTFileTest, PickedLevelBug) { std::atomic bg_compact_started(false); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BackgroundCompaction:Start", - [&](void* arg) { bg_compact_started.store(true); }); + [&](void* /*arg*/) { bg_compact_started.store(true); }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); @@ -1175,6 +1333,40 @@ TEST_F(ExternalSSTFileTest, PickedLevelBug) { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); } +TEST_F(ExternalSSTFileTest, IngestNonExistingFile) { + Options options = CurrentOptions(); + DestroyAndReopen(options); + + Status s = db_->IngestExternalFile({"non_existing_file"}, + IngestExternalFileOptions()); + ASSERT_NOK(s); + + // Verify file deletion is not impacted (verify a bug fix) + ASSERT_OK(Put(Key(1), Key(1))); + ASSERT_OK(Put(Key(9), Key(9))); + ASSERT_OK(Flush()); + + ASSERT_OK(Put(Key(1), Key(1))); + ASSERT_OK(Put(Key(9), Key(9))); + ASSERT_OK(Flush()); + + ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)); + ASSERT_OK(dbfull()->TEST_WaitForCompact(true)); + + // After full compaction, there should be only 1 file. + std::vector files; + env_->GetChildren(dbname_, &files); + int num_sst_files = 0; + for (auto& f : files) { + uint64_t number; + FileType type; + if (ParseFileName(f, &number, &type) && type == kTableFile) { + num_sst_files++; + } + } + ASSERT_EQ(1, num_sst_files); +} + TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) { Options options = CurrentOptions(); options.disable_auto_compactions = false; @@ -1192,8 +1384,9 @@ TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) { ASSERT_OK(GenerateAndAddExternalFile(options, file_keys, range_id)); }; + const int num_of_ranges = 1000; std::vector threads; - while (range_id < 5000) { + while (range_id < num_of_ranges) { int range_start = range_id * 10; int range_end = range_start + 10; @@ -1218,7 +1411,7 @@ TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) { range_id++; } - for (int rid = 0; rid < 5000; rid++) { + for (int rid = 0; rid < num_of_ranges; rid++) { int range_start = rid * 10; int range_end = range_start + 10; @@ -1270,12 +1463,12 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) { // This file overlaps with the output of the compaction (going to L3) // so the file will be added to L0 since L3 is the base level ASSERT_OK(GenerateAndAddExternalFile(options, {31, 32, 33, 34}, -1, false, - false, &true_data)); + false, true, false, false, &true_data)); EXPECT_EQ(FilesPerLevel(), "5"); // This file does not overlap with the current running compactiong ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false, - &true_data)); + true, false, false, &true_data)); EXPECT_EQ(FilesPerLevel(), "5,0,0,1"); // Hold compaction from finishing @@ -1290,25 +1483,25 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) { Reopen(options); ASSERT_OK(GenerateAndAddExternalFile(options, {1, 15, 19}, -1, false, false, - &true_data)); + true, false, false, &true_data)); ASSERT_EQ(FilesPerLevel(), "1,0,0,3"); ASSERT_OK(GenerateAndAddExternalFile(options, {1000, 1001, 1002}, -1, false, - false, &true_data)); + false, true, false, false, &true_data)); ASSERT_EQ(FilesPerLevel(), "1,0,0,4"); ASSERT_OK(GenerateAndAddExternalFile(options, {500, 600, 700}, -1, false, - false, &true_data)); + false, true, false, false, &true_data)); ASSERT_EQ(FilesPerLevel(), "1,0,0,5"); // File 5 overlaps with file 2 (L3 / base level) - ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, false, true, + false, false, &true_data)); ASSERT_EQ(FilesPerLevel(), "2,0,0,5"); // File 6 overlaps with file 2 (L3 / base level) and file 5 (L0) - ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, false, true, + false, false, &true_data)); ASSERT_EQ(FilesPerLevel(), "3,0,0,5"); // Verify data in files @@ -1327,7 +1520,7 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) { // File 7 overlaps with file 4 (L3) ASSERT_OK(GenerateAndAddExternalFile(options, {650, 651, 652}, -1, false, - false, &true_data)); + false, true, false, false, &true_data)); ASSERT_EQ(FilesPerLevel(), "5,0,0,5"); VerifyDBFromMap(true_data, &kcnt, false); @@ -1407,12 +1600,16 @@ TEST_F(ExternalSSTFileTest, AddFileTrivialMoveBug) { ASSERT_OK(GenerateAndAddExternalFile(options, {22, 23}, 6)); // L2 rocksdb::SyncPoint::GetInstance()->SetCallBack( - "CompactionJob::Run():Start", [&](void* arg) { + "CompactionJob::Run():Start", [&](void* /*arg*/) { // fit in L3 but will overlap with compaction so will be added // to L2 but a compaction will trivially move it to L3 // and break LSM consistency - ASSERT_OK(dbfull()->SetOptions({{"max_bytes_for_level_base", "1"}})); - ASSERT_OK(GenerateAndAddExternalFile(options, {15, 16}, 7)); + static std::atomic called = {false}; + if (!called) { + called = true; + ASSERT_OK(dbfull()->SetOptions({{"max_bytes_for_level_base", "1"}})); + ASSERT_OK(GenerateAndAddExternalFile(options, {15, 16}, 7)); + } }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); @@ -1457,19 +1654,21 @@ TEST_F(ExternalSSTFileTest, SstFileWriterNonSharedKeys) { ASSERT_OK(DeprecatedAddFile({file_path})); } -TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) { +TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) { Options options = CurrentOptions(); options.IncreaseParallelism(20); options.level0_slowdown_writes_trigger = 256; options.level0_stop_writes_trigger = 256; + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); for (int iter = 0; iter < 2; iter++) { bool write_to_memtable = (iter == 0); DestroyAndReopen(options); Random rnd(301); std::map true_data; - for (int i = 0; i < 2000; i++) { + for (int i = 0; i < 500; i++) { std::vector> random_data; for (int j = 0; j < 100; j++) { std::string k; @@ -1486,8 +1685,9 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) { true_data[entry.first] = entry.second; } } else { - ASSERT_OK(GenerateAndAddExternalFile(options, random_data, -1, true, - true, &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, random_data, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, true, &true_data)); } } size_t kcnt = 0; @@ -1497,7 +1697,7 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) { } } -TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { +TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { Options options = CurrentOptions(); options.num_levels = 5; options.disable_auto_compactions = true; @@ -1516,8 +1716,11 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { for (int i = 0; i <= 20; i++) { file_data.emplace_back(Key(i), "L4"); } - ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true, false, - &true_data)); + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); + ASSERT_OK(GenerateAndAddExternalFile( + options, file_data, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); // This file dont overlap with anything in the DB, will go to L4 ASSERT_EQ("0,0,0,0,1", FilesPerLevel()); @@ -1527,8 +1730,9 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { for (int i = 80; i <= 130; i++) { file_data.emplace_back(Key(i), "L0"); } - ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, file_data, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); // This file overlap with the memtable, so it will flush it and add // it self to L0 @@ -1539,8 +1743,9 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { for (int i = 30; i <= 50; i++) { file_data.emplace_back(Key(i), "L4"); } - ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, file_data, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); // This file dont overlap with anything in the DB and fit in L4 as well ASSERT_EQ("2,0,0,0,2", FilesPerLevel()); @@ -1550,8 +1755,9 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { for (int i = 10; i <= 40; i++) { file_data.emplace_back(Key(i), "L3"); } - ASSERT_OK(GenerateAndAddExternalFile(options, file_data, -1, true, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, file_data, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); // This file overlap with files in L4, we will ingest it in L3 ASSERT_EQ("2,0,0,1,2", FilesPerLevel()); @@ -1560,7 +1766,7 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) { VerifyDBFromMap(true_data, &kcnt, false); } -TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) { +TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) { Options options = CurrentOptions(); DestroyAndReopen(options); uint64_t entries_in_memtable; @@ -1574,16 +1780,20 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) { &entries_in_memtable); ASSERT_GE(entries_in_memtable, 1); + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); // No need for flush - ASSERT_OK(GenerateAndAddExternalFile(options, {90, 100, 110}, -1, true, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, {90, 100, 110}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, &entries_in_memtable); ASSERT_GE(entries_in_memtable, 1); // This file will flush the memtable - ASSERT_OK(GenerateAndAddExternalFile(options, {19, 20, 21}, -1, true, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, {19, 20, 21}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, &entries_in_memtable); ASSERT_EQ(entries_in_memtable, 0); @@ -1597,15 +1807,17 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) { ASSERT_GE(entries_in_memtable, 1); // No need for flush, this file keys fit between the memtable keys - ASSERT_OK(GenerateAndAddExternalFile(options, {202, 203, 204}, -1, true, - false, &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, {202, 203, 204}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, &entries_in_memtable); ASSERT_GE(entries_in_memtable, 1); // This file will flush the memtable - ASSERT_OK(GenerateAndAddExternalFile(options, {206, 207}, -1, true, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, {206, 207}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false, &true_data)); db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable, &entries_in_memtable); ASSERT_EQ(entries_in_memtable, 0); @@ -1614,7 +1826,7 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) { VerifyDBFromMap(true_data, &kcnt, false); } -TEST_F(ExternalSSTFileTest, L0SortingIssue) { +TEST_P(ExternalSSTFileTest, L0SortingIssue) { Options options = CurrentOptions(); options.num_levels = 2; DestroyAndReopen(options); @@ -1623,10 +1835,16 @@ TEST_F(ExternalSSTFileTest, L0SortingIssue) { ASSERT_OK(Put(Key(1), "memtable")); ASSERT_OK(Put(Key(10), "memtable")); + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); // No Flush needed, No global seqno needed, Ingest in L1 - ASSERT_OK(GenerateAndAddExternalFile(options, {7, 8}, -1, true, false)); + ASSERT_OK( + GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false)); // No Flush needed, but need a global seqno, Ingest in L0 - ASSERT_OK(GenerateAndAddExternalFile(options, {7, 8}, -1, true, false)); + ASSERT_OK( + GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, false)); printf("%s\n", FilesPerLevel().c_str()); // Overwrite what we added using external files @@ -1795,9 +2013,59 @@ TEST_F(ExternalSSTFileTest, FileWithCFInfo) { ASSERT_OK(db_->IngestExternalFile(handles_[2], {unknown_sst}, ifo)); } +/* + * Test and verify the functionality of ingestion_options.move_files. + */ +TEST_F(ExternalSSTFileTest, LinkExternalSst) { + Options options = CurrentOptions(); + options.disable_auto_compactions = true; + DestroyAndReopen(options); + const int kNumKeys = 10000; + + std::string file_path = sst_files_dir_ + "file1.sst"; + // Create SstFileWriter for default column family + SstFileWriter sst_file_writer(EnvOptions(), options); + ASSERT_OK(sst_file_writer.Open(file_path)); + for (int i = 0; i < kNumKeys; i++) { + ASSERT_OK(sst_file_writer.Put(Key(i), Key(i) + "_value")); + } + ASSERT_OK(sst_file_writer.Finish()); + uint64_t file_size = 0; + ASSERT_OK(env_->GetFileSize(file_path, &file_size)); + + IngestExternalFileOptions ifo; + ifo.move_files = true; + ASSERT_OK(db_->IngestExternalFile({file_path}, ifo)); + + ColumnFamilyHandleImpl* cfh = + static_cast(dbfull()->DefaultColumnFamily()); + ColumnFamilyData* cfd = cfh->cfd(); + const InternalStats* internal_stats_ptr = cfd->internal_stats(); + const std::vector& comp_stats = + internal_stats_ptr->TEST_GetCompactionStats(); + uint64_t bytes_copied = 0; + uint64_t bytes_moved = 0; + for (const auto& stats : comp_stats) { + bytes_copied += stats.bytes_written; + bytes_moved += stats.bytes_moved; + } + // If bytes_moved > 0, it means external sst resides on the same FS + // supporting hard link operation. Therefore, + // 0 bytes should be copied, and the bytes_moved == file_size. + // Otherwise, FS does not support hard link, or external sst file resides on + // a different file system, then the bytes_copied should be equal to + // file_size. + if (bytes_moved > 0) { + ASSERT_EQ(0, bytes_copied); + ASSERT_EQ(file_size, bytes_moved); + } else { + ASSERT_EQ(file_size, bytes_copied); + } +} + class TestIngestExternalFileListener : public EventListener { public: - void OnExternalFileIngested(DB* db, + void OnExternalFileIngested(DB* /*db*/, const ExternalFileIngestionInfo& info) override { ingested_files.push_back(info); } @@ -1805,16 +2073,19 @@ class TestIngestExternalFileListener : public EventListener { std::vector ingested_files; }; -TEST_F(ExternalSSTFileTest, IngestionListener) { +TEST_P(ExternalSSTFileTest, IngestionListener) { Options options = CurrentOptions(); TestIngestExternalFileListener* listener = new TestIngestExternalFileListener(); options.listeners.emplace_back(listener); CreateAndReopenWithCF({"koko", "toto"}, options); + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); // Ingest into default cf - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true, true, nullptr, - handles_[0])); + ASSERT_OK(GenerateAndAddExternalFile( + options, {1, 2}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, true, nullptr, handles_[0])); ASSERT_EQ(listener->ingested_files.size(), 1); ASSERT_EQ(listener->ingested_files.back().cf_name, "default"); ASSERT_EQ(listener->ingested_files.back().global_seqno, 0); @@ -1824,8 +2095,9 @@ TEST_F(ExternalSSTFileTest, IngestionListener) { "default"); // Ingest into cf1 - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true, true, nullptr, - handles_[1])); + ASSERT_OK(GenerateAndAddExternalFile( + options, {1, 2}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, true, nullptr, handles_[1])); ASSERT_EQ(listener->ingested_files.size(), 2); ASSERT_EQ(listener->ingested_files.back().cf_name, "koko"); ASSERT_EQ(listener->ingested_files.back().global_seqno, 0); @@ -1835,8 +2107,9 @@ TEST_F(ExternalSSTFileTest, IngestionListener) { "koko"); // Ingest into cf2 - ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, true, true, nullptr, - handles_[2])); + ASSERT_OK(GenerateAndAddExternalFile( + options, {1, 2}, -1, true, write_global_seqno, + verify_checksums_before_ingest, false, true, nullptr, handles_[2])); ASSERT_EQ(listener->ingested_files.size(), 3); ASSERT_EQ(listener->ingested_files.back().cf_name, "toto"); ASSERT_EQ(listener->ingested_files.back().global_seqno, 0); @@ -1878,7 +2151,7 @@ TEST_F(ExternalSSTFileTest, SnapshotInconsistencyBug) { db_->ReleaseSnapshot(snap); } -TEST_F(ExternalSSTFileTest, IngestBehind) { +TEST_P(ExternalSSTFileTest, IngestBehind) { Options options = CurrentOptions(); options.compaction_style = kCompactionStyleUniversal; options.num_levels = 3; @@ -1899,14 +2172,16 @@ TEST_F(ExternalSSTFileTest, IngestBehind) { file_data.emplace_back(Key(i), "ingest_behind"); } - IngestExternalFileOptions ifo; - ifo.allow_global_seqno = true; - ifo.ingest_behind = true; + bool allow_global_seqno = true; + bool ingest_behind = true; + bool write_global_seqno = std::get<0>(GetParam()); + bool verify_checksums_before_ingest = std::get<1>(GetParam()); // Can't ingest behind since allow_ingest_behind isn't set to true - ASSERT_NOK(GenerateAndAddExternalFileIngestBehind(options, ifo, - file_data, -1, false, - &true_data)); + ASSERT_NOK(GenerateAndAddExternalFile( + options, file_data, -1, allow_global_seqno, write_global_seqno, + verify_checksums_before_ingest, ingest_behind, false /*sort_data*/, + &true_data)); options.allow_ingest_behind = true; // check that we still can open the DB, as num_levels should be @@ -1924,14 +2199,16 @@ TEST_F(ExternalSSTFileTest, IngestBehind) { db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); // Universal picker should go at second from the bottom level ASSERT_EQ("0,1", FilesPerLevel()); - ASSERT_OK(GenerateAndAddExternalFileIngestBehind(options, ifo, - file_data, -1, false, - &true_data)); + ASSERT_OK(GenerateAndAddExternalFile( + options, file_data, -1, allow_global_seqno, write_global_seqno, + verify_checksums_before_ingest, true /*ingest_behind*/, + false /*sort_data*/, &true_data)); ASSERT_EQ("0,1,1", FilesPerLevel()); // this time ingest should fail as the file doesn't fit to the bottom level - ASSERT_NOK(GenerateAndAddExternalFileIngestBehind(options, ifo, - file_data, -1, false, - &true_data)); + ASSERT_NOK(GenerateAndAddExternalFile( + options, file_data, -1, allow_global_seqno, write_global_seqno, + verify_checksums_before_ingest, true /*ingest_behind*/, + false /*sort_data*/, &true_data)); ASSERT_EQ("0,1,1", FilesPerLevel()); db_->CompactRange(CompactRangeOptions(), nullptr, nullptr); // bottom level should be empty @@ -1940,6 +2217,455 @@ TEST_F(ExternalSSTFileTest, IngestBehind) { size_t kcnt = 0; VerifyDBFromMap(true_data, &kcnt, false); } + +TEST_F(ExternalSSTFileTest, SkipBloomFilter) { + Options options = CurrentOptions(); + + BlockBasedTableOptions table_options; + table_options.filter_policy.reset(NewBloomFilterPolicy(10)); + table_options.cache_index_and_filter_blocks = true; + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + + + // Create external SST file and include bloom filters + options.statistics = rocksdb::CreateDBStatistics(); + DestroyAndReopen(options); + { + std::string file_path = sst_files_dir_ + "sst_with_bloom.sst"; + SstFileWriter sst_file_writer(EnvOptions(), options); + ASSERT_OK(sst_file_writer.Open(file_path)); + ASSERT_OK(sst_file_writer.Put("Key1", "Value1")); + ASSERT_OK(sst_file_writer.Finish()); + + ASSERT_OK( + db_->IngestExternalFile({file_path}, IngestExternalFileOptions())); + + ASSERT_EQ(Get("Key1"), "Value1"); + ASSERT_GE( + options.statistics->getTickerCount(Tickers::BLOCK_CACHE_FILTER_ADD), 1); + } + + // Create external SST file but skip bloom filters + options.statistics = rocksdb::CreateDBStatistics(); + DestroyAndReopen(options); + { + std::string file_path = sst_files_dir_ + "sst_with_no_bloom.sst"; + SstFileWriter sst_file_writer(EnvOptions(), options, nullptr, true, + Env::IOPriority::IO_TOTAL, + true /* skip_filters */); + ASSERT_OK(sst_file_writer.Open(file_path)); + ASSERT_OK(sst_file_writer.Put("Key1", "Value1")); + ASSERT_OK(sst_file_writer.Finish()); + + ASSERT_OK( + db_->IngestExternalFile({file_path}, IngestExternalFileOptions())); + + ASSERT_EQ(Get("Key1"), "Value1"); + ASSERT_EQ( + options.statistics->getTickerCount(Tickers::BLOCK_CACHE_FILTER_ADD), 0); + } +} + +TEST_F(ExternalSSTFileTest, IngestFileWrittenWithCompressionDictionary) { + if (!ZSTD_Supported()) { + return; + } + const int kNumEntries = 1 << 10; + const int kNumBytesPerEntry = 1 << 10; + Options options = CurrentOptions(); + options.compression = kZSTD; + options.compression_opts.max_dict_bytes = 1 << 14; // 16KB + options.compression_opts.zstd_max_train_bytes = 1 << 18; // 256KB + DestroyAndReopen(options); + + std::atomic num_compression_dicts(0); + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "BlockBasedTableBuilder::WriteCompressionDictBlock:RawDict", + [&](void* /* arg */) { ++num_compression_dicts; }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + + Random rnd(301); + std::vector> random_data; + for (int i = 0; i < kNumEntries; i++) { + std::string val; + test::RandomString(&rnd, kNumBytesPerEntry, &val); + random_data.emplace_back(Key(i), std::move(val)); + } + ASSERT_OK(GenerateAndAddExternalFile(options, std::move(random_data))); + ASSERT_EQ(1, num_compression_dicts); +} + +TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) { + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + Options options = CurrentOptions(); + options.env = fault_injection_env.get(); + CreateAndReopenWithCF({"pikachu"}, options); + std::vector column_families; + column_families.push_back(handles_[0]); + column_families.push_back(handles_[1]); + std::vector ifos(column_families.size()); + for (auto& ifo : ifos) { + ifo.allow_global_seqno = true; // Always allow global_seqno + // May or may not write global_seqno + ifo.write_global_seqno = std::get<0>(GetParam()); + // Whether to verify checksums before ingestion + ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); + } + std::vector>> data; + data.push_back( + {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); + data.push_back( + {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); + // Resize the true_data vector upon construction to avoid re-alloc + std::vector> true_data( + column_families.size()); + Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data, + -1, true, true_data); + ASSERT_OK(s); + Close(); + ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); + ASSERT_EQ(2, handles_.size()); + int cf = 0; + for (const auto& verify_map : true_data) { + for (const auto& elem : verify_map) { + const std::string& key = elem.first; + const std::string& value = elem.second; + ASSERT_EQ(value, Get(cf, key)); + } + ++cf; + } + Close(); + Destroy(options, true /* delete_cf_paths */); +} + +TEST_P(ExternalSSTFileTest, + IngestFilesIntoMultipleColumnFamilies_NoMixedStateWithSnapshot) { + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->LoadDependency({ + {"DBImpl::IngestExternalFiles:InstallSVForFirstCF:0", + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" + "BeforeRead"}, + {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" + "AfterRead", + "DBImpl::IngestExternalFiles:InstallSVForFirstCF:1"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + + Options options = CurrentOptions(); + options.env = fault_injection_env.get(); + CreateAndReopenWithCF({"pikachu"}, options); + const std::vector> data_before_ingestion = + {{{"foo1", "fv1_0"}, {"foo2", "fv2_0"}, {"foo3", "fv3_0"}}, + {{"bar1", "bv1_0"}, {"bar2", "bv2_0"}, {"bar3", "bv3_0"}}}; + for (size_t i = 0; i != handles_.size(); ++i) { + int cf = static_cast(i); + const auto& orig_data = data_before_ingestion[i]; + for (const auto& kv : orig_data) { + ASSERT_OK(Put(cf, kv.first, kv.second)); + } + ASSERT_OK(Flush(cf)); + } + + std::vector column_families; + column_families.push_back(handles_[0]); + column_families.push_back(handles_[1]); + std::vector ifos(column_families.size()); + for (auto& ifo : ifos) { + ifo.allow_global_seqno = true; // Always allow global_seqno + // May or may not write global_seqno + ifo.write_global_seqno = std::get<0>(GetParam()); + // Whether to verify checksums before ingestion + ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); + } + std::vector>> data; + data.push_back( + {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); + data.push_back( + {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); + // Resize the true_data vector upon construction to avoid re-alloc + std::vector> true_data( + column_families.size()); + // Take snapshot before ingestion starts + ReadOptions read_opts; + read_opts.total_order_seek = true; + read_opts.snapshot = dbfull()->GetSnapshot(); + std::vector iters(handles_.size()); + + // Range scan checks first kv of each CF before ingestion starts. + for (size_t i = 0; i != handles_.size(); ++i) { + iters[i] = dbfull()->NewIterator(read_opts, handles_[i]); + iters[i]->SeekToFirst(); + ASSERT_TRUE(iters[i]->Valid()); + const std::string& key = iters[i]->key().ToString(); + const std::string& value = iters[i]->value().ToString(); + const std::map& orig_data = + data_before_ingestion[i]; + std::map::const_iterator it = orig_data.find(key); + ASSERT_NE(orig_data.end(), it); + ASSERT_EQ(it->second, value); + iters[i]->Next(); + } + port::Thread ingest_thread([&]() { + ASSERT_OK(GenerateAndAddExternalFiles(options, column_families, ifos, data, + -1, true, true_data)); + }); + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" + "BeforeRead"); + // Should see only data before ingestion + for (size_t i = 0; i != handles_.size(); ++i) { + const auto& orig_data = data_before_ingestion[i]; + for (; iters[i]->Valid(); iters[i]->Next()) { + const std::string& key = iters[i]->key().ToString(); + const std::string& value = iters[i]->value().ToString(); + std::map::const_iterator it = + orig_data.find(key); + ASSERT_NE(orig_data.end(), it); + ASSERT_EQ(it->second, value); + } + } + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_MixedState:" + "AfterRead"); + ingest_thread.join(); + for (auto* iter : iters) { + delete iter; + } + iters.clear(); + dbfull()->ReleaseSnapshot(read_opts.snapshot); + + Close(); + ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); + // Should see consistent state after ingestion for all column families even + // without snapshot. + ASSERT_EQ(2, handles_.size()); + int cf = 0; + for (const auto& verify_map : true_data) { + for (const auto& elem : verify_map) { + const std::string& key = elem.first; + const std::string& value = elem.second; + ASSERT_EQ(value, Get(cf, key)); + } + ++cf; + } + Close(); + Destroy(options, true /* delete_cf_paths */); +} + +TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) { + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + Options options = CurrentOptions(); + options.env = fault_injection_env.get(); + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->LoadDependency({ + {"DBImpl::IngestExternalFiles:BeforeLastJobPrepare:0", + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_PrepareFail:" + "0"}, + {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies:PrepareFail:" + "1", + "DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + CreateAndReopenWithCF({"pikachu"}, options); + std::vector column_families; + column_families.push_back(handles_[0]); + column_families.push_back(handles_[1]); + std::vector ifos(column_families.size()); + for (auto& ifo : ifos) { + ifo.allow_global_seqno = true; // Always allow global_seqno + // May or may not write global_seqno + ifo.write_global_seqno = std::get<0>(GetParam()); + // Whether to verify block checksums before ingest + ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); + } + std::vector>> data; + data.push_back( + {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); + data.push_back( + {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); + // Resize the true_data vector upon construction to avoid re-alloc + std::vector> true_data( + column_families.size()); + port::Thread ingest_thread([&]() { + Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data, + -1, true, true_data); + ASSERT_NOK(s); + }); + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_PrepareFail:" + "0"); + fault_injection_env->SetFilesystemActive(false); + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies:PrepareFail:" + "1"); + ingest_thread.join(); + + fault_injection_env->SetFilesystemActive(true); + Close(); + ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); + ASSERT_EQ(2, handles_.size()); + int cf = 0; + for (const auto& verify_map : true_data) { + for (const auto& elem : verify_map) { + const std::string& key = elem.first; + ASSERT_EQ("NOT_FOUND", Get(cf, key)); + } + ++cf; + } + Close(); + Destroy(options, true /* delete_cf_paths */); +} + +TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) { + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + Options options = CurrentOptions(); + options.env = fault_injection_env.get(); + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->LoadDependency({ + {"DBImpl::IngestExternalFiles:BeforeJobsRun:0", + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" + "0"}, + {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" + "1", + "DBImpl::IngestExternalFiles:BeforeJobsRun:1"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + CreateAndReopenWithCF({"pikachu"}, options); + std::vector column_families; + column_families.push_back(handles_[0]); + column_families.push_back(handles_[1]); + std::vector ifos(column_families.size()); + for (auto& ifo : ifos) { + ifo.allow_global_seqno = true; // Always allow global_seqno + // May or may not write global_seqno + ifo.write_global_seqno = std::get<0>(GetParam()); + // Whether to verify block checksums before ingestion + ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); + } + std::vector>> data; + data.push_back( + {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); + data.push_back( + {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); + // Resize the true_data vector upon construction to avoid re-alloc + std::vector> true_data( + column_families.size()); + port::Thread ingest_thread([&]() { + Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data, + -1, true, true_data); + ASSERT_NOK(s); + }); + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" + "0"); + fault_injection_env->SetFilesystemActive(false); + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_CommitFail:" + "1"); + ingest_thread.join(); + + fault_injection_env->SetFilesystemActive(true); + Close(); + ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); + ASSERT_EQ(2, handles_.size()); + int cf = 0; + for (const auto& verify_map : true_data) { + for (const auto& elem : verify_map) { + const std::string& key = elem.first; + ASSERT_EQ("NOT_FOUND", Get(cf, key)); + } + ++cf; + } + Close(); + Destroy(options, true /* delete_cf_paths */); +} + +TEST_P(ExternalSSTFileTest, + IngestFilesIntoMultipleColumnFamilies_PartialManifestWriteFail) { + std::unique_ptr fault_injection_env( + new FaultInjectionTestEnv(env_)); + Options options = CurrentOptions(); + options.env = fault_injection_env.get(); + + CreateAndReopenWithCF({"pikachu"}, options); + + SyncPoint::GetInstance()->ClearTrace(); + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->LoadDependency({ + {"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0", + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" + "PartialManifestWriteFail:0"}, + {"ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" + "PartialManifestWriteFail:1", + "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1"}, + }); + SyncPoint::GetInstance()->EnableProcessing(); + + std::vector column_families; + column_families.push_back(handles_[0]); + column_families.push_back(handles_[1]); + std::vector ifos(column_families.size()); + for (auto& ifo : ifos) { + ifo.allow_global_seqno = true; // Always allow global_seqno + // May or may not write global_seqno + ifo.write_global_seqno = std::get<0>(GetParam()); + // Whether to verify block checksums before ingestion + ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); + } + std::vector>> data; + data.push_back( + {std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")}); + data.push_back( + {std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")}); + // Resize the true_data vector upon construction to avoid re-alloc + std::vector> true_data( + column_families.size()); + port::Thread ingest_thread([&]() { + Status s = GenerateAndAddExternalFiles(options, column_families, ifos, data, + -1, true, true_data); + ASSERT_NOK(s); + }); + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" + "PartialManifestWriteFail:0"); + fault_injection_env->SetFilesystemActive(false); + TEST_SYNC_POINT( + "ExternalSSTFileTest::IngestFilesIntoMultipleColumnFamilies_" + "PartialManifestWriteFail:1"); + ingest_thread.join(); + + fault_injection_env->DropUnsyncedFileData(); + fault_injection_env->SetFilesystemActive(true); + Close(); + ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options); + ASSERT_EQ(2, handles_.size()); + int cf = 0; + for (const auto& verify_map : true_data) { + for (const auto& elem : verify_map) { + const std::string& key = elem.first; + ASSERT_EQ("NOT_FOUND", Get(cf, key)); + } + ++cf; + } + Close(); + Destroy(options, true /* delete_cf_paths */); +} + +INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest, + testing::Values(std::make_tuple(false, false), + std::make_tuple(false, true), + std::make_tuple(true, false), + std::make_tuple(true, true))); + } // namespace rocksdb int main(int argc, char** argv) { @@ -1951,7 +2677,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as External SST File Writer and Ingestion are not supported " "in ROCKSDB_LITE\n"); diff --git a/thirdparty/rocksdb/db/fault_injection_test.cc b/thirdparty/rocksdb/db/fault_injection_test.cc index adfcb4db5a..53de312c01 100644 --- a/thirdparty/rocksdb/db/fault_injection_test.cc +++ b/thirdparty/rocksdb/db/fault_injection_test.cc @@ -34,19 +34,22 @@ static const int kValueSize = 1000; static const int kMaxNumValues = 2000; static const size_t kNumIterations = 3; -class FaultInjectionTest : public testing::Test, - public testing::WithParamInterface { +enum FaultInjectionOptionConfig { + kDefault, + kDifferentDataDir, + kWalDir, + kSyncWal, + kWalDirSyncWal, + kMultiLevels, + kEnd, +}; +class FaultInjectionTest + : public testing::Test, + public testing::WithParamInterface> { protected: - enum OptionConfig { - kDefault, - kDifferentDataDir, - kWalDir, - kSyncWal, - kWalDirSyncWal, - kMultiLevels, - kEnd, - }; int option_config_; + int non_inclusive_end_range_; // kEnd or equivalent to that // When need to make sure data is persistent, sync WAL bool sync_use_wal_; // When need to make sure data is persistent, call DB::CompactRange() @@ -67,27 +70,27 @@ class FaultInjectionTest : public testing::Test, std::unique_ptr base_env_; FaultInjectionTestEnv* env_; std::string dbname_; - shared_ptr tiny_cache_; + std::shared_ptr tiny_cache_; Options options_; DB* db_; FaultInjectionTest() - : option_config_(kDefault), + : option_config_(std::get<1>(GetParam())), + non_inclusive_end_range_(std::get<2>(GetParam())), sync_use_wal_(false), sync_use_compact_(true), base_env_(nullptr), - env_(NULL), - db_(NULL) { - } + env_(nullptr), + db_(nullptr) {} - ~FaultInjectionTest() { + ~FaultInjectionTest() override { rocksdb::SyncPoint::GetInstance()->DisableProcessing(); rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks(); } bool ChangeOptions() { option_config_++; - if (option_config_ >= kEnd) { + if (option_config_ >= non_inclusive_end_range_) { return false; } else { if (option_config_ == kMultiLevels) { @@ -104,18 +107,18 @@ class FaultInjectionTest : public testing::Test, Options options; switch (option_config_) { case kWalDir: - options.wal_dir = test::TmpDir(env_) + "/fault_test_wal"; + options.wal_dir = test::PerThreadDBPath(env_, "fault_test_wal"); break; case kDifferentDataDir: - options.db_paths.emplace_back(test::TmpDir(env_) + "/fault_test_data", - 1000000U); + options.db_paths.emplace_back( + test::PerThreadDBPath(env_, "fault_test_data"), 1000000U); break; case kSyncWal: sync_use_wal_ = true; sync_use_compact_ = false; break; case kWalDirSyncWal: - options.wal_dir = test::TmpDir(env_) + "/fault_test_wal"; + options.wal_dir = test::PerThreadDBPath(env_, "/fault_test_wal"); sync_use_wal_ = true; sync_use_compact_ = false; break; @@ -139,9 +142,9 @@ class FaultInjectionTest : public testing::Test, } Status NewDB() { - assert(db_ == NULL); + assert(db_ == nullptr); assert(tiny_cache_ == nullptr); - assert(env_ == NULL); + assert(env_ == nullptr); env_ = new FaultInjectionTestEnv(base_env_ ? base_env_.get() : Env::Default()); @@ -155,7 +158,7 @@ class FaultInjectionTest : public testing::Test, table_options.block_cache = tiny_cache_; options_.table_factory.reset(NewBlockBasedTableFactory(table_options)); - dbname_ = test::TmpDir() + "/fault_test"; + dbname_ = test::PerThreadDBPath("fault_test"); EXPECT_OK(DestroyDB(dbname_, options_)); @@ -166,7 +169,7 @@ class FaultInjectionTest : public testing::Test, } void SetUp() override { - sequential_order_ = GetParam(); + sequential_order_ = std::get<0>(GetParam()); ASSERT_OK(NewDB()); } @@ -176,7 +179,7 @@ class FaultInjectionTest : public testing::Test, Status s = DestroyDB(dbname_, options_); delete env_; - env_ = NULL; + env_ = nullptr; tiny_cache_.reset(); @@ -228,16 +231,9 @@ class FaultInjectionTest : public testing::Test, return Status::OK(); } -#ifdef ROCKSDB_UBSAN_RUN -#if defined(__clang__) -__attribute__((__no_sanitize__("shift"), no_sanitize("signed-integer-overflow"))) -#elif defined(__GNUC__) -__attribute__((__no_sanitize_undefined__)) -#endif -#endif // Return the ith key Slice Key(int i, std::string* storage) const { - int num = i; + unsigned long long num = i; if (!sequential_order_) { // random transfer const int m = 0x5bd1e995; @@ -245,7 +241,7 @@ __attribute__((__no_sanitize_undefined__)) num ^= num << 24; } char buf[100]; - snprintf(buf, sizeof(buf), "%016d", num); + snprintf(buf, sizeof(buf), "%016d", static_cast(num)); storage->assign(buf, strlen(buf)); return Slice(*storage); } @@ -350,7 +346,9 @@ __attribute__((__no_sanitize_undefined__)) } }; -TEST_P(FaultInjectionTest, FaultTest) { +class FaultInjectionTestSplitted : public FaultInjectionTest {}; + +TEST_P(FaultInjectionTestSplitted, FaultTest) { do { Random rnd(301); @@ -463,10 +461,10 @@ TEST_P(FaultInjectionTest, UninstalledCompaction) { std::atomic opened(false); rocksdb::SyncPoint::GetInstance()->SetCallBack( - "DBImpl::Open:Opened", [&](void* arg) { opened.store(true); }); + "DBImpl::Open:Opened", [&](void* /*arg*/) { opened.store(true); }); rocksdb::SyncPoint::GetInstance()->SetCallBack( "DBImpl::BGWorkCompaction", - [&](void* arg) { ASSERT_TRUE(opened.load()); }); + [&](void* /*arg*/) { ASSERT_TRUE(opened.load()); }); rocksdb::SyncPoint::GetInstance()->EnableProcessing(); ASSERT_OK(OpenDB()); ASSERT_OK(Verify(0, kNumKeys, FaultInjectionTest::kValExpectFound)); @@ -537,7 +535,17 @@ TEST_P(FaultInjectionTest, WriteBatchWalTerminationTest) { ASSERT_EQ(db_->Get(ro, "boys", &val), Status::NotFound()); } -INSTANTIATE_TEST_CASE_P(FaultTest, FaultInjectionTest, ::testing::Bool()); +INSTANTIATE_TEST_CASE_P( + FaultTest, FaultInjectionTest, + ::testing::Values(std::make_tuple(false, kDefault, kEnd), + std::make_tuple(true, kDefault, kEnd))); + +INSTANTIATE_TEST_CASE_P( + FaultTest, FaultInjectionTestSplitted, + ::testing::Values(std::make_tuple(false, kDefault, kSyncWal), + std::make_tuple(true, kDefault, kSyncWal), + std::make_tuple(false, kSyncWal, kEnd), + std::make_tuple(true, kSyncWal, kEnd))); } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/file_indexer_test.cc b/thirdparty/rocksdb/db/file_indexer_test.cc index 5cd8c2d2cf..935a01ef8d 100644 --- a/thirdparty/rocksdb/db/file_indexer_test.cc +++ b/thirdparty/rocksdb/db/file_indexer_test.cc @@ -36,10 +36,10 @@ class IntComparator : public Comparator { const char* Name() const override { return "IntComparator"; } - void FindShortestSeparator(std::string* start, - const Slice& limit) const override {} + void FindShortestSeparator(std::string* /*start*/, + const Slice& /*limit*/) const override {} - void FindShortSuccessor(std::string* key) const override {} + void FindShortSuccessor(std::string* /*key*/) const override {} }; class FileIndexerTest : public testing::Test { @@ -47,7 +47,7 @@ class FileIndexerTest : public testing::Test { FileIndexerTest() : kNumLevels(4), files(new std::vector[kNumLevels]) {} - ~FileIndexerTest() { + ~FileIndexerTest() override { ClearFiles(); delete[] files; } diff --git a/thirdparty/rocksdb/db/flush_job.cc b/thirdparty/rocksdb/db/flush_job.cc index 778c9eca12..f03188141a 100644 --- a/thirdparty/rocksdb/db/flush_job.cc +++ b/thirdparty/rocksdb/db/flush_job.cc @@ -24,15 +24,15 @@ #include "db/event_helpers.h" #include "db/log_reader.h" #include "db/log_writer.h" +#include "db/memtable.h" #include "db/memtable_list.h" #include "db/merge_context.h" +#include "db/range_tombstone_fragmenter.h" #include "db/version_set.h" #include "monitoring/iostats_context_imp.h" #include "monitoring/perf_context_imp.h" #include "monitoring/thread_status_util.h" -#include "port/likely.h" #include "port/port.h" -#include "db/memtable.h" #include "rocksdb/db.h" #include "rocksdb/env.h" #include "rocksdb/statistics.h" @@ -55,28 +55,65 @@ namespace rocksdb { +const char* GetFlushReasonString (FlushReason flush_reason) { + switch (flush_reason) { + case FlushReason::kOthers: + return "Other Reasons"; + case FlushReason::kGetLiveFiles: + return "Get Live Files"; + case FlushReason::kShutDown: + return "Shut down"; + case FlushReason::kExternalFileIngestion: + return "External File Ingestion"; + case FlushReason::kManualCompaction: + return "Manual Compaction"; + case FlushReason::kWriteBufferManager: + return "Write Buffer Manager"; + case FlushReason::kWriteBufferFull: + return "Write Buffer Full"; + case FlushReason::kTest: + return "Test"; + case FlushReason::kDeleteFiles: + return "Delete Files"; + case FlushReason::kAutoCompaction: + return "Auto Compaction"; + case FlushReason::kManualFlush: + return "Manual Flush"; + case FlushReason::kErrorRecovery: + return "Error Recovery"; + default: + return "Invalid"; + } +} + FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd, const ImmutableDBOptions& db_options, const MutableCFOptions& mutable_cf_options, + const uint64_t* max_memtable_id, const EnvOptions& env_options, VersionSet* versions, InstrumentedMutex* db_mutex, std::atomic* shutting_down, std::vector existing_snapshots, SequenceNumber earliest_write_conflict_snapshot, - JobContext* job_context, LogBuffer* log_buffer, - Directory* db_directory, Directory* output_file_directory, + SnapshotChecker* snapshot_checker, JobContext* job_context, + LogBuffer* log_buffer, Directory* db_directory, + Directory* output_file_directory, CompressionType output_compression, Statistics* stats, - EventLogger* event_logger, bool measure_io_stats) + EventLogger* event_logger, bool measure_io_stats, + const bool sync_output_directory, const bool write_manifest, + Env::Priority thread_pri) : dbname_(dbname), cfd_(cfd), db_options_(db_options), mutable_cf_options_(mutable_cf_options), + max_memtable_id_(max_memtable_id), env_options_(env_options), versions_(versions), db_mutex_(db_mutex), shutting_down_(shutting_down), existing_snapshots_(std::move(existing_snapshots)), earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot), + snapshot_checker_(snapshot_checker), job_context_(job_context), log_buffer_(log_buffer), db_directory_(db_directory), @@ -85,7 +122,12 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd, stats_(stats), event_logger_(event_logger), measure_io_stats_(measure_io_stats), - pick_memtable_called(false) { + sync_output_directory_(sync_output_directory), + write_manifest_(write_manifest), + edit_(nullptr), + base_(nullptr), + pick_memtable_called(false), + thread_pri_(thread_pri) { // Update the thread status to indicate flush. ReportStartedFlush(); TEST_SYNC_POINT("FlushJob::FlushJob()"); @@ -127,7 +169,7 @@ void FlushJob::PickMemTable() { assert(!pick_memtable_called); pick_memtable_called = true; // Save the contents of the earliest memtable as a new Table - cfd_->imm()->PickMemtablesToFlush(&mems_); + cfd_->imm()->PickMemtablesToFlush(max_memtable_id_, &mems_); if (mems_.empty()) { return; } @@ -152,7 +194,9 @@ void FlushJob::PickMemTable() { base_->Ref(); // it is likely that we do not need this reference } -Status FlushJob::Run(FileMetaData* file_meta) { +Status FlushJob::Run(LogsWithPrepTracker* prep_tracker, + FileMetaData* file_meta) { + TEST_SYNC_POINT("FlushJob::Start"); db_mutex_->AssertHeld(); assert(pick_memtable_called); AutoThreadOperationStageUpdater stage_run( @@ -169,6 +213,8 @@ Status FlushJob::Run(FileMetaData* file_meta) { uint64_t prev_fsync_nanos = 0; uint64_t prev_range_sync_nanos = 0; uint64_t prev_prepare_write_nanos = 0; + uint64_t prev_cpu_write_nanos = 0; + uint64_t prev_cpu_read_nanos = 0; if (measure_io_stats_) { prev_perf_level = GetPerfLevel(); SetPerfLevel(PerfLevel::kEnableTime); @@ -176,6 +222,8 @@ Status FlushJob::Run(FileMetaData* file_meta) { prev_fsync_nanos = IOSTATS(fsync_nanos); prev_range_sync_nanos = IOSTATS(range_sync_nanos); prev_prepare_write_nanos = IOSTATS(prepare_write_nanos); + prev_cpu_write_nanos = IOSTATS(cpu_write_nanos); + prev_cpu_read_nanos = IOSTATS(cpu_read_nanos); } // This will release and re-acquire the mutex. @@ -189,11 +237,11 @@ Status FlushJob::Run(FileMetaData* file_meta) { if (!s.ok()) { cfd_->imm()->RollbackMemtableFlush(mems_, meta_.fd.GetNumber()); - } else { + } else if (write_manifest_) { TEST_SYNC_POINT("FlushJob::InstallResults"); // Replace immutable memtable with the generated Table - s = cfd_->imm()->InstallMemtableFlushResults( - cfd_, mutable_cf_options_, mems_, versions_, db_mutex_, + s = cfd_->imm()->TryInstallMemtableFlushResults( + cfd_, mutable_cf_options_, mems_, prep_tracker, versions_, db_mutex_, meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_, log_buffer_); } @@ -206,6 +254,8 @@ Status FlushJob::Run(FileMetaData* file_meta) { auto stream = event_logger_->LogToBuffer(log_buffer_); stream << "job" << job_context_->job_id << "event" << "flush_finished"; + stream << "output_compression" + << CompressionTypeToString(output_compression_); stream << "lsm_state"; stream.StartArray(); auto vstorage = cfd_->current()->storage_info(); @@ -225,6 +275,10 @@ Status FlushJob::Run(FileMetaData* file_meta) { stream << "file_fsync_nanos" << (IOSTATS(fsync_nanos) - prev_fsync_nanos); stream << "file_prepare_write_nanos" << (IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos); + stream << "file_cpu_write_nanos" + << (IOSTATS(cpu_write_nanos) - prev_cpu_write_nanos); + stream << "file_cpu_read_nanos" + << (IOSTATS(cpu_read_nanos) - prev_cpu_read_nanos); } return s; @@ -241,8 +295,10 @@ Status FlushJob::WriteLevel0Table() { ThreadStatus::STAGE_FLUSH_WRITE_L0); db_mutex_->AssertHeld(); const uint64_t start_micros = db_options_.env->NowMicros(); + const uint64_t start_cpu_micros = db_options_.env->NowCPUNanos() / 1000; Status s; { + auto write_hint = cfd_->CalculateSSTWriteHint(0); db_mutex_->Unlock(); if (log_buffer_) { log_buffer_->FlushBufferToLog(); @@ -251,11 +307,13 @@ Status FlushJob::WriteLevel0Table() { // memtable and its associated range deletion memtable, respectively, at // corresponding indexes. std::vector memtables; - std::vector range_del_iters; + std::vector> + range_del_iters; ReadOptions ro; ro.total_order_seek = true; Arena arena; uint64_t total_num_entries = 0, total_num_deletes = 0; + uint64_t total_data_size = 0; size_t total_memory_usage = 0; for (MemTable* m : mems_) { ROCKS_LOG_INFO( @@ -263,12 +321,14 @@ Status FlushJob::WriteLevel0Table() { "[%s] [JOB %d] Flushing memtable with next log file: %" PRIu64 "\n", cfd_->GetName().c_str(), job_context_->job_id, m->GetNextLogNumber()); memtables.push_back(m->NewIterator(ro, &arena)); - auto* range_del_iter = m->NewRangeTombstoneIterator(ro); + auto* range_del_iter = + m->NewRangeTombstoneIterator(ro, kMaxSequenceNumber); if (range_del_iter != nullptr) { - range_del_iters.push_back(range_del_iter); + range_del_iters.emplace_back(range_del_iter); } total_num_entries += m->num_entries(); total_num_deletes += m->num_deletes(); + total_data_size += m->get_data_size(); total_memory_usage += m->ApproximateMemoryUsage(); } @@ -276,17 +336,15 @@ Status FlushJob::WriteLevel0Table() { << "flush_started" << "num_memtables" << mems_.size() << "num_entries" << total_num_entries << "num_deletes" - << total_num_deletes << "memory_usage" - << total_memory_usage; + << total_num_deletes << "total_data_size" + << total_data_size << "memory_usage" + << total_memory_usage << "flush_reason" + << GetFlushReasonString(cfd_->GetFlushReason()); { ScopedArenaIterator iter( NewMergingIterator(&cfd_->internal_comparator(), &memtables[0], static_cast(memtables.size()), &arena)); - std::unique_ptr range_del_iter(NewMergingIterator( - &cfd_->internal_comparator(), - range_del_iters.empty() ? nullptr : &range_del_iters[0], - static_cast(range_del_iters.size()))); ROCKS_LOG_INFO(db_options_.info_log, "[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": started", cfd_->GetName().c_str(), job_context_->job_id, @@ -294,27 +352,34 @@ Status FlushJob::WriteLevel0Table() { TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:output_compression", &output_compression_); - EnvOptions optimized_env_options = - db_options_.env->OptimizeForCompactionTableWrite(env_options_, db_options_); - int64_t _current_time = 0; - db_options_.env->GetCurrentTime(&_current_time); // ignore error + auto status = db_options_.env->GetCurrentTime(&_current_time); + // Safe to proceed even if GetCurrentTime fails. So, log and proceed. + if (!status.ok()) { + ROCKS_LOG_WARN( + db_options_.info_log, + "Failed to get current time to populate creation_time property. " + "Status: %s", + status.ToString().c_str()); + } const uint64_t current_time = static_cast(_current_time); - uint64_t oldest_key_time = mems_.front()->ApproximateOldestKeyTime(); + uint64_t oldest_key_time = + mems_.front()->ApproximateOldestKeyTime(); s = BuildTable( dbname_, db_options_.env, *cfd_->ioptions(), mutable_cf_options_, - optimized_env_options, cfd_->table_cache(), iter.get(), - std::move(range_del_iter), &meta_, cfd_->internal_comparator(), + env_options_, cfd_->table_cache(), iter.get(), + std::move(range_del_iters), &meta_, cfd_->internal_comparator(), cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(), cfd_->GetName(), existing_snapshots_, - earliest_write_conflict_snapshot_, output_compression_, + earliest_write_conflict_snapshot_, snapshot_checker_, + output_compression_, mutable_cf_options_.sample_for_compression, cfd_->ioptions()->compression_opts, mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(), TableFileCreationReason::kFlush, event_logger_, job_context_->job_id, Env::IO_HIGH, &table_properties_, 0 /* level */, current_time, - oldest_key_time); + oldest_key_time, write_hint); LogFlush(db_options_.info_log); } ROCKS_LOG_INFO(db_options_.info_log, @@ -326,8 +391,8 @@ Status FlushJob::WriteLevel0Table() { s.ToString().c_str(), meta_.marked_for_compaction ? " (needs compaction)" : ""); - if (output_file_directory_ != nullptr) { - output_file_directory_->Fsync(); + if (s.ok() && output_file_directory_ != nullptr && sync_output_directory_) { + s = output_file_directory_->Fsync(); } TEST_SYNC_POINT("FlushJob::WriteLevel0Table"); db_mutex_->Lock(); @@ -344,15 +409,17 @@ Status FlushJob::WriteLevel0Table() { // Add file to L0 edit_->AddFile(0 /* level */, meta_.fd.GetNumber(), meta_.fd.GetPathId(), meta_.fd.GetFileSize(), meta_.smallest, meta_.largest, - meta_.smallest_seqno, meta_.largest_seqno, + meta_.fd.smallest_seqno, meta_.fd.largest_seqno, meta_.marked_for_compaction); } // Note that here we treat flush as level 0 compaction in internal stats - InternalStats::CompactionStats stats(1); + InternalStats::CompactionStats stats(CompactionReason::kFlush, 1); stats.micros = db_options_.env->NowMicros() - start_micros; + stats.cpu_micros = db_options_.env->NowCPUNanos() / 1000 - start_cpu_micros; stats.bytes_written = meta_.fd.GetFileSize(); - cfd_->internal_stats()->AddCompactionStats(0 /* level */, stats); + RecordTimeToHistogram(stats_, FLUSH_TIME, stats.micros); + cfd_->internal_stats()->AddCompactionStats(0 /* level */, thread_pri_, stats); cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED, meta_.fd.GetFileSize()); RecordFlushIOStats(); diff --git a/thirdparty/rocksdb/db/flush_job.h b/thirdparty/rocksdb/db/flush_job.h index 4698ae7b03..c408194562 100644 --- a/thirdparty/rocksdb/db/flush_job.h +++ b/thirdparty/rocksdb/db/flush_job.h @@ -22,6 +22,7 @@ #include "db/internal_stats.h" #include "db/job_context.h" #include "db/log_writer.h" +#include "db/logs_with_prep_tracker.h" #include "db/memtable_list.h" #include "db/snapshot_impl.h" #include "db/version_edit.h" @@ -42,7 +43,9 @@ namespace rocksdb { +class DBImpl; class MemTable; +class SnapshotChecker; class TableCache; class Version; class VersionEdit; @@ -56,39 +59,52 @@ class FlushJob { FlushJob(const std::string& dbname, ColumnFamilyData* cfd, const ImmutableDBOptions& db_options, const MutableCFOptions& mutable_cf_options, - const EnvOptions& env_options, VersionSet* versions, - InstrumentedMutex* db_mutex, std::atomic* shutting_down, + const uint64_t* max_memtable_id, const EnvOptions& env_options, + VersionSet* versions, InstrumentedMutex* db_mutex, + std::atomic* shutting_down, std::vector existing_snapshots, SequenceNumber earliest_write_conflict_snapshot, - JobContext* job_context, LogBuffer* log_buffer, - Directory* db_directory, Directory* output_file_directory, - CompressionType output_compression, Statistics* stats, - EventLogger* event_logger, bool measure_io_stats); + SnapshotChecker* snapshot_checker, JobContext* job_context, + LogBuffer* log_buffer, Directory* db_directory, + Directory* output_file_directory, CompressionType output_compression, + Statistics* stats, EventLogger* event_logger, bool measure_io_stats, + const bool sync_output_directory, const bool write_manifest, + Env::Priority thread_pri); ~FlushJob(); // Require db_mutex held. // Once PickMemTable() is called, either Run() or Cancel() has to be called. void PickMemTable(); - Status Run(FileMetaData* file_meta = nullptr); + Status Run(LogsWithPrepTracker* prep_tracker = nullptr, + FileMetaData* file_meta = nullptr); void Cancel(); TableProperties GetTableProperties() const { return table_properties_; } + const autovector& GetMemTables() const { return mems_; } private: void ReportStartedFlush(); void ReportFlushInputSize(const autovector& mems); void RecordFlushIOStats(); Status WriteLevel0Table(); + const std::string& dbname_; ColumnFamilyData* cfd_; const ImmutableDBOptions& db_options_; const MutableCFOptions& mutable_cf_options_; - const EnvOptions& env_options_; + // Pointer to a variable storing the largest memtable id to flush in this + // flush job. RocksDB uses this variable to select the memtables to flush in + // this job. All memtables in this column family with an ID smaller than or + // equal to *max_memtable_id_ will be selected for flush. If null, then all + // memtables in the column family will be selected. + const uint64_t* max_memtable_id_; + const EnvOptions env_options_; VersionSet* versions_; InstrumentedMutex* db_mutex_; std::atomic* shutting_down_; std::vector existing_snapshots_; SequenceNumber earliest_write_conflict_snapshot_; + SnapshotChecker* snapshot_checker_; JobContext* job_context_; LogBuffer* log_buffer_; Directory* db_directory_; @@ -98,6 +114,23 @@ class FlushJob { EventLogger* event_logger_; TableProperties table_properties_; bool measure_io_stats_; + // True if this flush job should call fsync on the output directory. False + // otherwise. + // Usually sync_output_directory_ is true. A flush job needs to call sync on + // the output directory before committing to the MANIFEST. + // However, an individual flush job does not have to call sync on the output + // directory if it is part of an atomic flush. After all flush jobs in the + // atomic flush succeed, call sync once on each distinct output directory. + const bool sync_output_directory_; + // True if this flush job should write to MANIFEST after successfully + // flushing memtables. False otherwise. + // Usually write_manifest_ is true. A flush job commits to the MANIFEST after + // flushing the memtables. + // However, an individual flush job cannot rashly write to the MANIFEST + // immediately after it finishes the flush if it is part of an atomic flush. + // In this case, only after all flush jobs succeed in flush can RocksDB + // commit to the MANIFEST. + const bool write_manifest_; // Variables below are set by PickMemTable(): FileMetaData meta_; @@ -105,6 +138,7 @@ class FlushJob { VersionEdit* edit_; Version* base_; bool pick_memtable_called; + Env::Priority thread_pri_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/flush_job_test.cc b/thirdparty/rocksdb/db/flush_job_test.cc index 34a3c983c3..199ed29cac 100644 --- a/thirdparty/rocksdb/db/flush_job_test.cc +++ b/thirdparty/rocksdb/db/flush_job_test.cc @@ -27,9 +27,10 @@ class FlushJobTest : public testing::Test { public: FlushJobTest() : env_(Env::Default()), - dbname_(test::TmpDir() + "/flush_job_test"), + dbname_(test::PerThreadDBPath("flush_job_test")), options_(), db_options_(options_), + column_family_names_({kDefaultColumnFamilyName, "foo", "bar"}), table_cache_(NewLRUCache(50000, 16)), write_buffer_manager_(db_options_.db_write_buffer_size), versions_(new VersionSet(dbname_, &db_options_, env_options_, @@ -40,11 +41,14 @@ class FlushJobTest : public testing::Test { EXPECT_OK(env_->CreateDirIfMissing(dbname_)); db_options_.db_paths.emplace_back(dbname_, std::numeric_limits::max()); + db_options_.statistics = rocksdb::CreateDBStatistics(); // TODO(icanadi) Remove this once we mock out VersionSet NewDB(); std::vector column_families; cf_options_.table_factory = mock_table_factory_; - column_families.emplace_back(kDefaultColumnFamilyName, cf_options_); + for (const auto& cf_name : column_family_names_) { + column_families.emplace_back(cf_name, cf_options_); + } EXPECT_OK(versions_->Recover(column_families, false)); } @@ -55,18 +59,38 @@ class FlushJobTest : public testing::Test { new_db.SetNextFile(2); new_db.SetLastSequence(0); + autovector new_cfs; + SequenceNumber last_seq = 1; + uint32_t cf_id = 1; + for (size_t i = 1; i != column_family_names_.size(); ++i) { + VersionEdit new_cf; + new_cf.AddColumnFamily(column_family_names_[i]); + new_cf.SetColumnFamily(cf_id++); + new_cf.SetLogNumber(0); + new_cf.SetNextFile(2); + new_cf.SetLastSequence(last_seq++); + new_cfs.emplace_back(new_cf); + } + const std::string manifest = DescriptorFileName(dbname_, 1); - unique_ptr file; + std::unique_ptr file; Status s = env_->NewWritableFile( manifest, &file, env_->OptimizeForManifestWrite(env_options_)); ASSERT_OK(s); - unique_ptr file_writer( - new WritableFileWriter(std::move(file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(file), manifest, EnvOptions())); { log::Writer log(std::move(file_writer), 0, false); std::string record; new_db.EncodeTo(&record); s = log.AddRecord(record); + + for (const auto& e : new_cfs) { + record.clear(); + e.EncodeTo(&record); + s = log.AddRecord(record); + ASSERT_OK(s); + } } ASSERT_OK(s); // Make "CURRENT" file that points to the new manifest file. @@ -78,6 +102,7 @@ class FlushJobTest : public testing::Test { EnvOptions env_options_; Options options_; ImmutableDBOptions db_options_; + const std::vector column_family_names_; std::shared_ptr table_cache_; WriteController write_controller_; WriteBufferManager write_buffer_manager_; @@ -92,11 +117,15 @@ TEST_F(FlushJobTest, Empty) { JobContext job_context(0); auto cfd = versions_->GetColumnFamilySet()->GetDefault(); EventLogger event_logger(db_options_.info_log.get()); + SnapshotChecker* snapshot_checker = nullptr; // not relavant FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, *cfd->GetLatestMutableCFOptions(), - env_options_, versions_.get(), &mutex_, &shutting_down_, - {}, kMaxSequenceNumber, &job_context, nullptr, nullptr, - nullptr, kNoCompression, nullptr, &event_logger, false); + nullptr /* memtable_id */, env_options_, versions_.get(), + &mutex_, &shutting_down_, {}, kMaxSequenceNumber, + snapshot_checker, &job_context, nullptr, nullptr, nullptr, + kNoCompression, nullptr, &event_logger, false, + true /* sync_output_directory */, + true /* write_manifest */, Env::Priority::USER); { InstrumentedMutexLock l(&mutex_); flush_job.PickMemTable(); @@ -136,42 +165,227 @@ TEST_F(FlushJobTest, NonEmpty) { } EventLogger event_logger(db_options_.info_log.get()); + SnapshotChecker* snapshot_checker = nullptr; // not relavant FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, *cfd->GetLatestMutableCFOptions(), - env_options_, versions_.get(), &mutex_, &shutting_down_, - {}, kMaxSequenceNumber, &job_context, nullptr, nullptr, - nullptr, kNoCompression, nullptr, &event_logger, true); - FileMetaData fd; + nullptr /* memtable_id */, env_options_, versions_.get(), + &mutex_, &shutting_down_, {}, kMaxSequenceNumber, + snapshot_checker, &job_context, nullptr, nullptr, nullptr, + kNoCompression, db_options_.statistics.get(), + &event_logger, true, true /* sync_output_directory */, + true /* write_manifest */, Env::Priority::USER); + + HistogramData hist; + FileMetaData file_meta; mutex_.Lock(); flush_job.PickMemTable(); - ASSERT_OK(flush_job.Run(&fd)); + ASSERT_OK(flush_job.Run(nullptr, &file_meta)); mutex_.Unlock(); - ASSERT_EQ(ToString(0), fd.smallest.user_key().ToString()); - ASSERT_EQ("9999a", - fd.largest.user_key().ToString()); // range tombstone end key - ASSERT_EQ(1, fd.smallest_seqno); - ASSERT_EQ(10000, fd.largest_seqno); // range tombstone seqnum 10000 + db_options_.statistics->histogramData(FLUSH_TIME, &hist); + ASSERT_GT(hist.average, 0.0); + + ASSERT_EQ(ToString(0), file_meta.smallest.user_key().ToString()); + ASSERT_EQ( + "9999a", + file_meta.largest.user_key().ToString()); // range tombstone end key + ASSERT_EQ(1, file_meta.fd.smallest_seqno); + ASSERT_EQ(10000, file_meta.fd.largest_seqno); // range tombstone seqnum 10000 mock_table_factory_->AssertSingleFile(inserted_keys); job_context.Clean(); } +TEST_F(FlushJobTest, FlushMemTablesSingleColumnFamily) { + const size_t num_mems = 2; + const size_t num_mems_to_flush = 1; + const size_t num_keys_per_table = 100; + JobContext job_context(0); + ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault(); + std::vector memtable_ids; + std::vector new_mems; + for (size_t i = 0; i != num_mems; ++i) { + MemTable* mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(), + kMaxSequenceNumber); + mem->SetID(i); + mem->Ref(); + new_mems.emplace_back(mem); + memtable_ids.push_back(mem->GetID()); + + for (size_t j = 0; j < num_keys_per_table; ++j) { + std::string key(ToString(j + i * num_keys_per_table)); + std::string value("value" + key); + mem->Add(SequenceNumber(j + i * num_keys_per_table), kTypeValue, key, + value); + } + } + + autovector to_delete; + for (auto mem : new_mems) { + cfd->imm()->Add(mem, &to_delete); + } + + EventLogger event_logger(db_options_.info_log.get()); + SnapshotChecker* snapshot_checker = nullptr; // not relavant + + assert(memtable_ids.size() == num_mems); + uint64_t smallest_memtable_id = memtable_ids.front(); + uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1; + + FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(), + db_options_, *cfd->GetLatestMutableCFOptions(), + &flush_memtable_id, env_options_, versions_.get(), &mutex_, + &shutting_down_, {}, kMaxSequenceNumber, snapshot_checker, + &job_context, nullptr, nullptr, nullptr, kNoCompression, + db_options_.statistics.get(), &event_logger, true, + true /* sync_output_directory */, + true /* write_manifest */, Env::Priority::USER); + HistogramData hist; + FileMetaData file_meta; + mutex_.Lock(); + flush_job.PickMemTable(); + ASSERT_OK(flush_job.Run(nullptr /* prep_tracker */, &file_meta)); + mutex_.Unlock(); + db_options_.statistics->histogramData(FLUSH_TIME, &hist); + ASSERT_GT(hist.average, 0.0); + + ASSERT_EQ(ToString(0), file_meta.smallest.user_key().ToString()); + ASSERT_EQ("99", file_meta.largest.user_key().ToString()); + ASSERT_EQ(0, file_meta.fd.smallest_seqno); + ASSERT_EQ(SequenceNumber(num_mems_to_flush * num_keys_per_table - 1), + file_meta.fd.largest_seqno); + + for (auto m : to_delete) { + delete m; + } + to_delete.clear(); + job_context.Clean(); +} + +TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) { + autovector all_cfds; + for (auto cfd : *versions_->GetColumnFamilySet()) { + all_cfds.push_back(cfd); + } + const std::vector num_memtables = {2, 1, 3}; + assert(num_memtables.size() == column_family_names_.size()); + const size_t num_keys_per_memtable = 1000; + JobContext job_context(0); + std::vector memtable_ids; + std::vector smallest_seqs; + std::vector largest_seqs; + autovector to_delete; + SequenceNumber curr_seqno = 0; + size_t k = 0; + for (auto cfd : all_cfds) { + smallest_seqs.push_back(curr_seqno); + for (size_t i = 0; i != num_memtables[k]; ++i) { + MemTable* mem = cfd->ConstructNewMemtable( + *cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber); + mem->SetID(i); + mem->Ref(); + + for (size_t j = 0; j != num_keys_per_memtable; ++j) { + std::string key(ToString(j + i * num_keys_per_memtable)); + std::string value("value" + key); + mem->Add(curr_seqno++, kTypeValue, key, value); + } + + cfd->imm()->Add(mem, &to_delete); + } + largest_seqs.push_back(curr_seqno - 1); + memtable_ids.push_back(num_memtables[k++] - 1); + } + + EventLogger event_logger(db_options_.info_log.get()); + SnapshotChecker* snapshot_checker = nullptr; // not relevant + std::vector flush_jobs; + k = 0; + for (auto cfd : all_cfds) { + std::vector snapshot_seqs; + flush_jobs.emplace_back( + dbname_, cfd, db_options_, *cfd->GetLatestMutableCFOptions(), + &memtable_ids[k], env_options_, versions_.get(), &mutex_, + &shutting_down_, snapshot_seqs, kMaxSequenceNumber, snapshot_checker, + &job_context, nullptr, nullptr, nullptr, kNoCompression, + db_options_.statistics.get(), &event_logger, true, + false /* sync_output_directory */, false /* write_manifest */, + Env::Priority::USER); + k++; + } + HistogramData hist; + std::vector file_metas; + // Call reserve to avoid auto-resizing + file_metas.reserve(flush_jobs.size()); + mutex_.Lock(); + for (auto& job : flush_jobs) { + job.PickMemTable(); + } + for (auto& job : flush_jobs) { + FileMetaData meta; + // Run will release and re-acquire mutex + ASSERT_OK(job.Run(nullptr /**/, &meta)); + file_metas.emplace_back(meta); + } + autovector file_meta_ptrs; + for (auto& meta : file_metas) { + file_meta_ptrs.push_back(&meta); + } + autovector*> mems_list; + for (size_t i = 0; i != all_cfds.size(); ++i) { + const auto& mems = flush_jobs[i].GetMemTables(); + mems_list.push_back(&mems); + } + autovector mutable_cf_options_list; + for (auto cfd : all_cfds) { + mutable_cf_options_list.push_back(cfd->GetLatestMutableCFOptions()); + } + + Status s = InstallMemtableAtomicFlushResults( + nullptr /* imm_lists */, all_cfds, mutable_cf_options_list, mems_list, + versions_.get(), &mutex_, file_meta_ptrs, &job_context.memtables_to_free, + nullptr /* db_directory */, nullptr /* log_buffer */); + ASSERT_OK(s); + + mutex_.Unlock(); + db_options_.statistics->histogramData(FLUSH_TIME, &hist); + ASSERT_GT(hist.average, 0.0); + k = 0; + for (const auto& file_meta : file_metas) { + ASSERT_EQ(ToString(0), file_meta.smallest.user_key().ToString()); + ASSERT_EQ("999", file_meta.largest.user_key() + .ToString()); // max key by bytewise comparator + ASSERT_EQ(smallest_seqs[k], file_meta.fd.smallest_seqno); + ASSERT_EQ(largest_seqs[k], file_meta.fd.largest_seqno); + // Verify that imm is empty + ASSERT_EQ(std::numeric_limits::max(), + all_cfds[k]->imm()->GetEarliestMemTableID()); + ASSERT_EQ(0, all_cfds[k]->imm()->GetLatestMemTableID()); + ++k; + } + + for (auto m : to_delete) { + delete m; + } + to_delete.clear(); + job_context.Clean(); +} + TEST_F(FlushJobTest, Snapshots) { JobContext job_context(0); auto cfd = versions_->GetColumnFamilySet()->GetDefault(); auto new_mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions(), kMaxSequenceNumber); - std::vector snapshots; std::set snapshots_set; int keys = 10000; int max_inserts_per_keys = 8; Random rnd(301); for (int i = 0; i < keys / 2; ++i) { - snapshots.push_back(rnd.Uniform(keys * (max_inserts_per_keys / 2)) + 1); - snapshots_set.insert(snapshots.back()); + snapshots_set.insert(rnd.Uniform(keys * (max_inserts_per_keys / 2)) + 1); } - std::sort(snapshots.begin(), snapshots.end()); + // set has already removed the duplicate snapshots + std::vector snapshots(snapshots_set.begin(), + snapshots_set.end()); new_mem->Ref(); SequenceNumber current_seqno = 0; @@ -202,16 +416,23 @@ TEST_F(FlushJobTest, Snapshots) { } EventLogger event_logger(db_options_.info_log.get()); - FlushJob flush_job( - dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_, - *cfd->GetLatestMutableCFOptions(), env_options_, versions_.get(), &mutex_, - &shutting_down_, snapshots, kMaxSequenceNumber, &job_context, nullptr, - nullptr, nullptr, kNoCompression, nullptr, &event_logger, true); + SnapshotChecker* snapshot_checker = nullptr; // not relavant + FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(), + db_options_, *cfd->GetLatestMutableCFOptions(), + nullptr /* memtable_id */, env_options_, versions_.get(), + &mutex_, &shutting_down_, snapshots, kMaxSequenceNumber, + snapshot_checker, &job_context, nullptr, nullptr, nullptr, + kNoCompression, db_options_.statistics.get(), + &event_logger, true, true /* sync_output_directory */, + true /* write_manifest */, Env::Priority::USER); mutex_.Lock(); flush_job.PickMemTable(); ASSERT_OK(flush_job.Run()); mutex_.Unlock(); mock_table_factory_->AssertSingleFile(inserted_keys); + HistogramData hist; + db_options_.statistics->histogramData(FLUSH_TIME, &hist); + ASSERT_GT(hist.average, 0.0); job_context.Clean(); } diff --git a/thirdparty/rocksdb/db/forward_iterator.cc b/thirdparty/rocksdb/db/forward_iterator.cc index 65fff95956..d1c073468b 100644 --- a/thirdparty/rocksdb/db/forward_iterator.cc +++ b/thirdparty/rocksdb/db/forward_iterator.cc @@ -15,6 +15,8 @@ #include "db/db_iter.h" #include "db/dbformat.h" #include "db/job_context.h" +#include "db/range_del_aggregator.h" +#include "db/range_tombstone_fragmenter.h" #include "rocksdb/env.h" #include "rocksdb/slice.h" #include "rocksdb/slice_transform.h" @@ -25,24 +27,26 @@ namespace rocksdb { // Usage: -// LevelIterator iter; +// ForwardLevelIterator iter; // iter.SetFileIndex(file_index); -// iter.Seek(target); +// iter.Seek(target); // or iter.SeekToFirst(); // iter.Next() -class LevelIterator : public InternalIterator { +class ForwardLevelIterator : public InternalIterator { public: - LevelIterator(const ColumnFamilyData* const cfd, - const ReadOptions& read_options, - const std::vector& files) + ForwardLevelIterator(const ColumnFamilyData* const cfd, + const ReadOptions& read_options, + const std::vector& files, + const SliceTransform* prefix_extractor) : cfd_(cfd), read_options_(read_options), files_(files), valid_(false), file_index_(std::numeric_limits::max()), file_iter_(nullptr), - pinned_iters_mgr_(nullptr) {} + pinned_iters_mgr_(nullptr), + prefix_extractor_(prefix_extractor) {} - ~LevelIterator() { + ~ForwardLevelIterator() override { // Reset current pointer if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) { pinned_iters_mgr_->PinIterator(file_iter_); @@ -53,11 +57,11 @@ class LevelIterator : public InternalIterator { void SetFileIndex(uint32_t file_index) { assert(file_index < files_.size()); + status_ = Status::OK(); if (file_index != file_index_) { file_index_ = file_index; Reset(); } - valid_ = false; } void Reset() { assert(file_index_ < files_.size()); @@ -69,51 +73,70 @@ class LevelIterator : public InternalIterator { delete file_iter_; } - RangeDelAggregator range_del_agg( - cfd_->internal_comparator(), {} /* snapshots */); + ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(), + kMaxSequenceNumber /* upper_bound */); file_iter_ = cfd_->table_cache()->NewIterator( read_options_, *(cfd_->soptions()), cfd_->internal_comparator(), - files_[file_index_]->fd, + *files_[file_index_], read_options_.ignore_range_deletions ? nullptr : &range_del_agg, - nullptr /* table_reader_ptr */, nullptr, false); + prefix_extractor_, nullptr /* table_reader_ptr */, nullptr, false); file_iter_->SetPinnedItersMgr(pinned_iters_mgr_); + valid_ = false; if (!range_del_agg.IsEmpty()) { status_ = Status::NotSupported( "Range tombstones unsupported with ForwardIterator"); - valid_ = false; } } void SeekToLast() override { - status_ = Status::NotSupported("LevelIterator::SeekToLast()"); + status_ = Status::NotSupported("ForwardLevelIterator::SeekToLast()"); valid_ = false; } void Prev() override { - status_ = Status::NotSupported("LevelIterator::Prev()"); + status_ = Status::NotSupported("ForwardLevelIterator::Prev()"); valid_ = false; } bool Valid() const override { return valid_; } void SeekToFirst() override { - SetFileIndex(0); + assert(file_iter_ != nullptr); + if (!status_.ok()) { + assert(!valid_); + return; + } file_iter_->SeekToFirst(); valid_ = file_iter_->Valid(); } void Seek(const Slice& internal_key) override { assert(file_iter_ != nullptr); + + // This deviates from the usual convention for InternalIterator::Seek() in + // that it doesn't discard pre-existing error status. That's because this + // Seek() is only supposed to be called immediately after SetFileIndex() + // (which discards pre-existing error status), and SetFileIndex() may set + // an error status, which we shouldn't discard. + if (!status_.ok()) { + assert(!valid_); + return; + } + file_iter_->Seek(internal_key); valid_ = file_iter_->Valid(); } - void SeekForPrev(const Slice& internal_key) override { - status_ = Status::NotSupported("LevelIterator::SeekForPrev()"); + void SeekForPrev(const Slice& /*internal_key*/) override { + status_ = Status::NotSupported("ForwardLevelIterator::SeekForPrev()"); valid_ = false; } void Next() override { assert(valid_); file_iter_->Next(); for (;;) { - if (file_iter_->status().IsIncomplete() || file_iter_->Valid()) { - valid_ = !file_iter_->status().IsIncomplete(); + valid_ = file_iter_->Valid(); + if (!file_iter_->status().ok()) { + assert(!valid_); + return; + } + if (valid_) { return; } if (file_index_ + 1 >= files_.size()) { @@ -121,6 +144,10 @@ class LevelIterator : public InternalIterator { return; } SetFileIndex(file_index_ + 1); + if (!status_.ok()) { + assert(!valid_); + return; + } file_iter_->SeekToFirst(); } } @@ -135,7 +162,7 @@ class LevelIterator : public InternalIterator { Status status() const override { if (!status_.ok()) { return status_; - } else if (file_iter_ && !file_iter_->status().ok()) { + } else if (file_iter_) { return file_iter_->status(); } return Status::OK(); @@ -165,6 +192,7 @@ class LevelIterator : public InternalIterator { Status status_; InternalIterator* file_iter_; PinnedIteratorsManager* pinned_iters_mgr_; + const SliceTransform* prefix_extractor_; }; ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options, @@ -173,7 +201,7 @@ ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options, : db_(db), read_options_(read_options), cfd_(cfd), - prefix_extractor_(cfd->ioptions()->prefix_extractor), + prefix_extractor_(current_sv->mutable_cf_options.prefix_extractor.get()), user_comparator_(cfd->user_comparator()), immutable_min_heap_(MinIterComparator(&cfd_->internal_comparator())), sv_(current_sv), @@ -196,38 +224,62 @@ ForwardIterator::~ForwardIterator() { Cleanup(true); } -namespace { -// Used in PinnedIteratorsManager to release pinned SuperVersion -static void ReleaseSuperVersionFunc(void* sv) { - delete reinterpret_cast(sv); -} -} // namespace - -void ForwardIterator::SVCleanup() { - if (sv_ != nullptr && sv_->Unref()) { +void ForwardIterator::SVCleanup(DBImpl* db, SuperVersion* sv, + bool background_purge_on_iterator_cleanup) { + if (sv->Unref()) { // Job id == 0 means that this is not our background process, but rather // user thread JobContext job_context(0); - db_->mutex_.Lock(); - sv_->Cleanup(); - db_->FindObsoleteFiles(&job_context, false, true); - if (read_options_.background_purge_on_iterator_cleanup) { - db_->ScheduleBgLogWriterClose(&job_context); - } - db_->mutex_.Unlock(); - if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) { - pinned_iters_mgr_->PinPtr(sv_, &ReleaseSuperVersionFunc); - } else { - delete sv_; - } + db->mutex_.Lock(); + sv->Cleanup(); + db->FindObsoleteFiles(&job_context, false, true); + if (background_purge_on_iterator_cleanup) { + db->ScheduleBgLogWriterClose(&job_context); + } + db->mutex_.Unlock(); + delete sv; if (job_context.HaveSomethingToDelete()) { - db_->PurgeObsoleteFiles( - job_context, read_options_.background_purge_on_iterator_cleanup); + db->PurgeObsoleteFiles(job_context, background_purge_on_iterator_cleanup); } job_context.Clean(); } } +namespace { +struct SVCleanupParams { + DBImpl* db; + SuperVersion* sv; + bool background_purge_on_iterator_cleanup; +}; +} + +// Used in PinnedIteratorsManager to release pinned SuperVersion +void ForwardIterator::DeferredSVCleanup(void* arg) { + auto d = reinterpret_cast(arg); + ForwardIterator::SVCleanup( + d->db, d->sv, d->background_purge_on_iterator_cleanup); + delete d; +} + +void ForwardIterator::SVCleanup() { + if (sv_ == nullptr) { + return; + } + bool background_purge = + read_options_.background_purge_on_iterator_cleanup || + db_->immutable_db_options().avoid_unnecessary_blocking_io; + if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) { + // pinned_iters_mgr_ tells us to make sure that all visited key-value slices + // are alive until pinned_iters_mgr_->ReleasePinnedData() is called. + // The slices may point into some memtables owned by sv_, so we need to keep + // sv_ referenced until pinned_iters_mgr_ unpins everything. + auto p = new SVCleanupParams{db_, sv_, background_purge}; + pinned_iters_mgr_->PinPtr(p, &ForwardIterator::DeferredSVCleanup); + } else { + SVCleanup(db_, sv_, background_purge); + } +} + void ForwardIterator::Cleanup(bool release_sv) { if (mutable_iter_ != nullptr) { DeleteIterator(mutable_iter_, true /* is_arena */); @@ -277,9 +329,6 @@ bool ForwardIterator::IsOverUpperBound(const Slice& internal_key) const { } void ForwardIterator::Seek(const Slice& internal_key) { - if (IsOverUpperBound(internal_key)) { - valid_ = false; - } if (sv_ == nullptr) { RebuildIterators(true); } else if (sv_->version_number != cfd_->GetSuperVersionNumber()) { @@ -361,14 +410,13 @@ void ForwardIterator::SeekInternal(const Slice& internal_key, if (!l0_iters_[i]->status().ok()) { immutable_status_ = l0_iters_[i]->status(); - } else if (l0_iters_[i]->Valid()) { - if (!IsOverUpperBound(l0_iters_[i]->key())) { - immutable_min_heap_.push(l0_iters_[i]); - } else { - has_iter_trimmed_for_upper_bound_ = true; - DeleteIterator(l0_iters_[i]); - l0_iters_[i] = nullptr; - } + } else if (l0_iters_[i]->Valid() && + !IsOverUpperBound(l0_iters_[i]->key())) { + immutable_min_heap_.push(l0_iters_[i]); + } else { + has_iter_trimmed_for_upper_bound_ = true; + DeleteIterator(l0_iters_[i]); + l0_iters_[i] = nullptr; } } @@ -395,15 +443,14 @@ void ForwardIterator::SeekInternal(const Slice& internal_key, if (!level_iters_[level - 1]->status().ok()) { immutable_status_ = level_iters_[level - 1]->status(); - } else if (level_iters_[level - 1]->Valid()) { - if (!IsOverUpperBound(level_iters_[level - 1]->key())) { - immutable_min_heap_.push(level_iters_[level - 1]); - } else { - // Nothing in this level is interesting. Remove. - has_iter_trimmed_for_upper_bound_ = true; - DeleteIterator(level_iters_[level - 1]); - level_iters_[level - 1] = nullptr; - } + } else if (level_iters_[level - 1]->Valid() && + !IsOverUpperBound(level_iters_[level - 1]->key())) { + immutable_min_heap_.push(level_iters_[level - 1]); + } else { + // Nothing in this level is interesting. Remove. + has_iter_trimmed_for_upper_bound_ = true; + DeleteIterator(level_iters_[level - 1]); + level_iters_[level - 1] = nullptr; } } } @@ -541,7 +588,7 @@ void ForwardIterator::UpdateChildrenPinnedItersMgr() { } // Set PinnedIteratorsManager for L1+ levels iterators. - for (LevelIterator* child_iter : level_iters_) { + for (ForwardLevelIterator* child_iter : level_iters_) { if (child_iter) { child_iter->SetPinnedItersMgr(pinned_iters_mgr_); } @@ -565,13 +612,14 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) { // New sv_ = cfd_->GetReferencedSuperVersion(&(db_->mutex_)); } - RangeDelAggregator range_del_agg( - InternalKeyComparator(cfd_->internal_comparator()), {} /* snapshots */); + ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(), + kMaxSequenceNumber /* upper_bound */); mutable_iter_ = sv_->mem->NewIterator(read_options_, &arena_); sv_->imm->AddIterators(read_options_, &imm_iters_, &arena_); if (!read_options_.ignore_range_deletions) { - std::unique_ptr range_del_iter( - sv_->mem->NewRangeTombstoneIterator(read_options_)); + std::unique_ptr range_del_iter( + sv_->mem->NewRangeTombstoneIterator( + read_options_, sv_->current->version_set()->LastSequence())); range_del_agg.AddTombstones(std::move(range_del_iter)); sv_->imm->AddRangeTombstoneIterators(read_options_, &arena_, &range_del_agg); @@ -585,13 +633,16 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) { if ((read_options_.iterate_upper_bound != nullptr) && cfd_->internal_comparator().user_comparator()->Compare( l0->smallest.user_key(), *read_options_.iterate_upper_bound) > 0) { - has_iter_trimmed_for_upper_bound_ = true; + // No need to set has_iter_trimmed_for_upper_bound_: this ForwardIterator + // will never be interested in files with smallest key above + // iterate_upper_bound, since iterate_upper_bound can't be changed. l0_iters_.push_back(nullptr); continue; } l0_iters_.push_back(cfd_->table_cache()->NewIterator( - read_options_, *cfd_->soptions(), cfd_->internal_comparator(), l0->fd, - read_options_.ignore_range_deletions ? nullptr : &range_del_agg)); + read_options_, *cfd_->soptions(), cfd_->internal_comparator(), *l0, + read_options_.ignore_range_deletions ? nullptr : &range_del_agg, + sv_->mutable_cf_options.prefix_extractor.get())); } BuildLevelIterators(vstorage); current_ = nullptr; @@ -620,14 +671,15 @@ void ForwardIterator::RenewIterators() { mutable_iter_ = svnew->mem->NewIterator(read_options_, &arena_); svnew->imm->AddIterators(read_options_, &imm_iters_, &arena_); - RangeDelAggregator range_del_agg( - InternalKeyComparator(cfd_->internal_comparator()), {} /* snapshots */); + ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(), + kMaxSequenceNumber /* upper_bound */); if (!read_options_.ignore_range_deletions) { - std::unique_ptr range_del_iter( - svnew->mem->NewRangeTombstoneIterator(read_options_)); + std::unique_ptr range_del_iter( + svnew->mem->NewRangeTombstoneIterator( + read_options_, sv_->current->version_set()->LastSequence())); range_del_agg.AddTombstones(std::move(range_del_iter)); - sv_->imm->AddRangeTombstoneIterators(read_options_, &arena_, - &range_del_agg); + svnew->imm->AddRangeTombstoneIterators(read_options_, &arena_, + &range_del_agg); } const auto* vstorage = sv_->current->storage_info(); @@ -660,8 +712,9 @@ void ForwardIterator::RenewIterators() { } l0_iters_new.push_back(cfd_->table_cache()->NewIterator( read_options_, *cfd_->soptions(), cfd_->internal_comparator(), - l0_files_new[inew]->fd, - read_options_.ignore_range_deletions ? nullptr : &range_del_agg)); + *l0_files_new[inew], + read_options_.ignore_range_deletions ? nullptr : &range_del_agg, + svnew->mutable_cf_options.prefix_extractor.get())); } for (auto* f : l0_iters_) { @@ -702,8 +755,9 @@ void ForwardIterator::BuildLevelIterators(const VersionStorageInfo* vstorage) { has_iter_trimmed_for_upper_bound_ = true; } } else { - level_iters_.push_back( - new LevelIterator(cfd_, read_options_, level_files)); + level_iters_.push_back(new ForwardLevelIterator( + cfd_, read_options_, level_files, + sv_->mutable_cf_options.prefix_extractor.get())); } } } @@ -718,7 +772,8 @@ void ForwardIterator::ResetIncompleteIterators() { DeleteIterator(l0_iters_[i]); l0_iters_[i] = cfd_->table_cache()->NewIterator( read_options_, *cfd_->soptions(), cfd_->internal_comparator(), - l0_files[i]->fd, nullptr /* range_del_agg */); + *l0_files[i], nullptr /* range_del_agg */, + sv_->mutable_cf_options.prefix_extractor.get()); l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_); } @@ -753,7 +808,7 @@ void ForwardIterator::UpdateCurrent() { current_ = mutable_iter_; } } - valid_ = (current_ != nullptr); + valid_ = current_ != nullptr && immutable_status_.ok(); if (!status_.ok()) { status_ = Status::OK(); } @@ -867,21 +922,13 @@ bool ForwardIterator::TEST_CheckDeletedIters(int* pdeleted_iters, uint32_t ForwardIterator::FindFileInRange( const std::vector& files, const Slice& internal_key, uint32_t left, uint32_t right) { - while (left < right) { - uint32_t mid = (left + right) / 2; - const FileMetaData* f = files[mid]; - if (cfd_->internal_comparator().InternalKeyComparator::Compare( - f->largest.Encode(), internal_key) < 0) { - // Key at "mid.largest" is < "target". Therefore all - // files at or before "mid" are uninteresting. - left = mid + 1; - } else { - // Key at "mid.largest" is >= "target". Therefore all files - // after "mid" are uninteresting. - right = mid; - } - } - return right; + auto cmp = [&](const FileMetaData* f, const Slice& key) -> bool { + return cfd_->internal_comparator().InternalKeyComparator::Compare( + f->largest.Encode(), key) < 0; + }; + const auto &b = files.begin(); + return static_cast(std::lower_bound(b + left, + b + right, internal_key, cmp) - b); } void ForwardIterator::DeleteIterator(InternalIterator* iter, bool is_arena) { diff --git a/thirdparty/rocksdb/db/forward_iterator.h b/thirdparty/rocksdb/db/forward_iterator.h index d4f32cba9f..146588d961 100644 --- a/thirdparty/rocksdb/db/forward_iterator.h +++ b/thirdparty/rocksdb/db/forward_iterator.h @@ -23,7 +23,7 @@ class DBImpl; class Env; struct SuperVersion; class ColumnFamilyData; -class LevelIterator; +class ForwardLevelIterator; class VersionStorageInfo; struct FileMetaData; @@ -55,7 +55,7 @@ class ForwardIterator : public InternalIterator { ColumnFamilyData* cfd, SuperVersion* current_sv = nullptr); virtual ~ForwardIterator(); - void SeekForPrev(const Slice& target) override { + void SeekForPrev(const Slice& /*target*/) override { status_ = Status::NotSupported("ForwardIterator::SeekForPrev()"); valid_ = false; } @@ -85,7 +85,14 @@ class ForwardIterator : public InternalIterator { private: void Cleanup(bool release_sv); + // Unreference and, if needed, clean up the current SuperVersion. This is + // either done immediately or deferred until this iterator is unpinned by + // PinnedIteratorsManager. void SVCleanup(); + static void SVCleanup( + DBImpl* db, SuperVersion* sv, bool background_purge_on_iterator_cleanup); + static void DeferredSVCleanup(void* arg); + void RebuildIterators(bool refresh_sv); void RenewIterators(); void BuildLevelIterators(const VersionStorageInfo* vstorage); @@ -119,7 +126,7 @@ class ForwardIterator : public InternalIterator { InternalIterator* mutable_iter_; std::vector imm_iters_; std::vector l0_iters_; - std::vector level_iters_; + std::vector level_iters_; InternalIterator* current_; bool valid_; diff --git a/thirdparty/rocksdb/db/forward_iterator_bench.cc b/thirdparty/rocksdb/db/forward_iterator_bench.cc index e9ae770cfa..113ded94b6 100644 --- a/thirdparty/rocksdb/db/forward_iterator_bench.cc +++ b/thirdparty/rocksdb/db/forward_iterator_bench.cc @@ -17,7 +17,6 @@ int main() { // Block forward_iterator_bench under MAC and Windows int main() { return 0; } #else -#include #include #include #include @@ -30,11 +29,12 @@ int main() { return 0; } #include #include +#include "port/port.h" #include "rocksdb/cache.h" #include "rocksdb/db.h" #include "rocksdb/status.h" #include "rocksdb/table.h" -#include "port/port.h" +#include "util/gflags_compat.h" #include "util/testharness.h" const int MAX_SHARDS = 100000; @@ -319,11 +319,11 @@ struct StatsThread { }; int main(int argc, char** argv) { - GFLAGS::ParseCommandLineFlags(&argc, &argv, true); + GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); std::mt19937 rng{std::random_device()()}; rocksdb::Status status; - std::string path = rocksdb::test::TmpDir() + "/forward_iterator_test"; + std::string path = rocksdb::test::PerThreadDBPath("forward_iterator_test"); fprintf(stderr, "db path is %s\n", path.c_str()); rocksdb::Options options; options.create_if_missing = true; diff --git a/thirdparty/rocksdb/db/in_memory_stats_history.cc b/thirdparty/rocksdb/db/in_memory_stats_history.cc new file mode 100644 index 0000000000..39355cfbe0 --- /dev/null +++ b/thirdparty/rocksdb/db/in_memory_stats_history.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2011-present, Facebook, 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). +// 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. + +#include "db/db_impl.h" +#include "db/in_memory_stats_history.h" + +namespace rocksdb { + +InMemoryStatsHistoryIterator::~InMemoryStatsHistoryIterator() {} + +bool InMemoryStatsHistoryIterator::Valid() const { return valid_; } + +Status InMemoryStatsHistoryIterator::status() const { return status_; } + +void InMemoryStatsHistoryIterator::Next() { + // increment start_time by 1 to avoid infinite loop + AdvanceIteratorByTime(GetStatsTime() + 1, end_time_); +} + +uint64_t InMemoryStatsHistoryIterator::GetStatsTime() const { return time_; } + +const std::map& +InMemoryStatsHistoryIterator::GetStatsMap() const { + return stats_map_; +} + +// advance the iterator to the next time between [start_time, end_time) +// if success, update time_ and stats_map_ with new_time and stats_map +void InMemoryStatsHistoryIterator::AdvanceIteratorByTime(uint64_t start_time, + uint64_t end_time) { + // try to find next entry in stats_history_ map + if (db_impl_ != nullptr) { + valid_ = + db_impl_->FindStatsByTime(start_time, end_time, &time_, &stats_map_); + } else { + valid_ = false; + } +} + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/in_memory_stats_history.h b/thirdparty/rocksdb/db/in_memory_stats_history.h new file mode 100644 index 0000000000..4b52e23fff --- /dev/null +++ b/thirdparty/rocksdb/db/in_memory_stats_history.h @@ -0,0 +1,55 @@ +// Copyright (c) 2011-present, Facebook, 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). +// 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. + +#pragma once + +#include "rocksdb/stats_history.h" + +namespace rocksdb { + +class InMemoryStatsHistoryIterator final : public StatsHistoryIterator { + public: + InMemoryStatsHistoryIterator(uint64_t start_time, uint64_t end_time, + DBImpl* db_impl) + : start_time_(start_time), + end_time_(end_time), + valid_(true), + db_impl_(db_impl) { + AdvanceIteratorByTime(start_time_, end_time_); + } + ~InMemoryStatsHistoryIterator() override; + bool Valid() const override; + Status status() const override; + + void Next() override; + uint64_t GetStatsTime() const override; + + const std::map& GetStatsMap() const override; + + private: + // advance the iterator to the next stats history record with timestamp + // between [start_time, end_time) + void AdvanceIteratorByTime(uint64_t start_time, uint64_t end_time); + + // No copying allowed + InMemoryStatsHistoryIterator(const InMemoryStatsHistoryIterator&) = delete; + void operator=(const InMemoryStatsHistoryIterator&) = delete; + InMemoryStatsHistoryIterator(InMemoryStatsHistoryIterator&&) = delete; + InMemoryStatsHistoryIterator& operator=(InMemoryStatsHistoryIterator&&) = + delete; + + uint64_t time_; + uint64_t start_time_; + uint64_t end_time_; + std::map stats_map_; + Status status_; + bool valid_; + DBImpl* db_impl_; +}; + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/internal_stats.cc b/thirdparty/rocksdb/db/internal_stats.cc index e98bd98cf7..51e55f5839 100644 --- a/thirdparty/rocksdb/db/internal_stats.cc +++ b/thirdparty/rocksdb/db/internal_stats.cc @@ -18,9 +18,10 @@ #include #include #include -#include "db/column_family.h" +#include "db/column_family.h" #include "db/db_impl.h" +#include "table/block_based_table_factory.h" #include "util/string_util.h" namespace rocksdb { @@ -44,6 +45,8 @@ const std::map InternalStats::compaction_level_stats = {LevelStatType::READ_MBPS, LevelStat{"ReadMBps", "Rd(MB/s)"}}, {LevelStatType::WRITE_MBPS, LevelStat{"WriteMBps", "Wr(MB/s)"}}, {LevelStatType::COMP_SEC, LevelStat{"CompSec", "Comp(sec)"}}, + {LevelStatType::COMP_CPU_SEC, + LevelStat{"CompMergeCPU", "CompMergeCPU(sec)"}}, {LevelStatType::COMP_COUNT, LevelStat{"CompCount", "Comp(cnt)"}}, {LevelStatType::AVG_SEC, LevelStat{"AvgSec", "Avg(sec)"}}, {LevelStatType::KEY_IN, LevelStat{"KeyIn", "KeyIn"}}, @@ -55,7 +58,8 @@ const double kMB = 1048576.0; const double kGB = kMB * 1024; const double kMicrosInSec = 1000000.0; -void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) { +void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name, + const std::string& group_by) { int written_size = snprintf(buf, len, "\n** Compaction Stats [%s] **\n", cf_name.c_str()); auto hdr = [](LevelStatType t) { @@ -63,15 +67,16 @@ void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) { }; int line_size = snprintf( buf + written_size, len - written_size, - "Level %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n", + "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n", // Note that we skip COMPACTED_FILES and merge it with Files column - hdr(LevelStatType::NUM_FILES), hdr(LevelStatType::SIZE_BYTES), - hdr(LevelStatType::SCORE), hdr(LevelStatType::READ_GB), - hdr(LevelStatType::RN_GB), hdr(LevelStatType::RNP1_GB), - hdr(LevelStatType::WRITE_GB), hdr(LevelStatType::W_NEW_GB), - hdr(LevelStatType::MOVED_GB), hdr(LevelStatType::WRITE_AMP), - hdr(LevelStatType::READ_MBPS), hdr(LevelStatType::WRITE_MBPS), - hdr(LevelStatType::COMP_SEC), hdr(LevelStatType::COMP_COUNT), + group_by.c_str(), hdr(LevelStatType::NUM_FILES), + hdr(LevelStatType::SIZE_BYTES), hdr(LevelStatType::SCORE), + hdr(LevelStatType::READ_GB), hdr(LevelStatType::RN_GB), + hdr(LevelStatType::RNP1_GB), hdr(LevelStatType::WRITE_GB), + hdr(LevelStatType::W_NEW_GB), hdr(LevelStatType::MOVED_GB), + hdr(LevelStatType::WRITE_AMP), hdr(LevelStatType::READ_MBPS), + hdr(LevelStatType::WRITE_MBPS), hdr(LevelStatType::COMP_SEC), + hdr(LevelStatType::COMP_CPU_SEC), hdr(LevelStatType::COMP_COUNT), hdr(LevelStatType::AVG_SEC), hdr(LevelStatType::KEY_IN), hdr(LevelStatType::KEY_DROP)); @@ -86,8 +91,7 @@ void PrepareLevelStats(std::map* level_stats, const InternalStats::CompactionStats& stats) { uint64_t bytes_read = stats.bytes_read_non_output_levels + stats.bytes_read_output_level; - int64_t bytes_new = - stats.bytes_written - stats.bytes_read_output_level; + int64_t bytes_new = stats.bytes_written - stats.bytes_read_output_level; double elapsed = (stats.micros + 1) / kMicrosInSec; (*level_stats)[LevelStatType::NUM_FILES] = num_files; @@ -106,6 +110,7 @@ void PrepareLevelStats(std::map* level_stats, (*level_stats)[LevelStatType::WRITE_MBPS] = stats.bytes_written / kMB / elapsed; (*level_stats)[LevelStatType::COMP_SEC] = stats.micros / kMicrosInSec; + (*level_stats)[LevelStatType::COMP_CPU_SEC] = stats.cpu_micros / kMicrosInSec; (*level_stats)[LevelStatType::COMP_COUNT] = stats.count; (*level_stats)[LevelStatType::AVG_SEC] = stats.count == 0 ? 0 : stats.micros / kMicrosInSec / stats.count; @@ -117,50 +122,52 @@ void PrepareLevelStats(std::map* level_stats, void PrintLevelStats(char* buf, size_t len, const std::string& name, const std::map& stat_value) { - snprintf(buf, len, - "%4s " /* Level */ - "%6d/%-3d " /* Files */ - "%8s " /* Size */ - "%5.1f " /* Score */ - "%8.1f " /* Read(GB) */ - "%7.1f " /* Rn(GB) */ - "%8.1f " /* Rnp1(GB) */ - "%9.1f " /* Write(GB) */ - "%8.1f " /* Wnew(GB) */ - "%9.1f " /* Moved(GB) */ - "%5.1f " /* W-Amp */ - "%8.1f " /* Rd(MB/s) */ - "%8.1f " /* Wr(MB/s) */ - "%9.0f " /* Comp(sec) */ - "%9d " /* Comp(cnt) */ - "%8.3f " /* Avg(sec) */ - "%7s " /* KeyIn */ - "%6s\n", /* KeyDrop */ - name.c_str(), - static_cast(stat_value.at(LevelStatType::NUM_FILES)), - static_cast(stat_value.at(LevelStatType::COMPACTED_FILES)), - BytesToHumanString( - static_cast(stat_value.at(LevelStatType::SIZE_BYTES))) - .c_str(), - stat_value.at(LevelStatType::SCORE), - stat_value.at(LevelStatType::READ_GB), - stat_value.at(LevelStatType::RN_GB), - stat_value.at(LevelStatType::RNP1_GB), - stat_value.at(LevelStatType::WRITE_GB), - stat_value.at(LevelStatType::W_NEW_GB), - stat_value.at(LevelStatType::MOVED_GB), - stat_value.at(LevelStatType::WRITE_AMP), - stat_value.at(LevelStatType::READ_MBPS), - stat_value.at(LevelStatType::WRITE_MBPS), - stat_value.at(LevelStatType::COMP_SEC), - static_cast(stat_value.at(LevelStatType::COMP_COUNT)), - stat_value.at(LevelStatType::AVG_SEC), - NumberToHumanString( - static_cast(stat_value.at(LevelStatType::KEY_IN))) - .c_str(), - NumberToHumanString(static_cast( - stat_value.at(LevelStatType::KEY_DROP))) - .c_str()); + snprintf( + buf, len, + "%4s " /* Level */ + "%6d/%-3d " /* Files */ + "%8s " /* Size */ + "%5.1f " /* Score */ + "%8.1f " /* Read(GB) */ + "%7.1f " /* Rn(GB) */ + "%8.1f " /* Rnp1(GB) */ + "%9.1f " /* Write(GB) */ + "%8.1f " /* Wnew(GB) */ + "%9.1f " /* Moved(GB) */ + "%5.1f " /* W-Amp */ + "%8.1f " /* Rd(MB/s) */ + "%8.1f " /* Wr(MB/s) */ + "%9.2f " /* Comp(sec) */ + "%17.2f " /* CompMergeCPU(sec) */ + "%9d " /* Comp(cnt) */ + "%8.3f " /* Avg(sec) */ + "%7s " /* KeyIn */ + "%6s\n", /* KeyDrop */ + name.c_str(), static_cast(stat_value.at(LevelStatType::NUM_FILES)), + static_cast(stat_value.at(LevelStatType::COMPACTED_FILES)), + BytesToHumanString( + static_cast(stat_value.at(LevelStatType::SIZE_BYTES))) + .c_str(), + stat_value.at(LevelStatType::SCORE), + stat_value.at(LevelStatType::READ_GB), + stat_value.at(LevelStatType::RN_GB), + stat_value.at(LevelStatType::RNP1_GB), + stat_value.at(LevelStatType::WRITE_GB), + stat_value.at(LevelStatType::W_NEW_GB), + stat_value.at(LevelStatType::MOVED_GB), + stat_value.at(LevelStatType::WRITE_AMP), + stat_value.at(LevelStatType::READ_MBPS), + stat_value.at(LevelStatType::WRITE_MBPS), + stat_value.at(LevelStatType::COMP_SEC), + stat_value.at(LevelStatType::COMP_CPU_SEC), + static_cast(stat_value.at(LevelStatType::COMP_COUNT)), + stat_value.at(LevelStatType::AVG_SEC), + NumberToHumanString( + static_cast(stat_value.at(LevelStatType::KEY_IN))) + .c_str(), + NumberToHumanString( + static_cast(stat_value.at(LevelStatType::KEY_DROP))) + .c_str()); } void PrintLevelStats(char* buf, size_t len, const std::string& name, @@ -208,31 +215,34 @@ static const std::string mem_table_flush_pending = "mem-table-flush-pending"; static const std::string compaction_pending = "compaction-pending"; static const std::string background_errors = "background-errors"; static const std::string cur_size_active_mem_table = - "cur-size-active-mem-table"; + "cur-size-active-mem-table"; static const std::string cur_size_all_mem_tables = "cur-size-all-mem-tables"; static const std::string size_all_mem_tables = "size-all-mem-tables"; static const std::string num_entries_active_mem_table = - "num-entries-active-mem-table"; + "num-entries-active-mem-table"; static const std::string num_entries_imm_mem_tables = - "num-entries-imm-mem-tables"; + "num-entries-imm-mem-tables"; static const std::string num_deletes_active_mem_table = - "num-deletes-active-mem-table"; + "num-deletes-active-mem-table"; static const std::string num_deletes_imm_mem_tables = - "num-deletes-imm-mem-tables"; + "num-deletes-imm-mem-tables"; static const std::string estimate_num_keys = "estimate-num-keys"; static const std::string estimate_table_readers_mem = - "estimate-table-readers-mem"; + "estimate-table-readers-mem"; static const std::string is_file_deletions_enabled = - "is-file-deletions-enabled"; + "is-file-deletions-enabled"; static const std::string num_snapshots = "num-snapshots"; static const std::string oldest_snapshot_time = "oldest-snapshot-time"; static const std::string num_live_versions = "num-live-versions"; static const std::string current_version_number = "current-super-version-number"; static const std::string estimate_live_data_size = "estimate-live-data-size"; -static const std::string min_log_number_to_keep = "min-log-number-to-keep"; -static const std::string base_level = "base-level"; +static const std::string min_log_number_to_keep_str = "min-log-number-to-keep"; +static const std::string min_obsolete_sst_number_to_keep_str = + "min-obsolete-sst-number-to-keep"; +static const std::string base_level_str = "base-level"; static const std::string total_sst_files_size = "total-sst-files-size"; +static const std::string live_sst_files_size = "live-sst-files-size"; static const std::string estimate_pending_comp_bytes = "estimate-pending-compaction-bytes"; static const std::string aggregated_table_properties = @@ -245,11 +255,15 @@ static const std::string actual_delayed_write_rate = "actual-delayed-write-rate"; static const std::string is_write_stopped = "is-write-stopped"; static const std::string estimate_oldest_key_time = "estimate-oldest-key-time"; +static const std::string block_cache_capacity = "block-cache-capacity"; +static const std::string block_cache_usage = "block-cache-usage"; +static const std::string block_cache_pinned_usage = "block-cache-pinned-usage"; +static const std::string options_statistics = "options-statistics"; const std::string DB::Properties::kNumFilesAtLevelPrefix = - rocksdb_prefix + num_files_at_level_prefix; + rocksdb_prefix + num_files_at_level_prefix; const std::string DB::Properties::kCompressionRatioAtLevelPrefix = - rocksdb_prefix + compression_ratio_at_level_prefix; + rocksdb_prefix + compression_ratio_at_level_prefix; const std::string DB::Properties::kStats = rocksdb_prefix + allstats; const std::string DB::Properties::kSSTables = rocksdb_prefix + sstables; const std::string DB::Properties::kCFStats = rocksdb_prefix + cfstats; @@ -260,54 +274,58 @@ const std::string DB::Properties::kCFFileHistogram = const std::string DB::Properties::kDBStats = rocksdb_prefix + dbstats; const std::string DB::Properties::kLevelStats = rocksdb_prefix + levelstats; const std::string DB::Properties::kNumImmutableMemTable = - rocksdb_prefix + num_immutable_mem_table; + rocksdb_prefix + num_immutable_mem_table; const std::string DB::Properties::kNumImmutableMemTableFlushed = rocksdb_prefix + num_immutable_mem_table_flushed; const std::string DB::Properties::kMemTableFlushPending = - rocksdb_prefix + mem_table_flush_pending; + rocksdb_prefix + mem_table_flush_pending; const std::string DB::Properties::kCompactionPending = - rocksdb_prefix + compaction_pending; + rocksdb_prefix + compaction_pending; const std::string DB::Properties::kNumRunningCompactions = rocksdb_prefix + num_running_compactions; const std::string DB::Properties::kNumRunningFlushes = rocksdb_prefix + num_running_flushes; const std::string DB::Properties::kBackgroundErrors = - rocksdb_prefix + background_errors; + rocksdb_prefix + background_errors; const std::string DB::Properties::kCurSizeActiveMemTable = - rocksdb_prefix + cur_size_active_mem_table; + rocksdb_prefix + cur_size_active_mem_table; const std::string DB::Properties::kCurSizeAllMemTables = rocksdb_prefix + cur_size_all_mem_tables; const std::string DB::Properties::kSizeAllMemTables = rocksdb_prefix + size_all_mem_tables; const std::string DB::Properties::kNumEntriesActiveMemTable = - rocksdb_prefix + num_entries_active_mem_table; + rocksdb_prefix + num_entries_active_mem_table; const std::string DB::Properties::kNumEntriesImmMemTables = - rocksdb_prefix + num_entries_imm_mem_tables; + rocksdb_prefix + num_entries_imm_mem_tables; const std::string DB::Properties::kNumDeletesActiveMemTable = - rocksdb_prefix + num_deletes_active_mem_table; + rocksdb_prefix + num_deletes_active_mem_table; const std::string DB::Properties::kNumDeletesImmMemTables = - rocksdb_prefix + num_deletes_imm_mem_tables; + rocksdb_prefix + num_deletes_imm_mem_tables; const std::string DB::Properties::kEstimateNumKeys = - rocksdb_prefix + estimate_num_keys; + rocksdb_prefix + estimate_num_keys; const std::string DB::Properties::kEstimateTableReadersMem = - rocksdb_prefix + estimate_table_readers_mem; + rocksdb_prefix + estimate_table_readers_mem; const std::string DB::Properties::kIsFileDeletionsEnabled = - rocksdb_prefix + is_file_deletions_enabled; + rocksdb_prefix + is_file_deletions_enabled; const std::string DB::Properties::kNumSnapshots = - rocksdb_prefix + num_snapshots; + rocksdb_prefix + num_snapshots; const std::string DB::Properties::kOldestSnapshotTime = - rocksdb_prefix + oldest_snapshot_time; + rocksdb_prefix + oldest_snapshot_time; const std::string DB::Properties::kNumLiveVersions = - rocksdb_prefix + num_live_versions; + rocksdb_prefix + num_live_versions; const std::string DB::Properties::kCurrentSuperVersionNumber = rocksdb_prefix + current_version_number; const std::string DB::Properties::kEstimateLiveDataSize = - rocksdb_prefix + estimate_live_data_size; + rocksdb_prefix + estimate_live_data_size; const std::string DB::Properties::kMinLogNumberToKeep = - rocksdb_prefix + min_log_number_to_keep; + rocksdb_prefix + min_log_number_to_keep_str; +const std::string DB::Properties::kMinObsoleteSstNumberToKeep = + rocksdb_prefix + min_obsolete_sst_number_to_keep_str; const std::string DB::Properties::kTotalSstFilesSize = - rocksdb_prefix + total_sst_files_size; -const std::string DB::Properties::kBaseLevel = rocksdb_prefix + base_level; + rocksdb_prefix + total_sst_files_size; +const std::string DB::Properties::kLiveSstFilesSize = + rocksdb_prefix + live_sst_files_size; +const std::string DB::Properties::kBaseLevel = rocksdb_prefix + base_level_str; const std::string DB::Properties::kEstimatePendingCompactionBytes = rocksdb_prefix + estimate_pending_comp_bytes; const std::string DB::Properties::kAggregatedTableProperties = @@ -320,107 +338,150 @@ const std::string DB::Properties::kIsWriteStopped = rocksdb_prefix + is_write_stopped; const std::string DB::Properties::kEstimateOldestKeyTime = rocksdb_prefix + estimate_oldest_key_time; +const std::string DB::Properties::kBlockCacheCapacity = + rocksdb_prefix + block_cache_capacity; +const std::string DB::Properties::kBlockCacheUsage = + rocksdb_prefix + block_cache_usage; +const std::string DB::Properties::kBlockCachePinnedUsage = + rocksdb_prefix + block_cache_pinned_usage; +const std::string DB::Properties::kOptionsStatistics = + rocksdb_prefix + options_statistics; const std::unordered_map InternalStats::ppt_name_to_info = { {DB::Properties::kNumFilesAtLevelPrefix, - {false, &InternalStats::HandleNumFilesAtLevel, nullptr, nullptr}}, + {false, &InternalStats::HandleNumFilesAtLevel, nullptr, nullptr, + nullptr}}, {DB::Properties::kCompressionRatioAtLevelPrefix, {false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr, - nullptr}}, + nullptr, nullptr}}, {DB::Properties::kLevelStats, - {false, &InternalStats::HandleLevelStats, nullptr, nullptr}}, + {false, &InternalStats::HandleLevelStats, nullptr, nullptr, nullptr}}, {DB::Properties::kStats, - {false, &InternalStats::HandleStats, nullptr, nullptr}}, + {false, &InternalStats::HandleStats, nullptr, nullptr, nullptr}}, {DB::Properties::kCFStats, {false, &InternalStats::HandleCFStats, nullptr, - &InternalStats::HandleCFMapStats}}, + &InternalStats::HandleCFMapStats, nullptr}}, {DB::Properties::kCFStatsNoFileHistogram, - {false, &InternalStats::HandleCFStatsNoFileHistogram, nullptr, + {false, &InternalStats::HandleCFStatsNoFileHistogram, nullptr, nullptr, nullptr}}, {DB::Properties::kCFFileHistogram, - {false, &InternalStats::HandleCFFileHistogram, nullptr, nullptr}}, + {false, &InternalStats::HandleCFFileHistogram, nullptr, nullptr, + nullptr}}, {DB::Properties::kDBStats, - {false, &InternalStats::HandleDBStats, nullptr, nullptr}}, + {false, &InternalStats::HandleDBStats, nullptr, nullptr, nullptr}}, {DB::Properties::kSSTables, - {false, &InternalStats::HandleSsTables, nullptr, nullptr}}, + {false, &InternalStats::HandleSsTables, nullptr, nullptr, nullptr}}, {DB::Properties::kAggregatedTableProperties, {false, &InternalStats::HandleAggregatedTableProperties, nullptr, - nullptr}}, + nullptr, nullptr}}, {DB::Properties::kAggregatedTablePropertiesAtLevel, {false, &InternalStats::HandleAggregatedTablePropertiesAtLevel, - nullptr, nullptr}}, + nullptr, nullptr, nullptr}}, {DB::Properties::kNumImmutableMemTable, - {false, nullptr, &InternalStats::HandleNumImmutableMemTable, nullptr}}, + {false, nullptr, &InternalStats::HandleNumImmutableMemTable, nullptr, + nullptr}}, {DB::Properties::kNumImmutableMemTableFlushed, {false, nullptr, &InternalStats::HandleNumImmutableMemTableFlushed, - nullptr}}, + nullptr, nullptr}}, {DB::Properties::kMemTableFlushPending, - {false, nullptr, &InternalStats::HandleMemTableFlushPending, nullptr}}, + {false, nullptr, &InternalStats::HandleMemTableFlushPending, nullptr, + nullptr}}, {DB::Properties::kCompactionPending, - {false, nullptr, &InternalStats::HandleCompactionPending, nullptr}}, + {false, nullptr, &InternalStats::HandleCompactionPending, nullptr, + nullptr}}, {DB::Properties::kBackgroundErrors, - {false, nullptr, &InternalStats::HandleBackgroundErrors, nullptr}}, + {false, nullptr, &InternalStats::HandleBackgroundErrors, nullptr, + nullptr}}, {DB::Properties::kCurSizeActiveMemTable, - {false, nullptr, &InternalStats::HandleCurSizeActiveMemTable, + {false, nullptr, &InternalStats::HandleCurSizeActiveMemTable, nullptr, nullptr}}, {DB::Properties::kCurSizeAllMemTables, - {false, nullptr, &InternalStats::HandleCurSizeAllMemTables, nullptr}}, + {false, nullptr, &InternalStats::HandleCurSizeAllMemTables, nullptr, + nullptr}}, {DB::Properties::kSizeAllMemTables, - {false, nullptr, &InternalStats::HandleSizeAllMemTables, nullptr}}, + {false, nullptr, &InternalStats::HandleSizeAllMemTables, nullptr, + nullptr}}, {DB::Properties::kNumEntriesActiveMemTable, {false, nullptr, &InternalStats::HandleNumEntriesActiveMemTable, - nullptr}}, + nullptr, nullptr}}, {DB::Properties::kNumEntriesImmMemTables, - {false, nullptr, &InternalStats::HandleNumEntriesImmMemTables, + {false, nullptr, &InternalStats::HandleNumEntriesImmMemTables, nullptr, nullptr}}, {DB::Properties::kNumDeletesActiveMemTable, {false, nullptr, &InternalStats::HandleNumDeletesActiveMemTable, - nullptr}}, + nullptr, nullptr}}, {DB::Properties::kNumDeletesImmMemTables, - {false, nullptr, &InternalStats::HandleNumDeletesImmMemTables, + {false, nullptr, &InternalStats::HandleNumDeletesImmMemTables, nullptr, nullptr}}, {DB::Properties::kEstimateNumKeys, - {false, nullptr, &InternalStats::HandleEstimateNumKeys, nullptr}}, + {false, nullptr, &InternalStats::HandleEstimateNumKeys, nullptr, + nullptr}}, {DB::Properties::kEstimateTableReadersMem, - {true, nullptr, &InternalStats::HandleEstimateTableReadersMem, + {true, nullptr, &InternalStats::HandleEstimateTableReadersMem, nullptr, nullptr}}, {DB::Properties::kIsFileDeletionsEnabled, - {false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled, + {false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled, nullptr, nullptr}}, {DB::Properties::kNumSnapshots, - {false, nullptr, &InternalStats::HandleNumSnapshots, nullptr}}, + {false, nullptr, &InternalStats::HandleNumSnapshots, nullptr, + nullptr}}, {DB::Properties::kOldestSnapshotTime, - {false, nullptr, &InternalStats::HandleOldestSnapshotTime, nullptr}}, + {false, nullptr, &InternalStats::HandleOldestSnapshotTime, nullptr, + nullptr}}, {DB::Properties::kNumLiveVersions, - {false, nullptr, &InternalStats::HandleNumLiveVersions, nullptr}}, + {false, nullptr, &InternalStats::HandleNumLiveVersions, nullptr, + nullptr}}, {DB::Properties::kCurrentSuperVersionNumber, {false, nullptr, &InternalStats::HandleCurrentSuperVersionNumber, - nullptr}}, + nullptr, nullptr}}, {DB::Properties::kEstimateLiveDataSize, - {true, nullptr, &InternalStats::HandleEstimateLiveDataSize, nullptr}}, + {true, nullptr, &InternalStats::HandleEstimateLiveDataSize, nullptr, + nullptr}}, {DB::Properties::kMinLogNumberToKeep, - {false, nullptr, &InternalStats::HandleMinLogNumberToKeep, nullptr}}, + {false, nullptr, &InternalStats::HandleMinLogNumberToKeep, nullptr, + nullptr}}, + {DB::Properties::kMinObsoleteSstNumberToKeep, + {false, nullptr, &InternalStats::HandleMinObsoleteSstNumberToKeep, + nullptr, nullptr}}, {DB::Properties::kBaseLevel, - {false, nullptr, &InternalStats::HandleBaseLevel, nullptr}}, + {false, nullptr, &InternalStats::HandleBaseLevel, nullptr, nullptr}}, {DB::Properties::kTotalSstFilesSize, - {false, nullptr, &InternalStats::HandleTotalSstFilesSize, nullptr}}, + {false, nullptr, &InternalStats::HandleTotalSstFilesSize, nullptr, + nullptr}}, + {DB::Properties::kLiveSstFilesSize, + {false, nullptr, &InternalStats::HandleLiveSstFilesSize, nullptr, + nullptr}}, {DB::Properties::kEstimatePendingCompactionBytes, {false, nullptr, &InternalStats::HandleEstimatePendingCompactionBytes, - nullptr}}, + nullptr, nullptr}}, {DB::Properties::kNumRunningFlushes, - {false, nullptr, &InternalStats::HandleNumRunningFlushes, nullptr}}, + {false, nullptr, &InternalStats::HandleNumRunningFlushes, nullptr, + nullptr}}, {DB::Properties::kNumRunningCompactions, - {false, nullptr, &InternalStats::HandleNumRunningCompactions, + {false, nullptr, &InternalStats::HandleNumRunningCompactions, nullptr, nullptr}}, {DB::Properties::kActualDelayedWriteRate, - {false, nullptr, &InternalStats::HandleActualDelayedWriteRate, + {false, nullptr, &InternalStats::HandleActualDelayedWriteRate, nullptr, nullptr}}, {DB::Properties::kIsWriteStopped, - {false, nullptr, &InternalStats::HandleIsWriteStopped, nullptr}}, + {false, nullptr, &InternalStats::HandleIsWriteStopped, nullptr, + nullptr}}, {DB::Properties::kEstimateOldestKeyTime, - {false, nullptr, &InternalStats::HandleEstimateOldestKeyTime, + {false, nullptr, &InternalStats::HandleEstimateOldestKeyTime, nullptr, + nullptr}}, + {DB::Properties::kBlockCacheCapacity, + {false, nullptr, &InternalStats::HandleBlockCacheCapacity, nullptr, + nullptr}}, + {DB::Properties::kBlockCacheUsage, + {false, nullptr, &InternalStats::HandleBlockCacheUsage, nullptr, nullptr}}, + {DB::Properties::kBlockCachePinnedUsage, + {false, nullptr, &InternalStats::HandleBlockCachePinnedUsage, nullptr, + nullptr}}, + {DB::Properties::kOptionsStatistics, + {false, nullptr, nullptr, nullptr, + &DBImpl::GetPropertyHandleOptionsStatistics}}, }; const DBPropertyInfo* GetPropertyInfo(const Slice& property) { @@ -442,8 +503,8 @@ bool InternalStats::GetStringProperty(const DBPropertyInfo& property_info, } bool InternalStats::GetMapProperty(const DBPropertyInfo& property_info, - const Slice& property, - std::map* value) { + const Slice& /*property*/, + std::map* value) { assert(value != nullptr); assert(property_info.handle_map != nullptr); return (this->*(property_info.handle_map))(value); @@ -494,7 +555,7 @@ bool InternalStats::HandleCompressionRatioAtLevelPrefix(std::string* value, return true; } -bool InternalStats::HandleLevelStats(std::string* value, Slice suffix) { +bool InternalStats::HandleLevelStats(std::string* value, Slice /*suffix*/) { char buf[1000]; const auto* vstorage = cfd_->current()->storage_info(); snprintf(buf, sizeof(buf), @@ -521,40 +582,42 @@ bool InternalStats::HandleStats(std::string* value, Slice suffix) { return true; } -bool InternalStats::HandleCFMapStats(std::map* cf_stats) { +bool InternalStats::HandleCFMapStats( + std::map* cf_stats) { DumpCFMapStats(cf_stats); return true; } -bool InternalStats::HandleCFStats(std::string* value, Slice suffix) { +bool InternalStats::HandleCFStats(std::string* value, Slice /*suffix*/) { DumpCFStats(value); return true; } bool InternalStats::HandleCFStatsNoFileHistogram(std::string* value, - Slice suffix) { + Slice /*suffix*/) { DumpCFStatsNoFileHistogram(value); return true; } -bool InternalStats::HandleCFFileHistogram(std::string* value, Slice suffix) { +bool InternalStats::HandleCFFileHistogram(std::string* value, + Slice /*suffix*/) { DumpCFFileHistogram(value); return true; } -bool InternalStats::HandleDBStats(std::string* value, Slice suffix) { +bool InternalStats::HandleDBStats(std::string* value, Slice /*suffix*/) { DumpDBStats(value); return true; } -bool InternalStats::HandleSsTables(std::string* value, Slice suffix) { +bool InternalStats::HandleSsTables(std::string* value, Slice /*suffix*/) { auto* current = cfd_->current(); *value = current->DebugString(true, true); return true; } bool InternalStats::HandleAggregatedTableProperties(std::string* value, - Slice suffix) { + Slice /*suffix*/) { std::shared_ptr tp; auto s = cfd_->current()->GetAggregatedTableProperties(&tp); if (!s.ok()) { @@ -581,34 +644,34 @@ bool InternalStats::HandleAggregatedTablePropertiesAtLevel(std::string* value, return true; } -bool InternalStats::HandleNumImmutableMemTable(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleNumImmutableMemTable(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { *value = cfd_->imm()->NumNotFlushed(); return true; } bool InternalStats::HandleNumImmutableMemTableFlushed(uint64_t* value, - DBImpl* db, - Version* version) { + DBImpl* /*db*/, + Version* /*version*/) { *value = cfd_->imm()->NumFlushed(); return true; } -bool InternalStats::HandleMemTableFlushPending(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleMemTableFlushPending(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { // Return number of mem tables that are ready to flush (made immutable) *value = (cfd_->imm()->IsFlushPending() ? 1 : 0); return true; } bool InternalStats::HandleNumRunningFlushes(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { *value = db->num_running_flushes(); return true; } -bool InternalStats::HandleCompactionPending(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleCompactionPending(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { // 1 if the system already determines at least one compaction is needed. // 0 otherwise, const auto* vstorage = cfd_->current()->storage_info(); @@ -617,70 +680,74 @@ bool InternalStats::HandleCompactionPending(uint64_t* value, DBImpl* db, } bool InternalStats::HandleNumRunningCompactions(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { *value = db->num_running_compactions_; return true; } -bool InternalStats::HandleBackgroundErrors(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleBackgroundErrors(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { // Accumulated number of errors in background flushes or compactions. *value = GetBackgroundErrorCount(); return true; } -bool InternalStats::HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { // Current size of the active memtable *value = cfd_->mem()->ApproximateMemoryUsage(); return true; } -bool InternalStats::HandleCurSizeAllMemTables(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleCurSizeAllMemTables(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { // Current size of the active memtable + immutable memtables *value = cfd_->mem()->ApproximateMemoryUsage() + cfd_->imm()->ApproximateUnflushedMemTablesMemoryUsage(); return true; } -bool InternalStats::HandleSizeAllMemTables(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleSizeAllMemTables(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { *value = cfd_->mem()->ApproximateMemoryUsage() + cfd_->imm()->ApproximateMemoryUsage(); return true; } -bool InternalStats::HandleNumEntriesActiveMemTable(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleNumEntriesActiveMemTable(uint64_t* value, + DBImpl* /*db*/, + Version* /*version*/) { // Current number of entires in the active memtable *value = cfd_->mem()->num_entries(); return true; } -bool InternalStats::HandleNumEntriesImmMemTables(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleNumEntriesImmMemTables(uint64_t* value, + DBImpl* /*db*/, + Version* /*version*/) { // Current number of entries in the immutable memtables *value = cfd_->imm()->current()->GetTotalNumEntries(); return true; } -bool InternalStats::HandleNumDeletesActiveMemTable(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleNumDeletesActiveMemTable(uint64_t* value, + DBImpl* /*db*/, + Version* /*version*/) { // Current number of entires in the active memtable *value = cfd_->mem()->num_deletes(); return true; } -bool InternalStats::HandleNumDeletesImmMemTables(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleNumDeletesImmMemTables(uint64_t* value, + DBImpl* /*db*/, + Version* /*version*/) { // Current number of entries in the immutable memtables *value = cfd_->imm()->current()->GetTotalNumDeletes(); return true; } -bool InternalStats::HandleEstimateNumKeys(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleEstimateNumKeys(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { // Estimate number of entries in the column family: // Use estimated entries in tables + total entries in memtables. const auto* vstorage = cfd_->current()->storage_info(); @@ -696,77 +763,92 @@ bool InternalStats::HandleEstimateNumKeys(uint64_t* value, DBImpl* db, } bool InternalStats::HandleNumSnapshots(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { *value = db->snapshots().count(); return true; } bool InternalStats::HandleOldestSnapshotTime(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { *value = static_cast(db->snapshots().GetOldestSnapshotTime()); return true; } -bool InternalStats::HandleNumLiveVersions(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleNumLiveVersions(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { *value = cfd_->GetNumLiveVersions(); return true; } -bool InternalStats::HandleCurrentSuperVersionNumber(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleCurrentSuperVersionNumber(uint64_t* value, + DBImpl* /*db*/, + Version* /*version*/) { *value = cfd_->GetSuperVersionNumber(); return true; } bool InternalStats::HandleIsFileDeletionsEnabled(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { *value = db->IsFileDeletionsEnabled(); return true; } -bool InternalStats::HandleBaseLevel(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleBaseLevel(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { const auto* vstorage = cfd_->current()->storage_info(); *value = vstorage->base_level(); return true; } -bool InternalStats::HandleTotalSstFilesSize(uint64_t* value, DBImpl* db, - Version* version) { +bool InternalStats::HandleTotalSstFilesSize(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { *value = cfd_->GetTotalSstFilesSize(); return true; } +bool InternalStats::HandleLiveSstFilesSize(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { + *value = cfd_->GetLiveSstFilesSize(); + return true; +} + bool InternalStats::HandleEstimatePendingCompactionBytes(uint64_t* value, - DBImpl* db, - Version* version) { + DBImpl* /*db*/, + Version* /*version*/) { const auto* vstorage = cfd_->current()->storage_info(); *value = vstorage->estimated_compaction_needed_bytes(); return true; } -bool InternalStats::HandleEstimateTableReadersMem(uint64_t* value, DBImpl* db, +bool InternalStats::HandleEstimateTableReadersMem(uint64_t* value, + DBImpl* /*db*/, Version* version) { *value = (version == nullptr) ? 0 : version->GetMemoryUsageByTableReaders(); return true; } -bool InternalStats::HandleEstimateLiveDataSize(uint64_t* value, DBImpl* db, +bool InternalStats::HandleEstimateLiveDataSize(uint64_t* value, DBImpl* /*db*/, Version* version) { - const auto* vstorage = cfd_->current()->storage_info(); + const auto* vstorage = version->storage_info(); *value = vstorage->EstimateLiveDataSize(); return true; } bool InternalStats::HandleMinLogNumberToKeep(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { *value = db->MinLogNumberToKeep(); return true; } +bool InternalStats::HandleMinObsoleteSstNumberToKeep(uint64_t* value, + DBImpl* db, + Version* /*version*/) { + *value = db->MinObsoleteSstNumberToKeep(); + return true; +} + bool InternalStats::HandleActualDelayedWriteRate(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { const WriteController& wc = db->write_controller(); if (!wc.NeedsDelay()) { *value = 0; @@ -777,7 +859,7 @@ bool InternalStats::HandleActualDelayedWriteRate(uint64_t* value, DBImpl* db, } bool InternalStats::HandleIsWriteStopped(uint64_t* value, DBImpl* db, - Version* version) { + Version* /*version*/) { *value = db->write_controller().IsStopped() ? 1 : 0; return true; } @@ -788,7 +870,8 @@ bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/, // with allow_compaction = false. This is because we don't propagate // oldest_key_time on compaction. if (cfd_->ioptions()->compaction_style != kCompactionStyleFIFO || - cfd_->ioptions()->compaction_options_fifo.allow_compaction) { + cfd_->GetCurrentMutableCFOptions() + ->compaction_options_fifo.allow_compaction) { return false; } @@ -811,6 +894,58 @@ bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/, return *value > 0 && *value < std::numeric_limits::max(); } +bool InternalStats::HandleBlockCacheStat(Cache** block_cache) { + assert(block_cache != nullptr); + auto* table_factory = cfd_->ioptions()->table_factory; + assert(table_factory != nullptr); + if (BlockBasedTableFactory::kName != table_factory->Name()) { + return false; + } + auto* table_options = + reinterpret_cast(table_factory->GetOptions()); + if (table_options == nullptr) { + return false; + } + *block_cache = table_options->block_cache.get(); + if (table_options->no_block_cache || *block_cache == nullptr) { + return false; + } + return true; +} + +bool InternalStats::HandleBlockCacheCapacity(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { + Cache* block_cache; + bool ok = HandleBlockCacheStat(&block_cache); + if (!ok) { + return false; + } + *value = static_cast(block_cache->GetCapacity()); + return true; +} + +bool InternalStats::HandleBlockCacheUsage(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { + Cache* block_cache; + bool ok = HandleBlockCacheStat(&block_cache); + if (!ok) { + return false; + } + *value = static_cast(block_cache->GetUsage()); + return true; +} + +bool InternalStats::HandleBlockCachePinnedUsage(uint64_t* value, DBImpl* /*db*/, + Version* /*version*/) { + Cache* block_cache; + bool ok = HandleBlockCacheStat(&block_cache); + if (!ok) { + return false; + } + *value = static_cast(block_cache->GetPinnedUsage()); + return true; +} + void InternalStats::DumpDBStats(std::string* value) { char buf[1000]; // DB-level stats, only available from default column family @@ -862,8 +997,7 @@ void InternalStats::DumpDBStats(std::string* value) { value->append(buf); // Stall AppendHumanMicros(write_stall_micros, human_micros, kHumanMicrosLen, true); - snprintf(buf, sizeof(buf), - "Cumulative stall: %s, %.1f percent\n", + snprintf(buf, sizeof(buf), "Cumulative stall: %s, %.1f percent\n", human_micros, // 10000 = divide by 1M to get secs, then multiply by 100 for pct write_stall_micros / 10000.0 / std::max(seconds_up, 0.001)); @@ -874,43 +1008,40 @@ void InternalStats::DumpDBStats(std::string* value) { uint64_t interval_write_self = write_self - db_stats_snapshot_.write_self; uint64_t interval_num_keys_written = num_keys_written - db_stats_snapshot_.num_keys_written; - snprintf(buf, sizeof(buf), - "Interval writes: %s writes, %s keys, %s commit groups, " - "%.1f writes per commit group, ingest: %.2f MB, %.2f MB/s\n", - NumberToHumanString( - interval_write_other + interval_write_self).c_str(), - NumberToHumanString(interval_num_keys_written).c_str(), - NumberToHumanString(interval_write_self).c_str(), - static_cast(interval_write_other + interval_write_self) / - (interval_write_self + 1), - (user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB, - (user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB / - std::max(interval_seconds_up, 0.001)), - value->append(buf); + snprintf( + buf, sizeof(buf), + "Interval writes: %s writes, %s keys, %s commit groups, " + "%.1f writes per commit group, ingest: %.2f MB, %.2f MB/s\n", + NumberToHumanString(interval_write_other + interval_write_self).c_str(), + NumberToHumanString(interval_num_keys_written).c_str(), + NumberToHumanString(interval_write_self).c_str(), + static_cast(interval_write_other + interval_write_self) / + (interval_write_self + 1), + (user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB, + (user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB / + std::max(interval_seconds_up, 0.001)), + value->append(buf); uint64_t interval_write_with_wal = write_with_wal - db_stats_snapshot_.write_with_wal; uint64_t interval_wal_synced = wal_synced - db_stats_snapshot_.wal_synced; uint64_t interval_wal_bytes = wal_bytes - db_stats_snapshot_.wal_bytes; - snprintf(buf, sizeof(buf), - "Interval WAL: %s writes, %s syncs, " - "%.2f writes per sync, written: %.2f MB, %.2f MB/s\n", - NumberToHumanString(interval_write_with_wal).c_str(), - NumberToHumanString(interval_wal_synced).c_str(), - interval_write_with_wal / - static_cast(interval_wal_synced + 1), - interval_wal_bytes / kGB, - interval_wal_bytes / kMB / std::max(interval_seconds_up, 0.001)); + snprintf( + buf, sizeof(buf), + "Interval WAL: %s writes, %s syncs, " + "%.2f writes per sync, written: %.2f MB, %.2f MB/s\n", + NumberToHumanString(interval_write_with_wal).c_str(), + NumberToHumanString(interval_wal_synced).c_str(), + interval_write_with_wal / static_cast(interval_wal_synced + 1), + interval_wal_bytes / kGB, + interval_wal_bytes / kMB / std::max(interval_seconds_up, 0.001)); value->append(buf); // Stall - AppendHumanMicros( - write_stall_micros - db_stats_snapshot_.write_stall_micros, - human_micros, kHumanMicrosLen, true); - snprintf(buf, sizeof(buf), - "Interval stall: %s, %.1f percent\n", - human_micros, + AppendHumanMicros(write_stall_micros - db_stats_snapshot_.write_stall_micros, + human_micros, kHumanMicrosLen, true); + snprintf(buf, sizeof(buf), "Interval stall: %s, %.1f percent\n", human_micros, // 10000 = divide by 1M to get secs, then multiply by 100 for pct (write_stall_micros - db_stats_snapshot_.write_stall_micros) / 10000.0 / std::max(interval_seconds_up, 0.001)); @@ -928,13 +1059,16 @@ void InternalStats::DumpDBStats(std::string* value) { } /** - * Dump Compaction Level stats to a map of stat name to value in double. - * The level in stat name is represented with a prefix "Lx" where "x" - * is the level number. A special level "Sum" represents the sum of a stat - * for all levels. + * Dump Compaction Level stats to a map of stat name with "compaction." prefix + * to value in double as string. The level in stat name is represented with + * a prefix "Lx" where "x" is the level number. A special level "Sum" + * represents the sum of a stat for all levels. + * The result also contains IO stall counters which keys start with "io_stalls." + * and values represent uint64 encoded as strings. */ -void InternalStats::DumpCFMapStats(std::map* cf_stats) { - CompactionStats compaction_stats_sum(0); +void InternalStats::DumpCFMapStats( + std::map* cf_stats) { + CompactionStats compaction_stats_sum; std::map> levels_stats; DumpCFMapStats(&levels_stats, &compaction_stats_sum); for (auto const& level_ent : levels_stats) { @@ -943,11 +1077,13 @@ void InternalStats::DumpCFMapStats(std::map* cf_stats) { for (auto const& stat_ent : level_ent.second) { auto stat_type = stat_ent.first; auto key_str = - level_str + "." + + "compaction." + level_str + "." + InternalStats::compaction_level_stats.at(stat_type).property_name; - (*cf_stats)[key_str] = stat_ent.second; + (*cf_stats)[key_str] = std::to_string(stat_ent.second); } } + + DumpCFMapStatsIOStalls(cf_stats); } void InternalStats::DumpCFMapStats( @@ -1018,6 +1154,52 @@ void InternalStats::DumpCFMapStats( (*levels_stats)[-1] = sum_stats; // -1 is for the Sum level } +void InternalStats::DumpCFMapStatsByPriority( + std::map>* priorities_stats) { + for (size_t priority = 0; priority < comp_stats_by_pri_.size(); priority++) { + if (comp_stats_by_pri_[priority].micros > 0) { + std::map priority_stats; + PrepareLevelStats(&priority_stats, 0 /* num_files */, + 0 /* being_compacted */, 0 /* total_file_size */, + 0 /* compaction_score */, 0 /* w_amp */, + comp_stats_by_pri_[priority]); + (*priorities_stats)[static_cast(priority)] = priority_stats; + } + } +} + +void InternalStats::DumpCFMapStatsIOStalls( + std::map* cf_stats) { + (*cf_stats)["io_stalls.level0_slowdown"] = + std::to_string(cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS]); + (*cf_stats)["io_stalls.level0_slowdown_with_compaction"] = + std::to_string(cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS]); + (*cf_stats)["io_stalls.level0_numfiles"] = + std::to_string(cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS]); + (*cf_stats)["io_stalls.level0_numfiles_with_compaction"] = + std::to_string(cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_STOPS]); + (*cf_stats)["io_stalls.stop_for_pending_compaction_bytes"] = + std::to_string(cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS]); + (*cf_stats)["io_stalls.slowdown_for_pending_compaction_bytes"] = + std::to_string(cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS]); + (*cf_stats)["io_stalls.memtable_compaction"] = + std::to_string(cf_stats_count_[MEMTABLE_LIMIT_STOPS]); + (*cf_stats)["io_stalls.memtable_slowdown"] = + std::to_string(cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS]); + + uint64_t total_stop = cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS] + + cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS] + + cf_stats_count_[MEMTABLE_LIMIT_STOPS]; + + uint64_t total_slowdown = + cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS] + + cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS] + + cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS]; + + (*cf_stats)["io_stalls.total_stop"] = std::to_string(total_stop); + (*cf_stats)["io_stalls.total_slowdown"] = std::to_string(total_slowdown); +} + void InternalStats::DumpCFStats(std::string* value) { DumpCFStatsNoFileHistogram(value); DumpCFFileHistogram(value); @@ -1026,12 +1208,12 @@ void InternalStats::DumpCFStats(std::string* value) { void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) { char buf[2000]; // Per-ColumnFamily stats - PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName()); + PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Level"); value->append(buf); // Print stats for each level std::map> levels_stats; - CompactionStats compaction_stats_sum(0); + CompactionStats compaction_stats_sum; DumpCFMapStats(&levels_stats, &compaction_stats_sum); for (int l = 0; l < number_levels_; ++l) { if (levels_stats.find(l) != levels_stats.end()) { @@ -1052,11 +1234,12 @@ void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) { uint64_t ingest_keys_addfile = cf_stats_value_[INGESTED_NUM_KEYS_TOTAL]; // Cumulative summary uint64_t total_stall_count = - cf_stats_count_[LEVEL0_SLOWDOWN_TOTAL] + - cf_stats_count_[LEVEL0_NUM_FILES_TOTAL] + - cf_stats_count_[SOFT_PENDING_COMPACTION_BYTES_LIMIT] + - cf_stats_count_[HARD_PENDING_COMPACTION_BYTES_LIMIT] + - cf_stats_count_[MEMTABLE_COMPACTION] + cf_stats_count_[MEMTABLE_SLOWDOWN]; + cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS] + + cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS] + + cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS] + + cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS] + + cf_stats_count_[MEMTABLE_LIMIT_STOPS] + + cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS]; // Interval summary uint64_t interval_flush_ingest = flush_ingest - cf_stats_snapshot_.ingest_bytes_flush; @@ -1071,6 +1254,21 @@ void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) { PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats); value->append(buf); + PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Priority"); + value->append(buf); + std::map> priorities_stats; + DumpCFMapStatsByPriority(&priorities_stats); + for (size_t priority = 0; priority < comp_stats_by_pri_.size(); ++priority) { + if (priorities_stats.find(static_cast(priority)) != + priorities_stats.end()) { + PrintLevelStats( + buf, sizeof(buf), + Env::PriorityToString(static_cast(priority)), + priorities_stats[static_cast(priority)]); + value->append(buf); + } + } + double seconds_up = (env_->NowMicros() - started_at_ + 1) / kMicrosInSec; double interval_seconds_up = seconds_up - cf_stats_snapshot_.seconds_up; snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n", @@ -1085,8 +1283,9 @@ void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) { uint64_t interval_ingest_files_addfile = ingest_files_addfile - cf_stats_snapshot_.ingest_files_addfile; - snprintf(buf, sizeof(buf), "AddFile(Total Files): cumulative %" PRIu64 - ", interval %" PRIu64 "\n", + snprintf(buf, sizeof(buf), + "AddFile(Total Files): cumulative %" PRIu64 ", interval %" PRIu64 + "\n", ingest_files_addfile, interval_ingest_files_addfile); value->append(buf); @@ -1145,31 +1344,32 @@ void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) { cf_stats_snapshot_.compact_bytes_read = compact_bytes_read; cf_stats_snapshot_.compact_micros = compact_micros; - snprintf(buf, sizeof(buf), "Stalls(count): %" PRIu64 - " level0_slowdown, " - "%" PRIu64 - " level0_slowdown_with_compaction, " - "%" PRIu64 - " level0_numfiles, " - "%" PRIu64 - " level0_numfiles_with_compaction, " - "%" PRIu64 - " stop for pending_compaction_bytes, " - "%" PRIu64 - " slowdown for pending_compaction_bytes, " - "%" PRIu64 - " memtable_compaction, " - "%" PRIu64 - " memtable_slowdown, " - "interval %" PRIu64 " total count\n", - cf_stats_count_[LEVEL0_SLOWDOWN_TOTAL], - cf_stats_count_[LEVEL0_SLOWDOWN_WITH_COMPACTION], - cf_stats_count_[LEVEL0_NUM_FILES_TOTAL], - cf_stats_count_[LEVEL0_NUM_FILES_WITH_COMPACTION], - cf_stats_count_[HARD_PENDING_COMPACTION_BYTES_LIMIT], - cf_stats_count_[SOFT_PENDING_COMPACTION_BYTES_LIMIT], - cf_stats_count_[MEMTABLE_COMPACTION], - cf_stats_count_[MEMTABLE_SLOWDOWN], + snprintf(buf, sizeof(buf), + "Stalls(count): %" PRIu64 + " level0_slowdown, " + "%" PRIu64 + " level0_slowdown_with_compaction, " + "%" PRIu64 + " level0_numfiles, " + "%" PRIu64 + " level0_numfiles_with_compaction, " + "%" PRIu64 + " stop for pending_compaction_bytes, " + "%" PRIu64 + " slowdown for pending_compaction_bytes, " + "%" PRIu64 + " memtable_compaction, " + "%" PRIu64 + " memtable_slowdown, " + "interval %" PRIu64 " total count\n", + cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS], + cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS], + cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS], + cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_STOPS], + cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS], + cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS], + cf_stats_count_[MEMTABLE_LIMIT_STOPS], + cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS], total_stall_count - cf_stats_snapshot_.stall_count); value->append(buf); @@ -1203,7 +1403,9 @@ void InternalStats::DumpCFFileHistogram(std::string* value) { #else -const DBPropertyInfo* GetPropertyInfo(const Slice& property) { return nullptr; } +const DBPropertyInfo* GetPropertyInfo(const Slice& /*property*/) { + return nullptr; +} #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/internal_stats.h b/thirdparty/rocksdb/db/internal_stats.h index a0b8a90271..20fb07f485 100644 --- a/thirdparty/rocksdb/db/internal_stats.h +++ b/thirdparty/rocksdb/db/internal_stats.h @@ -19,8 +19,8 @@ class ColumnFamilyData; namespace rocksdb { -class MemTableList; class DBImpl; +class MemTableList; // Config for retrieving a property's value. struct DBPropertyInfo { @@ -42,8 +42,13 @@ struct DBPropertyInfo { // holding db mutex, which is only supported for int properties. bool (InternalStats::*handle_int)(uint64_t* value, DBImpl* db, Version* version); - bool (InternalStats::*handle_map)( - std::map* compaction_stats); + + // @param props Map of general properties to populate + bool (InternalStats::*handle_map)(std::map* props); + + // handle the string type properties rely on DBImpl methods + // @param value Value-result argument for storing the property's string value + bool (DBImpl::*handle_string_dbimpl)(std::string* value); }; extern const DBPropertyInfo* GetPropertyInfo(const Slice& property); @@ -66,6 +71,7 @@ enum class LevelStatType { READ_MBPS, WRITE_MBPS, COMP_SEC, + COMP_CPU_SEC, COMP_COUNT, AVG_SEC, KEY_IN, @@ -85,14 +91,14 @@ class InternalStats { static const std::map compaction_level_stats; enum InternalCFStatsType { - LEVEL0_SLOWDOWN_TOTAL, - LEVEL0_SLOWDOWN_WITH_COMPACTION, - MEMTABLE_COMPACTION, - MEMTABLE_SLOWDOWN, - LEVEL0_NUM_FILES_TOTAL, - LEVEL0_NUM_FILES_WITH_COMPACTION, - SOFT_PENDING_COMPACTION_BYTES_LIMIT, - HARD_PENDING_COMPACTION_BYTES_LIMIT, + L0_FILE_COUNT_LIMIT_SLOWDOWNS, + LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, + MEMTABLE_LIMIT_STOPS, + MEMTABLE_LIMIT_SLOWDOWNS, + L0_FILE_COUNT_LIMIT_STOPS, + LOCKED_L0_FILE_COUNT_LIMIT_STOPS, + PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, + PENDING_COMPACTION_BYTES_LIMIT_STOPS, WRITE_STALLS_ENUM_MAX, BYTES_FLUSHED, BYTES_INGESTED_ADD_FILE, @@ -119,6 +125,7 @@ class InternalStats { cf_stats_value_{}, cf_stats_count_{}, comp_stats_(num_levels), + comp_stats_by_pri_(Env::Priority::TOTAL), file_read_latency_(num_levels), bg_error_count_(0), number_levels_(num_levels), @@ -130,6 +137,7 @@ class InternalStats { // compactions that produced data for the specified "level". struct CompactionStats { uint64_t micros; + uint64_t cpu_micros; // The number of bytes read from all non-output levels uint64_t bytes_read_non_output_levels; @@ -162,8 +170,31 @@ class InternalStats { // Number of compactions done int count; - explicit CompactionStats(int _count = 0) + // Number of compactions done per CompactionReason + int counts[static_cast(CompactionReason::kNumOfReasons)]; + + explicit CompactionStats() + : micros(0), + cpu_micros(0), + bytes_read_non_output_levels(0), + bytes_read_output_level(0), + bytes_written(0), + bytes_moved(0), + num_input_files_in_non_output_levels(0), + num_input_files_in_output_level(0), + num_output_files(0), + num_input_records(0), + num_dropped_records(0), + count(0) { + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + for (int i = 0; i < num_of_reasons; i++) { + counts[i] = 0; + } + } + + explicit CompactionStats(CompactionReason reason, int c) : micros(0), + cpu_micros(0), bytes_read_non_output_levels(0), bytes_read_output_level(0), bytes_written(0), @@ -173,25 +204,42 @@ class InternalStats { num_output_files(0), num_input_records(0), num_dropped_records(0), - count(_count) {} + count(c) { + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + for (int i = 0; i < num_of_reasons; i++) { + counts[i] = 0; + } + int r = static_cast(reason); + if (r >= 0 && r < num_of_reasons) { + counts[r] = c; + } else { + count = 0; + } + } explicit CompactionStats(const CompactionStats& c) : micros(c.micros), + cpu_micros(c.cpu_micros), bytes_read_non_output_levels(c.bytes_read_non_output_levels), bytes_read_output_level(c.bytes_read_output_level), bytes_written(c.bytes_written), bytes_moved(c.bytes_moved), num_input_files_in_non_output_levels( c.num_input_files_in_non_output_levels), - num_input_files_in_output_level( - c.num_input_files_in_output_level), + num_input_files_in_output_level(c.num_input_files_in_output_level), num_output_files(c.num_output_files), num_input_records(c.num_input_records), num_dropped_records(c.num_dropped_records), - count(c.count) {} + count(c.count) { + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + for (int i = 0; i < num_of_reasons; i++) { + counts[i] = c.counts[i]; + } + } void Clear() { this->micros = 0; + this->cpu_micros = 0; this->bytes_read_non_output_levels = 0; this->bytes_read_output_level = 0; this->bytes_written = 0; @@ -202,10 +250,15 @@ class InternalStats { this->num_input_records = 0; this->num_dropped_records = 0; this->count = 0; + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + for (int i = 0; i < num_of_reasons; i++) { + counts[i] = 0; + } } void Add(const CompactionStats& c) { this->micros += c.micros; + this->cpu_micros += c.cpu_micros; this->bytes_read_non_output_levels += c.bytes_read_non_output_levels; this->bytes_read_output_level += c.bytes_read_output_level; this->bytes_written += c.bytes_written; @@ -218,10 +271,15 @@ class InternalStats { this->num_input_records += c.num_input_records; this->num_dropped_records += c.num_dropped_records; this->count += c.count; + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + for (int i = 0; i< num_of_reasons; i++) { + counts[i] += c.counts[i]; + } } void Subtract(const CompactionStats& c) { this->micros -= c.micros; + this->cpu_micros -= c.cpu_micros; this->bytes_read_non_output_levels -= c.bytes_read_non_output_levels; this->bytes_read_output_level -= c.bytes_read_output_level; this->bytes_written -= c.bytes_written; @@ -234,6 +292,10 @@ class InternalStats { this->num_input_records -= c.num_input_records; this->num_dropped_records -= c.num_dropped_records; this->count -= c.count; + int num_of_reasons = static_cast(CompactionReason::kNumOfReasons); + for (int i = 0; i < num_of_reasons; i++) { + counts[i] -= c.counts[i]; + } } }; @@ -257,8 +319,10 @@ class InternalStats { started_at_ = env_->NowMicros(); } - void AddCompactionStats(int level, const CompactionStats& stats) { + void AddCompactionStats(int level, Env::Priority thread_pri, + const CompactionStats& stats) { comp_stats_[level].Add(stats); + comp_stats_by_pri_[thread_pri].Add(stats); } void IncBytesMoved(int level, uint64_t amount) { @@ -298,7 +362,7 @@ class InternalStats { bool GetMapProperty(const DBPropertyInfo& property_info, const Slice& property, - std::map* value); + std::map* value); bool GetIntProperty(const DBPropertyInfo& property_info, uint64_t* value, DBImpl* db); @@ -306,20 +370,29 @@ class InternalStats { bool GetIntPropertyOutOfMutex(const DBPropertyInfo& property_info, Version* version, uint64_t* value); + const std::vector& TEST_GetCompactionStats() const { + return comp_stats_; + } + // Store a mapping from the user-facing DB::Properties string to our // DBPropertyInfo struct used internally for retrieving properties. static const std::unordered_map ppt_name_to_info; private: void DumpDBStats(std::string* value); - void DumpCFMapStats(std::map* cf_stats); + void DumpCFMapStats(std::map* cf_stats); void DumpCFMapStats( std::map>* level_stats, CompactionStats* compaction_stats_sum); + void DumpCFMapStatsByPriority( + std::map>* priorities_stats); + void DumpCFMapStatsIOStalls(std::map* cf_stats); void DumpCFStats(std::string* value); void DumpCFStatsNoFileHistogram(std::string* value); void DumpCFFileHistogram(std::string* value); + bool HandleBlockCacheStat(Cache** block_cache); + // Per-DB stats std::atomic db_stats_[INTERNAL_DB_STATS_ENUM_MAX]; // Per-ColumnFamily stats @@ -327,6 +400,7 @@ class InternalStats { uint64_t cf_stats_count_[INTERNAL_CF_STATS_ENUM_MAX]; // Per-ColumnFamily/level compaction stats std::vector comp_stats_; + std::vector comp_stats_by_pri_; std::vector file_read_latency_; // Used to compute per-interval statistics @@ -348,8 +422,7 @@ class InternalStats { uint64_t ingest_keys_addfile; // Total number of keys ingested CFStatsSnapshot() - : comp_stats(0), - ingest_bytes_flush(0), + : ingest_bytes_flush(0), stall_count(0), compact_bytes_write(0), compact_bytes_read(0), @@ -424,7 +497,7 @@ class InternalStats { bool HandleCompressionRatioAtLevelPrefix(std::string* value, Slice suffix); bool HandleLevelStats(std::string* value, Slice suffix); bool HandleStats(std::string* value, Slice suffix); - bool HandleCFMapStats(std::map* compaction_stats); + bool HandleCFMapStats(std::map* compaction_stats); bool HandleCFStats(std::string* value, Slice suffix); bool HandleCFStatsNoFileHistogram(std::string* value, Slice suffix); bool HandleCFFileHistogram(std::string* value, Slice suffix); @@ -465,6 +538,7 @@ class InternalStats { Version* version); bool HandleBaseLevel(uint64_t* value, DBImpl* db, Version* version); bool HandleTotalSstFilesSize(uint64_t* value, DBImpl* db, Version* version); + bool HandleLiveSstFilesSize(uint64_t* value, DBImpl* db, Version* version); bool HandleEstimatePendingCompactionBytes(uint64_t* value, DBImpl* db, Version* version); bool HandleEstimateTableReadersMem(uint64_t* value, DBImpl* db, @@ -472,12 +546,17 @@ class InternalStats { bool HandleEstimateLiveDataSize(uint64_t* value, DBImpl* db, Version* version); bool HandleMinLogNumberToKeep(uint64_t* value, DBImpl* db, Version* version); + bool HandleMinObsoleteSstNumberToKeep(uint64_t* value, DBImpl* db, + Version* version); bool HandleActualDelayedWriteRate(uint64_t* value, DBImpl* db, Version* version); bool HandleIsWriteStopped(uint64_t* value, DBImpl* db, Version* version); bool HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* db, Version* version); - + bool HandleBlockCacheCapacity(uint64_t* value, DBImpl* db, Version* version); + bool HandleBlockCacheUsage(uint64_t* value, DBImpl* db, Version* version); + bool HandleBlockCachePinnedUsage(uint64_t* value, DBImpl* db, + Version* version); // Total number of background errors encountered. Every time a flush task // or compaction task fails, this counter is incremented. The failure can // be caused by any possible reason, including file system errors, out of @@ -496,14 +575,14 @@ class InternalStats { class InternalStats { public: enum InternalCFStatsType { - LEVEL0_SLOWDOWN_TOTAL, - LEVEL0_SLOWDOWN_WITH_COMPACTION, - MEMTABLE_COMPACTION, - MEMTABLE_SLOWDOWN, - LEVEL0_NUM_FILES_TOTAL, - LEVEL0_NUM_FILES_WITH_COMPACTION, - SOFT_PENDING_COMPACTION_BYTES_LIMIT, - HARD_PENDING_COMPACTION_BYTES_LIMIT, + L0_FILE_COUNT_LIMIT_SLOWDOWNS, + LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, + MEMTABLE_LIMIT_STOPS, + MEMTABLE_LIMIT_SLOWDOWNS, + L0_FILE_COUNT_LIMIT_STOPS, + LOCKED_L0_FILE_COUNT_LIMIT_STOPS, + PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, + PENDING_COMPACTION_BYTES_LIMIT_STOPS, WRITE_STALLS_ENUM_MAX, BYTES_FLUSHED, BYTES_INGESTED_ADD_FILE, @@ -525,10 +604,11 @@ class InternalStats { INTERNAL_DB_STATS_ENUM_MAX, }; - InternalStats(int num_levels, Env* env, ColumnFamilyData* cfd) {} + InternalStats(int /*num_levels*/, Env* /*env*/, ColumnFamilyData* /*cfd*/) {} struct CompactionStats { uint64_t micros; + uint64_t cpu_micros; uint64_t bytes_read_non_output_levels; uint64_t bytes_read_output_level; uint64_t bytes_written; @@ -540,48 +620,51 @@ class InternalStats { uint64_t num_dropped_records; int count; - explicit CompactionStats(int _count = 0) {} + explicit CompactionStats() {} + + explicit CompactionStats(CompactionReason /*reason*/, int /*c*/) {} - explicit CompactionStats(const CompactionStats& c) {} + explicit CompactionStats(const CompactionStats& /*c*/) {} - void Add(const CompactionStats& c) {} + void Add(const CompactionStats& /*c*/) {} - void Subtract(const CompactionStats& c) {} + void Subtract(const CompactionStats& /*c*/) {} }; - void AddCompactionStats(int level, const CompactionStats& stats) {} + void AddCompactionStats(int /*level*/, Env::Priority /*thread_pri*/, + const CompactionStats& /*stats*/) {} - void IncBytesMoved(int level, uint64_t amount) {} + void IncBytesMoved(int /*level*/, uint64_t /*amount*/) {} - void AddCFStats(InternalCFStatsType type, uint64_t value) {} + void AddCFStats(InternalCFStatsType /*type*/, uint64_t /*value*/) {} - void AddDBStats(InternalDBStatsType type, uint64_t value, - bool concurrent = false) {} + void AddDBStats(InternalDBStatsType /*type*/, uint64_t /*value*/, + bool /*concurrent */ = false) {} - HistogramImpl* GetFileReadHist(int level) { return nullptr; } + HistogramImpl* GetFileReadHist(int /*level*/) { return nullptr; } uint64_t GetBackgroundErrorCount() const { return 0; } uint64_t BumpAndGetBackgroundErrorCount() { return 0; } - bool GetStringProperty(const DBPropertyInfo& property_info, - const Slice& property, std::string* value) { + bool GetStringProperty(const DBPropertyInfo& /*property_info*/, + const Slice& /*property*/, std::string* /*value*/) { return false; } - bool GetMapProperty(const DBPropertyInfo& property_info, - const Slice& property, - std::map* value) { + bool GetMapProperty(const DBPropertyInfo& /*property_info*/, + const Slice& /*property*/, + std::map* /*value*/) { return false; } - bool GetIntProperty(const DBPropertyInfo& property_info, uint64_t* value, - DBImpl* db) const { + bool GetIntProperty(const DBPropertyInfo& /*property_info*/, uint64_t* /*value*/, + DBImpl* /*db*/) const { return false; } - bool GetIntPropertyOutOfMutex(const DBPropertyInfo& property_info, - Version* version, uint64_t* value) const { + bool GetIntPropertyOutOfMutex(const DBPropertyInfo& /*property_info*/, + Version* /*version*/, uint64_t* /*value*/) const { return false; } }; diff --git a/thirdparty/rocksdb/db/job_context.h b/thirdparty/rocksdb/db/job_context.h index 950a3a667d..3978fad33c 100644 --- a/thirdparty/rocksdb/db/job_context.h +++ b/thirdparty/rocksdb/db/job_context.h @@ -13,27 +13,120 @@ #include #include "db/log_writer.h" +#include "db/column_family.h" namespace rocksdb { class MemTable; +struct SuperVersion; + +struct SuperVersionContext { + struct WriteStallNotification { + WriteStallInfo write_stall_info; + const ImmutableCFOptions* immutable_cf_options; + }; + + autovector superversions_to_free; +#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION + autovector write_stall_notifications; +#endif + std::unique_ptr + new_superversion; // if nullptr no new superversion + + explicit SuperVersionContext(bool create_superversion = false) + : new_superversion(create_superversion ? new SuperVersion() : nullptr) {} + + explicit SuperVersionContext(SuperVersionContext&& other) + : superversions_to_free(std::move(other.superversions_to_free)), +#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION + write_stall_notifications(std::move(other.write_stall_notifications)), +#endif + new_superversion(std::move(other.new_superversion)) { + } + + void NewSuperVersion() { + new_superversion = std::unique_ptr(new SuperVersion()); + } + + inline bool HaveSomethingToDelete() const { +#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION + return !superversions_to_free.empty() || + !write_stall_notifications.empty(); +#else + return !superversions_to_free.empty(); +#endif + } + + void PushWriteStallNotification( + WriteStallCondition old_cond, WriteStallCondition new_cond, + const std::string& name, const ImmutableCFOptions* ioptions) { +#if !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION) + WriteStallNotification notif; + notif.write_stall_info.cf_name = name; + notif.write_stall_info.condition.prev = old_cond; + notif.write_stall_info.condition.cur = new_cond; + notif.immutable_cf_options = ioptions; + write_stall_notifications.push_back(notif); +#else + (void)old_cond; + (void)new_cond; + (void)name; + (void)ioptions; +#endif // !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION) + } + + void Clean() { +#if !defined(ROCKSDB_LITE) && !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION) + // notify listeners on changed write stall conditions + for (auto& notif : write_stall_notifications) { + for (auto& listener : notif.immutable_cf_options->listeners) { + listener->OnStallConditionsChanged(notif.write_stall_info); + } + } + write_stall_notifications.clear(); +#endif // !ROCKSDB_LITE + // free superversions + for (auto s : superversions_to_free) { + delete s; + } + superversions_to_free.clear(); + } + + ~SuperVersionContext() { +#ifndef ROCKSDB_DISABLE_STALL_NOTIFICATION + assert(write_stall_notifications.empty()); +#endif + assert(superversions_to_free.empty()); + } +}; struct JobContext { inline bool HaveSomethingToDelete() const { return full_scan_candidate_files.size() || sst_delete_files.size() || - log_delete_files.size() || manifest_delete_files.size() || - new_superversion != nullptr || superversions_to_free.size() > 0 || - memtables_to_free.size() > 0 || logs_to_free.size() > 0; + log_delete_files.size() || manifest_delete_files.size(); + } + + inline bool HaveSomethingToClean() const { + bool sv_have_sth = false; + for (const auto& sv_ctx : superversion_contexts) { + if (sv_ctx.HaveSomethingToDelete()) { + sv_have_sth = true; + break; + } + } + return memtables_to_free.size() > 0 || logs_to_free.size() > 0 || + sv_have_sth; } // Structure to store information for candidate files to delete. struct CandidateFileInfo { std::string file_name; - uint32_t path_id; - CandidateFileInfo(std::string name, uint32_t path) - : file_name(std::move(name)), path_id(path) {} + std::string file_path; + CandidateFileInfo(std::string name, std::string path) + : file_name(std::move(name)), file_path(std::move(path)) {} bool operator==(const CandidateFileInfo& other) const { - return file_name == other.file_name && path_id == other.path_id; + return file_name == other.file_name && + file_path == other.file_path; } }; @@ -50,7 +143,7 @@ struct JobContext { std::vector sst_live; // a list of sst files that we need to delete - std::vector sst_delete_files; + std::vector sst_delete_files; // a list of log files that we need to delete std::vector log_delete_files; @@ -65,12 +158,11 @@ struct JobContext { // a list of memtables to be free autovector memtables_to_free; - autovector superversions_to_free; + // contexts for installing superversions for multiple column families + std::vector superversion_contexts; autovector logs_to_free; - SuperVersion* new_superversion; // if nullptr no new superversion - // the current manifest_file_number, log_number and prev_log_number // that corresponds to the set of files in 'live'. uint64_t manifest_file_number; @@ -83,13 +175,17 @@ struct JobContext { size_t num_alive_log_files = 0; uint64_t size_log_to_delete = 0; + // Snapshot taken before flush/compaction job. + std::unique_ptr job_snapshot; + explicit JobContext(int _job_id, bool create_superversion = false) { job_id = _job_id; manifest_file_number = 0; pending_manifest_file_number = 0; log_number = 0; prev_log_number = 0; - new_superversion = create_superversion ? new SuperVersion() : nullptr; + superversion_contexts.emplace_back( + SuperVersionContext(create_superversion)); } // For non-empty JobContext Clean() has to be called at least once before @@ -97,31 +193,25 @@ struct JobContext { // unlocked DB mutex. Destructor doesn't call Clean() to avoid accidentally // doing potentially slow Clean() with locked DB mutex. void Clean() { + // free superversions + for (auto& sv_context : superversion_contexts) { + sv_context.Clean(); + } // free pending memtables for (auto m : memtables_to_free) { delete m; } - // free superversions - for (auto s : superversions_to_free) { - delete s; - } for (auto l : logs_to_free) { delete l; } - // if new_superversion was not used, it will be non-nullptr and needs - // to be freed here - delete new_superversion; memtables_to_free.clear(); - superversions_to_free.clear(); logs_to_free.clear(); - new_superversion = nullptr; + job_snapshot.reset(); } ~JobContext() { assert(memtables_to_free.size() == 0); - assert(superversions_to_free.size() == 0); - assert(new_superversion == nullptr); assert(logs_to_free.size() == 0); } }; diff --git a/thirdparty/rocksdb/db/listener_test.cc b/thirdparty/rocksdb/db/listener_test.cc index 5b5f2266b3..60d02ed0ae 100644 --- a/thirdparty/rocksdb/db/listener_test.cc +++ b/thirdparty/rocksdb/db/listener_test.cc @@ -46,22 +46,20 @@ class EventListenerTest : public DBTestBase { }; struct TestPropertiesCollector : public rocksdb::TablePropertiesCollector { - virtual rocksdb::Status AddUserKey(const rocksdb::Slice& key, - const rocksdb::Slice& value, - rocksdb::EntryType type, - rocksdb::SequenceNumber seq, - uint64_t file_size) override { + rocksdb::Status AddUserKey(const rocksdb::Slice& /*key*/, + const rocksdb::Slice& /*value*/, + rocksdb::EntryType /*type*/, + rocksdb::SequenceNumber /*seq*/, + uint64_t /*file_size*/) override { return Status::OK(); } - virtual rocksdb::Status Finish( + rocksdb::Status Finish( rocksdb::UserCollectedProperties* properties) override { properties->insert({"0", "1"}); return Status::OK(); } - virtual const char* Name() const override { - return "TestTablePropertiesCollector"; - } + const char* Name() const override { return "TestTablePropertiesCollector"; } rocksdb::UserCollectedProperties GetReadableProperties() const override { rocksdb::UserCollectedProperties ret; @@ -72,8 +70,8 @@ struct TestPropertiesCollector : public rocksdb::TablePropertiesCollector { class TestPropertiesCollectorFactory : public TablePropertiesCollectorFactory { public: - virtual TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context context) override { + TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollectorFactory::Context /*context*/) override { return new TestPropertiesCollector; } const char* Name() const override { return "TestTablePropertiesCollector"; } @@ -260,7 +258,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) { ASSERT_EQ(listener->flushed_column_family_names_.size(), i); } - // make sure call-back functions are called in the right order + // make sure callback functions are called in the right order for (size_t i = 0; i < cf_names.size(); ++i) { ASSERT_EQ(listener->flushed_dbs_[i], db_); ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]); @@ -296,7 +294,7 @@ TEST_F(EventListenerTest, MultiCF) { ASSERT_EQ(listener->flushed_column_family_names_.size(), i); } - // make sure call-back functions are called in the right order + // make sure callback functions are called in the right order for (size_t i = 0; i < cf_names.size(); i++) { ASSERT_EQ(listener->flushed_dbs_[i], db_); ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]); @@ -417,7 +415,9 @@ TEST_F(EventListenerTest, DisableBGCompaction) { for (int i = 0; static_cast(cf_meta.file_count) < kSlowdownTrigger * 10; ++i) { Put(1, ToString(i), std::string(10000, 'x'), WriteOptions()); - db_->Flush(FlushOptions(), handles_[1]); + FlushOptions fo; + fo.allow_write_stall = true; + db_->Flush(fo, handles_[1]); db_->GetColumnFamilyMetaData(handles_[1], &cf_meta); } ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9); @@ -425,7 +425,7 @@ TEST_F(EventListenerTest, DisableBGCompaction) { class TestCompactionReasonListener : public EventListener { public: - void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override { + void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override { std::lock_guard lock(mutex_); compaction_reasons_.push_back(ci.compaction_reason); } @@ -528,7 +528,7 @@ TEST_F(EventListenerTest, CompactionReasonUniversal) { ASSERT_GT(listener->compaction_reasons_.size(), 0); for (auto compaction_reason : listener->compaction_reasons_) { - ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSortedRunNum); + ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSizeRatio); } options.level0_file_num_compaction_trigger = 8; @@ -601,7 +601,7 @@ class TableFileCreationListener : public EventListener { Status NewWritableFile(const std::string& fname, std::unique_ptr* result, - const EnvOptions& options) { + const EnvOptions& options) override { if (fname.size() > 4 && fname.substr(fname.size() - 4) == ".sst") { if (!status_.ok()) { return status_; @@ -807,7 +807,8 @@ class BackgroundErrorListener : public EventListener { public: BackgroundErrorListener(SpecialEnv* env) : env_(env), counter_(0) {} - void OnBackgroundError(BackgroundErrorReason reason, Status* bg_error) override { + void OnBackgroundError(BackgroundErrorReason /*reason*/, + Status* bg_error) override { if (counter_ == 0) { // suppress the first error and disable write-dropping such that a retry // can succeed. @@ -879,10 +880,75 @@ TEST_F(EventListenerTest, BackgroundErrorListenerFailedCompactionTest) { ASSERT_EQ(1, listener->counter()); // trigger flush so compaction is triggered again; this time it succeeds + // The previous failed compaction may get retried automatically, so we may + // be left with 0 or 1 files in level 1, depending on when the retry gets + // scheduled ASSERT_OK(Put("key0", "val")); ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable()); ASSERT_OK(dbfull()->TEST_WaitForCompact()); - ASSERT_EQ(0, NumTableFilesAtLevel(0)); + ASSERT_LE(1, NumTableFilesAtLevel(0)); +} + +class TestFileOperationListener : public EventListener { + public: + TestFileOperationListener() { + file_reads_.store(0); + file_reads_success_.store(0); + file_writes_.store(0); + file_writes_success_.store(0); + } + + void OnFileReadFinish(const FileOperationInfo& info) override { + ++file_reads_; + if (info.status.ok()) { + ++file_reads_success_; + } + ReportDuration(info); + } + + void OnFileWriteFinish(const FileOperationInfo& info) override { + ++file_writes_; + if (info.status.ok()) { + ++file_writes_success_; + } + ReportDuration(info); + } + + bool ShouldBeNotifiedOnFileIO() override { return true; } + + std::atomic file_reads_; + std::atomic file_reads_success_; + std::atomic file_writes_; + std::atomic file_writes_success_; + + private: + void ReportDuration(const FileOperationInfo& info) const { + auto duration = std::chrono::duration_cast( + info.finish_timestamp - info.start_timestamp); + ASSERT_GT(duration.count(), 0); + } +}; + +TEST_F(EventListenerTest, OnFileOperationTest) { + Options options; + options.env = CurrentOptions().env; + options.create_if_missing = true; + + TestFileOperationListener* listener = new TestFileOperationListener(); + options.listeners.emplace_back(listener); + + DestroyAndReopen(options); + ASSERT_OK(Put("foo", "aaa")); + dbfull()->Flush(FlushOptions()); + dbfull()->TEST_WaitForFlushMemTable(); + ASSERT_GE(listener->file_writes_.load(), + listener->file_writes_success_.load()); + ASSERT_GT(listener->file_writes_.load(), 0); + Close(); + + Reopen(options); + ASSERT_GE(listener->file_reads_.load(), listener->file_reads_success_.load()); + ASSERT_GT(listener->file_reads_.load(), 0); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/log_format.h b/thirdparty/rocksdb/db/log_format.h index be22201af0..5aeb5aa5fd 100644 --- a/thirdparty/rocksdb/db/log_format.h +++ b/thirdparty/rocksdb/db/log_format.h @@ -37,9 +37,9 @@ static const unsigned int kBlockSize = 32768; // Header is checksum (4 bytes), length (2 bytes), type (1 byte) static const int kHeaderSize = 4 + 2 + 1; -// Recyclable header is checksum (4 bytes), type (1 byte), log number -// (4 bytes), length (2 bytes). -static const int kRecyclableHeaderSize = 4 + 1 + 4 + 2; +// Recyclable header is checksum (4 bytes), length (2 bytes), type (1 byte), +// log number (4 bytes). +static const int kRecyclableHeaderSize = 4 + 2 + 1 + 4; } // namespace log } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/log_reader.cc b/thirdparty/rocksdb/db/log_reader.cc index cae5d8ea08..e734e9d6c8 100644 --- a/thirdparty/rocksdb/db/log_reader.cc +++ b/thirdparty/rocksdb/db/log_reader.cc @@ -14,6 +14,7 @@ #include "util/coding.h" #include "util/crc32c.h" #include "util/file_reader_writer.h" +#include "util/util.h" namespace rocksdb { namespace log { @@ -22,8 +23,8 @@ Reader::Reporter::~Reporter() { } Reader::Reader(std::shared_ptr info_log, - unique_ptr&& _file, Reporter* reporter, - bool checksum, uint64_t initial_offset, uint64_t log_num) + std::unique_ptr&& _file, + Reporter* reporter, bool checksum, uint64_t log_num) : info_log_(info_log), file_(std::move(_file)), reporter_(reporter), @@ -35,7 +36,6 @@ Reader::Reader(std::shared_ptr info_log, eof_offset_(0), last_record_offset_(0), end_of_buffer_offset_(0), - initial_offset_(initial_offset), log_number_(log_num), recycled_(false) {} @@ -43,29 +43,6 @@ Reader::~Reader() { delete[] backing_store_; } -bool Reader::SkipToInitialBlock() { - size_t initial_offset_in_block = initial_offset_ % kBlockSize; - uint64_t block_start_location = initial_offset_ - initial_offset_in_block; - - // Don't search a block if we'd be in the trailer - if (initial_offset_in_block > kBlockSize - 6) { - block_start_location += kBlockSize; - } - - end_of_buffer_offset_ = block_start_location; - - // Skip to start of first block that can contain the initial record - if (block_start_location > 0) { - Status skip_status = file_->Skip(block_start_location); - if (!skip_status.ok()) { - ReportDrop(static_cast(block_start_location), skip_status); - return false; - } - } - - return true; -} - // For kAbsoluteConsistency, on clean shutdown we don't expect any error // in the log files. For other modes, we can ignore only incomplete records // in the last log file, which are presumably due to a write in progress @@ -75,12 +52,6 @@ bool Reader::SkipToInitialBlock() { // restrict the inconsistency to only the last log bool Reader::ReadRecord(Slice* record, std::string* scratch, WALRecoveryMode wal_recovery_mode) { - if (last_record_offset_ < initial_offset_) { - if (!SkipToInitialBlock()) { - return false; - } - } - scratch->clear(); record->clear(); bool in_fragmented_record = false; @@ -151,7 +122,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch, // in clean shutdown we don't expect any error in the log files ReportCorruption(drop_size, "truncated header"); } - // fall-thru + FALLTHROUGH_INTENDED; case kEof: if (in_fragmented_record) { @@ -181,7 +152,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch, } return false; } - // fall-thru + FALLTHROUGH_INTENDED; case kBadRecord: if (in_fragmented_record) { @@ -234,13 +205,14 @@ void Reader::UnmarkEOF() { if (read_error_) { return; } - eof_ = false; - if (eof_offset_ == 0) { return; } + UnmarkEOFInternal(); +} +void Reader::UnmarkEOFInternal() { // If the EOF was in the middle of a block (a partial block was read) we have // to read the rest of the block as ReadPhysicalRecord can only read full // blocks and expects the file position indicator to be aligned to the start @@ -298,8 +270,7 @@ void Reader::ReportCorruption(size_t bytes, const char* reason) { } void Reader::ReportDrop(size_t bytes, const Status& reason) { - if (reporter_ != nullptr && - end_of_buffer_offset_ - buffer_.size() - bytes >= initial_offset_) { + if (reporter_ != nullptr) { reporter_->Corruption(bytes, reason); } } @@ -316,7 +287,7 @@ bool Reader::ReadMore(size_t* drop_size, int *error) { read_error_ = true; *error = kEof; return false; - } else if (buffer_.size() < (size_t)kBlockSize) { + } else if (buffer_.size() < static_cast(kBlockSize)) { eof_ = true; eof_offset_ = buffer_.size(); } @@ -341,8 +312,11 @@ bool Reader::ReadMore(size_t* drop_size, int *error) { unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) { while (true) { // We need at least the minimum header size - if (buffer_.size() < (size_t)kHeaderSize) { - int r; + if (buffer_.size() < static_cast(kHeaderSize)) { + // the default value of r is meaningless because ReadMore will overwrite + // it if it returns false; in case it returns true, the return value will + // not be used anyway + int r = kEof; if (!ReadMore(drop_size, &r)) { return r; } @@ -362,8 +336,8 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) { } header_size = kRecyclableHeaderSize; // We need enough for the larger header - if (buffer_.size() < (size_t)kRecyclableHeaderSize) { - int r; + if (buffer_.size() < static_cast(kRecyclableHeaderSize)) { + int r = kEof; if (!ReadMore(drop_size, &r)) { return r; } @@ -380,9 +354,9 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) { if (!eof_) { return kBadRecordLen; } - // If the end of the file has been reached without reading |length| bytes - // of payload, assume the writer died in the middle of writing the record. - // Don't report a corruption unless requested. + // If the end of the file has been reached without reading |length| + // bytes of payload, assume the writer died in the middle of writing the + // record. Don't report a corruption unless requested. if (*drop_size) { return kBadHeader; } @@ -416,17 +390,234 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) { buffer_.remove_prefix(header_size + length); - // Skip physical record that started before initial_offset_ - if (end_of_buffer_offset_ - buffer_.size() - header_size - length < - initial_offset_) { - result->clear(); - return kBadRecord; - } - *result = Slice(header + header_size, length); return type; } } +bool FragmentBufferedReader::ReadRecord(Slice* record, std::string* scratch, + WALRecoveryMode /*unused*/) { + assert(record != nullptr); + assert(scratch != nullptr); + record->clear(); + scratch->clear(); + + uint64_t prospective_record_offset = 0; + uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size(); + size_t drop_size = 0; + unsigned int fragment_type_or_err = 0; // Initialize to make compiler happy + Slice fragment; + while (TryReadFragment(&fragment, &drop_size, &fragment_type_or_err)) { + switch (fragment_type_or_err) { + case kFullType: + case kRecyclableFullType: + if (in_fragmented_record_ && !fragments_.empty()) { + ReportCorruption(fragments_.size(), "partial record without end(1)"); + } + fragments_.clear(); + *record = fragment; + prospective_record_offset = physical_record_offset; + last_record_offset_ = prospective_record_offset; + in_fragmented_record_ = false; + return true; + + case kFirstType: + case kRecyclableFirstType: + if (in_fragmented_record_ || !fragments_.empty()) { + ReportCorruption(fragments_.size(), "partial record without end(2)"); + } + prospective_record_offset = physical_record_offset; + fragments_.assign(fragment.data(), fragment.size()); + in_fragmented_record_ = true; + break; + + case kMiddleType: + case kRecyclableMiddleType: + if (!in_fragmented_record_) { + ReportCorruption(fragment.size(), + "missing start of fragmented record(1)"); + } else { + fragments_.append(fragment.data(), fragment.size()); + } + break; + + case kLastType: + case kRecyclableLastType: + if (!in_fragmented_record_) { + ReportCorruption(fragment.size(), + "missing start of fragmented record(2)"); + } else { + fragments_.append(fragment.data(), fragment.size()); + scratch->assign(fragments_.data(), fragments_.size()); + fragments_.clear(); + *record = Slice(*scratch); + last_record_offset_ = prospective_record_offset; + in_fragmented_record_ = false; + return true; + } + break; + + case kBadHeader: + case kBadRecord: + case kEof: + case kOldRecord: + if (in_fragmented_record_) { + ReportCorruption(fragments_.size(), "error in middle of record"); + in_fragmented_record_ = false; + fragments_.clear(); + } + break; + + case kBadRecordChecksum: + if (recycled_) { + fragments_.clear(); + return false; + } + ReportCorruption(drop_size, "checksum mismatch"); + if (in_fragmented_record_) { + ReportCorruption(fragments_.size(), "error in middle of record"); + in_fragmented_record_ = false; + fragments_.clear(); + } + break; + + default: { + char buf[40]; + snprintf(buf, sizeof(buf), "unknown record type %u", + fragment_type_or_err); + ReportCorruption( + fragment.size() + (in_fragmented_record_ ? fragments_.size() : 0), + buf); + in_fragmented_record_ = false; + fragments_.clear(); + break; + } + } + } + return false; +} + +void FragmentBufferedReader::UnmarkEOF() { + if (read_error_) { + return; + } + eof_ = false; + UnmarkEOFInternal(); +} + +bool FragmentBufferedReader::TryReadMore(size_t* drop_size, int* error) { + if (!eof_ && !read_error_) { + // Last read was a full read, so this is a trailer to skip + buffer_.clear(); + Status status = file_->Read(kBlockSize, &buffer_, backing_store_); + end_of_buffer_offset_ += buffer_.size(); + if (!status.ok()) { + buffer_.clear(); + ReportDrop(kBlockSize, status); + read_error_ = true; + *error = kEof; + return false; + } else if (buffer_.size() < static_cast(kBlockSize)) { + eof_ = true; + eof_offset_ = buffer_.size(); + TEST_SYNC_POINT_CALLBACK( + "FragmentBufferedLogReader::TryReadMore:FirstEOF", nullptr); + } + return true; + } else if (!read_error_) { + UnmarkEOF(); + } + if (!read_error_) { + return true; + } + *error = kEof; + *drop_size = buffer_.size(); + if (buffer_.size() > 0) { + *error = kBadHeader; + } + buffer_.clear(); + return false; +} + +// return true if the caller should process the fragment_type_or_err. +bool FragmentBufferedReader::TryReadFragment( + Slice* fragment, size_t* drop_size, unsigned int* fragment_type_or_err) { + assert(fragment != nullptr); + assert(drop_size != nullptr); + assert(fragment_type_or_err != nullptr); + + while (buffer_.size() < static_cast(kHeaderSize)) { + size_t old_size = buffer_.size(); + int error = kEof; + if (!TryReadMore(drop_size, &error)) { + *fragment_type_or_err = error; + return false; + } else if (old_size == buffer_.size()) { + return false; + } + } + const char* header = buffer_.data(); + const uint32_t a = static_cast(header[4]) & 0xff; + const uint32_t b = static_cast(header[5]) & 0xff; + const unsigned int type = header[6]; + const uint32_t length = a | (b << 8); + int header_size = kHeaderSize; + if (type >= kRecyclableFullType && type <= kRecyclableLastType) { + if (end_of_buffer_offset_ - buffer_.size() == 0) { + recycled_ = true; + } + header_size = kRecyclableHeaderSize; + while (buffer_.size() < static_cast(kRecyclableHeaderSize)) { + size_t old_size = buffer_.size(); + int error = kEof; + if (!TryReadMore(drop_size, &error)) { + *fragment_type_or_err = error; + return false; + } else if (old_size == buffer_.size()) { + return false; + } + } + const uint32_t log_num = DecodeFixed32(header + 7); + if (log_num != log_number_) { + *fragment_type_or_err = kOldRecord; + return true; + } + } + + while (header_size + length > buffer_.size()) { + size_t old_size = buffer_.size(); + int error = kEof; + if (!TryReadMore(drop_size, &error)) { + *fragment_type_or_err = error; + return false; + } else if (old_size == buffer_.size()) { + return false; + } + } + + if (type == kZeroType && length == 0) { + buffer_.clear(); + *fragment_type_or_err = kBadRecord; + return true; + } + + if (checksum_) { + uint32_t expected_crc = crc32c::Unmask(DecodeFixed32(header)); + uint32_t actual_crc = crc32c::Value(header + 6, length + header_size - 6); + if (actual_crc != expected_crc) { + *drop_size = buffer_.size(); + buffer_.clear(); + *fragment_type_or_err = kBadRecordChecksum; + return true; + } + } + + buffer_.remove_prefix(header_size + length); + + *fragment = Slice(header + header_size, length); + *fragment_type_or_err = type; + return true; +} + } // namespace log } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/log_reader.h b/thirdparty/rocksdb/db/log_reader.h index c6a471cda4..058382b8a3 100644 --- a/thirdparty/rocksdb/db/log_reader.h +++ b/thirdparty/rocksdb/db/log_reader.h @@ -20,7 +20,6 @@ namespace rocksdb { class SequentialFileReader; class Logger; -using std::unique_ptr; namespace log { @@ -50,24 +49,21 @@ class Reader { // live while this Reader is in use. // // If "checksum" is true, verify checksums if available. - // - // The Reader will start reading at the first record located at physical - // position >= initial_offset within the file. Reader(std::shared_ptr info_log, - unique_ptr&& file, - Reporter* reporter, bool checksum, uint64_t initial_offset, - uint64_t log_num); + // @lint-ignore TXT2 T25377293 Grandfathered in + std::unique_ptr&& file, Reporter* reporter, + bool checksum, uint64_t log_num); - ~Reader(); + virtual ~Reader(); // Read the next record into *record. Returns true if read // successfully, false if we hit end of the input. May use // "*scratch" as temporary storage. The contents filled in *record // will only be valid until the next mutating operation on this // reader or the next mutation to *scratch. - bool ReadRecord(Slice* record, std::string* scratch, - WALRecoveryMode wal_recovery_mode = - WALRecoveryMode::kTolerateCorruptedTailRecords); + virtual bool ReadRecord(Slice* record, std::string* scratch, + WALRecoveryMode wal_recovery_mode = + WALRecoveryMode::kTolerateCorruptedTailRecords); // Returns the physical offset of the last record returned by ReadRecord. // @@ -79,21 +75,28 @@ class Reader { return eof_; } + // returns true if the reader has encountered read error. + bool hasReadError() const { return read_error_; } + // when we know more data has been written to the file. we can use this // function to force the reader to look again in the file. // Also aligns the file position indicator to the start of the next block // by reading the rest of the data from the EOF position to the end of the // block that was partially read. - void UnmarkEOF(); + virtual void UnmarkEOF(); SequentialFileReader* file() { return file_.get(); } - private: + Reporter* GetReporter() const { return reporter_; } + + protected: std::shared_ptr info_log_; - const unique_ptr file_; + const std::unique_ptr file_; Reporter* const reporter_; bool const checksum_; char* const backing_store_; + + // Internal state variables used for reading records Slice buffer_; bool eof_; // Last Read() indicated EOF by returning < kBlockSize bool read_error_; // Error occurred while reading from file @@ -107,9 +110,6 @@ class Reader { // Offset of the first location past the end of buffer_. uint64_t end_of_buffer_offset_; - // Offset at which to start looking for the first record to return - uint64_t const initial_offset_; - // which log number this is uint64_t const log_number_; @@ -123,7 +123,6 @@ class Reader { // Currently there are three situations in which this happens: // * The record has an invalid CRC (ReadPhysicalRecord reports a drop) // * The record is a 0-length record (No drop is reported) - // * The record is below constructor's initial_offset (No drop is reported) kBadRecord = kMaxRecordType + 2, // Returned when we fail to read a valid header. kBadHeader = kMaxRecordType + 3, @@ -135,26 +134,53 @@ class Reader { kBadRecordChecksum = kMaxRecordType + 6, }; - // Skips all blocks that are completely before "initial_offset_". - // - // Returns true on success. Handles reporting. - bool SkipToInitialBlock(); - // Return type, or one of the preceding special values unsigned int ReadPhysicalRecord(Slice* result, size_t* drop_size); // Read some more bool ReadMore(size_t* drop_size, int *error); + void UnmarkEOFInternal(); + // Reports dropped bytes to the reporter. // buffer_ must be updated to remove the dropped bytes prior to invocation. void ReportCorruption(size_t bytes, const char* reason); void ReportDrop(size_t bytes, const Status& reason); + private: // No copying allowed Reader(const Reader&); void operator=(const Reader&); }; +class FragmentBufferedReader : public Reader { + public: + FragmentBufferedReader(std::shared_ptr info_log, + // @lint-ignore TXT2 T25377293 Grandfathered in + std::unique_ptr&& _file, + Reporter* reporter, bool checksum, uint64_t log_num) + : Reader(info_log, std::move(_file), reporter, checksum, log_num), + fragments_(), + in_fragmented_record_(false) {} + ~FragmentBufferedReader() override {} + bool ReadRecord(Slice* record, std::string* scratch, + WALRecoveryMode wal_recovery_mode = + WALRecoveryMode::kTolerateCorruptedTailRecords) override; + void UnmarkEOF() override; + + private: + std::string fragments_; + bool in_fragmented_record_; + + bool TryReadFragment(Slice* result, size_t* drop_size, + unsigned int* fragment_type_or_err); + + bool TryReadMore(size_t* drop_size, int* error); + + // No copy allowed + FragmentBufferedReader(const FragmentBufferedReader&); + void operator=(const FragmentBufferedReader&); +}; + } // namespace log } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/log_test.cc b/thirdparty/rocksdb/db/log_test.cc index 651a1d0eee..fd237b030e 100644 --- a/thirdparty/rocksdb/db/log_test.cc +++ b/thirdparty/rocksdb/db/log_test.cc @@ -43,7 +43,10 @@ static std::string RandomSkewedString(int i, Random* rnd) { return BigString(NumberString(i), rnd->Skewed(17)); } -class LogTest : public ::testing::TestWithParam { +// Param type is tuple +// get<0>(tuple): non-zero if recycling log, zero if regular log +// get<1>(tuple): true if allow retry after read EOF, false otherwise +class LogTest : public ::testing::TestWithParam> { private: class StringSource : public SequentialFile { public: @@ -53,16 +56,20 @@ class LogTest : public ::testing::TestWithParam { bool force_eof_; size_t force_eof_position_; bool returned_partial_; - explicit StringSource(Slice& contents) : - contents_(contents), - force_error_(false), - force_error_position_(0), - force_eof_(false), - force_eof_position_(0), - returned_partial_(false) { } - - virtual Status Read(size_t n, Slice* result, char* scratch) override { - EXPECT_TRUE(!returned_partial_) << "must not Read() after eof/error"; + bool fail_after_read_partial_; + explicit StringSource(Slice& contents, bool fail_after_read_partial) + : contents_(contents), + force_error_(false), + force_error_position_(0), + force_eof_(false), + force_eof_position_(0), + returned_partial_(false), + fail_after_read_partial_(fail_after_read_partial) {} + + Status Read(size_t n, Slice* result, char* scratch) override { + if (fail_after_read_partial_) { + EXPECT_TRUE(!returned_partial_) << "must not Read() after eof/error"; + } if (force_error_) { if (force_error_position_ >= n) { @@ -100,7 +107,7 @@ class LogTest : public ::testing::TestWithParam { return Status::OK(); } - virtual Status Skip(uint64_t n) override { + Status Skip(uint64_t n) override { if (n > contents_.size()) { contents_.clear(); return Status::NotFound("in-memory file skipepd past end"); @@ -118,7 +125,7 @@ class LogTest : public ::testing::TestWithParam { std::string message_; ReportCollector() : dropped_bytes_(0) { } - virtual void Corruption(size_t bytes, const Status& status) override { + void Corruption(size_t bytes, const Status& status) override { dropped_bytes_ += bytes; message_.append(status.ToString()); } @@ -139,39 +146,39 @@ class LogTest : public ::testing::TestWithParam { } void reset_source_contents() { - auto src = dynamic_cast(reader_.file()->file()); + auto src = dynamic_cast(reader_->file()->file()); assert(src); src->contents_ = dest_contents(); } Slice reader_contents_; - unique_ptr dest_holder_; - unique_ptr source_holder_; + std::unique_ptr dest_holder_; + std::unique_ptr source_holder_; ReportCollector report_; Writer writer_; - Reader reader_; + std::unique_ptr reader_; - // Record metadata for testing initial offset functionality - static size_t initial_offset_record_sizes_[]; - uint64_t initial_offset_last_record_offsets_[4]; + protected: + bool allow_retry_read_; public: LogTest() : reader_contents_(), dest_holder_(test::GetWritableFileWriter( - new test::StringSink(&reader_contents_))), - source_holder_( - test::GetSequentialFileReader(new StringSource(reader_contents_))), - writer_(std::move(dest_holder_), 123, GetParam()), - reader_(NULL, std::move(source_holder_), &report_, true /*checksum*/, - 0 /*initial_offset*/, 123) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - initial_offset_last_record_offsets_[0] = 0; - initial_offset_last_record_offsets_[1] = header_size + 10000; - initial_offset_last_record_offsets_[2] = 2 * (header_size + 10000); - initial_offset_last_record_offsets_[3] = 2 * (header_size + 10000) + - (2 * log::kBlockSize - 1000) + - 3 * header_size; + new test::StringSink(&reader_contents_), "" /* don't care */)), + source_holder_(test::GetSequentialFileReader( + new StringSource(reader_contents_, !std::get<1>(GetParam())), + "" /* file name */)), + writer_(std::move(dest_holder_), 123, std::get<0>(GetParam())), + allow_retry_read_(std::get<1>(GetParam())) { + if (allow_retry_read_) { + reader_.reset(new FragmentBufferedReader( + nullptr, std::move(source_holder_), &report_, true /* checksum */, + 123 /* log_number */)); + } else { + reader_.reset(new Reader(nullptr, std::move(source_holder_), &report_, + true /* checksum */, 123 /* log_number */)); + } } Slice* get_reader_contents() { return &reader_contents_; } @@ -188,14 +195,16 @@ class LogTest : public ::testing::TestWithParam { WALRecoveryMode::kTolerateCorruptedTailRecords) { std::string scratch; Slice record; - if (reader_.ReadRecord(&record, &scratch, wal_recovery_mode)) { + bool ret = false; + ret = reader_->ReadRecord(&record, &scratch, wal_recovery_mode); + if (ret) { return record.ToString(); } else { return "EOF"; } } - void IncrementByte(int offset, int delta) { + void IncrementByte(int offset, char delta) { dest_contents()[offset] += delta; } @@ -220,7 +229,7 @@ class LogTest : public ::testing::TestWithParam { } void ForceError(size_t position = 0) { - auto src = dynamic_cast(reader_.file()->file()); + auto src = dynamic_cast(reader_->file()->file()); src->force_error_ = true; src->force_error_position_ = position; } @@ -234,20 +243,18 @@ class LogTest : public ::testing::TestWithParam { } void ForceEOF(size_t position = 0) { - auto src = dynamic_cast(reader_.file()->file()); + auto src = dynamic_cast(reader_->file()->file()); src->force_eof_ = true; src->force_eof_position_ = position; } void UnmarkEOF() { - auto src = dynamic_cast(reader_.file()->file()); + auto src = dynamic_cast(reader_->file()->file()); src->returned_partial_ = false; - reader_.UnmarkEOF(); + reader_->UnmarkEOF(); } - bool IsEOF() { - return reader_.IsEOF(); - } + bool IsEOF() { return reader_->IsEOF(); } // Returns OK iff recorded error message contains "msg" std::string MatchError(const std::string& msg) const { @@ -257,53 +264,8 @@ class LogTest : public ::testing::TestWithParam { return "OK"; } } - - void WriteInitialOffsetLog() { - for (int i = 0; i < 4; i++) { - std::string record(initial_offset_record_sizes_[i], - static_cast('a' + i)); - Write(record); - } - } - - void CheckOffsetPastEndReturnsNoRecords(uint64_t offset_past_end) { - WriteInitialOffsetLog(); - unique_ptr file_reader( - test::GetSequentialFileReader(new StringSource(reader_contents_))); - unique_ptr offset_reader( - new Reader(NULL, std::move(file_reader), &report_, - true /*checksum*/, WrittenBytes() + offset_past_end, 123)); - Slice record; - std::string scratch; - ASSERT_TRUE(!offset_reader->ReadRecord(&record, &scratch)); - } - - void CheckInitialOffsetRecord(uint64_t initial_offset, - int expected_record_offset) { - WriteInitialOffsetLog(); - unique_ptr file_reader( - test::GetSequentialFileReader(new StringSource(reader_contents_))); - unique_ptr offset_reader( - new Reader(NULL, std::move(file_reader), &report_, - true /*checksum*/, initial_offset, 123)); - Slice record; - std::string scratch; - ASSERT_TRUE(offset_reader->ReadRecord(&record, &scratch)); - ASSERT_EQ(initial_offset_record_sizes_[expected_record_offset], - record.size()); - ASSERT_EQ(initial_offset_last_record_offsets_[expected_record_offset], - offset_reader->LastRecordOffset()); - ASSERT_EQ((char)('a' + expected_record_offset), record.data()[0]); - } - }; -size_t LogTest::initial_offset_record_sizes_[] = - {10000, // Two sizable records in first block - 10000, - 2 * log::kBlockSize - 1000, // Span three blocks - 1}; - TEST_P(LogTest, Empty) { ASSERT_EQ("EOF", Read()); } TEST_P(LogTest, ReadWrite) { @@ -341,7 +303,8 @@ TEST_P(LogTest, Fragmentation) { TEST_P(LogTest, MarginalTrailer) { // Make a trailer that is exactly the same length as an empty record. - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + int header_size = + std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; const int n = kBlockSize - 2 * header_size; Write(BigString("foo", n)); ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes()); @@ -355,7 +318,8 @@ TEST_P(LogTest, MarginalTrailer) { TEST_P(LogTest, MarginalTrailer2) { // Make a trailer that is exactly the same length as an empty record. - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + int header_size = + std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; const int n = kBlockSize - 2 * header_size; Write(BigString("foo", n)); ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes()); @@ -368,7 +332,8 @@ TEST_P(LogTest, MarginalTrailer2) { } TEST_P(LogTest, ShortTrailer) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + int header_size = + std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; const int n = kBlockSize - 2 * header_size + 4; Write(BigString("foo", n)); ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes()); @@ -381,7 +346,8 @@ TEST_P(LogTest, ShortTrailer) { } TEST_P(LogTest, AlignedEof) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + int header_size = + std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize; const int n = kBlockSize - 2 * header_size + 4; Write(BigString("foo", n)); ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes()); @@ -432,6 +398,11 @@ TEST_P(LogTest, TruncatedTrailingRecordIsIgnored) { } TEST_P(LogTest, TruncatedTrailingRecordIsNotIgnored) { + if (allow_retry_read_) { + // If read retry is allowed, then truncated trailing record should not + // raise an error. + return; + } Write("foo"); ShrinkSize(4); // Drop all payload as well as a header byte ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency)); @@ -441,13 +412,20 @@ TEST_P(LogTest, TruncatedTrailingRecordIsNotIgnored) { } TEST_P(LogTest, BadLength) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + if (allow_retry_read_) { + // If read retry is allowed, then we should not raise an error when the + // record length specified in header is longer than data currently + // available. It's possible that the body of the record is not written yet. + return; + } + bool recyclable_log = (std::get<0>(GetParam()) != 0); + int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize; const int kPayloadSize = kBlockSize - header_size; Write(BigString("bar", kPayloadSize)); Write("foo"); // Least significant size byte is stored in header[4]. IncrementByte(4, 1); - if (!GetParam()) { + if (!recyclable_log) { ASSERT_EQ("foo", Read()); ASSERT_EQ(kBlockSize, DroppedBytes()); ASSERT_EQ("OK", MatchError("bad record length")); @@ -457,6 +435,12 @@ TEST_P(LogTest, BadLength) { } TEST_P(LogTest, BadLengthAtEndIsIgnored) { + if (allow_retry_read_) { + // If read retry is allowed, then we should not raise an error when the + // record length specified in header is longer than data currently + // available. It's possible that the body of the record is not written yet. + return; + } Write("foo"); ShrinkSize(1); ASSERT_EQ("EOF", Read()); @@ -465,6 +449,12 @@ TEST_P(LogTest, BadLengthAtEndIsIgnored) { } TEST_P(LogTest, BadLengthAtEndIsNotIgnored) { + if (allow_retry_read_) { + // If read retry is allowed, then we should not raise an error when the + // record length specified in header is longer than data currently + // available. It's possible that the body of the record is not written yet. + return; + } Write("foo"); ShrinkSize(1); ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency)); @@ -476,7 +466,8 @@ TEST_P(LogTest, ChecksumMismatch) { Write("foooooo"); IncrementByte(0, 14); ASSERT_EQ("EOF", Read()); - if (!GetParam()) { + bool recyclable_log = (std::get<0>(GetParam()) != 0); + if (!recyclable_log) { ASSERT_EQ(14U, DroppedBytes()); ASSERT_EQ("OK", MatchError("checksum mismatch")); } else { @@ -487,8 +478,10 @@ TEST_P(LogTest, ChecksumMismatch) { TEST_P(LogTest, UnexpectedMiddleType) { Write("foo"); - SetByte(6, GetParam() ? kRecyclableMiddleType : kMiddleType); - FixChecksum(0, 3, !!GetParam()); + bool recyclable_log = (std::get<0>(GetParam()) != 0); + SetByte(6, static_cast(recyclable_log ? kRecyclableMiddleType + : kMiddleType)); + FixChecksum(0, 3, !!recyclable_log); ASSERT_EQ("EOF", Read()); ASSERT_EQ(3U, DroppedBytes()); ASSERT_EQ("OK", MatchError("missing start")); @@ -496,8 +489,10 @@ TEST_P(LogTest, UnexpectedMiddleType) { TEST_P(LogTest, UnexpectedLastType) { Write("foo"); - SetByte(6, GetParam() ? kRecyclableLastType : kLastType); - FixChecksum(0, 3, !!GetParam()); + bool recyclable_log = (std::get<0>(GetParam()) != 0); + SetByte(6, + static_cast(recyclable_log ? kRecyclableLastType : kLastType)); + FixChecksum(0, 3, !!recyclable_log); ASSERT_EQ("EOF", Read()); ASSERT_EQ(3U, DroppedBytes()); ASSERT_EQ("OK", MatchError("missing start")); @@ -506,8 +501,10 @@ TEST_P(LogTest, UnexpectedLastType) { TEST_P(LogTest, UnexpectedFullType) { Write("foo"); Write("bar"); - SetByte(6, GetParam() ? kRecyclableFirstType : kFirstType); - FixChecksum(0, 3, !!GetParam()); + bool recyclable_log = (std::get<0>(GetParam()) != 0); + SetByte( + 6, static_cast(recyclable_log ? kRecyclableFirstType : kFirstType)); + FixChecksum(0, 3, !!recyclable_log); ASSERT_EQ("bar", Read()); ASSERT_EQ("EOF", Read()); ASSERT_EQ(3U, DroppedBytes()); @@ -517,8 +514,10 @@ TEST_P(LogTest, UnexpectedFullType) { TEST_P(LogTest, UnexpectedFirstType) { Write("foo"); Write(BigString("bar", 100000)); - SetByte(6, GetParam() ? kRecyclableFirstType : kFirstType); - FixChecksum(0, 3, !!GetParam()); + bool recyclable_log = (std::get<0>(GetParam()) != 0); + SetByte( + 6, static_cast(recyclable_log ? kRecyclableFirstType : kFirstType)); + FixChecksum(0, 3, !!recyclable_log); ASSERT_EQ(BigString("bar", 100000), Read()); ASSERT_EQ("EOF", Read()); ASSERT_EQ(3U, DroppedBytes()); @@ -535,6 +534,11 @@ TEST_P(LogTest, MissingLastIsIgnored) { } TEST_P(LogTest, MissingLastIsNotIgnored) { + if (allow_retry_read_) { + // If read retry is allowed, then truncated trailing record should not + // raise an error. + return; + } Write(BigString("bar", kBlockSize)); // Remove the LAST block, including header. ShrinkSize(14); @@ -553,6 +557,11 @@ TEST_P(LogTest, PartialLastIsIgnored) { } TEST_P(LogTest, PartialLastIsNotIgnored) { + if (allow_retry_read_) { + // If read retry is allowed, then truncated trailing record should not + // raise an error. + return; + } Write(BigString("bar", kBlockSize)); // Cause a bad record length in the LAST block. ShrinkSize(1); @@ -579,7 +588,8 @@ TEST_P(LogTest, ErrorJoinsRecords) { SetByte(offset, 'x'); } - if (!GetParam()) { + bool recyclable_log = (std::get<0>(GetParam()) != 0); + if (!recyclable_log) { ASSERT_EQ("correct", Read()); ASSERT_EQ("EOF", Read()); size_t dropped = DroppedBytes(); @@ -590,59 +600,11 @@ TEST_P(LogTest, ErrorJoinsRecords) { } } -TEST_P(LogTest, ReadStart) { CheckInitialOffsetRecord(0, 0); } - -TEST_P(LogTest, ReadSecondOneOff) { CheckInitialOffsetRecord(1, 1); } - -TEST_P(LogTest, ReadSecondTenThousand) { CheckInitialOffsetRecord(10000, 1); } - -TEST_P(LogTest, ReadSecondStart) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - CheckInitialOffsetRecord(10000 + header_size, 1); -} - -TEST_P(LogTest, ReadThirdOneOff) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - CheckInitialOffsetRecord(10000 + header_size + 1, 2); -} - -TEST_P(LogTest, ReadThirdStart) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - CheckInitialOffsetRecord(20000 + 2 * header_size, 2); -} - -TEST_P(LogTest, ReadFourthOneOff) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - CheckInitialOffsetRecord(20000 + 2 * header_size + 1, 3); -} - -TEST_P(LogTest, ReadFourthFirstBlockTrailer) { - CheckInitialOffsetRecord(log::kBlockSize - 4, 3); -} - -TEST_P(LogTest, ReadFourthMiddleBlock) { - CheckInitialOffsetRecord(log::kBlockSize + 1, 3); -} - -TEST_P(LogTest, ReadFourthLastBlock) { - CheckInitialOffsetRecord(2 * log::kBlockSize + 1, 3); -} - -TEST_P(LogTest, ReadFourthStart) { - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; - CheckInitialOffsetRecord( - 2 * (header_size + 1000) + (2 * log::kBlockSize - 1000) + 3 * header_size, - 3); -} - -TEST_P(LogTest, ReadEnd) { CheckOffsetPastEndReturnsNoRecords(0); } - -TEST_P(LogTest, ReadPastEnd) { CheckOffsetPastEndReturnsNoRecords(5); } - TEST_P(LogTest, ClearEofSingleBlock) { Write("foo"); Write("bar"); - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + bool recyclable_log = (std::get<0>(GetParam()) != 0); + int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize; ForceEOF(3 + header_size + 2); ASSERT_EQ("foo", Read()); UnmarkEOF(); @@ -657,7 +619,8 @@ TEST_P(LogTest, ClearEofSingleBlock) { TEST_P(LogTest, ClearEofMultiBlock) { size_t num_full_blocks = 5; - int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + bool recyclable_log = (std::get<0>(GetParam()) != 0); + int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize; size_t n = (kBlockSize - header_size) * num_full_blocks + 25; Write(BigString("foo", n)); Write(BigString("bar", n)); @@ -706,7 +669,8 @@ TEST_P(LogTest, ClearEofError2) { } TEST_P(LogTest, Recycle) { - if (!GetParam()) { + bool recyclable_log = (std::get<0>(GetParam()) != 0); + if (!recyclable_log) { return; // test is only valid for recycled logs } Write("foo"); @@ -717,8 +681,9 @@ TEST_P(LogTest, Recycle) { while (get_reader_contents()->size() < log::kBlockSize * 2) { Write("xxxxxxxxxxxxxxxx"); } - unique_ptr dest_holder(test::GetWritableFileWriter( - new test::OverwritingStringSink(get_reader_contents()))); + std::unique_ptr dest_holder(test::GetWritableFileWriter( + new test::OverwritingStringSink(get_reader_contents()), + "" /* don't care */)); Writer recycle_writer(std::move(dest_holder), 123, true); recycle_writer.AddRecord(Slice("foooo")); recycle_writer.AddRecord(Slice("bar")); @@ -728,7 +693,224 @@ TEST_P(LogTest, Recycle) { ASSERT_EQ("EOF", Read()); } -INSTANTIATE_TEST_CASE_P(bool, LogTest, ::testing::Values(0, 2)); +INSTANTIATE_TEST_CASE_P(bool, LogTest, + ::testing::Values(std::make_tuple(0, false), + std::make_tuple(0, true), + std::make_tuple(1, false), + std::make_tuple(1, true))); + +class RetriableLogTest : public ::testing::TestWithParam { + private: + class ReportCollector : public Reader::Reporter { + public: + size_t dropped_bytes_; + std::string message_; + + ReportCollector() : dropped_bytes_(0) {} + void Corruption(size_t bytes, const Status& status) override { + dropped_bytes_ += bytes; + message_.append(status.ToString()); + } + }; + + Slice contents_; + std::unique_ptr dest_holder_; + std::unique_ptr log_writer_; + Env* env_; + EnvOptions env_options_; + const std::string test_dir_; + const std::string log_file_; + std::unique_ptr writer_; + std::unique_ptr reader_; + ReportCollector report_; + std::unique_ptr log_reader_; + + public: + RetriableLogTest() + : contents_(), + dest_holder_(nullptr), + log_writer_(nullptr), + env_(Env::Default()), + test_dir_(test::PerThreadDBPath("retriable_log_test")), + log_file_(test_dir_ + "/log"), + writer_(nullptr), + reader_(nullptr), + log_reader_(nullptr) {} + + Status SetupTestEnv() { + dest_holder_.reset(test::GetWritableFileWriter( + new test::StringSink(&contents_), "" /* file name */)); + assert(dest_holder_ != nullptr); + log_writer_.reset(new Writer(std::move(dest_holder_), 123, GetParam())); + assert(log_writer_ != nullptr); + + Status s; + s = env_->CreateDirIfMissing(test_dir_); + std::unique_ptr writable_file; + if (s.ok()) { + s = env_->NewWritableFile(log_file_, &writable_file, env_options_); + } + if (s.ok()) { + writer_.reset(new WritableFileWriter(std::move(writable_file), log_file_, + env_options_)); + assert(writer_ != nullptr); + } + std::unique_ptr seq_file; + if (s.ok()) { + s = env_->NewSequentialFile(log_file_, &seq_file, env_options_); + } + if (s.ok()) { + reader_.reset(new SequentialFileReader(std::move(seq_file), log_file_)); + assert(reader_ != nullptr); + log_reader_.reset(new FragmentBufferedReader( + nullptr, std::move(reader_), &report_, true /* checksum */, + 123 /* log_number */)); + assert(log_reader_ != nullptr); + } + return s; + } + + std::string contents() { + auto file = + dynamic_cast(log_writer_->file()->writable_file()); + assert(file != nullptr); + return file->contents_; + } + + void Encode(const std::string& msg) { log_writer_->AddRecord(Slice(msg)); } + + void Write(const Slice& data) { + writer_->Append(data); + writer_->Sync(true); + } + + bool TryRead(std::string* result) { + assert(result != nullptr); + result->clear(); + std::string scratch; + Slice record; + bool r = log_reader_->ReadRecord(&record, &scratch); + if (r) { + result->assign(record.data(), record.size()); + return true; + } else { + return false; + } + } +}; + +TEST_P(RetriableLogTest, TailLog_PartialHeader) { + ASSERT_OK(SetupTestEnv()); + std::vector remaining_bytes_in_last_record; + size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + bool eof = false; + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->LoadDependency( + {{"RetriableLogTest::TailLog:AfterPart1", + "RetriableLogTest::TailLog:BeforeReadRecord"}, + {"FragmentBufferedLogReader::TryReadMore:FirstEOF", + "RetriableLogTest::TailLog:BeforePart2"}}); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->SetCallBack( + "FragmentBufferedLogReader::TryReadMore:FirstEOF", + [&](void* /*arg*/) { eof = true; }); + SyncPoint::GetInstance()->EnableProcessing(); + + size_t delta = header_size - 1; + port::Thread log_writer_thread([&]() { + size_t old_sz = contents().size(); + Encode("foo"); + size_t new_sz = contents().size(); + std::string part1 = contents().substr(old_sz, delta); + std::string part2 = + contents().substr(old_sz + delta, new_sz - old_sz - delta); + Write(Slice(part1)); + TEST_SYNC_POINT("RetriableLogTest::TailLog:AfterPart1"); + TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforePart2"); + Write(Slice(part2)); + }); + + std::string record; + port::Thread log_reader_thread([&]() { + TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforeReadRecord"); + while (!TryRead(&record)) { + } + }); + log_reader_thread.join(); + log_writer_thread.join(); + ASSERT_EQ("foo", record); + ASSERT_TRUE(eof); +} + +TEST_P(RetriableLogTest, TailLog_FullHeader) { + ASSERT_OK(SetupTestEnv()); + std::vector remaining_bytes_in_last_record; + size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + bool eof = false; + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->LoadDependency( + {{"RetriableLogTest::TailLog:AfterPart1", + "RetriableLogTest::TailLog:BeforeReadRecord"}, + {"FragmentBufferedLogReader::TryReadMore:FirstEOF", + "RetriableLogTest::TailLog:BeforePart2"}}); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->SetCallBack( + "FragmentBufferedLogReader::TryReadMore:FirstEOF", + [&](void* /*arg*/) { eof = true; }); + SyncPoint::GetInstance()->EnableProcessing(); + + size_t delta = header_size + 1; + port::Thread log_writer_thread([&]() { + size_t old_sz = contents().size(); + Encode("foo"); + size_t new_sz = contents().size(); + std::string part1 = contents().substr(old_sz, delta); + std::string part2 = + contents().substr(old_sz + delta, new_sz - old_sz - delta); + Write(Slice(part1)); + TEST_SYNC_POINT("RetriableLogTest::TailLog:AfterPart1"); + TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforePart2"); + Write(Slice(part2)); + ASSERT_TRUE(eof); + }); + + std::string record; + port::Thread log_reader_thread([&]() { + TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforeReadRecord"); + while (!TryRead(&record)) { + } + }); + log_reader_thread.join(); + log_writer_thread.join(); + ASSERT_EQ("foo", record); +} + +TEST_P(RetriableLogTest, NonBlockingReadFullRecord) { + // Clear all sync point callbacks even if this test does not use sync point. + // It is necessary, otherwise the execute of this test may hit a sync point + // with which a callback is registered. The registered callback may access + // some dead variable, causing segfault. + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + ASSERT_OK(SetupTestEnv()); + size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize; + size_t delta = header_size - 1; + size_t old_sz = contents().size(); + Encode("foo-bar"); + size_t new_sz = contents().size(); + std::string part1 = contents().substr(old_sz, delta); + std::string part2 = + contents().substr(old_sz + delta, new_sz - old_sz - delta); + Write(Slice(part1)); + std::string record; + ASSERT_FALSE(TryRead(&record)); + ASSERT_TRUE(record.empty()); + Write(Slice(part2)); + ASSERT_TRUE(TryRead(&record)); + ASSERT_EQ("foo-bar", record); +} + +INSTANTIATE_TEST_CASE_P(bool, RetriableLogTest, ::testing::Values(0, 2)); } // namespace log } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/log_writer.cc b/thirdparty/rocksdb/db/log_writer.cc index b02eec89dd..bc99931b9a 100644 --- a/thirdparty/rocksdb/db/log_writer.cc +++ b/thirdparty/rocksdb/db/log_writer.cc @@ -18,7 +18,7 @@ namespace rocksdb { namespace log { -Writer::Writer(unique_ptr&& dest, uint64_t log_number, +Writer::Writer(std::unique_ptr&& dest, uint64_t log_number, bool recycle_log_files, bool manual_flush) : dest_(std::move(dest)), block_offset_(0), @@ -57,8 +57,11 @@ Status Writer::AddRecord(const Slice& slice) { // Fill the trailer (literal below relies on kHeaderSize and // kRecyclableHeaderSize being <= 11) assert(header_size <= 11); - dest_->Append( - Slice("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", leftover)); + s = dest_->Append(Slice("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + static_cast(leftover))); + if (!s.ok()) { + break; + } } block_offset_ = 0; } @@ -89,6 +92,8 @@ Status Writer::AddRecord(const Slice& slice) { return s; } +bool Writer::TEST_BufferIsEmpty() { return dest_->TEST_BufferIsEmpty(); } + Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) { assert(n <= 0xffff); // Must fit in two bytes diff --git a/thirdparty/rocksdb/db/log_writer.h b/thirdparty/rocksdb/db/log_writer.h index a3a879924e..3638beb7eb 100644 --- a/thirdparty/rocksdb/db/log_writer.h +++ b/thirdparty/rocksdb/db/log_writer.h @@ -20,8 +20,6 @@ namespace rocksdb { class WritableFileWriter; -using std::unique_ptr; - namespace log { /** @@ -49,7 +47,7 @@ namespace log { * |CRC (4B) | Size (2B) | Type (1B) | Payload | * +---------+-----------+-----------+--- ... ---+ * - * CRC = 32bit hash computed over the payload using CRC + * CRC = 32bit hash computed over the record type and payload using CRC * Size = Length of the payload data * Type = Type of record * (kZeroType, kFullType, kFirstType, kLastType, kMiddleType ) @@ -72,8 +70,9 @@ class Writer { // Create a writer that will append data to "*dest". // "*dest" must be initially empty. // "*dest" must remain live while this Writer is in use. - explicit Writer(unique_ptr&& dest, uint64_t log_number, - bool recycle_log_files, bool manual_flush = false); + explicit Writer(std::unique_ptr&& dest, + uint64_t log_number, bool recycle_log_files, + bool manual_flush = false); ~Writer(); Status AddRecord(const Slice& slice); @@ -85,8 +84,10 @@ class Writer { Status WriteBuffer(); + bool TEST_BufferIsEmpty(); + private: - unique_ptr dest_; + std::unique_ptr dest_; size_t block_offset_; // Current offset in block uint64_t log_number_; bool recycle_log_files_; diff --git a/thirdparty/rocksdb/db/logs_with_prep_tracker.cc b/thirdparty/rocksdb/db/logs_with_prep_tracker.cc new file mode 100644 index 0000000000..1082dc102a --- /dev/null +++ b/thirdparty/rocksdb/db/logs_with_prep_tracker.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +#include "db/logs_with_prep_tracker.h" + +#include "port/likely.h" + +namespace rocksdb { +void LogsWithPrepTracker::MarkLogAsHavingPrepSectionFlushed(uint64_t log) { + assert(log != 0); + std::lock_guard lock(prepared_section_completed_mutex_); + auto it = prepared_section_completed_.find(log); + if (UNLIKELY(it == prepared_section_completed_.end())) { + prepared_section_completed_[log] = 1; + } else { + it->second += 1; + } +} + +void LogsWithPrepTracker::MarkLogAsContainingPrepSection(uint64_t log) { + assert(log != 0); + std::lock_guard lock(logs_with_prep_mutex_); + + auto rit = logs_with_prep_.rbegin(); + bool updated = false; + // Most probably the last log is the one that is being marked for + // having a prepare section; so search from the end. + for (; rit != logs_with_prep_.rend() && rit->log >= log; ++rit) { + if (rit->log == log) { + rit->cnt++; + updated = true; + break; + } + } + if (!updated) { + // We are either at the start, or at a position with rit->log < log + logs_with_prep_.insert(rit.base(), {log, 1}); + } +} + +uint64_t LogsWithPrepTracker::FindMinLogContainingOutstandingPrep() { + std::lock_guard lock(logs_with_prep_mutex_); + auto it = logs_with_prep_.begin(); + // start with the smallest log + for (; it != logs_with_prep_.end();) { + auto min_log = it->log; + { + std::lock_guard lock2(prepared_section_completed_mutex_); + auto completed_it = prepared_section_completed_.find(min_log); + if (completed_it == prepared_section_completed_.end() || + completed_it->second < it->cnt) { + return min_log; + } + assert(completed_it != prepared_section_completed_.end() && + completed_it->second == it->cnt); + prepared_section_completed_.erase(completed_it); + } + // erase from beginning in vector is not efficient but this function is not + // on the fast path. + it = logs_with_prep_.erase(it); + } + // no such log found + return 0; +} +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/logs_with_prep_tracker.h b/thirdparty/rocksdb/db/logs_with_prep_tracker.h new file mode 100644 index 0000000000..639d8f8069 --- /dev/null +++ b/thirdparty/rocksdb/db/logs_with_prep_tracker.h @@ -0,0 +1,61 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace rocksdb { + +// This class is used to track the log files with outstanding prepare entries. +class LogsWithPrepTracker { + public: + // Called when a transaction prepared in `log` has been committed or aborted. + void MarkLogAsHavingPrepSectionFlushed(uint64_t log); + // Called when a transaction is prepared in `log`. + void MarkLogAsContainingPrepSection(uint64_t log); + // Return the earliest log file with outstanding prepare entries. + uint64_t FindMinLogContainingOutstandingPrep(); + size_t TEST_PreparedSectionCompletedSize() { + return prepared_section_completed_.size(); + } + size_t TEST_LogsWithPrepSize() { return logs_with_prep_.size(); } + + private: + // REQUIRES: logs_with_prep_mutex_ held + // + // sorted list of log numbers still containing prepared data. + // this is used by FindObsoleteFiles to determine which + // flushed logs we must keep around because they still + // contain prepared data which has not been committed or rolled back + struct LogCnt { + uint64_t log; // the log number + uint64_t cnt; // number of prepared sections in the log + }; + std::vector logs_with_prep_; + std::mutex logs_with_prep_mutex_; + + // REQUIRES: prepared_section_completed_mutex_ held + // + // to be used in conjunction with logs_with_prep_. + // once a transaction with data in log L is committed or rolled back + // rather than updating logs_with_prep_ directly we keep track of that + // in prepared_section_completed_ which maps LOG -> instance_count. This helps + // avoiding contention between a commit thread and the prepare threads. + // + // when trying to determine the minimum log still active we first + // consult logs_with_prep_. while that root value maps to + // an equal value in prepared_section_completed_ we erase the log from + // both logs_with_prep_ and prepared_section_completed_. + std::unordered_map prepared_section_completed_; + std::mutex prepared_section_completed_mutex_; + +}; +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/malloc_stats.cc b/thirdparty/rocksdb/db/malloc_stats.cc index 7acca65123..bcee5c3fbf 100644 --- a/thirdparty/rocksdb/db/malloc_stats.cc +++ b/thirdparty/rocksdb/db/malloc_stats.cc @@ -13,10 +13,16 @@ #include #include +#include "port/jemalloc_helper.h" + + namespace rocksdb { #ifdef ROCKSDB_JEMALLOC -#include "jemalloc/jemalloc.h" + +#ifdef JEMALLOC_NO_RENAME +#define malloc_stats_print je_malloc_stats_print +#endif typedef struct { char* cur; @@ -34,19 +40,20 @@ static void GetJemallocStatus(void* mstat_arg, const char* status) { snprintf(mstat->cur, buf_size, "%s", status); mstat->cur += status_len; } -#endif // ROCKSDB_JEMALLOC - void DumpMallocStats(std::string* stats) { -#ifdef ROCKSDB_JEMALLOC + if (!HasJemalloc()) { + return; + } MallocStatus mstat; const unsigned int kMallocStatusLen = 1000000; std::unique_ptr buf{new char[kMallocStatusLen + 1]}; mstat.cur = buf.get(); mstat.end = buf.get() + kMallocStatusLen; - je_malloc_stats_print(GetJemallocStatus, &mstat, ""); + malloc_stats_print(GetJemallocStatus, &mstat, ""); stats->append(buf.get()); -#endif // ROCKSDB_JEMALLOC -} - } +#else +void DumpMallocStats(std::string*) {} +#endif // ROCKSDB_JEMALLOC +} // namespace rocksdb #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/managed_iterator.cc b/thirdparty/rocksdb/db/managed_iterator.cc deleted file mode 100644 index c393eb5a6f..0000000000 --- a/thirdparty/rocksdb/db/managed_iterator.cc +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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 "db/managed_iterator.h" - -#include -#include -#include - -#include "db/column_family.h" -#include "db/db_impl.h" -#include "db/db_iter.h" -#include "db/dbformat.h" -#include "rocksdb/env.h" -#include "rocksdb/slice.h" -#include "rocksdb/slice_transform.h" -#include "table/merging_iterator.h" - -namespace rocksdb { - -namespace { -// Helper class that locks a mutex on construction and unlocks the mutex when -// the destructor of the MutexLock object is invoked. -// -// Typical usage: -// -// void MyClass::MyMethod() { -// MILock l(&mu_); // mu_ is an instance variable -// ... some complex code, possibly with multiple return paths ... -// } - -class MILock { - public: - explicit MILock(std::mutex* mu, ManagedIterator* mi) : mu_(mu), mi_(mi) { - this->mu_->lock(); - } - ~MILock() { - this->mu_->unlock(); - } - ManagedIterator* GetManagedIterator() { return mi_; } - - private: - std::mutex* const mu_; - ManagedIterator* mi_; - // No copying allowed - MILock(const MILock&) = delete; - void operator=(const MILock&) = delete; -}; -} // anonymous namespace - -// -// Synchronization between modifiers, releasers, creators -// If iterator operation, wait till (!in_use), set in_use, do op, reset in_use -// if modifying mutable_iter, atomically exchange in_use: -// return if in_use set / otherwise set in use, -// atomically replace new iter with old , reset in use -// The releaser is the new operation and it holds a lock for a very short time -// The existing non-const iterator operations are supposed to be single -// threaded and hold the lock for the duration of the operation -// The existing const iterator operations use the cached key/values -// and don't do any locking. -ManagedIterator::ManagedIterator(DBImpl* db, const ReadOptions& read_options, - ColumnFamilyData* cfd) - : db_(db), - read_options_(read_options), - cfd_(cfd), - svnum_(cfd->GetSuperVersionNumber()), - mutable_iter_(nullptr), - valid_(false), - snapshot_created_(false), - release_supported_(true) { - read_options_.managed = false; - if ((!read_options_.tailing) && (read_options_.snapshot == nullptr)) { - assert(nullptr != (read_options_.snapshot = db_->GetSnapshot())); - snapshot_created_ = true; - } - cfh_.SetCFD(cfd); - mutable_iter_ = unique_ptr(db->NewIterator(read_options_, &cfh_)); -} - -ManagedIterator::~ManagedIterator() { - Lock(); - if (snapshot_created_) { - db_->ReleaseSnapshot(read_options_.snapshot); - snapshot_created_ = false; - read_options_.snapshot = nullptr; - } - UnLock(); -} - -bool ManagedIterator::Valid() const { return valid_; } - -void ManagedIterator::SeekToLast() { - MILock l(&in_use_, this); - if (NeedToRebuild()) { - RebuildIterator(); - } - assert(mutable_iter_ != nullptr); - mutable_iter_->SeekToLast(); - if (mutable_iter_->status().ok()) { - UpdateCurrent(); - } -} - -void ManagedIterator::SeekToFirst() { - MILock l(&in_use_, this); - SeekInternal(Slice(), true); -} - -void ManagedIterator::Seek(const Slice& user_key) { - MILock l(&in_use_, this); - SeekInternal(user_key, false); -} - -void ManagedIterator::SeekForPrev(const Slice& user_key) { - MILock l(&in_use_, this); - if (NeedToRebuild()) { - RebuildIterator(); - } - assert(mutable_iter_ != nullptr); - mutable_iter_->SeekForPrev(user_key); - UpdateCurrent(); -} - -void ManagedIterator::SeekInternal(const Slice& user_key, bool seek_to_first) { - if (NeedToRebuild()) { - RebuildIterator(); - } - assert(mutable_iter_ != nullptr); - if (seek_to_first) { - mutable_iter_->SeekToFirst(); - } else { - mutable_iter_->Seek(user_key); - } - UpdateCurrent(); -} - -void ManagedIterator::Prev() { - if (!valid_) { - status_ = Status::InvalidArgument("Iterator value invalid"); - return; - } - MILock l(&in_use_, this); - if (NeedToRebuild()) { - std::string current_key = key().ToString(); - Slice old_key(current_key); - RebuildIterator(); - SeekInternal(old_key, false); - UpdateCurrent(); - if (!valid_) { - return; - } - if (key().compare(old_key) != 0) { - valid_ = false; - status_ = Status::Incomplete("Cannot do Prev now"); - return; - } - } - mutable_iter_->Prev(); - if (mutable_iter_->status().ok()) { - UpdateCurrent(); - status_ = Status::OK(); - } else { - status_ = mutable_iter_->status(); - } -} - -void ManagedIterator::Next() { - if (!valid_) { - status_ = Status::InvalidArgument("Iterator value invalid"); - return; - } - MILock l(&in_use_, this); - if (NeedToRebuild()) { - std::string current_key = key().ToString(); - Slice old_key(current_key.data(), cached_key_.Size()); - RebuildIterator(); - SeekInternal(old_key, false); - UpdateCurrent(); - if (!valid_) { - return; - } - if (key().compare(old_key) != 0) { - valid_ = false; - status_ = Status::Incomplete("Cannot do Next now"); - return; - } - } - mutable_iter_->Next(); - UpdateCurrent(); -} - -Slice ManagedIterator::key() const { - assert(valid_); - return cached_key_.GetUserKey(); -} - -Slice ManagedIterator::value() const { - assert(valid_); - return cached_value_.GetUserKey(); -} - -Status ManagedIterator::status() const { return status_; } - -void ManagedIterator::RebuildIterator() { - svnum_ = cfd_->GetSuperVersionNumber(); - mutable_iter_ = unique_ptr(db_->NewIterator(read_options_, &cfh_)); -} - -void ManagedIterator::UpdateCurrent() { - assert(mutable_iter_ != nullptr); - - valid_ = mutable_iter_->Valid(); - if (!valid_) { - status_ = mutable_iter_->status(); - return; - } - - status_ = Status::OK(); - cached_key_.SetUserKey(mutable_iter_->key()); - cached_value_.SetUserKey(mutable_iter_->value()); -} - -void ManagedIterator::ReleaseIter(bool only_old) { - if ((mutable_iter_ == nullptr) || (!release_supported_)) { - return; - } - if (svnum_ != cfd_->GetSuperVersionNumber() || !only_old) { - if (!TryLock()) { // Don't release iter if in use - return; - } - mutable_iter_ = nullptr; // in_use for a very short time - UnLock(); - } -} - -bool ManagedIterator::NeedToRebuild() { - if ((mutable_iter_ == nullptr) || (status_.IsIncomplete()) || - (!only_drop_old_ && (svnum_ != cfd_->GetSuperVersionNumber()))) { - return true; - } - return false; -} - -void ManagedIterator::Lock() { - in_use_.lock(); - return; -} - -bool ManagedIterator::TryLock() { return in_use_.try_lock(); } - -void ManagedIterator::UnLock() { - in_use_.unlock(); -} - -} // namespace rocksdb - -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/managed_iterator.h b/thirdparty/rocksdb/db/managed_iterator.h deleted file mode 100644 index 8e962f781a..0000000000 --- a/thirdparty/rocksdb/db/managed_iterator.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). -#pragma once - -#ifndef ROCKSDB_LITE - -#include -#include -#include -#include - -#include "db/column_family.h" -#include "rocksdb/db.h" -#include "rocksdb/iterator.h" -#include "rocksdb/options.h" -#include "util/arena.h" - -namespace rocksdb { - -class DBImpl; -struct SuperVersion; -class ColumnFamilyData; - -/** - * ManagedIterator is a special type of iterator that supports freeing the - * underlying iterator and still being able to access the current key/value - * pair. This is done by copying the key/value pair so that clients can - * continue to access the data without getting a SIGSEGV. - * The underlying iterator can be freed manually through the call to - * ReleaseIter or automatically (as needed on space pressure or age.) - * The iterator is recreated using the saved original arguments. - */ -class ManagedIterator : public Iterator { - public: - ManagedIterator(DBImpl* db, const ReadOptions& read_options, - ColumnFamilyData* cfd); - virtual ~ManagedIterator(); - - virtual void SeekToLast() override; - virtual void Prev() override; - virtual bool Valid() const override; - void SeekToFirst() override; - virtual void Seek(const Slice& target) override; - virtual void SeekForPrev(const Slice& target) override; - virtual void Next() override; - virtual Slice key() const override; - virtual Slice value() const override; - virtual Status status() const override; - void ReleaseIter(bool only_old); - void SetDropOld(bool only_old) { - only_drop_old_ = read_options_.tailing || only_old; - } - - private: - void RebuildIterator(); - void UpdateCurrent(); - void SeekInternal(const Slice& user_key, bool seek_to_first); - bool NeedToRebuild(); - void Lock(); - bool TryLock(); - void UnLock(); - DBImpl* const db_; - ReadOptions read_options_; - ColumnFamilyData* const cfd_; - ColumnFamilyHandleInternal cfh_; - - uint64_t svnum_; - std::unique_ptr mutable_iter_; - // internal iterator status - Status status_; - bool valid_; - - IterKey cached_key_; - IterKey cached_value_; - - bool only_drop_old_ = true; - bool snapshot_created_; - bool release_supported_; - std::mutex in_use_; // is managed iterator in use -}; - -} // namespace rocksdb -#endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/manual_compaction_test.cc b/thirdparty/rocksdb/db/manual_compaction_test.cc index 039b9080ed..02732a5558 100644 --- a/thirdparty/rocksdb/db/manual_compaction_test.cc +++ b/thirdparty/rocksdb/db/manual_compaction_test.cc @@ -19,7 +19,12 @@ using namespace rocksdb; namespace { -const int kNumKeys = 1100000; +// Reasoning: previously the number was 1100000. Since the keys are written to +// the batch in one write each write will result into one SST file. each write +// will result into one SST file. We reduced the write_buffer_size to 1K to +// basically have the same effect with however less number of keys, which +// results into less test runtime. +const int kNumKeys = 1100; std::string Key1(int i) { char buf[100]; @@ -35,7 +40,7 @@ class ManualCompactionTest : public testing::Test { public: ManualCompactionTest() { // Get rid of any state from an old run. - dbname_ = rocksdb::test::TmpDir() + "/rocksdb_cbug_test"; + dbname_ = rocksdb::test::PerThreadDBPath("rocksdb_cbug_test"); DestroyDB(dbname_, rocksdb::Options()); } @@ -46,15 +51,13 @@ class DestroyAllCompactionFilter : public CompactionFilter { public: DestroyAllCompactionFilter() {} - virtual bool Filter(int level, const Slice& key, const Slice& existing_value, - std::string* new_value, - bool* value_changed) const override { + bool Filter(int /*level*/, const Slice& /*key*/, const Slice& existing_value, + std::string* /*new_value*/, + bool* /*value_changed*/) const override { return existing_value.ToString() == "destroy"; } - virtual const char* Name() const override { - return "DestroyAllCompactionFilter"; - } + const char* Name() const override { return "DestroyAllCompactionFilter"; } }; TEST_F(ManualCompactionTest, CompactTouchesAllKeys) { @@ -99,6 +102,7 @@ TEST_F(ManualCompactionTest, Test) { // specific scenario. rocksdb::DB* db; rocksdb::Options db_options; + db_options.write_buffer_size = 1024; db_options.create_if_missing = true; db_options.compression = rocksdb::kNoCompression; ASSERT_OK(rocksdb::DB::Open(db_options, dbname_, &db)); diff --git a/thirdparty/rocksdb/db/memtable.cc b/thirdparty/rocksdb/db/memtable.cc index d51b261873..16b5c8ee0f 100644 --- a/thirdparty/rocksdb/db/memtable.cc +++ b/thirdparty/rocksdb/db/memtable.cc @@ -17,6 +17,8 @@ #include "db/merge_context.h" #include "db/merge_helper.h" #include "db/pinned_iterators_manager.h" +#include "db/range_tombstone_fragmenter.h" +#include "db/read_callback.h" #include "monitoring/perf_context_imp.h" #include "monitoring/statistics.h" #include "port/port.h" @@ -33,8 +35,8 @@ #include "util/autovector.h" #include "util/coding.h" #include "util/memory_usage.h" -#include "util/murmurhash.h" #include "util/mutexlock.h" +#include "util/util.h" namespace rocksdb { @@ -48,6 +50,8 @@ ImmutableMemTableOptions::ImmutableMemTableOptions( mutable_cf_options.memtable_prefix_bloom_size_ratio) * 8u), memtable_huge_page_size(mutable_cf_options.memtable_huge_page_size), + memtable_whole_key_filtering( + mutable_cf_options.memtable_whole_key_filtering), inplace_update_support(ioptions.inplace_update_support), inplace_update_num_locks(mutable_cf_options.inplace_update_num_locks), inplace_callback(ioptions.inplace_callback), @@ -66,15 +70,16 @@ MemTable::MemTable(const InternalKeyComparator& cmp, refs_(0), kArenaBlockSize(OptimizeBlockSize(moptions_.arena_block_size)), mem_tracker_(write_buffer_manager), - arena_( - moptions_.arena_block_size, - (write_buffer_manager != nullptr && write_buffer_manager->enabled()) - ? &mem_tracker_ - : nullptr, - mutable_cf_options.memtable_huge_page_size), + arena_(moptions_.arena_block_size, + (write_buffer_manager != nullptr && + (write_buffer_manager->enabled() || + write_buffer_manager->cost_to_cache())) + ? &mem_tracker_ + : nullptr, + mutable_cf_options.memtable_huge_page_size), table_(ioptions.memtable_factory->CreateMemTableRep( - comparator_, &arena_, ioptions.prefix_extractor, ioptions.info_log, - column_family_id)), + comparator_, &arena_, mutable_cf_options.prefix_extractor.get(), + ioptions.info_log, column_family_id)), range_del_table_(SkipListFactory().CreateMemTableRep( comparator_, &arena_, nullptr /* transform */, ioptions.info_log, column_family_id)), @@ -94,21 +99,24 @@ MemTable::MemTable(const InternalKeyComparator& cmp, locks_(moptions_.inplace_update_support ? moptions_.inplace_update_num_locks : 0), - prefix_extractor_(ioptions.prefix_extractor), + prefix_extractor_(mutable_cf_options.prefix_extractor.get()), flush_state_(FLUSH_NOT_REQUESTED), env_(ioptions.env), insert_with_hint_prefix_extractor_( ioptions.memtable_insert_with_hint_prefix_extractor), - oldest_key_time_(std::numeric_limits::max()) { + oldest_key_time_(std::numeric_limits::max()), + atomic_flush_seqno_(kMaxSequenceNumber) { UpdateFlushState(); // something went wrong if we need to flush before inserting anything assert(!ShouldScheduleFlush()); - if (prefix_extractor_ && moptions_.memtable_prefix_bloom_bits > 0) { - prefix_bloom_.reset(new DynamicBloom( - &arena_, moptions_.memtable_prefix_bloom_bits, ioptions.bloom_locality, - 6 /* hard coded 6 probes */, nullptr, moptions_.memtable_huge_page_size, - ioptions.info_log)); + // use bloom_filter_ for both whole key and prefix bloom filter + if ((prefix_extractor_ || moptions_.memtable_whole_key_filtering) && + moptions_.memtable_prefix_bloom_bits > 0) { + bloom_filter_.reset( + new DynamicBloom(&arena_, moptions_.memtable_prefix_bloom_bits, + ioptions.bloom_locality, 6 /* hard coded 6 probes */, + moptions_.memtable_huge_page_size, ioptions.info_log)); } } @@ -224,15 +232,23 @@ int MemTable::KeyComparator::operator()(const char* prefix_len_key1, // Internal keys are encoded as length-prefixed strings. Slice k1 = GetLengthPrefixedSlice(prefix_len_key1); Slice k2 = GetLengthPrefixedSlice(prefix_len_key2); - return comparator.Compare(k1, k2); + return comparator.CompareKeySeq(k1, k2); } int MemTable::KeyComparator::operator()(const char* prefix_len_key, - const Slice& key) + const KeyComparator::DecodedType& key) const { // Internal keys are encoded as length-prefixed strings. Slice a = GetLengthPrefixedSlice(prefix_len_key); - return comparator.Compare(a, key); + return comparator.CompareKeySeq(a, key); +} + +void MemTableRep::InsertConcurrently(KeyHandle /*handle*/) { +#ifndef ROCKSDB_LITE + throw std::runtime_error("concurrent insert not supported"); +#else + abort(); +#endif } Slice MemTableRep::UserKey(const char* key) const { @@ -269,19 +285,18 @@ class MemTableIterator : public InternalIterator { if (use_range_del_table) { iter_ = mem.range_del_table_->GetIterator(arena); } else if (prefix_extractor_ != nullptr && !read_options.total_order_seek) { - bloom_ = mem.prefix_bloom_.get(); + bloom_ = mem.bloom_filter_.get(); iter_ = mem.table_->GetDynamicPrefixIterator(arena); } else { iter_ = mem.table_->GetIterator(arena); } } - ~MemTableIterator() { + ~MemTableIterator() override { #ifndef NDEBUG // Assert that the MemTableIterator is never deleted while // Pinning is Enabled. - assert(!pinned_iters_mgr_ || - (pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled())); + assert(!pinned_iters_mgr_ || !pinned_iters_mgr_->PinningEnabled()); #endif if (arena_mode_) { iter_->~Iterator(); @@ -291,18 +306,18 @@ class MemTableIterator : public InternalIterator { } #ifndef NDEBUG - virtual void SetPinnedItersMgr( - PinnedIteratorsManager* pinned_iters_mgr) override { + void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override { pinned_iters_mgr_ = pinned_iters_mgr; } PinnedIteratorsManager* pinned_iters_mgr_ = nullptr; #endif - virtual bool Valid() const override { return valid_; } - virtual void Seek(const Slice& k) override { + bool Valid() const override { return valid_; } + void Seek(const Slice& k) override { PERF_TIMER_GUARD(seek_on_memtable_time); PERF_COUNTER_ADD(seek_on_memtable_count, 1); - if (bloom_ != nullptr) { + if (bloom_) { + // iterator should only use prefix bloom filter if (!bloom_->MayContain( prefix_extractor_->Transform(ExtractUserKey(k)))) { PERF_COUNTER_ADD(bloom_memtable_miss_count, 1); @@ -315,10 +330,10 @@ class MemTableIterator : public InternalIterator { iter_->Seek(k, nullptr); valid_ = iter_->Valid(); } - virtual void SeekForPrev(const Slice& k) override { + void SeekForPrev(const Slice& k) override { PERF_TIMER_GUARD(seek_on_memtable_time); PERF_COUNTER_ADD(seek_on_memtable_count, 1); - if (bloom_ != nullptr) { + if (bloom_) { if (!bloom_->MayContain( prefix_extractor_->Transform(ExtractUserKey(k)))) { PERF_COUNTER_ADD(bloom_memtable_miss_count, 1); @@ -337,44 +352,44 @@ class MemTableIterator : public InternalIterator { Prev(); } } - virtual void SeekToFirst() override { + void SeekToFirst() override { iter_->SeekToFirst(); valid_ = iter_->Valid(); } - virtual void SeekToLast() override { + void SeekToLast() override { iter_->SeekToLast(); valid_ = iter_->Valid(); } - virtual void Next() override { + void Next() override { PERF_COUNTER_ADD(next_on_memtable_count, 1); assert(Valid()); iter_->Next(); valid_ = iter_->Valid(); } - virtual void Prev() override { + void Prev() override { PERF_COUNTER_ADD(prev_on_memtable_count, 1); assert(Valid()); iter_->Prev(); valid_ = iter_->Valid(); } - virtual Slice key() const override { + Slice key() const override { assert(Valid()); return GetLengthPrefixedSlice(iter_->key()); } - virtual Slice value() const override { + Slice value() const override { assert(Valid()); Slice key_slice = GetLengthPrefixedSlice(iter_->key()); return GetLengthPrefixedSlice(key_slice.data() + key_slice.size()); } - virtual Status status() const override { return Status::OK(); } + Status status() const override { return Status::OK(); } - virtual bool IsKeyPinned() const override { + bool IsKeyPinned() const override { // memtable data is always pinned return true; } - virtual bool IsValuePinned() const override { + bool IsValuePinned() const override { // memtable value is always pinned, except if we allow inplace update. return value_pinned_; } @@ -400,18 +415,29 @@ InternalIterator* MemTable::NewIterator(const ReadOptions& read_options, return new (mem) MemTableIterator(*this, read_options, arena); } -InternalIterator* MemTable::NewRangeTombstoneIterator( - const ReadOptions& read_options) { - if (read_options.ignore_range_deletions || is_range_del_table_empty_) { +FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator( + const ReadOptions& read_options, SequenceNumber read_seq) { + if (read_options.ignore_range_deletions || + is_range_del_table_empty_.load(std::memory_order_relaxed)) { + return nullptr; + } + auto* unfragmented_iter = new MemTableIterator( + *this, read_options, nullptr /* arena */, true /* use_range_del_table */); + if (unfragmented_iter == nullptr) { return nullptr; } - return new MemTableIterator(*this, read_options, nullptr /* arena */, - true /* use_range_del_table */); + auto fragmented_tombstone_list = + std::make_shared( + std::unique_ptr(unfragmented_iter), + comparator_.comparator); + + auto* fragmented_iter = new FragmentedRangeTombstoneIterator( + fragmented_tombstone_list, comparator_.comparator, read_seq); + return fragmented_iter; } port::RWMutex* MemTable::GetLock(const Slice& key) { - static murmur_hash hash; - return &locks_[hash(key) % locks_.size()]; + return &locks_[static_cast(GetSliceNPHash64(key)) % locks_.size()]; } MemTable::MemTableStats MemTable::ApproximateStats(const Slice& start_ikey, @@ -435,7 +461,7 @@ MemTable::MemTableStats MemTable::ApproximateStats(const Slice& start_ikey, return {entry_count * (data_size / n), entry_count}; } -void MemTable::Add(SequenceNumber s, ValueType type, +bool MemTable::Add(SequenceNumber s, ValueType type, const Slice& key, /* user key */ const Slice& value, bool allow_concurrent, MemTablePostProcessInfo* post_process_info) { @@ -470,9 +496,15 @@ void MemTable::Add(SequenceNumber s, ValueType type, if (insert_with_hint_prefix_extractor_ != nullptr && insert_with_hint_prefix_extractor_->InDomain(key_slice)) { Slice prefix = insert_with_hint_prefix_extractor_->Transform(key_slice); - table->InsertWithHint(handle, &insert_hints_[prefix]); + bool res = table->InsertKeyWithHint(handle, &insert_hints_[prefix]); + if (UNLIKELY(!res)) { + return res; + } } else { - table->Insert(handle); + bool res = table->InsertKey(handle); + if (UNLIKELY(!res)) { + return res; + } } // this is a bit ugly, but is the way to avoid locked instructions @@ -486,13 +518,15 @@ void MemTable::Add(SequenceNumber s, ValueType type, std::memory_order_relaxed); } - if (prefix_bloom_) { - assert(prefix_extractor_); - prefix_bloom_->Add(prefix_extractor_->Transform(key)); + if (bloom_filter_ && prefix_extractor_) { + bloom_filter_->Add(prefix_extractor_->Transform(key)); + } + if (bloom_filter_ && moptions_.memtable_whole_key_filtering) { + bloom_filter_->Add(key); } // The first sequence number inserted into the memtable - assert(first_seqno_ == 0 || s > first_seqno_); + assert(first_seqno_ == 0 || s >= first_seqno_); if (first_seqno_ == 0) { first_seqno_.store(s, std::memory_order_relaxed); @@ -505,7 +539,10 @@ void MemTable::Add(SequenceNumber s, ValueType type, assert(post_process_info == nullptr); UpdateFlushState(); } else { - table->InsertConcurrently(handle); + bool res = table->InsertKeyConcurrently(handle); + if (UNLIKELY(!res)) { + return res; + } assert(post_process_info != nullptr); post_process_info->num_entries++; @@ -514,9 +551,11 @@ void MemTable::Add(SequenceNumber s, ValueType type, post_process_info->num_deletes++; } - if (prefix_bloom_) { - assert(prefix_extractor_); - prefix_bloom_->AddConcurrently(prefix_extractor_->Transform(key)); + if (bloom_filter_ && prefix_extractor_) { + bloom_filter_->AddConcurrently(prefix_extractor_->Transform(key)); + } + if (bloom_filter_ && moptions_.memtable_whole_key_filtering) { + bloom_filter_->AddConcurrently(key); } // atomically update first_seqno_ and earliest_seqno_. @@ -531,10 +570,11 @@ void MemTable::Add(SequenceNumber s, ValueType type, !first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) { } } - if (is_range_del_table_empty_ && type == kTypeRangeDeletion) { - is_range_del_table_empty_ = false; + if (type == kTypeRangeDeletion) { + is_range_del_table_empty_.store(false, std::memory_order_relaxed); } UpdateOldestKeyTime(); + return true; } // Callback from MemTable::Get() @@ -550,23 +590,32 @@ struct Saver { const MergeOperator* merge_operator; // the merge operations encountered; MergeContext* merge_context; - RangeDelAggregator* range_del_agg; + SequenceNumber max_covering_tombstone_seq; MemTable* mem; Logger* logger; Statistics* statistics; bool inplace_update_support; Env* env_; + ReadCallback* callback_; bool* is_blob_index; + + bool CheckCallback(SequenceNumber _seq) { + if (callback_) { + return callback_->IsVisible(_seq); + } + return true; + } }; } // namespace static bool SaveValue(void* arg, const char* entry) { Saver* s = reinterpret_cast(arg); + assert(s != nullptr); MergeContext* merge_context = s->merge_context; - RangeDelAggregator* range_del_agg = s->range_del_agg; + SequenceNumber max_covering_tombstone_seq = s->max_covering_tombstone_seq; const MergeOperator* merge_operator = s->merge_operator; - assert(s != nullptr && merge_context != nullptr && range_del_agg != nullptr); + assert(merge_context != nullptr); // entry format is: // klength varint32 @@ -584,10 +633,17 @@ static bool SaveValue(void* arg, const char* entry) { // Correct user key const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8); ValueType type; - UnPackSequenceAndType(tag, &s->seq, &type); + SequenceNumber seq; + UnPackSequenceAndType(tag, &seq, &type); + // If the value is not in the snapshot, skip it + if (!s->CheckCallback(seq)) { + return true; // to continue to the next seq + } + + s->seq = seq; if ((type == kTypeValue || type == kTypeMerge || type == kTypeBlobIndex) && - range_del_agg->ShouldDelete(Slice(key_ptr, key_length))) { + max_covering_tombstone_seq > seq) { type = kTypeRangeDeletion; } switch (type) { @@ -605,7 +661,7 @@ static bool SaveValue(void* arg, const char* entry) { *(s->found_final_value) = true; return false; } - // intentional fallthrough + FALLTHROUGH_INTENDED; case kTypeValue: { if (s->inplace_update_support) { s->mem->GetLock(s->key->user_key())->ReadLock(); @@ -613,10 +669,12 @@ static bool SaveValue(void* arg, const char* entry) { Slice v = GetLengthPrefixedSlice(key_ptr + key_length); *(s->status) = Status::OK(); if (*(s->merge_in_progress)) { - *(s->status) = MergeHelper::TimedFullMerge( - merge_operator, s->key->user_key(), &v, - merge_context->GetOperands(), s->value, s->logger, s->statistics, - s->env_, nullptr /* result_operand */, true); + if (s->value != nullptr) { + *(s->status) = MergeHelper::TimedFullMerge( + merge_operator, s->key->user_key(), &v, + merge_context->GetOperands(), s->value, s->logger, + s->statistics, s->env_, nullptr /* result_operand */, true); + } } else if (s->value != nullptr) { s->value->assign(v.data(), v.size()); } @@ -633,10 +691,12 @@ static bool SaveValue(void* arg, const char* entry) { case kTypeSingleDeletion: case kTypeRangeDeletion: { if (*(s->merge_in_progress)) { - *(s->status) = MergeHelper::TimedFullMerge( - merge_operator, s->key->user_key(), nullptr, - merge_context->GetOperands(), s->value, s->logger, s->statistics, - s->env_, nullptr /* result_operand */, true); + if (s->value != nullptr) { + *(s->status) = MergeHelper::TimedFullMerge( + merge_operator, s->key->user_key(), nullptr, + merge_context->GetOperands(), s->value, s->logger, + s->statistics, s->env_, nullptr /* result_operand */, true); + } } else { *(s->status) = Status::NotFound(); } @@ -658,6 +718,14 @@ static bool SaveValue(void* arg, const char* entry) { *(s->merge_in_progress) = true; merge_context->PushOperand( v, s->inplace_update_support == false /* operand_pinned */); + if (merge_operator->ShouldMerge(merge_context->GetOperandsDirectionBackward())) { + *(s->status) = MergeHelper::TimedFullMerge( + merge_operator, s->key->user_key(), nullptr, + merge_context->GetOperands(), s->value, s->logger, s->statistics, + s->env_, nullptr /* result_operand */, true); + *(s->found_final_value) = true; + return false; + } return true; } default: @@ -672,8 +740,9 @@ static bool SaveValue(void* arg, const char* entry) { bool MemTable::Get(const LookupKey& key, std::string* value, Status* s, MergeContext* merge_context, - RangeDelAggregator* range_del_agg, SequenceNumber* seq, - const ReadOptions& read_opts, bool* is_blob_index) { + SequenceNumber* max_covering_tombstone_seq, + SequenceNumber* seq, const ReadOptions& read_opts, + ReadCallback* callback, bool* is_blob_index) { // The sequence number is updated synchronously in version_set.h if (IsEmpty()) { // Avoiding recording stats for speed. @@ -681,27 +750,36 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s, } PERF_TIMER_GUARD(get_from_memtable_time); - std::unique_ptr range_del_iter( - NewRangeTombstoneIterator(read_opts)); - Status status = range_del_agg->AddTombstones(std::move(range_del_iter)); - if (!status.ok()) { - *s = status; - return false; + std::unique_ptr range_del_iter( + NewRangeTombstoneIterator(read_opts, + GetInternalKeySeqno(key.internal_key()))); + if (range_del_iter != nullptr) { + *max_covering_tombstone_seq = + std::max(*max_covering_tombstone_seq, + range_del_iter->MaxCoveringTombstoneSeqnum(key.user_key())); } Slice user_key = key.user_key(); bool found_final_value = false; bool merge_in_progress = s->IsMergeInProgress(); - bool const may_contain = - nullptr == prefix_bloom_ - ? false - : prefix_bloom_->MayContain(prefix_extractor_->Transform(user_key)); - if (prefix_bloom_ && !may_contain) { + bool may_contain = true; + if (bloom_filter_) { + // when both memtable_whole_key_filtering and prefix_extractor_ are set, + // only do whole key filtering for Get() to save CPU + if (moptions_.memtable_whole_key_filtering) { + may_contain = bloom_filter_->MayContain(user_key); + } else { + assert(prefix_extractor_); + may_contain = + bloom_filter_->MayContain(prefix_extractor_->Transform(user_key)); + } + } + if (bloom_filter_ && !may_contain) { // iter is null if prefix bloom says the key does not exist PERF_COUNTER_ADD(bloom_memtable_miss_count, 1); *seq = kMaxSequenceNumber; } else { - if (prefix_bloom_) { + if (bloom_filter_) { PERF_COUNTER_ADD(bloom_memtable_hit_count, 1); } Saver saver; @@ -713,12 +791,13 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s, saver.seq = kMaxSequenceNumber; saver.mem = this; saver.merge_context = merge_context; - saver.range_del_agg = range_del_agg; + saver.max_covering_tombstone_seq = *max_covering_tombstone_seq; saver.merge_operator = moptions_.merge_operator; saver.logger = moptions_.info_log; saver.inplace_update_support = moptions_.inplace_update_support; saver.statistics = moptions_.statistics; saver.env_ = env_; + saver.callback_ = callback; saver.is_blob_index = is_blob_index; table_->Get(key, &saver, SaveValue); @@ -761,8 +840,9 @@ void MemTable::Update(SequenceNumber seq, // Correct user key const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8); ValueType type; - SequenceNumber unused; - UnPackSequenceAndType(tag, &unused, &type); + SequenceNumber existing_seq; + UnPackSequenceAndType(tag, &existing_seq, &type); + assert(existing_seq != seq); if (type == kTypeValue) { Slice prev_value = GetLengthPrefixedSlice(key_ptr + key_length); uint32_t prev_size = static_cast(prev_value.size()); @@ -777,6 +857,7 @@ void MemTable::Update(SequenceNumber seq, assert((unsigned)((p + value.size()) - entry) == (unsigned)(VarintLength(key_length) + key_length + VarintLength(value.size()) + value.size())); + RecordTick(moptions_.statistics, NUMBER_KEYS_UPDATED); return; } } @@ -784,7 +865,10 @@ void MemTable::Update(SequenceNumber seq, } // key doesn't exist - Add(seq, kTypeValue, key, value); + bool add_res __attribute__((__unused__)); + add_res = Add(seq, kTypeValue, key, value); + // We already checked unused != seq above. In that case, Add should not fail. + assert(add_res); } bool MemTable::UpdateCallback(SequenceNumber seq, diff --git a/thirdparty/rocksdb/db/memtable.h b/thirdparty/rocksdb/db/memtable.h index 4f63818eee..709e2061e5 100644 --- a/thirdparty/rocksdb/db/memtable.h +++ b/thirdparty/rocksdb/db/memtable.h @@ -16,7 +16,8 @@ #include #include #include "db/dbformat.h" -#include "db/range_del_aggregator.h" +#include "db/range_tombstone_fragmenter.h" +#include "db/read_callback.h" #include "db/version_edit.h" #include "monitoring/instrumented_mutex.h" #include "options/cf_options.h" @@ -33,7 +34,6 @@ namespace rocksdb { class Mutex; class MemTableIterator; class MergeContext; -class InternalIterator; struct ImmutableMemTableOptions { explicit ImmutableMemTableOptions(const ImmutableCFOptions& ioptions, @@ -41,6 +41,7 @@ struct ImmutableMemTableOptions { size_t arena_block_size; uint32_t memtable_prefix_bloom_bits; size_t memtable_huge_page_size; + bool memtable_whole_key_filtering; bool inplace_update_support; size_t inplace_update_num_locks; UpdateStatus (*inplace_callback)(char* existing_value, @@ -63,7 +64,7 @@ struct MemTablePostProcessInfo { }; // Note: Many of the methods in this class have comments indicating that -// external synchromization is required as these methods are not thread-safe. +// external synchronization is required as these methods are not thread-safe. // It is up to higher layers of code to decide how to prevent concurrent // invokation of these methods. This is usually done by acquiring either // the db mutex or the single writer thread. @@ -83,7 +84,7 @@ class MemTable { virtual int operator()(const char* prefix_len_key1, const char* prefix_len_key2) const override; virtual int operator()(const char* prefix_len_key, - const Slice& key) const override; + const DecodedType& key) const override; }; // MemTables are reference counted. The initial reference count @@ -158,7 +159,8 @@ class MemTable { // those allocated in arena. InternalIterator* NewIterator(const ReadOptions& read_options, Arena* arena); - InternalIterator* NewRangeTombstoneIterator(const ReadOptions& read_options); + FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator( + const ReadOptions& read_options, SequenceNumber read_seq); // Add an entry into memtable that maps key to value at the // specified sequence number and with the specified type. @@ -166,7 +168,10 @@ class MemTable { // // REQUIRES: if allow_concurrent = false, external synchronization to prevent // simultaneous operations on the same MemTable. - void Add(SequenceNumber seq, ValueType type, const Slice& key, + // + // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and + // the already exists. + bool Add(SequenceNumber seq, ValueType type, const Slice& key, const Slice& value, bool allow_concurrent = false, MemTablePostProcessInfo* post_process_info = nullptr); @@ -184,16 +189,19 @@ class MemTable { // On success, *s may be set to OK, NotFound, or MergeInProgress. Any other // status returned indicates a corruption or other unexpected error. bool Get(const LookupKey& key, std::string* value, Status* s, - MergeContext* merge_context, RangeDelAggregator* range_del_agg, - SequenceNumber* seq, const ReadOptions& read_opts, + MergeContext* merge_context, + SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq, + const ReadOptions& read_opts, ReadCallback* callback = nullptr, bool* is_blob_index = nullptr); bool Get(const LookupKey& key, std::string* value, Status* s, - MergeContext* merge_context, RangeDelAggregator* range_del_agg, - const ReadOptions& read_opts, bool* is_blob_index = nullptr) { + MergeContext* merge_context, + SequenceNumber* max_covering_tombstone_seq, + const ReadOptions& read_opts, ReadCallback* callback = nullptr, + bool* is_blob_index = nullptr) { SequenceNumber seq; - return Get(key, value, s, merge_context, range_del_agg, &seq, read_opts, - is_blob_index); + return Get(key, value, s, merge_context, max_covering_tombstone_seq, &seq, + read_opts, callback, is_blob_index); } // Attempts to update the new_value inplace, else does normal Add @@ -258,12 +266,16 @@ class MemTable { return num_deletes_.load(std::memory_order_relaxed); } + uint64_t get_data_size() const { + return data_size_.load(std::memory_order_relaxed); + } + // Dynamically change the memtable's capacity. If set below the current usage, // the next key added will trigger a flush. Can only increase size when // memtable prefix bloom is disabled, since we can't easily allocate more // space. void UpdateWriteBufferSize(size_t new_write_buffer_size) { - if (prefix_bloom_ == nullptr || + if (bloom_filter_ == nullptr || new_write_buffer_size < write_buffer_size_) { write_buffer_size_.store(new_write_buffer_size, std::memory_order_relaxed); @@ -332,6 +344,14 @@ class MemTable { mem_tracker_.DoneAllocating(); } + // Notify the underlying storage that all data it contained has been + // persisted. + // REQUIRES: external synchronization to prevent simultaneous + // operations on the same MemTable. + void MarkFlushed() { + table_->MarkFlushed(); + } + // return true if the current MemTableRep supports merge operator. bool IsMergeOperatorSupported() const { return table_->IsMergeOperatorSupported(); @@ -366,6 +386,21 @@ class MemTable { return oldest_key_time_.load(std::memory_order_relaxed); } + // REQUIRES: db_mutex held. + void SetID(uint64_t id) { id_ = id; } + + uint64_t GetID() const { return id_; } + + void SetFlushCompleted(bool completed) { flush_completed_ = completed; } + + uint64_t GetFileNumber() const { return file_number_; } + + void SetFileNumber(uint64_t file_num) { file_number_ = file_num; } + + void SetFlushInProgress(bool in_progress) { + flush_in_progress_ = in_progress; + } + private: enum FlushStateEnum { FLUSH_NOT_REQUESTED, FLUSH_REQUESTED, FLUSH_SCHEDULED }; @@ -379,9 +414,9 @@ class MemTable { const size_t kArenaBlockSize; AllocTracker mem_tracker_; ConcurrentArena arena_; - unique_ptr table_; - unique_ptr range_del_table_; - bool is_range_del_table_empty_; + std::unique_ptr table_; + std::unique_ptr range_del_table_; + std::atomic_bool is_range_del_table_empty_; // Total data size of all data inserted std::atomic data_size_; @@ -420,7 +455,7 @@ class MemTable { std::vector locks_; const SliceTransform* const prefix_extractor_; - std::unique_ptr prefix_bloom_; + std::unique_ptr bloom_filter_; std::atomic flush_state_; @@ -435,6 +470,15 @@ class MemTable { // Timestamp of oldest key std::atomic oldest_key_time_; + // Memtable id to track flush. + uint64_t id_ = 0; + + // Sequence number of the atomic flush that is responsible for this memtable. + // The sequence number of atomic flush is a seq, such that no writes with + // sequence numbers greater than or equal to seq are flushed, while all + // writes with sequence number smaller than seq are flushed. + SequenceNumber atomic_flush_seqno_; + // Returns a heuristic flush decision bool ShouldFlushNow() const; diff --git a/thirdparty/rocksdb/db/memtable_list.cc b/thirdparty/rocksdb/db/memtable_list.cc index 5921a50b35..5abe59b363 100644 --- a/thirdparty/rocksdb/db/memtable_list.cc +++ b/thirdparty/rocksdb/db/memtable_list.cc @@ -11,8 +11,11 @@ #include #include +#include #include +#include "db/db_impl.h" #include "db/memtable.h" +#include "db/range_tombstone_fragmenter.h" #include "db/version_set.h" #include "monitoring/thread_status_util.h" #include "rocksdb/db.h" @@ -40,7 +43,6 @@ void MemTableListVersion::UnrefMemTable(autovector* to_delete, to_delete->push_back(m); assert(*parent_memtable_list_memory_usage_ >= m->ApproximateMemoryUsage()); *parent_memtable_list_memory_usage_ -= m->ApproximateMemoryUsage(); - } else { } } @@ -103,42 +105,49 @@ int MemTableList::NumFlushed() const { // Operands stores the list of merge operations to apply, so far. bool MemTableListVersion::Get(const LookupKey& key, std::string* value, Status* s, MergeContext* merge_context, - RangeDelAggregator* range_del_agg, + SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq, const ReadOptions& read_opts, - bool* is_blob_index) { - return GetFromList(&memlist_, key, value, s, merge_context, range_del_agg, - seq, read_opts, is_blob_index); + ReadCallback* callback, bool* is_blob_index) { + return GetFromList(&memlist_, key, value, s, merge_context, + max_covering_tombstone_seq, seq, read_opts, callback, + is_blob_index); } bool MemTableListVersion::GetFromHistory( const LookupKey& key, std::string* value, Status* s, - MergeContext* merge_context, RangeDelAggregator* range_del_agg, + MergeContext* merge_context, SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq, const ReadOptions& read_opts, bool* is_blob_index) { return GetFromList(&memlist_history_, key, value, s, merge_context, - range_del_agg, seq, read_opts, is_blob_index); + max_covering_tombstone_seq, seq, read_opts, + nullptr /*read_callback*/, is_blob_index); } bool MemTableListVersion::GetFromList( std::list* list, const LookupKey& key, std::string* value, - Status* s, MergeContext* merge_context, RangeDelAggregator* range_del_agg, - SequenceNumber* seq, const ReadOptions& read_opts, bool* is_blob_index) { + Status* s, MergeContext* merge_context, + SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq, + const ReadOptions& read_opts, ReadCallback* callback, bool* is_blob_index) { *seq = kMaxSequenceNumber; for (auto& memtable : *list) { SequenceNumber current_seq = kMaxSequenceNumber; - bool done = memtable->Get(key, value, s, merge_context, range_del_agg, - ¤t_seq, read_opts, is_blob_index); + bool done = + memtable->Get(key, value, s, merge_context, max_covering_tombstone_seq, + ¤t_seq, read_opts, callback, is_blob_index); if (*seq == kMaxSequenceNumber) { // Store the most recent sequence number of any operation on this key. // Since we only care about the most recent change, we only need to // return the first operation found when searching memtables in // reverse-chronological order. + // current_seq would be equal to kMaxSequenceNumber if the value was to be + // skipped. This allows seq to be assigned again when the next value is + // read. *seq = current_seq; } if (done) { - assert(*seq != kMaxSequenceNumber); + assert(*seq != kMaxSequenceNumber || s->IsNotFound()); return true; } if (!done && !s->ok() && !s->IsMergeInProgress() && !s->IsNotFound()) { @@ -149,28 +158,15 @@ bool MemTableListVersion::GetFromList( } Status MemTableListVersion::AddRangeTombstoneIterators( - const ReadOptions& read_opts, Arena* arena, + const ReadOptions& read_opts, Arena* /*arena*/, RangeDelAggregator* range_del_agg) { assert(range_del_agg != nullptr); for (auto& m : memlist_) { - std::unique_ptr range_del_iter( - m->NewRangeTombstoneIterator(read_opts)); - Status s = range_del_agg->AddTombstones(std::move(range_del_iter)); - if (!s.ok()) { - return s; - } - } - return Status::OK(); -} - -Status MemTableListVersion::AddRangeTombstoneIterators( - const ReadOptions& read_opts, - std::vector* range_del_iters) { - for (auto& m : memlist_) { - auto* range_del_iter = m->NewRangeTombstoneIterator(read_opts); - if (range_del_iter != nullptr) { - range_del_iters->push_back(range_del_iter); - } + // Using kMaxSequenceNumber is OK because these are immutable memtables. + std::unique_ptr range_del_iter( + m->NewRangeTombstoneIterator(read_opts, + kMaxSequenceNumber /* read_seq */)); + range_del_agg->AddTombstones(std::move(range_del_iter)); } return Status::OK(); } @@ -243,6 +239,7 @@ void MemTableListVersion::Remove(MemTable* m, assert(refs_ == 1); // only when refs_ == 1 is MemTableListVersion mutable memlist_.remove(m); + m->MarkFlushed(); if (max_write_buffer_number_to_maintain_ > 0) { memlist_history_.push_front(m); TrimHistory(to_delete); @@ -266,7 +263,7 @@ void MemTableListVersion::TrimHistory(autovector* to_delete) { // Returns true if there is at least one memtable on which flush has // not yet started. bool MemTableList::IsFlushPending() const { - if ((flush_requested_ && num_flush_not_started_ >= 1) || + if ((flush_requested_ && num_flush_not_started_ > 0) || (num_flush_not_started_ >= min_write_buffer_number_to_merge_)) { assert(imm_flush_needed.load(std::memory_order_relaxed)); return true; @@ -275,12 +272,16 @@ bool MemTableList::IsFlushPending() const { } // Returns the memtables that need to be flushed. -void MemTableList::PickMemtablesToFlush(autovector* ret) { +void MemTableList::PickMemtablesToFlush(const uint64_t* max_memtable_id, + autovector* ret) { AutoThreadOperationStageUpdater stage_updater( ThreadStatus::STAGE_PICK_MEMTABLES_TO_FLUSH); const auto& memlist = current_->memlist_; for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) { MemTable* m = *it; + if (max_memtable_id != nullptr && m->GetID() > *max_memtable_id) { + break; + } if (!m->flush_in_progress_) { assert(!m->flush_completed_); num_flush_not_started_--; @@ -295,7 +296,7 @@ void MemTableList::PickMemtablesToFlush(autovector* ret) { } void MemTableList::RollbackMemtableFlush(const autovector& mems, - uint64_t file_number) { + uint64_t /*file_number*/) { AutoThreadOperationStageUpdater stage_updater( ThreadStatus::STAGE_MEMTABLE_ROLLBACK); assert(!mems.empty()); @@ -314,17 +315,21 @@ void MemTableList::RollbackMemtableFlush(const autovector& mems, imm_flush_needed.store(true, std::memory_order_release); } -// Record a successful flush in the manifest file -Status MemTableList::InstallMemtableFlushResults( +// Try record a successful flush in the manifest file. It might just return +// Status::OK letting a concurrent flush to do actual the recording.. +Status MemTableList::TryInstallMemtableFlushResults( ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options, - const autovector& mems, VersionSet* vset, InstrumentedMutex* mu, - uint64_t file_number, autovector* to_delete, - Directory* db_directory, LogBuffer* log_buffer) { + const autovector& mems, LogsWithPrepTracker* prep_tracker, + VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number, + autovector* to_delete, Directory* db_directory, + LogBuffer* log_buffer) { AutoThreadOperationStageUpdater stage_updater( ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS); mu->AssertHeld(); - // flush was successful + // Flush was successful + // Record the status on the memtable object. Either this call or a call by a + // concurrent flush thread will read the status and write it to manifest. for (size_t i = 0; i < mems.size(); ++i) { // All the edits are associated with the first memtable of this batch. assert(i == 0 || mems[i]->GetEdits()->NumEntries() == 0); @@ -336,7 +341,7 @@ Status MemTableList::InstallMemtableFlushResults( // if some other thread is already committing, then return Status s; if (commit_in_progress_) { - TEST_SYNC_POINT("MemTableList::InstallMemtableFlushResults:InProgress"); + TEST_SYNC_POINT("MemTableList::TryInstallMemtableFlushResults:InProgress"); return s; } @@ -347,15 +352,21 @@ Status MemTableList::InstallMemtableFlushResults( // while the current thread is writing manifest where mutex is released. while (s.ok()) { auto& memlist = current_->memlist_; + // The back is the oldest; if flush_completed_ is not set to it, it means + // that we were assigned a more recent memtable. The memtables' flushes must + // be recorded in manifest in order. A concurrent flush thread, who is + // assigned to flush the oldest memtable, will later wake up and does all + // the pending writes to manifest, in order. if (memlist.empty() || !memlist.back()->flush_completed_) { break; } // scan all memtables from the earliest, and commit those - // (in that order) that have finished flushing. Memetables + // (in that order) that have finished flushing. Memtables // are always committed in the order that they were created. uint64_t batch_file_number = 0; size_t batch_count = 0; autovector edit_list; + autovector memtables_to_flush; // enumerate from the last (earliest) element to see how many batch finished for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) { MemTable* m = *it; @@ -368,11 +379,21 @@ Status MemTableList::InstallMemtableFlushResults( "[%s] Level-0 commit table #%" PRIu64 " started", cfd->GetName().c_str(), m->file_number_); edit_list.push_back(&m->edit_); + memtables_to_flush.push_back(m); } batch_count++; } + // TODO(myabandeh): Not sure how batch_count could be 0 here. if (batch_count > 0) { + if (vset->db_options()->allow_2pc) { + assert(edit_list.size() > 0); + // We piggyback the information of earliest log file to keep in the + // manifest entry for the last file flushed. + edit_list.back()->SetMinLogNumberToKeep(PrecomputeMinLogNumberToKeep( + vset, *cfd, edit_list, memtables_to_flush, prep_tracker)); + } + // this can release and reacquire the mutex. s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu, db_directory); @@ -384,7 +405,22 @@ Status MemTableList::InstallMemtableFlushResults( // All the later memtables that have the same filenum // are part of the same batch. They can be committed now. uint64_t mem_id = 1; // how many memtables have been flushed. - if (s.ok()) { // commit new state + + // commit new state only if the column family is NOT dropped. + // The reason is as follows (refer to + // ColumnFamilyTest.FlushAndDropRaceCondition). + // If the column family is dropped, then according to LogAndApply, its + // corresponding flush operation is NOT written to the MANIFEST. This + // means the DB is not aware of the L0 files generated from the flush. + // By committing the new state, we remove the memtable from the memtable + // list. Creating an iterator on this column family will not be able to + // read full data since the memtable is removed, and the DB is not aware + // of the L0 files, causing MergingIterator unable to build child + // iterators. RocksDB contract requires that the iterator can be created + // on a dropped column family, and we must be able to + // read full data as long as column family handle is not deleted, even if + // the column family is dropped. + if (s.ok() && !cfd->IsDropped()) { // commit new state while (batch_count-- > 0) { MemTable* m = current_->memlist_.back(); ROCKS_LOG_BUFFER(log_buffer, "[%s] Level-0 commit table #%" PRIu64 @@ -463,13 +499,21 @@ void MemTableList::InstallNewVersion() { } } -uint64_t MemTableList::GetMinLogContainingPrepSection() { +uint64_t MemTableList::PrecomputeMinLogContainingPrepSection( + const autovector& memtables_to_flush) { uint64_t min_log = 0; for (auto& m : current_->memlist_) { - // this mem has been flushed it no longer - // needs to hold on the its prep section - if (m->flush_completed_) { + // Assume the list is very short, we can live with O(m*n). We can optimize + // if the performance has some problem. + bool should_skip = false; + for (MemTable* m_to_flush : memtables_to_flush) { + if (m == m_to_flush) { + should_skip = true; + break; + } + } + if (should_skip) { continue; } @@ -483,4 +527,109 @@ uint64_t MemTableList::GetMinLogContainingPrepSection() { return min_log; } +// Commit a successful atomic flush in the manifest file. +Status InstallMemtableAtomicFlushResults( + const autovector* imm_lists, + const autovector& cfds, + const autovector& mutable_cf_options_list, + const autovector*>& mems_list, VersionSet* vset, + InstrumentedMutex* mu, const autovector& file_metas, + autovector* to_delete, Directory* db_directory, + LogBuffer* log_buffer) { + AutoThreadOperationStageUpdater stage_updater( + ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS); + mu->AssertHeld(); + + size_t num = mems_list.size(); + assert(cfds.size() == num); + if (imm_lists != nullptr) { + assert(imm_lists->size() == num); + } + for (size_t k = 0; k != num; ++k) { +#ifndef NDEBUG + const auto* imm = + (imm_lists == nullptr) ? cfds[k]->imm() : imm_lists->at(k); + if (!mems_list[k]->empty()) { + assert((*mems_list[k])[0]->GetID() == imm->GetEarliestMemTableID()); + } +#endif + assert(nullptr != file_metas[k]); + for (size_t i = 0; i != mems_list[k]->size(); ++i) { + assert(i == 0 || (*mems_list[k])[i]->GetEdits()->NumEntries() == 0); + (*mems_list[k])[i]->SetFlushCompleted(true); + (*mems_list[k])[i]->SetFileNumber(file_metas[k]->fd.GetNumber()); + } + } + + Status s; + + autovector> edit_lists; + uint32_t num_entries = 0; + for (const auto mems : mems_list) { + assert(mems != nullptr); + autovector edits; + assert(!mems->empty()); + edits.emplace_back((*mems)[0]->GetEdits()); + ++num_entries; + edit_lists.emplace_back(edits); + } + // Mark the version edits as an atomic group if the number of version edits + // exceeds 1. + if (cfds.size() > 1) { + for (auto& edits : edit_lists) { + assert(edits.size() == 1); + edits[0]->MarkAtomicGroup(--num_entries); + } + assert(0 == num_entries); + } + + // this can release and reacquire the mutex. + s = vset->LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu, + db_directory); + + for (size_t k = 0; k != cfds.size(); ++k) { + auto* imm = (imm_lists == nullptr) ? cfds[k]->imm() : imm_lists->at(k); + imm->InstallNewVersion(); + } + + if (s.ok() || s.IsShutdownInProgress()) { + for (size_t i = 0; i != cfds.size(); ++i) { + if (cfds[i]->IsDropped()) { + continue; + } + auto* imm = (imm_lists == nullptr) ? cfds[i]->imm() : imm_lists->at(i); + for (auto m : *mems_list[i]) { + assert(m->GetFileNumber() > 0); + uint64_t mem_id = m->GetID(); + ROCKS_LOG_BUFFER(log_buffer, + "[%s] Level-0 commit table #%" PRIu64 + ": memtable #%" PRIu64 " done", + cfds[i]->GetName().c_str(), m->GetFileNumber(), + mem_id); + imm->current_->Remove(m, to_delete); + } + } + } else { + for (size_t i = 0; i != cfds.size(); ++i) { + auto* imm = (imm_lists == nullptr) ? cfds[i]->imm() : imm_lists->at(i); + for (auto m : *mems_list[i]) { + uint64_t mem_id = m->GetID(); + ROCKS_LOG_BUFFER(log_buffer, + "[%s] Level-0 commit table #%" PRIu64 + ": memtable #%" PRIu64 " failed", + cfds[i]->GetName().c_str(), m->GetFileNumber(), + mem_id); + m->SetFlushCompleted(false); + m->SetFlushInProgress(false); + m->GetEdits()->Clear(); + m->SetFileNumber(0); + imm->num_flush_not_started_++; + } + imm->imm_flush_needed.store(true, std::memory_order_release); + } + } + + return s; +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/memtable_list.h b/thirdparty/rocksdb/db/memtable_list.h index 69038af500..b56ad4932c 100644 --- a/thirdparty/rocksdb/db/memtable_list.h +++ b/thirdparty/rocksdb/db/memtable_list.h @@ -5,13 +5,15 @@ // #pragma once -#include +#include +#include #include -#include #include -#include +#include +#include #include "db/dbformat.h" +#include "db/logs_with_prep_tracker.h" #include "db/memtable.h" #include "db/range_del_aggregator.h" #include "monitoring/instrumented_mutex.h" @@ -29,6 +31,7 @@ class ColumnFamilyData; class InternalKeyComparator; class InstrumentedMutex; class MergeIteratorBuilder; +class MemTableList; // keeps a list of immutable memtables in a vector. the list is immutable // if refcount is bigger than one. It is used as a state for Get() and @@ -53,16 +56,19 @@ class MemTableListVersion { // will be stored in *seq on success (regardless of whether true/false is // returned). Otherwise, *seq will be set to kMaxSequenceNumber. bool Get(const LookupKey& key, std::string* value, Status* s, - MergeContext* merge_context, RangeDelAggregator* range_del_agg, - SequenceNumber* seq, const ReadOptions& read_opts, + MergeContext* merge_context, + SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq, + const ReadOptions& read_opts, ReadCallback* callback = nullptr, bool* is_blob_index = nullptr); bool Get(const LookupKey& key, std::string* value, Status* s, - MergeContext* merge_context, RangeDelAggregator* range_del_agg, - const ReadOptions& read_opts, bool* is_blob_index = nullptr) { + MergeContext* merge_context, + SequenceNumber* max_covering_tombstone_seq, + const ReadOptions& read_opts, ReadCallback* callback = nullptr, + bool* is_blob_index = nullptr) { SequenceNumber seq; - return Get(key, value, s, merge_context, range_del_agg, &seq, read_opts, - is_blob_index); + return Get(key, value, s, merge_context, max_covering_tombstone_seq, &seq, + read_opts, callback, is_blob_index); } // Similar to Get(), but searches the Memtable history of memtables that @@ -71,24 +77,22 @@ class MemTableListVersion { // writes that are also present in the SST files. bool GetFromHistory(const LookupKey& key, std::string* value, Status* s, MergeContext* merge_context, - RangeDelAggregator* range_del_agg, SequenceNumber* seq, - const ReadOptions& read_opts, + SequenceNumber* max_covering_tombstone_seq, + SequenceNumber* seq, const ReadOptions& read_opts, bool* is_blob_index = nullptr); bool GetFromHistory(const LookupKey& key, std::string* value, Status* s, MergeContext* merge_context, - RangeDelAggregator* range_del_agg, + SequenceNumber* max_covering_tombstone_seq, const ReadOptions& read_opts, bool* is_blob_index = nullptr) { SequenceNumber seq; - return GetFromHistory(key, value, s, merge_context, range_del_agg, &seq, - read_opts, is_blob_index); + return GetFromHistory(key, value, s, merge_context, + max_covering_tombstone_seq, &seq, read_opts, + is_blob_index); } Status AddRangeTombstoneIterators(const ReadOptions& read_opts, Arena* arena, RangeDelAggregator* range_del_agg); - Status AddRangeTombstoneIterators( - const ReadOptions& read_opts, - std::vector* range_del_iters); void AddIterators(const ReadOptions& options, std::vector* iterator_list, @@ -111,6 +115,18 @@ class MemTableListVersion { SequenceNumber GetEarliestSequenceNumber(bool include_history = false) const; private: + friend class MemTableList; + + friend Status InstallMemtableAtomicFlushResults( + const autovector* imm_lists, + const autovector& cfds, + const autovector& mutable_cf_options_list, + const autovector*>& mems_list, + VersionSet* vset, InstrumentedMutex* mu, + const autovector& file_meta, + autovector* to_delete, Directory* db_directory, + LogBuffer* log_buffer); + // REQUIRE: m is an immutable memtable void Add(MemTable* m, autovector* to_delete); // REQUIRE: m is an immutable memtable @@ -120,15 +136,15 @@ class MemTableListVersion { bool GetFromList(std::list* list, const LookupKey& key, std::string* value, Status* s, MergeContext* merge_context, - RangeDelAggregator* range_del_agg, SequenceNumber* seq, - const ReadOptions& read_opts, bool* is_blob_index = nullptr); + SequenceNumber* max_covering_tombstone_seq, + SequenceNumber* seq, const ReadOptions& read_opts, + ReadCallback* callback = nullptr, + bool* is_blob_index = nullptr); void AddMemTable(MemTable* m); void UnrefMemTable(autovector* to_delete, MemTable* m); - friend class MemTableList; - // Immutable MemTables that have not yet been flushed. std::list memlist_; @@ -196,19 +212,22 @@ class MemTableList { // Returns the earliest memtables that needs to be flushed. The returned // memtables are guaranteed to be in the ascending order of created time. - void PickMemtablesToFlush(autovector* mems); + void PickMemtablesToFlush(const uint64_t* max_memtable_id, + autovector* mems); // Reset status of the given memtable list back to pending state so that // they can get picked up again on the next round of flush. void RollbackMemtableFlush(const autovector& mems, uint64_t file_number); - // Commit a successful flush in the manifest file - Status InstallMemtableFlushResults( + // Try commit a successful flush in the manifest file. It might just return + // Status::OK letting a concurrent flush to do the actual the recording. + Status TryInstallMemtableFlushResults( ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options, - const autovector& m, VersionSet* vset, InstrumentedMutex* mu, - uint64_t file_number, autovector* to_delete, - Directory* db_directory, LogBuffer* log_buffer); + const autovector& m, LogsWithPrepTracker* prep_tracker, + VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number, + autovector* to_delete, Directory* db_directory, + LogBuffer* log_buffer); // New memtables are inserted at the front of the list. // Takes ownership of the referenced held on *m by the caller of Add(). @@ -239,9 +258,53 @@ class MemTableList { size_t* current_memory_usage() { return ¤t_memory_usage_; } - uint64_t GetMinLogContainingPrepSection(); + // Returns the min log containing the prep section after memtables listsed in + // `memtables_to_flush` are flushed and their status is persisted in manifest. + uint64_t PrecomputeMinLogContainingPrepSection( + const autovector& memtables_to_flush); + + uint64_t GetEarliestMemTableID() const { + auto& memlist = current_->memlist_; + if (memlist.empty()) { + return std::numeric_limits::max(); + } + return memlist.back()->GetID(); + } + + uint64_t GetLatestMemTableID() const { + auto& memlist = current_->memlist_; + if (memlist.empty()) { + return 0; + } + return memlist.front()->GetID(); + } + + void AssignAtomicFlushSeq(const SequenceNumber& seq) { + const auto& memlist = current_->memlist_; + // Scan the memtable list from new to old + for (auto it = memlist.begin(); it != memlist.end(); ++it) { + MemTable* mem = *it; + if (mem->atomic_flush_seqno_ == kMaxSequenceNumber) { + mem->atomic_flush_seqno_ = seq; + } else { + // Earlier memtables must have been assigned a atomic flush seq, no + // need to continue scan. + break; + } + } + } private: + friend Status InstallMemtableAtomicFlushResults( + const autovector* imm_lists, + const autovector& cfds, + const autovector& mutable_cf_options_list, + const autovector*>& mems_list, + VersionSet* vset, InstrumentedMutex* mu, + const autovector& file_meta, + autovector* to_delete, Directory* db_directory, + LogBuffer* log_buffer); + // DB mutex held void InstallNewVersion(); @@ -255,11 +318,26 @@ class MemTableList { // committing in progress bool commit_in_progress_; - // Requested a flush of all memtables to storage + // Requested a flush of memtables to storage. It's possible to request that + // a subset of memtables be flushed. bool flush_requested_; // The current memory usage. size_t current_memory_usage_; }; +// Installs memtable atomic flush results. +// In most cases, imm_lists is nullptr, and the function simply uses the +// immutable memtable lists associated with the cfds. There are unit tests that +// installs flush results for external immutable memtable lists other than the +// cfds' own immutable memtable lists, e.g. MemTableLIstTest. In this case, +// imm_lists parameter is not nullptr. +extern Status InstallMemtableAtomicFlushResults( + const autovector* imm_lists, + const autovector& cfds, + const autovector& mutable_cf_options_list, + const autovector*>& mems_list, VersionSet* vset, + InstrumentedMutex* mu, const autovector& file_meta, + autovector* to_delete, Directory* db_directory, + LogBuffer* log_buffer); } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/memtable_list_test.cc b/thirdparty/rocksdb/db/memtable_list_test.cc index 30e5166637..a14c13b893 100644 --- a/thirdparty/rocksdb/db/memtable_list_test.cc +++ b/thirdparty/rocksdb/db/memtable_list_test.cc @@ -8,7 +8,6 @@ #include #include #include "db/merge_context.h" -#include "db/range_del_aggregator.h" #include "db/version_set.h" #include "db/write_controller.h" #include "rocksdb/db.h" @@ -25,9 +24,13 @@ class MemTableListTest : public testing::Test { std::string dbname; DB* db; Options options; + std::vector handles; + std::atomic file_number; - MemTableListTest() : db(nullptr) { - dbname = test::TmpDir() + "/memtable_list_test"; + MemTableListTest() : db(nullptr), file_number(1) { + dbname = test::PerThreadDBPath("memtable_list_test"); + options.create_if_missing = true; + DestroyDB(dbname, options); } // Create a test db if not yet created @@ -35,19 +38,49 @@ class MemTableListTest : public testing::Test { if (db == nullptr) { options.create_if_missing = true; DestroyDB(dbname, options); - Status s = DB::Open(options, dbname, &db); + // Open DB only with default column family + ColumnFamilyOptions cf_options; + std::vector cf_descs; + cf_descs.emplace_back(kDefaultColumnFamilyName, cf_options); + Status s = DB::Open(options, dbname, cf_descs, &handles, &db); EXPECT_OK(s); + + ColumnFamilyOptions cf_opt1, cf_opt2; + cf_opt1.cf_paths.emplace_back(dbname + "_one_1", + std::numeric_limits::max()); + cf_opt2.cf_paths.emplace_back(dbname + "_two_1", + std::numeric_limits::max()); + int sz = static_cast(handles.size()); + handles.resize(sz + 2); + s = db->CreateColumnFamily(cf_opt1, "one", &handles[1]); + EXPECT_OK(s); + s = db->CreateColumnFamily(cf_opt2, "two", &handles[2]); + EXPECT_OK(s); + + cf_descs.emplace_back("one", cf_options); + cf_descs.emplace_back("two", cf_options); } } - ~MemTableListTest() { + ~MemTableListTest() override { if (db) { + std::vector cf_descs(handles.size()); + for (int i = 0; i != static_cast(handles.size()); ++i) { + handles[i]->GetDescriptor(&cf_descs[i]); + } + for (auto h : handles) { + if (h) { + db->DestroyColumnFamilyHandle(h); + } + } + handles.clear(); delete db; - DestroyDB(dbname, options); + db = nullptr; + DestroyDB(dbname, options, cf_descs); } } - // Calls MemTableList::InstallMemtableFlushResults() and sets up all + // Calls MemTableList::TryInstallMemtableFlushResults() and sets up all // structures needed to call this function. Status Mock_InstallMemtableFlushResults( MemTableList* list, const MutableCFOptions& mutable_cf_options, @@ -56,36 +89,95 @@ class MemTableListTest : public testing::Test { test::NullLogger logger; LogBuffer log_buffer(DEBUG_LEVEL, &logger); + CreateDB(); // Create a mock VersionSet DBOptions db_options; ImmutableDBOptions immutable_db_options(db_options); EnvOptions env_options; - shared_ptr table_cache(NewLRUCache(50000, 16)); + std::shared_ptr table_cache(NewLRUCache(50000, 16)); WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size); WriteController write_controller(10000000u); + VersionSet versions(dbname, &immutable_db_options, env_options, + table_cache.get(), &write_buffer_manager, + &write_controller); + std::vector cf_descs; + cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions()); + cf_descs.emplace_back("one", ColumnFamilyOptions()); + cf_descs.emplace_back("two", ColumnFamilyOptions()); + + EXPECT_OK(versions.Recover(cf_descs, false)); + + // Create mock default ColumnFamilyData + auto column_family_set = versions.GetColumnFamilySet(); + LogsWithPrepTracker dummy_prep_tracker; + auto cfd = column_family_set->GetDefault(); + EXPECT_TRUE(nullptr != cfd); + uint64_t file_num = file_number.fetch_add(1); + // Create dummy mutex. + InstrumentedMutex mutex; + InstrumentedMutexLock l(&mutex); + return list->TryInstallMemtableFlushResults( + cfd, mutable_cf_options, m, &dummy_prep_tracker, &versions, &mutex, + file_num, to_delete, nullptr, &log_buffer); + } + + // Calls MemTableList::InstallMemtableFlushResults() and sets up all + // structures needed to call this function. + Status Mock_InstallMemtableAtomicFlushResults( + autovector& lists, const autovector& cf_ids, + const autovector& mutable_cf_options_list, + const autovector*>& mems_list, + autovector* to_delete) { + // Create a mock Logger + test::NullLogger logger; + LogBuffer log_buffer(DEBUG_LEVEL, &logger); + CreateDB(); + // Create a mock VersionSet + DBOptions db_options; + ImmutableDBOptions immutable_db_options(db_options); + EnvOptions env_options; + std::shared_ptr table_cache(NewLRUCache(50000, 16)); + WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size); + WriteController write_controller(10000000u); + VersionSet versions(dbname, &immutable_db_options, env_options, table_cache.get(), &write_buffer_manager, &write_controller); + std::vector cf_descs; + cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions()); + cf_descs.emplace_back("one", ColumnFamilyOptions()); + cf_descs.emplace_back("two", ColumnFamilyOptions()); + EXPECT_OK(versions.Recover(cf_descs, false)); // Create mock default ColumnFamilyData - ColumnFamilyOptions cf_options; - std::vector column_families; - column_families.emplace_back(kDefaultColumnFamilyName, cf_options); - EXPECT_OK(versions.Recover(column_families, false)); auto column_family_set = versions.GetColumnFamilySet(); - auto cfd = column_family_set->GetColumnFamily(0); - EXPECT_TRUE(cfd != nullptr); - // Create dummy mutex. + LogsWithPrepTracker dummy_prep_tracker; + autovector cfds; + for (int i = 0; i != static_cast(cf_ids.size()); ++i) { + cfds.emplace_back(column_family_set->GetColumnFamily(cf_ids[i])); + EXPECT_NE(nullptr, cfds[i]); + } + std::vector file_metas; + file_metas.reserve(cf_ids.size()); + for (size_t i = 0; i != cf_ids.size(); ++i) { + FileMetaData meta; + uint64_t file_num = file_number.fetch_add(1); + meta.fd = FileDescriptor(file_num, 0, 0); + file_metas.emplace_back(meta); + } + autovector file_meta_ptrs; + for (auto& meta : file_metas) { + file_meta_ptrs.push_back(&meta); + } InstrumentedMutex mutex; InstrumentedMutexLock l(&mutex); - - return list->InstallMemtableFlushResults(cfd, mutable_cf_options, m, - &versions, &mutex, 1, to_delete, - nullptr, &log_buffer); + return InstallMemtableAtomicFlushResults( + &lists, cfds, mutable_cf_options_list, mems_list, &versions, &mutex, + file_meta_ptrs, to_delete, nullptr, &log_buffer); } }; @@ -98,7 +190,7 @@ TEST_F(MemTableListTest, Empty) { ASSERT_FALSE(list.IsFlushPending()); autovector mems; - list.PickMemtablesToFlush(&mems); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &mems); ASSERT_EQ(0, mems.size()); autovector to_delete; @@ -118,12 +210,12 @@ TEST_F(MemTableListTest, GetTest) { Status s; MergeContext merge_context; InternalKeyComparator ikey_cmp(options.comparator); - RangeDelAggregator range_del_agg(ikey_cmp, {} /* snapshots */); + SequenceNumber max_covering_tombstone_seq = 0; autovector to_delete; LookupKey lkey("key1", seq); bool found = list.current()->Get(lkey, &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); // Create a MemTable @@ -146,19 +238,19 @@ TEST_F(MemTableListTest, GetTest) { // Fetch the newly written keys merge_context.Clear(); found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(s.ok() && found); ASSERT_EQ(value, "value1"); merge_context.Clear(); found = mem->Get(LookupKey("key1", 2), &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); // MemTable found out that this key is *not* found (at this sequence#) ASSERT_TRUE(found && s.IsNotFound()); merge_context.Clear(); found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(s.ok() && found); ASSERT_EQ(value, "value2.2"); @@ -184,25 +276,28 @@ TEST_F(MemTableListTest, GetTest) { // Fetch keys via MemTableList merge_context.Clear(); - found = list.current()->Get(LookupKey("key1", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(found && s.IsNotFound()); merge_context.Clear(); found = list.current()->Get(LookupKey("key1", saved_seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + &merge_context, &max_covering_tombstone_seq, + ReadOptions()); ASSERT_TRUE(s.ok() && found); ASSERT_EQ("value1", value); merge_context.Clear(); - found = list.current()->Get(LookupKey("key2", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(s.ok() && found); ASSERT_EQ(value, "value2.3"); merge_context.Clear(); found = list.current()->Get(LookupKey("key2", 1), &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); ASSERT_EQ(2, list.NumNotFlushed()); @@ -225,12 +320,12 @@ TEST_F(MemTableListTest, GetFromHistoryTest) { Status s; MergeContext merge_context; InternalKeyComparator ikey_cmp(options.comparator); - RangeDelAggregator range_del_agg(ikey_cmp, {} /* snapshots */); + SequenceNumber max_covering_tombstone_seq = 0; autovector to_delete; LookupKey lkey("key1", seq); bool found = list.current()->Get(lkey, &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); // Create a MemTable @@ -252,13 +347,13 @@ TEST_F(MemTableListTest, GetFromHistoryTest) { // Fetch the newly written keys merge_context.Clear(); found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); // MemTable found out that this key is *not* found (at this sequence#) ASSERT_TRUE(found && s.IsNotFound()); merge_context.Clear(); found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context, - &range_del_agg, ReadOptions()); + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(s.ok() && found); ASSERT_EQ(value, "value2.2"); @@ -268,24 +363,27 @@ TEST_F(MemTableListTest, GetFromHistoryTest) { // Fetch keys via MemTableList merge_context.Clear(); - found = list.current()->Get(LookupKey("key1", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(found && s.IsNotFound()); merge_context.Clear(); - found = list.current()->Get(LookupKey("key2", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(s.ok() && found); ASSERT_EQ("value2.2", value); // Flush this memtable from the list. // (It will then be a part of the memtable history). autovector to_flush; - list.PickMemtablesToFlush(&to_flush); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush); ASSERT_EQ(1, to_flush.size()); - s = Mock_InstallMemtableFlushResults(&list, MutableCFOptions(options), - to_flush, &to_delete); + MutableCFOptions mutable_cf_options(options); + s = Mock_InstallMemtableFlushResults(&list, mutable_cf_options, to_flush, + &to_delete); ASSERT_OK(s); ASSERT_EQ(0, list.NumNotFlushed()); ASSERT_EQ(1, list.NumFlushed()); @@ -293,26 +391,28 @@ TEST_F(MemTableListTest, GetFromHistoryTest) { // Verify keys are no longer in MemTableList merge_context.Clear(); - found = list.current()->Get(LookupKey("key1", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); merge_context.Clear(); - found = list.current()->Get(LookupKey("key2", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); // Verify keys are present in history merge_context.Clear(); - found = list.current()->GetFromHistory(LookupKey("key1", seq), &value, &s, - &merge_context, &range_del_agg, - ReadOptions()); + found = list.current()->GetFromHistory( + LookupKey("key1", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(found && s.IsNotFound()); merge_context.Clear(); - found = list.current()->GetFromHistory(LookupKey("key2", seq), &value, &s, - &merge_context, &range_del_agg, - ReadOptions()); + found = list.current()->GetFromHistory( + LookupKey("key2", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(found); ASSERT_EQ("value2.2", value); @@ -330,12 +430,12 @@ TEST_F(MemTableListTest, GetFromHistoryTest) { ASSERT_EQ(0, to_delete.size()); to_flush.clear(); - list.PickMemtablesToFlush(&to_flush); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush); ASSERT_EQ(1, to_flush.size()); // Flush second memtable - s = Mock_InstallMemtableFlushResults(&list, MutableCFOptions(options), - to_flush, &to_delete); + s = Mock_InstallMemtableFlushResults(&list, mutable_cf_options, to_flush, + &to_delete); ASSERT_OK(s); ASSERT_EQ(0, list.NumNotFlushed()); ASSERT_EQ(2, list.NumFlushed()); @@ -353,38 +453,42 @@ TEST_F(MemTableListTest, GetFromHistoryTest) { // Verify keys are no longer in MemTableList merge_context.Clear(); - found = list.current()->Get(LookupKey("key1", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); merge_context.Clear(); - found = list.current()->Get(LookupKey("key2", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); merge_context.Clear(); - found = list.current()->Get(LookupKey("key3", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key3", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); // Verify that the second memtable's keys are in the history merge_context.Clear(); - found = list.current()->GetFromHistory(LookupKey("key1", seq), &value, &s, - &merge_context, &range_del_agg, - ReadOptions()); + found = list.current()->GetFromHistory( + LookupKey("key1", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(found && s.IsNotFound()); merge_context.Clear(); - found = list.current()->GetFromHistory(LookupKey("key3", seq), &value, &s, - &merge_context, &range_del_agg, - ReadOptions()); + found = list.current()->GetFromHistory( + LookupKey("key3", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_TRUE(found); ASSERT_EQ("value3", value); // Verify that key2 from the first memtable is no longer in the history merge_context.Clear(); - found = list.current()->Get(LookupKey("key2", seq), &value, &s, - &merge_context, &range_del_agg, ReadOptions()); + found = + list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context, + &max_covering_tombstone_seq, ReadOptions()); ASSERT_FALSE(found); // Cleanup @@ -396,7 +500,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) { } TEST_F(MemTableListTest, FlushPendingTest) { - const int num_tables = 5; + const int num_tables = 6; SequenceNumber seq = 1; Status s; @@ -414,11 +518,13 @@ TEST_F(MemTableListTest, FlushPendingTest) { max_write_buffer_number_to_maintain); // Create some MemTables + uint64_t memtable_id = 0; std::vector tables; MutableCFOptions mutable_cf_options(options); for (int i = 0; i < num_tables; i++) { MemTable* mem = new MemTable(cmp, ioptions, mutable_cf_options, &wb, kMaxSequenceNumber, 0 /* column_family_id */); + mem->SetID(memtable_id++); mem->Ref(); std::string value; @@ -437,7 +543,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { ASSERT_FALSE(list.IsFlushPending()); ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); autovector to_flush; - list.PickMemtablesToFlush(&to_flush); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush); ASSERT_EQ(0, to_flush.size()); // Request a flush even though there is nothing to flush @@ -446,7 +552,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); // Attempt to 'flush' to clear request for flush - list.PickMemtablesToFlush(&to_flush); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush); ASSERT_EQ(0, to_flush.size()); ASSERT_FALSE(list.IsFlushPending()); ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); @@ -470,7 +576,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); // Pick tables to flush - list.PickMemtablesToFlush(&to_flush); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush); ASSERT_EQ(2, to_flush.size()); ASSERT_EQ(2, list.NumNotFlushed()); ASSERT_FALSE(list.IsFlushPending()); @@ -491,7 +597,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { ASSERT_EQ(0, to_delete.size()); // Pick tables to flush - list.PickMemtablesToFlush(&to_flush); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush); ASSERT_EQ(3, to_flush.size()); ASSERT_EQ(3, list.NumNotFlushed()); ASSERT_FALSE(list.IsFlushPending()); @@ -499,7 +605,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { // Pick tables to flush again autovector to_flush2; - list.PickMemtablesToFlush(&to_flush2); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush2); ASSERT_EQ(0, to_flush2.size()); ASSERT_EQ(3, list.NumNotFlushed()); ASSERT_FALSE(list.IsFlushPending()); @@ -517,7 +623,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); // Pick tables to flush again - list.PickMemtablesToFlush(&to_flush2); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush2); ASSERT_EQ(1, to_flush2.size()); ASSERT_EQ(4, list.NumNotFlushed()); ASSERT_FALSE(list.IsFlushPending()); @@ -538,7 +644,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { ASSERT_EQ(0, to_delete.size()); // Pick tables to flush - list.PickMemtablesToFlush(&to_flush); + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush); // Should pick 4 of 5 since 1 table has been picked in to_flush2 ASSERT_EQ(4, to_flush.size()); ASSERT_EQ(5, list.NumNotFlushed()); @@ -547,20 +653,21 @@ TEST_F(MemTableListTest, FlushPendingTest) { // Pick tables to flush again autovector to_flush3; + list.PickMemtablesToFlush(nullptr /* memtable_id */, &to_flush3); ASSERT_EQ(0, to_flush3.size()); // nothing not in progress of being flushed ASSERT_EQ(5, list.NumNotFlushed()); ASSERT_FALSE(list.IsFlushPending()); ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); // Flush the 4 memtables that were picked in to_flush - s = Mock_InstallMemtableFlushResults(&list, MutableCFOptions(options), - to_flush, &to_delete); + s = Mock_InstallMemtableFlushResults(&list, mutable_cf_options, to_flush, + &to_delete); ASSERT_OK(s); // Note: now to_flush contains tables[0,1,2,4]. to_flush2 contains // tables[3]. // Current implementation will only commit memtables in the order they were - // created. So InstallMemtableFlushResults will install the first 3 tables + // created. So TryInstallMemtableFlushResults will install the first 3 tables // in to_flush and stop when it encounters a table not yet flushed. ASSERT_EQ(2, list.NumNotFlushed()); int num_in_history = std::min(3, max_write_buffer_number_to_maintain); @@ -574,7 +681,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { // Flush the 1 memtable that was picked in to_flush2 s = MemTableListTest::Mock_InstallMemtableFlushResults( - &list, MutableCFOptions(options), to_flush2, &to_delete); + &list, mutable_cf_options, to_flush2, &to_delete); ASSERT_OK(s); // This will actually install 2 tables. The 1 we told it to flush, and also @@ -585,7 +692,7 @@ TEST_F(MemTableListTest, FlushPendingTest) { ASSERT_EQ(5 - list.NumNotFlushed() - num_in_history, to_delete.size()); for (const auto& m : to_delete) { - // Refcount should be 0 after calling InstallMemtableFlushResults. + // Refcount should be 0 after calling TryInstallMemtableFlushResults. // Verify this, by Ref'ing then UnRef'ing: m->Ref(); ASSERT_EQ(m, m->Unref()); @@ -593,12 +700,41 @@ TEST_F(MemTableListTest, FlushPendingTest) { } to_delete.clear(); + // Add another table + list.Add(tables[5], &to_delete); + ASSERT_EQ(1, list.NumNotFlushed()); + ASSERT_EQ(5, list.GetLatestMemTableID()); + memtable_id = 4; + // Pick tables to flush. The tables to pick must have ID smaller than or + // equal to 4. Therefore, no table will be selected in this case. + autovector to_flush4; + list.FlushRequested(); + ASSERT_TRUE(list.HasFlushRequested()); + list.PickMemtablesToFlush(&memtable_id, &to_flush4); + ASSERT_TRUE(to_flush4.empty()); + ASSERT_EQ(1, list.NumNotFlushed()); + ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire)); + ASSERT_FALSE(list.IsFlushPending()); + ASSERT_FALSE(list.HasFlushRequested()); + + // Pick tables to flush. The tables to pick must have ID smaller than or + // equal to 5. Therefore, only tables[5] will be selected. + memtable_id = 5; + list.FlushRequested(); + list.PickMemtablesToFlush(&memtable_id, &to_flush4); + ASSERT_EQ(1, static_cast(to_flush4.size())); + ASSERT_EQ(1, list.NumNotFlushed()); + ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire)); + ASSERT_FALSE(list.IsFlushPending()); + to_delete.clear(); + list.current()->Unref(&to_delete); - int to_delete_size = std::min(5, max_write_buffer_number_to_maintain); + int to_delete_size = + std::min(num_tables, max_write_buffer_number_to_maintain); ASSERT_EQ(to_delete_size, to_delete.size()); for (const auto& m : to_delete) { - // Refcount should be 0 after calling InstallMemtableFlushResults. + // Refcount should be 0 after calling TryInstallMemtableFlushResults. // Verify this, by Ref'ing then UnRef'ing: m->Ref(); ASSERT_EQ(m, m->Unref()); @@ -607,6 +743,157 @@ TEST_F(MemTableListTest, FlushPendingTest) { to_delete.clear(); } +TEST_F(MemTableListTest, EmptyAtomicFlusTest) { + autovector lists; + autovector cf_ids; + autovector options_list; + autovector*> to_flush; + autovector to_delete; + Status s = Mock_InstallMemtableAtomicFlushResults(lists, cf_ids, options_list, + to_flush, &to_delete); + ASSERT_OK(s); + ASSERT_TRUE(to_delete.empty()); +} + +TEST_F(MemTableListTest, AtomicFlusTest) { + const int num_cfs = 3; + const int num_tables_per_cf = 2; + SequenceNumber seq = 1; + + auto factory = std::make_shared(); + options.memtable_factory = factory; + ImmutableCFOptions ioptions(options); + InternalKeyComparator cmp(BytewiseComparator()); + WriteBufferManager wb(options.db_write_buffer_size); + + // Create MemTableLists + int min_write_buffer_number_to_merge = 3; + int max_write_buffer_number_to_maintain = 7; + autovector lists; + for (int i = 0; i != num_cfs; ++i) { + lists.emplace_back(new MemTableList(min_write_buffer_number_to_merge, + max_write_buffer_number_to_maintain)); + } + + autovector cf_ids; + std::vector> tables(num_cfs); + autovector mutable_cf_options_list; + uint32_t cf_id = 0; + for (auto& elem : tables) { + mutable_cf_options_list.emplace_back(new MutableCFOptions(options)); + uint64_t memtable_id = 0; + for (int i = 0; i != num_tables_per_cf; ++i) { + MemTable* mem = + new MemTable(cmp, ioptions, *(mutable_cf_options_list.back()), &wb, + kMaxSequenceNumber, cf_id); + mem->SetID(memtable_id++); + mem->Ref(); + + std::string value; + + mem->Add(++seq, kTypeValue, "key1", ToString(i)); + mem->Add(++seq, kTypeValue, "keyN" + ToString(i), "valueN"); + mem->Add(++seq, kTypeValue, "keyX" + ToString(i), "value"); + mem->Add(++seq, kTypeValue, "keyM" + ToString(i), "valueM"); + mem->Add(++seq, kTypeDeletion, "keyX" + ToString(i), ""); + + elem.push_back(mem); + } + cf_ids.push_back(cf_id++); + } + + std::vector> flush_candidates(num_cfs); + + // Nothing to flush + for (auto i = 0; i != num_cfs; ++i) { + auto* list = lists[i]; + ASSERT_FALSE(list->IsFlushPending()); + ASSERT_FALSE(list->imm_flush_needed.load(std::memory_order_acquire)); + list->PickMemtablesToFlush(nullptr /* memtable_id */, &flush_candidates[i]); + ASSERT_EQ(0, flush_candidates[i].size()); + } + // Request flush even though there is nothing to flush + for (auto i = 0; i != num_cfs; ++i) { + auto* list = lists[i]; + list->FlushRequested(); + ASSERT_FALSE(list->IsFlushPending()); + ASSERT_FALSE(list->imm_flush_needed.load(std::memory_order_acquire)); + } + autovector to_delete; + // Add tables to the immutable memtalbe lists associated with column families + for (auto i = 0; i != num_cfs; ++i) { + for (auto j = 0; j != num_tables_per_cf; ++j) { + lists[i]->Add(tables[i][j], &to_delete); + } + ASSERT_EQ(num_tables_per_cf, lists[i]->NumNotFlushed()); + ASSERT_TRUE(lists[i]->IsFlushPending()); + ASSERT_TRUE(lists[i]->imm_flush_needed.load(std::memory_order_acquire)); + } + std::vector flush_memtable_ids = {1, 1, 0}; + // +----+ + // list[0]: |0 1| + // list[1]: |0 1| + // | +--+ + // list[2]: |0| 1 + // +-+ + // Pick memtables to flush + for (auto i = 0; i != num_cfs; ++i) { + flush_candidates[i].clear(); + lists[i]->PickMemtablesToFlush(&flush_memtable_ids[i], + &flush_candidates[i]); + ASSERT_EQ(flush_memtable_ids[i] - 0 + 1, + static_cast(flush_candidates[i].size())); + } + autovector tmp_lists; + autovector tmp_cf_ids; + autovector tmp_options_list; + autovector*> to_flush; + for (auto i = 0; i != num_cfs; ++i) { + if (!flush_candidates[i].empty()) { + to_flush.push_back(&flush_candidates[i]); + tmp_lists.push_back(lists[i]); + tmp_cf_ids.push_back(i); + tmp_options_list.push_back(mutable_cf_options_list[i]); + } + } + Status s = Mock_InstallMemtableAtomicFlushResults( + tmp_lists, tmp_cf_ids, tmp_options_list, to_flush, &to_delete); + ASSERT_OK(s); + + for (auto i = 0; i != num_cfs; ++i) { + for (auto j = 0; j != num_tables_per_cf; ++j) { + if (static_cast(j) <= flush_memtable_ids[i]) { + ASSERT_LT(0, tables[i][j]->GetFileNumber()); + } + } + ASSERT_EQ( + static_cast(num_tables_per_cf) - flush_candidates[i].size(), + lists[i]->NumNotFlushed()); + } + + to_delete.clear(); + for (auto list : lists) { + list->current()->Unref(&to_delete); + delete list; + } + for (auto& mutable_cf_options : mutable_cf_options_list) { + if (mutable_cf_options != nullptr) { + delete mutable_cf_options; + mutable_cf_options = nullptr; + } + } + // All memtables in tables array must have been flushed, thus ready to be + // deleted. + ASSERT_EQ(to_delete.size(), tables.size() * tables.front().size()); + for (const auto& m : to_delete) { + // Refcount should be 0 after calling InstallMemtableFlushResults. + // Verify this by Ref'ing and then Unref'ing. + m->Ref(); + ASSERT_EQ(m, m->Unref()); + delete m; + } +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/merge_context.h b/thirdparty/rocksdb/db/merge_context.h index 5e75e09973..fd06441f7c 100644 --- a/thirdparty/rocksdb/db/merge_context.h +++ b/thirdparty/rocksdb/db/merge_context.h @@ -74,8 +74,14 @@ class MergeContext { return (*operand_list_)[index]; } - // Return all the operands. + // Same as GetOperandsDirectionForward const std::vector& GetOperands() { + return GetOperandsDirectionForward(); + } + + // Return all the operands in the order as they were merged (passed to + // FullMerge or FullMergeV2) + const std::vector& GetOperandsDirectionForward() { if (!operand_list_) { return empty_operand_list; } @@ -84,6 +90,17 @@ class MergeContext { return *operand_list_; } + // Return all the operands in the reversed order relative to how they were + // merged (passed to FullMerge or FullMergeV2) + const std::vector& GetOperandsDirectionBackward() { + if (!operand_list_) { + return empty_operand_list; + } + + SetDirectionBackward(); + return *operand_list_; + } + private: void Initialize() { if (!operand_list_) { diff --git a/thirdparty/rocksdb/db/merge_helper.cc b/thirdparty/rocksdb/db/merge_helper.cc index 55f8254cf0..b5ae924ffc 100644 --- a/thirdparty/rocksdb/db/merge_helper.cc +++ b/thirdparty/rocksdb/db/merge_helper.cc @@ -5,15 +5,16 @@ #include "db/merge_helper.h" -#include #include #include "db/dbformat.h" #include "monitoring/perf_context_imp.h" #include "monitoring/statistics.h" +#include "port/likely.h" #include "rocksdb/comparator.h" #include "rocksdb/db.h" #include "rocksdb/merge_operator.h" +#include "table/format.h" #include "table/internal_iterator.h" namespace rocksdb { @@ -22,7 +23,8 @@ MergeHelper::MergeHelper(Env* env, const Comparator* user_comparator, const MergeOperator* user_merge_operator, const CompactionFilter* compaction_filter, Logger* logger, bool assert_valid_internal_key, - SequenceNumber latest_snapshot, int level, + SequenceNumber latest_snapshot, + const SnapshotChecker* snapshot_checker, int level, Statistics* stats, const std::atomic* shutting_down) : env_(env), @@ -34,6 +36,7 @@ MergeHelper::MergeHelper(Env* env, const Comparator* user_comparator, assert_valid_internal_key_(assert_valid_internal_key), allow_single_operand_(false), latest_snapshot_(latest_snapshot), + snapshot_checker_(snapshot_checker), level_(level), keys_(), filter_timer_(env_), @@ -61,8 +64,8 @@ Status MergeHelper::TimedFullMerge(const MergeOperator* merge_operator, } if (update_num_ops_stats) { - MeasureTime(statistics, READ_NUM_MERGE_OPERANDS, - static_cast(operands.size())); + RecordInHistogram(statistics, READ_NUM_MERGE_OPERANDS, + static_cast(operands.size())); } bool success; @@ -107,8 +110,11 @@ Status MergeHelper::TimedFullMerge(const MergeOperator* merge_operator, // keys_ stores the list of keys encountered while merging. // operands_ stores the list of merge operands encountered while merging. // keys_[i] corresponds to operands_[i] for each i. +// +// TODO: Avoid the snapshot stripe map lookup in CompactionRangeDelAggregator +// and just pass the StripeRep corresponding to the stripe being merged. Status MergeHelper::MergeUntil(InternalIterator* iter, - RangeDelAggregator* range_del_agg, + CompactionRangeDelAggregator* range_del_agg, const SequenceNumber stop_before, const bool at_bottom) { // Get a copy of the internal key, before it's invalidated by iter->Next() @@ -132,7 +138,11 @@ Status MergeHelper::MergeUntil(InternalIterator* iter, // orig_ikey is backed by original_key if keys_.empty() // orig_ikey is backed by keys_.back() if !keys_.empty() ParsedInternalKey orig_ikey; - ParseInternalKey(original_key, &orig_ikey); + bool succ = ParseInternalKey(original_key, &orig_ikey); + assert(succ); + if (!succ) { + return Status::Corruption("Cannot parse key in MergeUntil"); + } Status s; bool hit_the_next_user_key = false; @@ -158,8 +168,13 @@ Status MergeHelper::MergeUntil(InternalIterator* iter, // hit a different user key, stop right here hit_the_next_user_key = true; break; - } else if (stop_before && ikey.sequence <= stop_before) { - // hit an entry that's visible by the previous snapshot, can't touch that + } else if (stop_before > 0 && ikey.sequence <= stop_before && + LIKELY(snapshot_checker_ == nullptr || + snapshot_checker_->CheckInSnapshot(ikey.sequence, + stop_before) != + SnapshotCheckerResult::kNotInSnapshot)) { + // hit an entry that's possibly visible by the previous snapshot, can't + // touch that break; } @@ -186,7 +201,15 @@ Status MergeHelper::MergeUntil(InternalIterator* iter, // want. Also if we're in compaction and it's a put, it would be nice to // run compaction filter on it. const Slice val = iter->value(); - const Slice* val_ptr = (kTypeValue == ikey.type) ? &val : nullptr; + const Slice* val_ptr; + if (kTypeValue == ikey.type && + (range_del_agg == nullptr || + !range_del_agg->ShouldDelete( + ikey, RangeDelPositioningMode::kForwardTraversal))) { + val_ptr = &val; + } else { + val_ptr = nullptr; + } std::string merge_result; s = TimedFullMerge(user_merge_operator_, ikey.user_key, val_ptr, merge_context_.GetOperands(), &merge_result, logger_, @@ -231,8 +254,7 @@ Status MergeHelper::MergeUntil(InternalIterator* iter, if (filter != CompactionFilter::Decision::kRemoveAndSkipUntil && range_del_agg != nullptr && range_del_agg->ShouldDelete( - iter->key(), - RangeDelAggregator::RangePositioningMode::kForwardTraversal)) { + iter->key(), RangeDelPositioningMode::kForwardTraversal)) { filter = CompactionFilter::Decision::kRemove; } if (filter == CompactionFilter::Decision::kKeep || @@ -272,22 +294,24 @@ Status MergeHelper::MergeUntil(InternalIterator* iter, return Status::OK(); } - // We are sure we have seen this key's entire history if we are at the - // last level and exhausted all internal keys of this user key. - // NOTE: !iter->Valid() does not necessarily mean we hit the - // beginning of a user key, as versions of a user key might be - // split into multiple files (even files on the same level) - // and some files might not be included in the compaction/merge. + // We are sure we have seen this key's entire history if: + // at_bottom == true (this does not necessarily mean it is the bottommost + // layer, but rather that we are confident the key does not appear on any of + // the lower layers, at_bottom == false doesn't mean it does appear, just + // that we can't be sure, see Compaction::IsBottommostLevel for details) + // AND + // we have either encountered another key or end of key history on this + // layer. // - // There are also cases where we have seen the root of history of this - // key without being sure of it. Then, we simply miss the opportunity + // When these conditions are true we are able to merge all the keys + // using full merge. + // + // For these cases we are not sure about, we simply miss the opportunity // to combine the keys. Since VersionSet::SetupOtherInputs() always makes // sure that all merge-operands on the same level get compacted together, // this will simply lead to these merge operands moving to the next level. - // - // So, we only perform the following logic (to merge all operands together - // without a Put/Delete) if we are certain that we have seen the end of key. - bool surely_seen_the_beginning = hit_the_next_user_key && at_bottom; + bool surely_seen_the_beginning = + (hit_the_next_user_key || !iter->Valid()) && at_bottom; if (surely_seen_the_beginning) { // do a final merge with nullptr as the existing value and say // bye to the merge type (it's now converted to a Put) @@ -367,7 +391,7 @@ CompactionFilter::Decision MergeHelper::FilterMerge(const Slice& user_key, if (compaction_filter_ == nullptr) { return CompactionFilter::Decision::kKeep; } - if (stats_ != nullptr) { + if (stats_ != nullptr && ShouldReportDetailedTime(env_, stats_)) { filter_timer_.Start(); } compaction_filter_value_.clear(); diff --git a/thirdparty/rocksdb/db/merge_helper.h b/thirdparty/rocksdb/db/merge_helper.h index b9ef12a4cf..670cba5983 100644 --- a/thirdparty/rocksdb/db/merge_helper.h +++ b/thirdparty/rocksdb/db/merge_helper.h @@ -3,8 +3,7 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). // -#ifndef MERGE_HELPER_H -#define MERGE_HELPER_H +#pragma once #include #include @@ -13,6 +12,7 @@ #include "db/dbformat.h" #include "db/merge_context.h" #include "db/range_del_aggregator.h" +#include "db/snapshot_checker.h" #include "rocksdb/compaction_filter.h" #include "rocksdb/env.h" #include "rocksdb/slice.h" @@ -25,7 +25,6 @@ class Iterator; class Logger; class MergeOperator; class Statistics; -class InternalIterator; class MergeHelper { public: @@ -33,7 +32,8 @@ class MergeHelper { const MergeOperator* user_merge_operator, const CompactionFilter* compaction_filter, Logger* logger, bool assert_valid_internal_key, SequenceNumber latest_snapshot, - int level = 0, Statistics* stats = nullptr, + const SnapshotChecker* snapshot_checker = nullptr, int level = 0, + Statistics* stats = nullptr, const std::atomic* shutting_down = nullptr); // Wrapper around MergeOperator::FullMergeV2() that records perf statistics. @@ -78,7 +78,7 @@ class MergeHelper { // // REQUIRED: The first key in the input is not corrupted. Status MergeUntil(InternalIterator* iter, - RangeDelAggregator* range_del_agg = nullptr, + CompactionRangeDelAggregator* range_del_agg = nullptr, const SequenceNumber stop_before = 0, const bool at_bottom = false); @@ -145,6 +145,7 @@ class MergeHelper { bool assert_valid_internal_key_; // enforce no internal key corruption? bool allow_single_operand_; SequenceNumber latest_snapshot_; + const SnapshotChecker* const snapshot_checker_; int level_; // the scratch area that holds the result of MergeUntil @@ -191,5 +192,3 @@ class MergeOutputIterator { }; } // namespace rocksdb - -#endif diff --git a/thirdparty/rocksdb/db/merge_helper_test.cc b/thirdparty/rocksdb/db/merge_helper_test.cc index dc43db0d10..b61092ee57 100644 --- a/thirdparty/rocksdb/db/merge_helper_test.cc +++ b/thirdparty/rocksdb/db/merge_helper_test.cc @@ -20,7 +20,7 @@ class MergeHelperTest : public testing::Test { public: MergeHelperTest() { env_ = Env::Default(); } - ~MergeHelperTest() = default; + ~MergeHelperTest() override = default; Status Run(SequenceNumber stop_before, bool at_bottom, SequenceNumber latest_snapshot = 0) { @@ -130,7 +130,7 @@ TEST_F(MergeHelperTest, SingleOperand) { AddKeyVal("a", 50, kTypeMerge, test::EncodeInt(1U)); - ASSERT_TRUE(Run(31, true).IsMergeInProgress()); + ASSERT_TRUE(Run(31, false).IsMergeInProgress()); ASSERT_FALSE(iter_->Valid()); ASSERT_EQ(test::KeyStr("a", 50, kTypeMerge), merge_helper_->keys()[0]); ASSERT_EQ(test::EncodeInt(1U), merge_helper_->values()[0]); diff --git a/thirdparty/rocksdb/db/merge_test.cc b/thirdparty/rocksdb/db/merge_test.cc index b6582b7a59..3bd4b9a600 100644 --- a/thirdparty/rocksdb/db/merge_test.cc +++ b/thirdparty/rocksdb/db/merge_test.cc @@ -20,15 +20,17 @@ #include "utilities/merge_operators.h" #include "util/testharness.h" -using namespace rocksdb; +namespace rocksdb { + +bool use_compression; + +class MergeTest : public testing::Test {}; -namespace { size_t num_merge_operator_calls; void resetNumMergeOperatorCalls() { num_merge_operator_calls = 0; } size_t num_partial_merge_calls; void resetNumPartialMergeCalls() { num_partial_merge_calls = 0; } -} class CountMergeOperator : public AssociativeMergeOperator { public: @@ -36,11 +38,8 @@ class CountMergeOperator : public AssociativeMergeOperator { mergeOperator_ = MergeOperators::CreateUInt64AddOperator(); } - virtual bool Merge(const Slice& key, - const Slice* existing_value, - const Slice& value, - std::string* new_value, - Logger* logger) const override { + bool Merge(const Slice& key, const Slice* existing_value, const Slice& value, + std::string* new_value, Logger* logger) const override { assert(new_value->empty()); ++num_merge_operator_calls; if (existing_value == nullptr) { @@ -56,25 +55,22 @@ class CountMergeOperator : public AssociativeMergeOperator { logger); } - virtual bool PartialMergeMulti(const Slice& key, - const std::deque& operand_list, - std::string* new_value, - Logger* logger) const override { + bool PartialMergeMulti(const Slice& key, + const std::deque& operand_list, + std::string* new_value, + Logger* logger) const override { assert(new_value->empty()); ++num_partial_merge_calls; return mergeOperator_->PartialMergeMulti(key, operand_list, new_value, logger); } - virtual const char* Name() const override { - return "UInt64AddOperator"; - } + const char* Name() const override { return "UInt64AddOperator"; } private: std::shared_ptr mergeOperator_; }; -namespace { std::shared_ptr OpenDb(const std::string& dbname, const bool ttl = false, const size_t max_successive_merges = 0) { DB* db; @@ -87,7 +83,6 @@ std::shared_ptr OpenDb(const std::string& dbname, const bool ttl = false, // DBWithTTL is not supported in ROCKSDB_LITE #ifndef ROCKSDB_LITE if (ttl) { - std::cout << "Opening database with TTL\n"; DBWithTTL* db_with_ttl; s = DBWithTTL::Open(options, dbname, &db_with_ttl); db = db_with_ttl; @@ -104,7 +99,6 @@ std::shared_ptr OpenDb(const std::string& dbname, const bool ttl = false, } return std::shared_ptr(db); } -} // namespace // Imagine we are maintaining a set of uint64 counters. // Each counter has a distinct name. And we would like @@ -231,7 +225,7 @@ class MergeBasedCounters : public Counters { } // mapped to a rocksdb Merge operation - virtual bool add(const std::string& key, uint64_t value) override { + bool add(const std::string& key, uint64_t value) override { char encoded[sizeof(uint64_t)]; EncodeFixed64(encoded, value); Slice slice(encoded, sizeof(uint64_t)); @@ -246,12 +240,11 @@ class MergeBasedCounters : public Counters { } }; -namespace { void dumpDb(DB* db) { - auto it = unique_ptr(db->NewIterator(ReadOptions())); + auto it = std::unique_ptr(db->NewIterator(ReadOptions())); for (it->SeekToFirst(); it->Valid(); it->Next()) { - uint64_t value = DecodeFixed64(it->value().data()); - std::cout << it->key().ToString() << ": " << value << std::endl; + //uint64_t value = DecodeFixed64(it->value().data()); + //std::cout << it->key().ToString() << ": " << value << std::endl; } assert(it->status().ok()); // Check for any errors found during the scan } @@ -281,8 +274,6 @@ void testCounters(Counters& counters, DB* db, bool test_compaction) { dumpDb(db); - std::cout << "1\n"; - // 1+...+49 = ? uint64_t sum = 0; for (int i = 1; i < 50; i++) { @@ -291,17 +282,12 @@ void testCounters(Counters& counters, DB* db, bool test_compaction) { } assert(counters.assert_get("b") == sum); - std::cout << "2\n"; dumpDb(db); - std::cout << "3\n"; - if (test_compaction) { db->Flush(o); - std::cout << "Compaction started ...\n"; db->CompactRange(CompactRangeOptions(), nullptr, nullptr); - std::cout << "Compaction ended\n"; dumpDb(db); @@ -411,44 +397,35 @@ void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges, static_cast((num_merges % (max_num_merges + 1)))); } -void runTest(int argc, const std::string& dbname, const bool use_ttl = false) { - bool compact = false; - if (argc > 1) { - compact = true; - std::cout << "Turn on Compaction\n"; - } +void runTest(const std::string& dbname, const bool use_ttl = false) { { auto db = OpenDb(dbname, use_ttl); { - std::cout << "Test read-modify-write counters... \n"; Counters counters(db, 0); testCounters(counters, db.get(), true); } { - std::cout << "Test merge-based counters... \n"; MergeBasedCounters counters(db, 0); - testCounters(counters, db.get(), compact); + testCounters(counters, db.get(), use_compression); } } DestroyDB(dbname, Options()); { - std::cout << "Test merge in memtable... \n"; size_t max_merge = 5; auto db = OpenDb(dbname, use_ttl, max_merge); MergeBasedCounters counters(db, 0); - testCounters(counters, db.get(), compact); + testCounters(counters, db.get(), use_compression); testSuccessiveMerge(counters, max_merge, max_merge * 2); testSingleBatchSuccessiveMerge(db.get(), 5, 7); DestroyDB(dbname, Options()); } { - std::cout << "Test Partial-Merge\n"; size_t max_merge = 100; // Min merge is hard-coded to 2. uint32_t min_merge = 2; @@ -468,7 +445,6 @@ void runTest(int argc, const std::string& dbname, const bool use_ttl = false) { } { - std::cout << "Test merge-operator not set after reopen\n"; { auto db = OpenDb(dbname); MergeBasedCounters counters(db, 0); @@ -502,16 +478,27 @@ void runTest(int argc, const std::string& dbname, const bool use_ttl = false) { } */ } -} // namespace -int main(int argc, char *argv[]) { - //TODO: Make this test like a general rocksdb unit-test - rocksdb::port::InstallStackTraceHandler(); - runTest(argc, test::TmpDir() + "/merge_testdb"); -// DBWithTTL is not supported in ROCKSDB_LITE +TEST_F(MergeTest, MergeDbTest) { + runTest(test::PerThreadDBPath("merge_testdb")); +} + #ifndef ROCKSDB_LITE - runTest(argc, test::TmpDir() + "/merge_testdbttl", true); // Run test on TTL database +TEST_F(MergeTest, MergeDbTtlTest) { + runTest(test::PerThreadDBPath("merge_testdbttl"), + true); // Run test on TTL database +} #endif // !ROCKSDB_LITE - printf("Passed all tests!\n"); - return 0; + +} // namespace rocksdb + +int main(int argc, char** argv) { + rocksdb::use_compression = false; + if (argc > 1) { + rocksdb::use_compression = true; + } + + rocksdb::port::InstallStackTraceHandler(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/thirdparty/rocksdb/db/obsolete_files_test.cc b/thirdparty/rocksdb/db/obsolete_files_test.cc new file mode 100644 index 0000000000..52175a07b7 --- /dev/null +++ b/thirdparty/rocksdb/db/obsolete_files_test.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#ifndef ROCKSDB_LITE + +#include +#include +#include +#include +#include "db/db_impl.h" +#include "db/version_set.h" +#include "db/write_batch_internal.h" +#include "rocksdb/db.h" +#include "rocksdb/env.h" +#include "rocksdb/transaction_log.h" +#include "util/filename.h" +#include "util/string_util.h" +#include "util/sync_point.h" +#include "util/testharness.h" +#include "util/testutil.h" + +using std::cerr; +using std::cout; +using std::endl; +using std::flush; + +namespace rocksdb { + +class ObsoleteFilesTest : public testing::Test { + public: + std::string dbname_; + Options options_; + DB* db_; + Env* env_; + int numlevels_; + + ObsoleteFilesTest() { + db_ = nullptr; + env_ = Env::Default(); + // Trigger compaction when the number of level 0 files reaches 2. + options_.level0_file_num_compaction_trigger = 2; + options_.disable_auto_compactions = false; + options_.delete_obsolete_files_period_micros = 0; // always do full purge + options_.enable_thread_tracking = true; + options_.write_buffer_size = 1024*1024*1000; + options_.target_file_size_base = 1024*1024*1000; + options_.max_bytes_for_level_base = 1024*1024*1000; + options_.WAL_ttl_seconds = 300; // Used to test log files + options_.WAL_size_limit_MB = 1024; // Used to test log files + dbname_ = test::PerThreadDBPath("obsolete_files_test"); + options_.wal_dir = dbname_ + "/wal_files"; + + // clean up all the files that might have been there before + std::vector old_files; + env_->GetChildren(dbname_, &old_files); + for (auto file : old_files) { + env_->DeleteFile(dbname_ + "/" + file); + } + env_->GetChildren(options_.wal_dir, &old_files); + for (auto file : old_files) { + env_->DeleteFile(options_.wal_dir + "/" + file); + } + + DestroyDB(dbname_, options_); + numlevels_ = 7; + EXPECT_OK(ReopenDB(true)); + } + + Status ReopenDB(bool create) { + delete db_; + if (create) { + DestroyDB(dbname_, options_); + } + db_ = nullptr; + options_.create_if_missing = create; + return DB::Open(options_, dbname_, &db_); + } + + void CloseDB() { + delete db_; + db_ = nullptr; + } + + void AddKeys(int numkeys, int startkey) { + WriteOptions options; + options.sync = false; + for (int i = startkey; i < (numkeys + startkey) ; i++) { + std::string temp = ToString(i); + Slice key(temp); + Slice value(temp); + ASSERT_OK(db_->Put(options, key, value)); + } + } + + int numKeysInLevels( + std::vector &metadata, + std::vector *keysperlevel = nullptr) { + + if (keysperlevel != nullptr) { + keysperlevel->resize(numlevels_); + } + + int numKeys = 0; + for (size_t i = 0; i < metadata.size(); i++) { + int startkey = atoi(metadata[i].smallestkey.c_str()); + int endkey = atoi(metadata[i].largestkey.c_str()); + int numkeysinfile = (endkey - startkey + 1); + numKeys += numkeysinfile; + if (keysperlevel != nullptr) { + (*keysperlevel)[(int)metadata[i].level] += numkeysinfile; + } + fprintf(stderr, "level %d name %s smallest %s largest %s\n", + metadata[i].level, metadata[i].name.c_str(), + metadata[i].smallestkey.c_str(), + metadata[i].largestkey.c_str()); + } + return numKeys; + } + + void createLevel0Files(int numFiles, int numKeysPerFile) { + int startKey = 0; + DBImpl* dbi = reinterpret_cast(db_); + for (int i = 0; i < numFiles; i++) { + AddKeys(numKeysPerFile, startKey); + startKey += numKeysPerFile; + ASSERT_OK(dbi->TEST_FlushMemTable()); + ASSERT_OK(dbi->TEST_WaitForFlushMemTable()); + } + } + + void CheckFileTypeCounts(std::string& dir, + int required_log, + int required_sst, + int required_manifest) { + std::vector filenames; + env_->GetChildren(dir, &filenames); + + int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0; + for (auto file : filenames) { + uint64_t number; + FileType type; + if (ParseFileName(file, &number, &type)) { + log_cnt += (type == kLogFile); + sst_cnt += (type == kTableFile); + manifest_cnt += (type == kDescriptorFile); + } + } + ASSERT_EQ(required_log, log_cnt); + ASSERT_EQ(required_sst, sst_cnt); + ASSERT_EQ(required_manifest, manifest_cnt); + } +}; + +TEST_F(ObsoleteFilesTest, RaceForObsoleteFileDeletion) { + SyncPoint::GetInstance()->LoadDependency({ + {"DBImpl::BackgroundCallCompaction:FoundObsoleteFiles", + "ObsoleteFilesTest::RaceForObsoleteFileDeletion:1"}, + {"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles", + "ObsoleteFilesTest::RaceForObsoleteFileDeletion:2"}, + }); + SyncPoint::GetInstance()->SetCallBack( + "DBImpl::DeleteObsoleteFileImpl:AfterDeletion", [&](void* arg) { + Status* p_status = reinterpret_cast(arg); + ASSERT_OK(*p_status); + }); + SyncPoint::GetInstance()->SetCallBack( + "DBImpl::CloseHelper:PendingPurgeFinished", [&](void* arg) { + std::vector* files_grabbed_for_purge_ptr = + reinterpret_cast*>(arg); + ASSERT_TRUE(files_grabbed_for_purge_ptr->empty()); + }); + SyncPoint::GetInstance()->EnableProcessing(); + + createLevel0Files(2, 50000); + CheckFileTypeCounts(options_.wal_dir, 1, 0, 0); + + DBImpl* dbi = reinterpret_cast(db_); + port::Thread user_thread([&]() { + JobContext jobCxt(0); + TEST_SYNC_POINT("ObsoleteFilesTest::RaceForObsoleteFileDeletion:1"); + dbi->TEST_LockMutex(); + dbi->FindObsoleteFiles(&jobCxt, + true /* force=true */, false /* no_full_scan=false */); + dbi->TEST_UnlockMutex(); + TEST_SYNC_POINT("ObsoleteFilesTest::RaceForObsoleteFileDeletion:2"); + dbi->PurgeObsoleteFiles(jobCxt); + jobCxt.Clean(); + }); + + user_thread.join(); + + CloseDB(); + SyncPoint::GetInstance()->DisableProcessing(); +} + +TEST_F(ObsoleteFilesTest, DeleteObsoleteOptionsFile) { + std::vector optsfiles_nums; + std::vector optsfiles_keep; + SyncPoint::GetInstance()->SetCallBack( + "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:1", [&](void* arg) { + optsfiles_nums.push_back(*reinterpret_cast(arg)); + }); + SyncPoint::GetInstance()->SetCallBack( + "DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:2", [&](void* arg) { + optsfiles_keep.push_back(*reinterpret_cast(arg)); + }); + SyncPoint::GetInstance()->EnableProcessing(); + + createLevel0Files(2, 50000); + CheckFileTypeCounts(options_.wal_dir, 1, 0, 0); + + DBImpl* dbi = static_cast(db_); + ASSERT_OK(dbi->DisableFileDeletions()); + for (int i = 0; i != 4; ++i) { + if (i % 2) { + ASSERT_OK(dbi->SetOptions(dbi->DefaultColumnFamily(), + {{"paranoid_file_checks", "false"}})); + } else { + ASSERT_OK(dbi->SetOptions(dbi->DefaultColumnFamily(), + {{"paranoid_file_checks", "true"}})); + } + } + ASSERT_OK(dbi->EnableFileDeletions(true /* force */)); + ASSERT_EQ(optsfiles_nums.size(), optsfiles_keep.size()); + + CloseDB(); + + std::vector files; + int opts_file_count = 0; + ASSERT_OK(env_->GetChildren(dbname_, &files)); + for (const auto& file : files) { + uint64_t file_num; + Slice dummy_info_log_name_prefix; + FileType type; + WalFileType log_type; + if (ParseFileName(file, &file_num, dummy_info_log_name_prefix, &type, + &log_type) && + type == kOptionsFile) { + opts_file_count++; + } + } + ASSERT_EQ(2, opts_file_count); + SyncPoint::GetInstance()->DisableProcessing(); +} + +} //namespace rocksdb + +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 DBImpl::DeleteFile is not supported in ROCKSDB_LITE\n"); + return 0; +} + +#endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/db/options_file_test.cc b/thirdparty/rocksdb/db/options_file_test.cc index fc62840eb4..0a9a34ff0b 100644 --- a/thirdparty/rocksdb/db/options_file_test.cc +++ b/thirdparty/rocksdb/db/options_file_test.cc @@ -15,7 +15,7 @@ namespace rocksdb { class OptionsFileTest : public testing::Test { public: - OptionsFileTest() : dbname_(test::TmpDir() + "/options_file_test") {} + OptionsFileTest() : dbname_(test::PerThreadDBPath("options_file_test")) {} std::string dbname_; }; @@ -112,7 +112,7 @@ int main(int argc, char** argv) { #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { printf("Skipped as Options file is not supported in RocksDBLite.\n"); return 0; } diff --git a/thirdparty/rocksdb/db/perf_context_test.cc b/thirdparty/rocksdb/db/perf_context_test.cc index d06843a830..b7efec182a 100644 --- a/thirdparty/rocksdb/db/perf_context_test.cc +++ b/thirdparty/rocksdb/db/perf_context_test.cc @@ -10,6 +10,7 @@ #include "monitoring/histogram.h" #include "monitoring/instrumented_mutex.h" +#include "monitoring/perf_context_imp.h" #include "monitoring/thread_status_util.h" #include "port/port.h" #include "rocksdb/db.h" @@ -30,7 +31,7 @@ int FLAGS_min_write_buffer_number_to_merge = 7; bool FLAGS_verbose = false; // Path to the database on file system -const std::string kDbName = rocksdb::test::TmpDir() + "/perf_context_test"; +const std::string kDbName = rocksdb::test::PerThreadDBPath("perf_context_test"); namespace rocksdb { @@ -227,6 +228,9 @@ void ProfileQueries(bool enabled_time = false) { HistogramImpl hist_write_pre_post; HistogramImpl hist_write_wal_time; HistogramImpl hist_write_memtable_time; + HistogramImpl hist_write_delay_time; + HistogramImpl hist_write_thread_wait_nanos; + HistogramImpl hist_write_scheduling_time; uint64_t total_db_mutex_nanos = 0; @@ -270,9 +274,15 @@ void ProfileQueries(bool enabled_time = false) { ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT, 0U); #endif } - hist_write_pre_post.Add(get_perf_context()->write_pre_and_post_process_time); + hist_write_pre_post.Add( + get_perf_context()->write_pre_and_post_process_time); hist_write_wal_time.Add(get_perf_context()->write_wal_time); hist_write_memtable_time.Add(get_perf_context()->write_memtable_time); + hist_write_delay_time.Add(get_perf_context()->write_delay_time); + hist_write_thread_wait_nanos.Add( + get_perf_context()->write_thread_wait_nanos); + hist_write_scheduling_time.Add( + get_perf_context()->write_scheduling_flushes_compactions_time); hist_put.Add(get_perf_context()->user_key_comparison_count); total_db_mutex_nanos += get_perf_context()->db_mutex_lock_nanos; } @@ -320,6 +330,11 @@ void ProfileQueries(bool enabled_time = false) { << hist_write_wal_time.ToString() << "\n" << " Writing Mem Table time: \n" << hist_write_memtable_time.ToString() << "\n" + << " Write Delay: \n" << hist_write_delay_time.ToString() << "\n" + << " Waiting for Batch time: \n" + << hist_write_thread_wait_nanos.ToString() << "\n" + << " Scheduling Flushes and Compactions Time: \n" + << hist_write_scheduling_time.ToString() << "\n" << " Total DB mutex nanos: \n" << total_db_mutex_nanos << "\n"; std::cout << "Get(): Time to get snapshot: \n" @@ -359,6 +374,14 @@ void ProfileQueries(bool enabled_time = false) { ASSERT_GT(hist_mget_files.Average(), 0); ASSERT_GT(hist_mget_post_process.Average(), 0); ASSERT_GT(hist_mget_num_memtable_checked.Average(), 0); + + EXPECT_GT(hist_write_pre_post.Average(), 0); + EXPECT_GT(hist_write_wal_time.Average(), 0); + EXPECT_GT(hist_write_memtable_time.Average(), 0); + EXPECT_EQ(hist_write_delay_time.Average(), 0); + EXPECT_EQ(hist_write_thread_wait_nanos.Average(), 0); + EXPECT_GT(hist_write_scheduling_time.Average(), 0); + #ifndef NDEBUG ASSERT_GT(total_db_mutex_nanos, 2000U); #endif @@ -447,7 +470,7 @@ void ProfileQueries(bool enabled_time = false) { ASSERT_GT(hist_num_memtable_checked.Average(), 0); // In read-only mode Get(), no super version operation is needed ASSERT_EQ(hist_get_post_process.Average(), 0); - ASSERT_EQ(hist_get_snapshot.Average(), 0); + ASSERT_GT(hist_get_snapshot.Average(), 0); ASSERT_GT(hist_mget.Average(), 0); ASSERT_GT(hist_mget_snapshot.Average(), 0); @@ -557,18 +580,18 @@ TEST_F(PerfContextTest, SeekKeyComparison) { TEST_F(PerfContextTest, DBMutexLockCounter) { int stats_code[] = {0, static_cast(DB_MUTEX_WAIT_MICROS)}; - for (PerfLevel perf_level : + for (PerfLevel perf_level_test : {PerfLevel::kEnableTimeExceptForMutex, PerfLevel::kEnableTime}) { for (int c = 0; c < 2; ++c) { InstrumentedMutex mutex(nullptr, Env::Default(), stats_code[c]); mutex.Lock(); rocksdb::port::Thread child_thread([&] { - SetPerfLevel(perf_level); + SetPerfLevel(perf_level_test); get_perf_context()->Reset(); ASSERT_EQ(get_perf_context()->db_mutex_lock_nanos, 0); mutex.Lock(); mutex.Unlock(); - if (perf_level == PerfLevel::kEnableTimeExceptForMutex || + if (perf_level_test == PerfLevel::kEnableTimeExceptForMutex || stats_code[c] != DB_MUTEX_WAIT_MICROS) { ASSERT_EQ(get_perf_context()->db_mutex_lock_nanos, 0); } else { @@ -664,7 +687,258 @@ TEST_F(PerfContextTest, MergeOperatorTime) { delete db; } + +TEST_F(PerfContextTest, CopyAndMove) { + // Assignment operator + { + get_perf_context()->Reset(); + get_perf_context()->EnablePerLevelPerfContext(); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); + ASSERT_EQ( + 1, + (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); + PerfContext perf_context_assign; + perf_context_assign = *get_perf_context(); + ASSERT_EQ( + 1, + (*(perf_context_assign.level_to_perf_context))[5].bloom_filter_useful); + get_perf_context()->ClearPerLevelPerfContext(); + get_perf_context()->Reset(); + ASSERT_EQ( + 1, + (*(perf_context_assign.level_to_perf_context))[5].bloom_filter_useful); + perf_context_assign.ClearPerLevelPerfContext(); + perf_context_assign.Reset(); + } + // Copy constructor + { + get_perf_context()->Reset(); + get_perf_context()->EnablePerLevelPerfContext(); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); + ASSERT_EQ( + 1, + (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); + PerfContext perf_context_copy(*get_perf_context()); + ASSERT_EQ( + 1, (*(perf_context_copy.level_to_perf_context))[5].bloom_filter_useful); + get_perf_context()->ClearPerLevelPerfContext(); + get_perf_context()->Reset(); + ASSERT_EQ( + 1, (*(perf_context_copy.level_to_perf_context))[5].bloom_filter_useful); + perf_context_copy.ClearPerLevelPerfContext(); + perf_context_copy.Reset(); + } + // Move constructor + { + get_perf_context()->Reset(); + get_perf_context()->EnablePerLevelPerfContext(); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); + ASSERT_EQ( + 1, + (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); + PerfContext perf_context_move = std::move(*get_perf_context()); + ASSERT_EQ( + 1, (*(perf_context_move.level_to_perf_context))[5].bloom_filter_useful); + get_perf_context()->ClearPerLevelPerfContext(); + get_perf_context()->Reset(); + ASSERT_EQ( + 1, (*(perf_context_move.level_to_perf_context))[5].bloom_filter_useful); + perf_context_move.ClearPerLevelPerfContext(); + perf_context_move.Reset(); + } +} + +TEST_F(PerfContextTest, PerfContextDisableEnable) { + get_perf_context()->Reset(); + get_perf_context()->EnablePerLevelPerfContext(); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_positive, 1, 0); + get_perf_context()->DisablePerLevelPerfContext(); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); + get_perf_context()->EnablePerLevelPerfContext(); + PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 1, 0); + get_perf_context()->DisablePerLevelPerfContext(); + PerfContext perf_context_copy(*get_perf_context()); + ASSERT_EQ(1, (*(perf_context_copy.level_to_perf_context))[0] + .bloom_filter_full_positive); + // this was set when per level perf context is disabled, should not be copied + ASSERT_NE( + 1, (*(perf_context_copy.level_to_perf_context))[5].bloom_filter_useful); + ASSERT_EQ( + 1, (*(perf_context_copy.level_to_perf_context))[0].block_cache_hit_count); + perf_context_copy.ClearPerLevelPerfContext(); + perf_context_copy.Reset(); + get_perf_context()->ClearPerLevelPerfContext(); + get_perf_context()->Reset(); +} + +TEST_F(PerfContextTest, PerfContextByLevelGetSet) { + get_perf_context()->Reset(); + get_perf_context()->EnablePerLevelPerfContext(); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_positive, 1, 0); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 5); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 7); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, 7); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_true_positive, 1, 2); + PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 1, 0); + PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 5, 2); + PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 2, 3); + PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 4, 1); + ASSERT_EQ( + 0, (*(get_perf_context()->level_to_perf_context))[0].bloom_filter_useful); + ASSERT_EQ( + 1, (*(get_perf_context()->level_to_perf_context))[5].bloom_filter_useful); + ASSERT_EQ( + 2, (*(get_perf_context()->level_to_perf_context))[7].bloom_filter_useful); + ASSERT_EQ(1, (*(get_perf_context()->level_to_perf_context))[0] + .bloom_filter_full_positive); + ASSERT_EQ(1, (*(get_perf_context()->level_to_perf_context))[2] + .bloom_filter_full_true_positive); + ASSERT_EQ(1, (*(get_perf_context()->level_to_perf_context))[0] + .block_cache_hit_count); + ASSERT_EQ(5, (*(get_perf_context()->level_to_perf_context))[2] + .block_cache_hit_count); + ASSERT_EQ(2, (*(get_perf_context()->level_to_perf_context))[3] + .block_cache_miss_count); + ASSERT_EQ(4, (*(get_perf_context()->level_to_perf_context))[1] + .block_cache_miss_count); + std::string zero_excluded = get_perf_context()->ToString(true); + ASSERT_NE(std::string::npos, + zero_excluded.find("bloom_filter_useful = 1@level5, 2@level7")); + ASSERT_NE(std::string::npos, + zero_excluded.find("bloom_filter_full_positive = 1@level0")); + ASSERT_NE(std::string::npos, + zero_excluded.find("bloom_filter_full_true_positive = 1@level2")); + ASSERT_NE(std::string::npos, + zero_excluded.find("block_cache_hit_count = 1@level0, 5@level2")); + ASSERT_NE(std::string::npos, + zero_excluded.find("block_cache_miss_count = 4@level1, 2@level3")); +} + +TEST_F(PerfContextTest, CPUTimer) { + DestroyDB(kDbName, Options()); + auto db = OpenDb(); + WriteOptions write_options; + ReadOptions read_options; + SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); + + std::string max_str = "0"; + for (int i = 0; i < FLAGS_total_keys; ++i) { + std::string i_str = ToString(i); + std::string key = "k" + i_str; + std::string value = "v" + i_str; + max_str = max_str > i_str ? max_str : i_str; + + db->Put(write_options, key, value); + } + std::string last_key = "k" + max_str; + std::string last_value = "v" + max_str; + + { + // Get + get_perf_context()->Reset(); + std::string value; + ASSERT_OK(db->Get(read_options, "k0", &value)); + ASSERT_EQ(value, "v0"); + + if (FLAGS_verbose) { + std::cout << "Get CPU time nanos: " << get_perf_context()->get_cpu_nanos + << "ns\n"; + } + + // Iter + std::unique_ptr iter(db->NewIterator(read_options)); + + // Seek + get_perf_context()->Reset(); + iter->Seek(last_key); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(last_value, iter->value().ToString()); + + if (FLAGS_verbose) { + std::cout << "Iter Seek CPU time nanos: " + << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; + } + + // SeekForPrev + get_perf_context()->Reset(); + iter->SeekForPrev(last_key); + ASSERT_TRUE(iter->Valid()); + + if (FLAGS_verbose) { + std::cout << "Iter SeekForPrev CPU time nanos: " + << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; + } + + // SeekToLast + get_perf_context()->Reset(); + iter->SeekToLast(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(last_value, iter->value().ToString()); + + if (FLAGS_verbose) { + std::cout << "Iter SeekToLast CPU time nanos: " + << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; + } + + // SeekToFirst + get_perf_context()->Reset(); + iter->SeekToFirst(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + + if (FLAGS_verbose) { + std::cout << "Iter SeekToFirst CPU time nanos: " + << get_perf_context()->iter_seek_cpu_nanos << "ns\n"; + } + + // Next + get_perf_context()->Reset(); + iter->Next(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v1", iter->value().ToString()); + + if (FLAGS_verbose) { + std::cout << "Iter Next CPU time nanos: " + << get_perf_context()->iter_next_cpu_nanos << "ns\n"; + } + + // Prev + get_perf_context()->Reset(); + iter->Prev(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v0", iter->value().ToString()); + + if (FLAGS_verbose) { + std::cout << "Iter Prev CPU time nanos: " + << get_perf_context()->iter_prev_cpu_nanos << "ns\n"; + } + + // monotonically increasing + get_perf_context()->Reset(); + auto count = get_perf_context()->iter_seek_cpu_nanos; + for (int i = 0; i < FLAGS_total_keys; ++i) { + iter->Seek("k" + ToString(i)); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ("v" + ToString(i), iter->value().ToString()); + auto next_count = get_perf_context()->iter_seek_cpu_nanos; + ASSERT_GT(next_count, count); + count = next_count; + } + + // iterator creation/destruction; multiple iterators + { + std::unique_ptr iter2(db->NewIterator(read_options)); + ASSERT_EQ(count, get_perf_context()->iter_seek_cpu_nanos); + iter2->Seek(last_key); + ASSERT_TRUE(iter2->Valid()); + ASSERT_EQ(last_value, iter2->value().ToString()); + ASSERT_GT(get_perf_context()->iter_seek_cpu_nanos, count); + count = get_perf_context()->iter_seek_cpu_nanos; + } + ASSERT_EQ(count, get_perf_context()->iter_seek_cpu_nanos); + } } +} // namespace rocksdb int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/thirdparty/rocksdb/db/plain_table_db_test.cc b/thirdparty/rocksdb/db/plain_table_db_test.cc index 0b60332e53..2dd0cff0b4 100644 --- a/thirdparty/rocksdb/db/plain_table_db_test.cc +++ b/thirdparty/rocksdb/db/plain_table_db_test.cc @@ -50,10 +50,11 @@ TEST_F(PlainTableKeyDecoderTest, ReadNonMmap) { test::StringSource* string_source = new test::StringSource(contents, 0, false); - unique_ptr file_reader( + std::unique_ptr file_reader( test::GetRandomAccessFileReader(string_source)); - unique_ptr file_info(new PlainTableReaderFileInfo( - std::move(file_reader), EnvOptions(), kLength)); + std::unique_ptr file_info( + new PlainTableReaderFileInfo(std::move(file_reader), EnvOptions(), + kLength)); { PlainTableFileReader reader(file_info.get()); @@ -108,14 +109,14 @@ class PlainTableDBTest : public testing::Test, public: PlainTableDBTest() : env_(Env::Default()) {} - ~PlainTableDBTest() { + ~PlainTableDBTest() override { delete db_; EXPECT_OK(DestroyDB(dbname_, Options())); } void SetUp() override { mmap_mode_ = GetParam(); - dbname_ = test::TmpDir() + "/plain_table_db_test"; + dbname_ = test::PerThreadDBPath("plain_table_db_test"); EXPECT_OK(DestroyDB(dbname_, Options())); db_ = nullptr; Reopen(); @@ -157,6 +158,8 @@ class PlainTableDBTest : public testing::Test, db_ = nullptr; } + bool mmap_mode() const { return mmap_mode_; } + void DestroyAndReopen(Options* options = nullptr) { //Destroy using last options Destroy(&last_options_); @@ -173,6 +176,12 @@ class PlainTableDBTest : public testing::Test, return DB::Open(*options, dbname_, db); } + Status ReopenForReadOnly(Options* options) { + delete db_; + db_ = nullptr; + return DB::OpenForReadOnly(*options, dbname_, &db_); + } + Status TryReopen(Options* options = nullptr) { delete db_; db_ = nullptr; @@ -260,13 +269,15 @@ class TestPlainTableReader : public PlainTableReader { int bloom_bits_per_key, double hash_table_ratio, size_t index_sparseness, const TableProperties* table_properties, - unique_ptr&& file, + std::unique_ptr&& file, const ImmutableCFOptions& ioptions, + const SliceTransform* prefix_extractor, bool* expect_bloom_not_match, bool store_index_in_file, uint32_t column_family_id, const std::string& column_family_name) : PlainTableReader(ioptions, std::move(file), env_options, icomparator, - encoding_type, file_size, table_properties), + encoding_type, file_size, table_properties, + prefix_extractor), expect_bloom_not_match_(expect_bloom_not_match) { Status s = MmapDataIfNeeded(); EXPECT_TRUE(s.ok()); @@ -292,10 +303,10 @@ class TestPlainTableReader : public PlainTableReader { } } - virtual ~TestPlainTableReader() {} + ~TestPlainTableReader() override {} private: - virtual bool MatchBloom(uint32_t hash) const override { + bool MatchBloom(uint32_t hash) const override { bool ret = PlainTableReader::MatchBloom(hash); if (*expect_bloom_not_match_) { EXPECT_TRUE(!ret); @@ -325,27 +336,29 @@ class TestPlainTableFactory : public PlainTableFactory { Status NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table, - bool prefetch_index_and_filter_in_cache) const override { + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table, + bool /*prefetch_index_and_filter_in_cache*/) const override { TableProperties* props = nullptr; auto s = ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber, - table_reader_options.ioptions, &props); + table_reader_options.ioptions, &props, + true /* compression_type_missing */); EXPECT_TRUE(s.ok()); if (store_index_in_file_) { BlockHandle bloom_block_handle; s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber, table_reader_options.ioptions, - BloomBlockBuilder::kBloomBlock, &bloom_block_handle); + BloomBlockBuilder::kBloomBlock, &bloom_block_handle, + /* compression_type_missing */ true); EXPECT_TRUE(s.ok()); BlockHandle index_block_handle; s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber, table_reader_options.ioptions, PlainTableIndexBuilder::kPlainTableIndexBlock, - &index_block_handle); + &index_block_handle, /* compression_type_missing */ true); EXPECT_TRUE(s.ok()); } @@ -360,7 +373,8 @@ class TestPlainTableFactory : public PlainTableFactory { table_reader_options.env_options, table_reader_options.internal_comparator, encoding_type, file_size, bloom_bits_per_key_, hash_table_ratio_, index_sparseness_, props, - std::move(file), table_reader_options.ioptions, expect_bloom_not_match_, + std::move(file), table_reader_options.ioptions, + table_reader_options.prefix_extractor, expect_bloom_not_match_, store_index_in_file_, column_family_id_, column_family_name_)); *table = std::move(new_reader); @@ -541,6 +555,50 @@ TEST_P(PlainTableDBTest, Flush2) { } } +TEST_P(PlainTableDBTest, Immortal) { + for (EncodingType encoding_type : {kPlain, kPrefix}) { + Options options = CurrentOptions(); + options.create_if_missing = true; + options.max_open_files = -1; + // Set only one bucket to force bucket conflict. + // Test index interval for the same prefix to be 1, 2 and 4 + PlainTableOptions plain_table_options; + plain_table_options.hash_table_ratio = 0.75; + plain_table_options.index_sparseness = 16; + plain_table_options.user_key_len = kPlainTableVariableLength; + plain_table_options.bloom_bits_per_key = 10; + plain_table_options.encoding_type = encoding_type; + options.table_factory.reset(NewPlainTableFactory(plain_table_options)); + + DestroyAndReopen(&options); + ASSERT_OK(Put("0000000000000bar", "b")); + ASSERT_OK(Put("1000000000000foo", "v1")); + dbfull()->TEST_FlushMemTable(); + + int copied = 0; + rocksdb::SyncPoint::GetInstance()->SetCallBack( + "GetContext::SaveValue::PinSelf", [&](void* /*arg*/) { copied++; }); + rocksdb::SyncPoint::GetInstance()->EnableProcessing(); + ASSERT_EQ("b", Get("0000000000000bar")); + ASSERT_EQ("v1", Get("1000000000000foo")); + ASSERT_EQ(2, copied); + copied = 0; + + Close(); + ASSERT_OK(ReopenForReadOnly(&options)); + + ASSERT_EQ("b", Get("0000000000000bar")); + ASSERT_EQ("v1", Get("1000000000000foo")); + ASSERT_EQ("NOT_FOUND", Get("1000000000000bar")); + if (mmap_mode()) { + ASSERT_EQ(0, copied); + } else { + ASSERT_EQ(2, copied); + } + rocksdb::SyncPoint::GetInstance()->DisableProcessing(); + } +} + TEST_P(PlainTableDBTest, Iterator) { for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024; huge_page_tlb_size += 2 * 1024 * 1024) { @@ -1170,7 +1228,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as plain table is not supported in ROCKSDB_LITE\n"); return 0; } diff --git a/thirdparty/rocksdb/db/pre_release_callback.h b/thirdparty/rocksdb/db/pre_release_callback.h new file mode 100644 index 0000000000..f91ef1b27a --- /dev/null +++ b/thirdparty/rocksdb/db/pre_release_callback.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#include "rocksdb/status.h" + +namespace rocksdb { + +class DB; + +class PreReleaseCallback { + public: + virtual ~PreReleaseCallback() {} + + // Will be called while on the write thread after the write to the WAL and + // before the write to memtable. This is useful if any operation needs to be + // done before the write gets visible to the readers, or if we want to reduce + // the overhead of locking by updating something sequentially while we are on + // the write thread. If the callback fails, this function returns a non-OK + // status, the sequence number will not be released, and same status will be + // propagated to all the writers in the write group. + // seq is the sequence number that is used for this write and will be + // released. + // is_mem_disabled is currently used for debugging purposes to assert that + // the callback is done from the right write queue. + // If non-zero, log_number indicates the WAL log to which we wrote. + virtual Status Callback(SequenceNumber seq, bool is_mem_disabled, + uint64_t log_number) = 0; +}; + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/prefix_test.cc b/thirdparty/rocksdb/db/prefix_test.cc index a4ed201dad..ac854cb3db 100644 --- a/thirdparty/rocksdb/db/prefix_test.cc +++ b/thirdparty/rocksdb/db/prefix_test.cc @@ -17,7 +17,6 @@ int main() { #include #include -#include #include "db/db_impl.h" #include "monitoring/histogram.h" #include "rocksdb/comparator.h" @@ -27,14 +26,15 @@ int main() { #include "rocksdb/perf_context.h" #include "rocksdb/slice_transform.h" #include "rocksdb/table.h" +#include "util/coding.h" +#include "util/gflags_compat.h" #include "util/random.h" #include "util/stop_watch.h" #include "util/string_util.h" #include "util/testharness.h" #include "utilities/merge_operators.h" -#include "util/coding.h" -using GFLAGS::ParseCommandLineFlags; +using GFLAGS_NAMESPACE::ParseCommandLineFlags; DEFINE_bool(trigger_deadlock, false, "issue delete in range scan to trigger PrefixHashMap deadlock"); @@ -53,7 +53,7 @@ DEFINE_int32(value_size, 40, ""); DEFINE_bool(enable_print, false, "Print options generated to console."); // Path to the database on file system -const std::string kDbName = rocksdb::test::TmpDir() + "/prefix_test"; +const std::string kDbName = rocksdb::test::PerThreadDBPath("prefix_test"); namespace rocksdb { @@ -83,7 +83,7 @@ class TestKeyComparator : public Comparator { // Compare needs to be aware of the possibility of a and/or b is // prefix only - virtual int Compare(const Slice& a, const Slice& b) const override { + int Compare(const Slice& a, const Slice& b) const override { const TestKey kkey_a = SliceToTestKey(a); const TestKey kkey_b = SliceToTestKey(b); const TestKey *key_a = &kkey_a; @@ -122,14 +122,12 @@ class TestKeyComparator : public Comparator { return Compare(TestKeyToSlice(sa, a), TestKeyToSlice(sb, b)) < 0; } - virtual const char* Name() const override { - return "TestKeyComparator"; - } + const char* Name() const override { return "TestKeyComparator"; } - virtual void FindShortestSeparator(std::string* start, - const Slice& limit) const override {} + void FindShortestSeparator(std::string* /*start*/, + const Slice& /*limit*/) const override {} - virtual void FindShortSuccessor(std::string* key) const override {} + void FindShortSuccessor(std::string* /*key*/) const override {} }; namespace { @@ -195,23 +193,23 @@ class SamePrefixTransform : public SliceTransform { explicit SamePrefixTransform(const Slice& prefix) : prefix_(prefix), name_("rocksdb.SamePrefix." + prefix.ToString()) {} - virtual const char* Name() const override { return name_.c_str(); } + const char* Name() const override { return name_.c_str(); } - virtual Slice Transform(const Slice& src) const override { + Slice Transform(const Slice& src) const override { assert(InDomain(src)); return prefix_; } - virtual bool InDomain(const Slice& src) const override { + bool InDomain(const Slice& src) const override { if (src.size() >= prefix_.size()) { return Slice(src.data(), prefix_.size()) == prefix_; } return false; } - virtual bool InRange(const Slice& dst) const override { - return dst == prefix_; - } + bool InRange(const Slice& dst) const override { return dst == prefix_; } + + bool FullLengthEnabled(size_t* /*len*/) const override { return false; } }; } // namespace @@ -279,9 +277,8 @@ class PrefixTest : public testing::Test { PrefixTest() : option_config_(kBegin) { options.comparator = new TestKeyComparator(); } - ~PrefixTest() { - delete options.comparator; - } + ~PrefixTest() override { delete options.comparator; } + protected: enum OptionConfig { kBegin, @@ -879,8 +876,6 @@ TEST_F(PrefixTest, PrefixSeekModePrev3) { int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ParseCommandLineFlags(&argc, &argv, true); - std::cout << kDbName << "\n"; - return RUN_ALL_TESTS(); } @@ -889,7 +884,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as HashSkipList and HashLinkList are not supported in " "ROCKSDB_LITE\n"); diff --git a/thirdparty/rocksdb/db/range_del_aggregator.cc b/thirdparty/rocksdb/db/range_del_aggregator.cc index c83f5a88cd..68216fc92f 100644 --- a/thirdparty/rocksdb/db/range_del_aggregator.cc +++ b/thirdparty/rocksdb/db/range_del_aggregator.cc @@ -1,520 +1,484 @@ -// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. +// Copyright (c) 2018-present, Facebook, 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). #include "db/range_del_aggregator.h" -#include +#include "db/compaction_iteration_stats.h" +#include "db/dbformat.h" +#include "db/pinned_iterators_manager.h" +#include "db/range_del_aggregator.h" +#include "db/range_tombstone_fragmenter.h" +#include "db/version_edit.h" +#include "include/rocksdb/comparator.h" +#include "include/rocksdb/types.h" +#include "table/internal_iterator.h" +#include "table/scoped_arena_iterator.h" +#include "table/table_builder.h" +#include "util/heap.h" +#include "util/kv_map.h" +#include "util/vector_iterator.h" namespace rocksdb { -RangeDelAggregator::RangeDelAggregator( - const InternalKeyComparator& icmp, - const std::vector& snapshots, - bool collapse_deletions /* = true */) - : upper_bound_(kMaxSequenceNumber), +TruncatedRangeDelIterator::TruncatedRangeDelIterator( + std::unique_ptr iter, + const InternalKeyComparator* icmp, const InternalKey* smallest, + const InternalKey* largest) + : iter_(std::move(iter)), icmp_(icmp), - collapse_deletions_(collapse_deletions) { - InitRep(snapshots); + smallest_ikey_(smallest), + largest_ikey_(largest) { + if (smallest != nullptr) { + pinned_bounds_.emplace_back(); + auto& parsed_smallest = pinned_bounds_.back(); + if (!ParseInternalKey(smallest->Encode(), &parsed_smallest)) { + assert(false); + } + smallest_ = &parsed_smallest; + } + if (largest != nullptr) { + pinned_bounds_.emplace_back(); + auto& parsed_largest = pinned_bounds_.back(); + if (!ParseInternalKey(largest->Encode(), &parsed_largest)) { + assert(false); + } + if (parsed_largest.type == kTypeRangeDeletion && + parsed_largest.sequence == kMaxSequenceNumber) { + // The file boundary has been artificially extended by a range tombstone. + // We do not need to adjust largest to properly truncate range + // tombstones that extend past the boundary. + } else if (parsed_largest.sequence == 0) { + // The largest key in the sstable has a sequence number of 0. Since we + // guarantee that no internal keys with the same user key and sequence + // number can exist in a DB, we know that the largest key in this sstable + // cannot exist as the smallest key in the next sstable. This further + // implies that no range tombstone in this sstable covers largest; + // otherwise, the file boundary would have been artificially extended. + // + // Therefore, we will never truncate a range tombstone at largest, so we + // can leave it unchanged. + } else { + // The same user key may straddle two sstable boundaries. To ensure that + // the truncated end key can cover the largest key in this sstable, reduce + // its sequence number by 1. + parsed_largest.sequence -= 1; + } + largest_ = &parsed_largest; + } } -RangeDelAggregator::RangeDelAggregator(const InternalKeyComparator& icmp, - SequenceNumber snapshot, - bool collapse_deletions /* = false */) - : upper_bound_(snapshot), - icmp_(icmp), - collapse_deletions_(collapse_deletions) {} - -void RangeDelAggregator::InitRep(const std::vector& snapshots) { - assert(rep_ == nullptr); - rep_.reset(new Rep()); - for (auto snapshot : snapshots) { - rep_->stripe_map_.emplace( - snapshot, - PositionalTombstoneMap(TombstoneMap( - stl_wrappers::LessOfComparator(icmp_.user_comparator())))); - } - // Data newer than any snapshot falls in this catch-all stripe - rep_->stripe_map_.emplace( - kMaxSequenceNumber, - PositionalTombstoneMap(TombstoneMap( - stl_wrappers::LessOfComparator(icmp_.user_comparator())))); - rep_->pinned_iters_mgr_.StartPinning(); +bool TruncatedRangeDelIterator::Valid() const { + return iter_->Valid() && + (smallest_ == nullptr || + icmp_->Compare(*smallest_, iter_->parsed_end_key()) < 0) && + (largest_ == nullptr || + icmp_->Compare(iter_->parsed_start_key(), *largest_) < 0); } -bool RangeDelAggregator::ShouldDelete( - const Slice& internal_key, RangeDelAggregator::RangePositioningMode mode) { - if (rep_ == nullptr) { - return false; +void TruncatedRangeDelIterator::Next() { iter_->TopNext(); } + +void TruncatedRangeDelIterator::Prev() { iter_->TopPrev(); } + +void TruncatedRangeDelIterator::InternalNext() { iter_->Next(); } + +// NOTE: target is a user key +void TruncatedRangeDelIterator::Seek(const Slice& target) { + if (largest_ != nullptr && + icmp_->Compare(*largest_, ParsedInternalKey(target, kMaxSequenceNumber, + kTypeRangeDeletion)) <= 0) { + iter_->Invalidate(); + return; } - ParsedInternalKey parsed; - if (!ParseInternalKey(internal_key, &parsed)) { - assert(false); + if (smallest_ != nullptr && + icmp_->user_comparator()->Compare(target, smallest_->user_key) < 0) { + iter_->Seek(smallest_->user_key); + return; } - return ShouldDelete(parsed, mode); + iter_->Seek(target); } -bool RangeDelAggregator::ShouldDelete( - const ParsedInternalKey& parsed, - RangeDelAggregator::RangePositioningMode mode) { - assert(IsValueType(parsed.type)); - if (rep_ == nullptr) { - return false; - } - auto& positional_tombstone_map = GetPositionalTombstoneMap(parsed.sequence); - const auto& tombstone_map = positional_tombstone_map.raw_map; - if (tombstone_map.empty()) { - return false; +// NOTE: target is a user key +void TruncatedRangeDelIterator::SeekForPrev(const Slice& target) { + if (smallest_ != nullptr && + icmp_->Compare(ParsedInternalKey(target, 0, kTypeRangeDeletion), + *smallest_) < 0) { + iter_->Invalidate(); + return; } - auto& tombstone_map_iter = positional_tombstone_map.iter; - if (tombstone_map_iter == tombstone_map.end() && - (mode == kForwardTraversal || mode == kBackwardTraversal)) { - // invalid (e.g., if AddTombstones() changed the deletions), so need to - // reseek - mode = kBinarySearch; + if (largest_ != nullptr && + icmp_->user_comparator()->Compare(largest_->user_key, target) < 0) { + iter_->SeekForPrev(largest_->user_key); + return; } - switch (mode) { - case kFullScan: - assert(!collapse_deletions_); - // The maintained state (PositionalTombstoneMap::iter) isn't useful when - // we linear scan from the beginning each time, but we maintain it anyways - // for consistency. - tombstone_map_iter = tombstone_map.begin(); - while (tombstone_map_iter != tombstone_map.end()) { - const auto& tombstone = tombstone_map_iter->second; - if (icmp_.user_comparator()->Compare(parsed.user_key, - tombstone.start_key_) < 0) { - break; - } - if (parsed.sequence < tombstone.seq_ && - icmp_.user_comparator()->Compare(parsed.user_key, - tombstone.end_key_) < 0) { - return true; - } - ++tombstone_map_iter; - } - return false; - case kForwardTraversal: - assert(collapse_deletions_ && tombstone_map_iter != tombstone_map.end()); - if (tombstone_map_iter == tombstone_map.begin() && - icmp_.user_comparator()->Compare(parsed.user_key, - tombstone_map_iter->first) < 0) { - // before start of deletion intervals - return false; - } - while (std::next(tombstone_map_iter) != tombstone_map.end() && - icmp_.user_comparator()->Compare( - std::next(tombstone_map_iter)->first, parsed.user_key) <= 0) { - ++tombstone_map_iter; - } - break; - case kBackwardTraversal: - assert(collapse_deletions_ && tombstone_map_iter != tombstone_map.end()); - while (tombstone_map_iter != tombstone_map.begin() && - icmp_.user_comparator()->Compare(parsed.user_key, - tombstone_map_iter->first) < 0) { - --tombstone_map_iter; - } - if (tombstone_map_iter == tombstone_map.begin() && - icmp_.user_comparator()->Compare(parsed.user_key, - tombstone_map_iter->first) < 0) { - // before start of deletion intervals - return false; - } - break; - case kBinarySearch: - assert(collapse_deletions_); - tombstone_map_iter = - tombstone_map.upper_bound(parsed.user_key); - if (tombstone_map_iter == tombstone_map.begin()) { - // before start of deletion intervals - return false; - } - --tombstone_map_iter; - break; + iter_->SeekForPrev(target); +} + +void TruncatedRangeDelIterator::SeekToFirst() { + if (smallest_ != nullptr) { + iter_->Seek(smallest_->user_key); + return; } - assert(mode != kFullScan); - assert(tombstone_map_iter != tombstone_map.end() && - icmp_.user_comparator()->Compare(tombstone_map_iter->first, - parsed.user_key) <= 0); - assert(std::next(tombstone_map_iter) == tombstone_map.end() || - icmp_.user_comparator()->Compare( - parsed.user_key, std::next(tombstone_map_iter)->first) < 0); - return parsed.sequence < tombstone_map_iter->second.seq_; + iter_->SeekToTopFirst(); } -bool RangeDelAggregator::ShouldAddTombstones( - bool bottommost_level /* = false */) { - // TODO(andrewkr): can we just open a file and throw it away if it ends up - // empty after AddToBuilder()? This function doesn't take into subcompaction - // boundaries so isn't completely accurate. - if (rep_ == nullptr) { - return false; +void TruncatedRangeDelIterator::SeekToLast() { + if (largest_ != nullptr) { + iter_->SeekForPrev(largest_->user_key); + return; } - auto stripe_map_iter = rep_->stripe_map_.begin(); - assert(stripe_map_iter != rep_->stripe_map_.end()); - if (bottommost_level) { - // For the bottommost level, keys covered by tombstones in the first - // (oldest) stripe have been compacted away, so the tombstones are obsolete. - ++stripe_map_iter; + iter_->SeekToTopLast(); +} + +std::map> +TruncatedRangeDelIterator::SplitBySnapshot( + const std::vector& snapshots) { + using FragmentedIterPair = + std::pair>; + + auto split_untruncated_iters = iter_->SplitBySnapshot(snapshots); + std::map> + split_truncated_iters; + std::for_each( + split_untruncated_iters.begin(), split_untruncated_iters.end(), + [&](FragmentedIterPair& iter_pair) { + std::unique_ptr truncated_iter( + new TruncatedRangeDelIterator(std::move(iter_pair.second), icmp_, + smallest_ikey_, largest_ikey_)); + split_truncated_iters.emplace(iter_pair.first, + std::move(truncated_iter)); + }); + return split_truncated_iters; +} + +ForwardRangeDelIterator::ForwardRangeDelIterator( + const InternalKeyComparator* icmp) + : icmp_(icmp), + unused_idx_(0), + active_seqnums_(SeqMaxComparator()), + active_iters_(EndKeyMinComparator(icmp)), + inactive_iters_(StartKeyMinComparator(icmp)) {} + +bool ForwardRangeDelIterator::ShouldDelete(const ParsedInternalKey& parsed) { + // Move active iterators that end before parsed. + while (!active_iters_.empty() && + icmp_->Compare((*active_iters_.top())->end_key(), parsed) <= 0) { + TruncatedRangeDelIterator* iter = PopActiveIter(); + do { + iter->Next(); + } while (iter->Valid() && icmp_->Compare(iter->end_key(), parsed) <= 0); + PushIter(iter, parsed); + assert(active_iters_.size() == active_seqnums_.size()); } - while (stripe_map_iter != rep_->stripe_map_.end()) { - if (!stripe_map_iter->second.raw_map.empty()) { - return true; + + // Move inactive iterators that start before parsed. + while (!inactive_iters_.empty() && + icmp_->Compare(inactive_iters_.top()->start_key(), parsed) <= 0) { + TruncatedRangeDelIterator* iter = PopInactiveIter(); + while (iter->Valid() && icmp_->Compare(iter->end_key(), parsed) <= 0) { + iter->Next(); } - ++stripe_map_iter; + PushIter(iter, parsed); + assert(active_iters_.size() == active_seqnums_.size()); } - return false; + + return active_seqnums_.empty() + ? false + : (*active_seqnums_.begin())->seq() > parsed.sequence; } -Status RangeDelAggregator::AddTombstones( - std::unique_ptr input) { - if (input == nullptr) { - return Status::OK(); +void ForwardRangeDelIterator::Invalidate() { + unused_idx_ = 0; + active_iters_.clear(); + active_seqnums_.clear(); + inactive_iters_.clear(); +} + +ReverseRangeDelIterator::ReverseRangeDelIterator( + const InternalKeyComparator* icmp) + : icmp_(icmp), + unused_idx_(0), + active_seqnums_(SeqMaxComparator()), + active_iters_(StartKeyMaxComparator(icmp)), + inactive_iters_(EndKeyMaxComparator(icmp)) {} + +bool ReverseRangeDelIterator::ShouldDelete(const ParsedInternalKey& parsed) { + // Move active iterators that start after parsed. + while (!active_iters_.empty() && + icmp_->Compare(parsed, (*active_iters_.top())->start_key()) < 0) { + TruncatedRangeDelIterator* iter = PopActiveIter(); + do { + iter->Prev(); + } while (iter->Valid() && icmp_->Compare(parsed, iter->start_key()) < 0); + PushIter(iter, parsed); + assert(active_iters_.size() == active_seqnums_.size()); } - input->SeekToFirst(); - bool first_iter = true; - while (input->Valid()) { - if (first_iter) { - if (rep_ == nullptr) { - InitRep({upper_bound_}); - } else { - InvalidateTombstoneMapPositions(); - } - first_iter = false; - } - ParsedInternalKey parsed_key; - if (!ParseInternalKey(input->key(), &parsed_key)) { - return Status::Corruption("Unable to parse range tombstone InternalKey"); + + // Move inactive iterators that end after parsed. + while (!inactive_iters_.empty() && + icmp_->Compare(parsed, inactive_iters_.top()->end_key()) < 0) { + TruncatedRangeDelIterator* iter = PopInactiveIter(); + while (iter->Valid() && icmp_->Compare(parsed, iter->start_key()) < 0) { + iter->Prev(); } - RangeTombstone tombstone(parsed_key, input->value()); - AddTombstone(std::move(tombstone)); - input->Next(); + PushIter(iter, parsed); + assert(active_iters_.size() == active_seqnums_.size()); } - if (!first_iter) { - rep_->pinned_iters_mgr_.PinIterator(input.release(), false /* arena */); - } - return Status::OK(); + + return active_seqnums_.empty() + ? false + : (*active_seqnums_.begin())->seq() > parsed.sequence; } -void RangeDelAggregator::InvalidateTombstoneMapPositions() { - if (rep_ == nullptr) { - return; - } - for (auto stripe_map_iter = rep_->stripe_map_.begin(); - stripe_map_iter != rep_->stripe_map_.end(); ++stripe_map_iter) { - stripe_map_iter->second.iter = stripe_map_iter->second.raw_map.end(); - } +void ReverseRangeDelIterator::Invalidate() { + unused_idx_ = 0; + active_iters_.clear(); + active_seqnums_.clear(); + inactive_iters_.clear(); } -Status RangeDelAggregator::AddTombstone(RangeTombstone tombstone) { - auto& positional_tombstone_map = GetPositionalTombstoneMap(tombstone.seq_); - auto& tombstone_map = positional_tombstone_map.raw_map; - if (collapse_deletions_) { - // In collapsed mode, we only fill the seq_ field in the TombstoneMap's - // values. The end_key is unneeded because we assume the tombstone extends - // until the next tombstone starts. For gaps between real tombstones and - // for the last real tombstone, we denote end keys by inserting fake - // tombstones with sequence number zero. - std::vector new_range_dels{ - tombstone, RangeTombstone(tombstone.end_key_, Slice(), 0)}; - auto new_range_dels_iter = new_range_dels.begin(); - // Position at the first overlapping existing tombstone; if none exists, - // insert until we find an existing one overlapping a new point - const Slice* tombstone_map_begin = nullptr; - if (!tombstone_map.empty()) { - tombstone_map_begin = &tombstone_map.begin()->first; - } - auto last_range_dels_iter = new_range_dels_iter; - while (new_range_dels_iter != new_range_dels.end() && - (tombstone_map_begin == nullptr || - icmp_.user_comparator()->Compare(new_range_dels_iter->start_key_, - *tombstone_map_begin) < 0)) { - tombstone_map.emplace( - new_range_dels_iter->start_key_, - RangeTombstone(Slice(), Slice(), new_range_dels_iter->seq_)); - last_range_dels_iter = new_range_dels_iter; - ++new_range_dels_iter; - } - if (new_range_dels_iter == new_range_dels.end()) { - return Status::OK(); - } - // above loop advances one too far - new_range_dels_iter = last_range_dels_iter; - auto tombstone_map_iter = - tombstone_map.upper_bound(new_range_dels_iter->start_key_); - // if nothing overlapped we would've already inserted all the new points - // and returned early - assert(tombstone_map_iter != tombstone_map.begin()); - tombstone_map_iter--; - - // untermed_seq is non-kMaxSequenceNumber when we covered an existing point - // but haven't seen its corresponding endpoint. It's used for (1) deciding - // whether to forcibly insert the new interval's endpoint; and (2) possibly - // raising the seqnum for the to-be-inserted element (we insert the max - // seqnum between the next new interval and the unterminated interval). - SequenceNumber untermed_seq = kMaxSequenceNumber; - while (tombstone_map_iter != tombstone_map.end() && - new_range_dels_iter != new_range_dels.end()) { - const Slice *tombstone_map_iter_end = nullptr, - *new_range_dels_iter_end = nullptr; - if (tombstone_map_iter != tombstone_map.end()) { - auto next_tombstone_map_iter = std::next(tombstone_map_iter); - if (next_tombstone_map_iter != tombstone_map.end()) { - tombstone_map_iter_end = &next_tombstone_map_iter->first; - } - } - if (new_range_dels_iter != new_range_dels.end()) { - auto next_new_range_dels_iter = std::next(new_range_dels_iter); - if (next_new_range_dels_iter != new_range_dels.end()) { - new_range_dels_iter_end = &next_new_range_dels_iter->start_key_; - } - } +bool RangeDelAggregator::StripeRep::ShouldDelete( + const ParsedInternalKey& parsed, RangeDelPositioningMode mode) { + if (!InStripe(parsed.sequence) || IsEmpty()) { + return false; + } + switch (mode) { + case RangeDelPositioningMode::kForwardTraversal: + InvalidateReverseIter(); - // our positions in existing/new tombstone collections should always - // overlap. The non-overlapping cases are handled above and below this - // loop. - assert(new_range_dels_iter_end == nullptr || - icmp_.user_comparator()->Compare(tombstone_map_iter->first, - *new_range_dels_iter_end) < 0); - assert(tombstone_map_iter_end == nullptr || - icmp_.user_comparator()->Compare(new_range_dels_iter->start_key_, - *tombstone_map_iter_end) < 0); - - int new_to_old_start_cmp = icmp_.user_comparator()->Compare( - new_range_dels_iter->start_key_, tombstone_map_iter->first); - // nullptr end means extends infinitely rightwards, set new_to_old_end_cmp - // accordingly so we can use common code paths later. - int new_to_old_end_cmp; - if (new_range_dels_iter_end == nullptr && - tombstone_map_iter_end == nullptr) { - new_to_old_end_cmp = 0; - } else if (new_range_dels_iter_end == nullptr) { - new_to_old_end_cmp = 1; - } else if (tombstone_map_iter_end == nullptr) { - new_to_old_end_cmp = -1; - } else { - new_to_old_end_cmp = icmp_.user_comparator()->Compare( - *new_range_dels_iter_end, *tombstone_map_iter_end); + // Pick up previously unseen iterators. + for (auto it = std::next(iters_.begin(), forward_iter_.UnusedIdx()); + it != iters_.end(); ++it, forward_iter_.IncUnusedIdx()) { + auto& iter = *it; + forward_iter_.AddNewIter(iter.get(), parsed); } - if (new_to_old_start_cmp < 0) { - // the existing one's left endpoint comes after, so raise/delete it if - // it's covered. - if (tombstone_map_iter->second.seq_ < new_range_dels_iter->seq_) { - untermed_seq = tombstone_map_iter->second.seq_; - if (tombstone_map_iter != tombstone_map.begin() && - std::prev(tombstone_map_iter)->second.seq_ == - new_range_dels_iter->seq_) { - tombstone_map_iter = tombstone_map.erase(tombstone_map_iter); - --tombstone_map_iter; - } else { - tombstone_map_iter->second.seq_ = new_range_dels_iter->seq_; - } - } - } else if (new_to_old_start_cmp > 0) { - if (untermed_seq != kMaxSequenceNumber || - tombstone_map_iter->second.seq_ < new_range_dels_iter->seq_) { - auto seq = tombstone_map_iter->second.seq_; - // need to adjust this element if not intended to span beyond the new - // element (i.e., was_tombstone_map_iter_raised == true), or if it - // can be raised - tombstone_map_iter = tombstone_map.emplace( - new_range_dels_iter->start_key_, - RangeTombstone( - Slice(), Slice(), - std::max( - untermed_seq == kMaxSequenceNumber ? 0 : untermed_seq, - new_range_dels_iter->seq_))); - untermed_seq = seq; - } - } else { - // their left endpoints coincide, so raise the existing one if needed - if (tombstone_map_iter->second.seq_ < new_range_dels_iter->seq_) { - untermed_seq = tombstone_map_iter->second.seq_; - tombstone_map_iter->second.seq_ = new_range_dels_iter->seq_; - } + return forward_iter_.ShouldDelete(parsed); + case RangeDelPositioningMode::kBackwardTraversal: + InvalidateForwardIter(); + + // Pick up previously unseen iterators. + for (auto it = std::next(iters_.begin(), reverse_iter_.UnusedIdx()); + it != iters_.end(); ++it, reverse_iter_.IncUnusedIdx()) { + auto& iter = *it; + reverse_iter_.AddNewIter(iter.get(), parsed); } - // advance whichever one ends earlier, or both if their right endpoints - // coincide - if (new_to_old_end_cmp < 0) { - ++new_range_dels_iter; - } else if (new_to_old_end_cmp > 0) { - ++tombstone_map_iter; - untermed_seq = kMaxSequenceNumber; - } else { - ++new_range_dels_iter; - ++tombstone_map_iter; - untermed_seq = kMaxSequenceNumber; + return reverse_iter_.ShouldDelete(parsed); + default: + assert(false); + return false; + } +} + +bool RangeDelAggregator::StripeRep::IsRangeOverlapped(const Slice& start, + const Slice& end) { + Invalidate(); + + // Set the internal start/end keys so that: + // - if start_ikey has the same user key and sequence number as the + // current end key, start_ikey will be considered greater; and + // - if end_ikey has the same user key and sequence number as the current + // start key, end_ikey will be considered greater. + ParsedInternalKey start_ikey(start, kMaxSequenceNumber, + static_cast(0)); + ParsedInternalKey end_ikey(end, 0, static_cast(0)); + for (auto& iter : iters_) { + bool checked_candidate_tombstones = false; + for (iter->SeekForPrev(start); + iter->Valid() && icmp_->Compare(iter->start_key(), end_ikey) <= 0; + iter->Next()) { + checked_candidate_tombstones = true; + if (icmp_->Compare(start_ikey, iter->end_key()) < 0 && + icmp_->Compare(iter->start_key(), end_ikey) <= 0) { + return true; } } - while (new_range_dels_iter != new_range_dels.end()) { - tombstone_map.emplace( - new_range_dels_iter->start_key_, - RangeTombstone(Slice(), Slice(), new_range_dels_iter->seq_)); - ++new_range_dels_iter; + + if (!checked_candidate_tombstones) { + // Do an additional check for when the end of the range is the begin + // key of a tombstone, which we missed earlier since SeekForPrev'ing + // to the start was invalid. + iter->SeekForPrev(end); + if (iter->Valid() && icmp_->Compare(start_ikey, iter->end_key()) < 0 && + icmp_->Compare(iter->start_key(), end_ikey) <= 0) { + return true; + } } - } else { - auto start_key = tombstone.start_key_; - tombstone_map.emplace(start_key, std::move(tombstone)); } - return Status::OK(); + return false; } -RangeDelAggregator::PositionalTombstoneMap& -RangeDelAggregator::GetPositionalTombstoneMap(SequenceNumber seq) { - assert(rep_ != nullptr); - // The stripe includes seqnum for the snapshot above and excludes seqnum for - // the snapshot below. - StripeMap::iterator iter; - if (seq > 0) { - // upper_bound() checks strict inequality so need to subtract one - iter = rep_->stripe_map_.upper_bound(seq - 1); - } else { - iter = rep_->stripe_map_.begin(); +void ReadRangeDelAggregator::AddTombstones( + std::unique_ptr input_iter, + const InternalKey* smallest, const InternalKey* largest) { + if (input_iter == nullptr || input_iter->empty()) { + return; } - // catch-all stripe justifies this assertion in either of above cases - assert(iter != rep_->stripe_map_.end()); - return iter->second; + rep_.AddTombstones( + std::unique_ptr(new TruncatedRangeDelIterator( + std::move(input_iter), icmp_, smallest, largest))); +} + +bool ReadRangeDelAggregator::ShouldDelete(const ParsedInternalKey& parsed, + RangeDelPositioningMode mode) { + return rep_.ShouldDelete(parsed, mode); } -// TODO(andrewkr): We should implement an iterator over range tombstones in our -// map. It'd enable compaction to open tables on-demand, i.e., only once range -// tombstones are known to be available, without the code duplication we have -// in ShouldAddTombstones(). It'll also allow us to move the table-modifying -// code into more coherent places: CompactionJob and BuildTable(). -void RangeDelAggregator::AddToBuilder( - TableBuilder* builder, const Slice* lower_bound, const Slice* upper_bound, - FileMetaData* meta, - CompactionIterationStats* range_del_out_stats /* = nullptr */, - bool bottommost_level /* = false */) { - if (rep_ == nullptr) { +bool ReadRangeDelAggregator::IsRangeOverlapped(const Slice& start, + const Slice& end) { + InvalidateRangeDelMapPositions(); + return rep_.IsRangeOverlapped(start, end); +} + +void CompactionRangeDelAggregator::AddTombstones( + std::unique_ptr input_iter, + const InternalKey* smallest, const InternalKey* largest) { + if (input_iter == nullptr || input_iter->empty()) { return; } - auto stripe_map_iter = rep_->stripe_map_.begin(); - assert(stripe_map_iter != rep_->stripe_map_.end()); - if (bottommost_level) { - // TODO(andrewkr): these are counted for each compaction output file, so - // lots of double-counting. - if (!stripe_map_iter->second.raw_map.empty()) { - range_del_out_stats->num_range_del_drop_obsolete += - static_cast(stripe_map_iter->second.raw_map.size()) - - (collapse_deletions_ ? 1 : 0); - range_del_out_stats->num_record_drop_obsolete += - static_cast(stripe_map_iter->second.raw_map.size()) - - (collapse_deletions_ ? 1 : 0); + assert(input_iter->lower_bound() == 0); + assert(input_iter->upper_bound() == kMaxSequenceNumber); + parent_iters_.emplace_back(new TruncatedRangeDelIterator( + std::move(input_iter), icmp_, smallest, largest)); + + auto split_iters = parent_iters_.back()->SplitBySnapshot(*snapshots_); + for (auto& split_iter : split_iters) { + auto it = reps_.find(split_iter.first); + if (it == reps_.end()) { + bool inserted; + SequenceNumber upper_bound = split_iter.second->upper_bound(); + SequenceNumber lower_bound = split_iter.second->lower_bound(); + std::tie(it, inserted) = reps_.emplace( + split_iter.first, StripeRep(icmp_, upper_bound, lower_bound)); + assert(inserted); } - // For the bottommost level, keys covered by tombstones in the first - // (oldest) stripe have been compacted away, so the tombstones are obsolete. - ++stripe_map_iter; + assert(it != reps_.end()); + it->second.AddTombstones(std::move(split_iter.second)); } +} - // Note the order in which tombstones are stored is insignificant since we - // insert them into a std::map on the read path. - while (stripe_map_iter != rep_->stripe_map_.end()) { - bool first_added = false; - for (auto tombstone_map_iter = stripe_map_iter->second.raw_map.begin(); - tombstone_map_iter != stripe_map_iter->second.raw_map.end(); - ++tombstone_map_iter) { - RangeTombstone tombstone; - if (collapse_deletions_) { - auto next_tombstone_map_iter = std::next(tombstone_map_iter); - if (next_tombstone_map_iter == stripe_map_iter->second.raw_map.end() || - tombstone_map_iter->second.seq_ == 0) { - // it's a sentinel tombstone - continue; - } - tombstone.start_key_ = tombstone_map_iter->first; - tombstone.end_key_ = next_tombstone_map_iter->first; - tombstone.seq_ = tombstone_map_iter->second.seq_; - } else { - tombstone = tombstone_map_iter->second; - } - if (upper_bound != nullptr && - icmp_.user_comparator()->Compare(*upper_bound, - tombstone.start_key_) <= 0) { - // Tombstones starting at upper_bound or later only need to be included - // in the next table. Break because subsequent tombstones will start - // even later. - break; - } - if (lower_bound != nullptr && - icmp_.user_comparator()->Compare(tombstone.end_key_, - *lower_bound) <= 0) { - // Tombstones ending before or at lower_bound only need to be included - // in the prev table. Continue because subsequent tombstones may still - // overlap [lower_bound, upper_bound). - continue; - } +bool CompactionRangeDelAggregator::ShouldDelete(const ParsedInternalKey& parsed, + RangeDelPositioningMode mode) { + auto it = reps_.lower_bound(parsed.sequence); + if (it == reps_.end()) { + return false; + } + return it->second.ShouldDelete(parsed, mode); +} - auto ikey_and_end_key = tombstone.Serialize(); - builder->Add(ikey_and_end_key.first.Encode(), ikey_and_end_key.second); - if (!first_added) { - first_added = true; - InternalKey smallest_candidate = std::move(ikey_and_end_key.first); - if (lower_bound != nullptr && - icmp_.user_comparator()->Compare(smallest_candidate.user_key(), - *lower_bound) <= 0) { - // Pretend the smallest key has the same user key as lower_bound - // (the max key in the previous table or subcompaction) in order for - // files to appear key-space partitioned. - // - // Choose lowest seqnum so this file's smallest internal key comes - // after the previous file's/subcompaction's largest. The fake seqnum - // is OK because the read path's file-picking code only considers user - // key. - smallest_candidate = InternalKey(*lower_bound, 0, kTypeRangeDeletion); - } - if (meta->smallest.size() == 0 || - icmp_.Compare(smallest_candidate, meta->smallest) < 0) { - meta->smallest = std::move(smallest_candidate); - } +namespace { + +class TruncatedRangeDelMergingIter : public InternalIterator { + public: + TruncatedRangeDelMergingIter( + const InternalKeyComparator* icmp, const Slice* lower_bound, + const Slice* upper_bound, bool upper_bound_inclusive, + const std::vector>& children) + : icmp_(icmp), + lower_bound_(lower_bound), + upper_bound_(upper_bound), + upper_bound_inclusive_(upper_bound_inclusive), + heap_(StartKeyMinComparator(icmp)) { + for (auto& child : children) { + if (child != nullptr) { + assert(child->lower_bound() == 0); + assert(child->upper_bound() == kMaxSequenceNumber); + children_.push_back(child.get()); } - InternalKey largest_candidate = tombstone.SerializeEndKey(); - if (upper_bound != nullptr && - icmp_.user_comparator()->Compare(*upper_bound, - largest_candidate.user_key()) <= 0) { - // Pretend the largest key has the same user key as upper_bound (the - // min key in the following table or subcompaction) in order for files - // to appear key-space partitioned. - // - // Choose highest seqnum so this file's largest internal key comes - // before the next file's/subcompaction's smallest. The fake seqnum is - // OK because the read path's file-picking code only considers the user - // key portion. - // - // Note Seek() also creates InternalKey with (user_key, - // kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of - // kTypeRangeDeletion (0xF), so the range tombstone comes before the - // Seek() key in InternalKey's ordering. So Seek() will look in the - // next file for the user key. - largest_candidate = InternalKey(*upper_bound, kMaxSequenceNumber, - kTypeRangeDeletion); + } + } + + bool Valid() const override { + return !heap_.empty() && BeforeEndKey(heap_.top()); + } + Status status() const override { return Status::OK(); } + + void SeekToFirst() override { + heap_.clear(); + for (auto& child : children_) { + if (lower_bound_ != nullptr) { + child->Seek(*lower_bound_); + } else { + child->SeekToFirst(); } - if (meta->largest.size() == 0 || - icmp_.Compare(meta->largest, largest_candidate) < 0) { - meta->largest = std::move(largest_candidate); + if (child->Valid()) { + heap_.push(child); } - meta->smallest_seqno = std::min(meta->smallest_seqno, tombstone.seq_); - meta->largest_seqno = std::max(meta->largest_seqno, tombstone.seq_); } - ++stripe_map_iter; } -} -bool RangeDelAggregator::IsEmpty() { - if (rep_ == nullptr) { - return true; + void Next() override { + auto* top = heap_.top(); + top->InternalNext(); + if (top->Valid()) { + heap_.replace_top(top); + } else { + heap_.pop(); + } } - for (auto stripe_map_iter = rep_->stripe_map_.begin(); - stripe_map_iter != rep_->stripe_map_.end(); ++stripe_map_iter) { - if (!stripe_map_iter->second.raw_map.empty()) { - return false; + + Slice key() const override { + auto* top = heap_.top(); + cur_start_key_.Set(top->start_key().user_key, top->seq(), + kTypeRangeDeletion); + return cur_start_key_.Encode(); + } + + Slice value() const override { + auto* top = heap_.top(); + assert(top->end_key().sequence == kMaxSequenceNumber); + return top->end_key().user_key; + } + + // Unused InternalIterator methods + void Prev() override { assert(false); } + void Seek(const Slice& /* target */) override { assert(false); } + void SeekForPrev(const Slice& /* target */) override { assert(false); } + void SeekToLast() override { assert(false); } + + private: + bool BeforeEndKey(const TruncatedRangeDelIterator* iter) const { + if (upper_bound_ == nullptr) { + return true; } + int cmp = icmp_->user_comparator()->Compare(iter->start_key().user_key, + *upper_bound_); + return upper_bound_inclusive_ ? cmp <= 0 : cmp < 0; } - return true; + + const InternalKeyComparator* icmp_; + const Slice* lower_bound_; + const Slice* upper_bound_; + bool upper_bound_inclusive_; + BinaryHeap heap_; + std::vector children_; + + mutable InternalKey cur_start_key_; +}; + +} // namespace + +std::unique_ptr +CompactionRangeDelAggregator::NewIterator(const Slice* lower_bound, + const Slice* upper_bound, + bool upper_bound_inclusive) { + InvalidateRangeDelMapPositions(); + std::unique_ptr merging_iter( + new TruncatedRangeDelMergingIter(icmp_, lower_bound, upper_bound, + upper_bound_inclusive, parent_iters_)); + + auto fragmented_tombstone_list = + std::make_shared( + std::move(merging_iter), *icmp_, true /* for_compaction */, + *snapshots_); + + return std::unique_ptr( + new FragmentedRangeTombstoneIterator( + fragmented_tombstone_list, *icmp_, + kMaxSequenceNumber /* upper_bound */)); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/range_del_aggregator.h b/thirdparty/rocksdb/db/range_del_aggregator.h index 9d4b8ca168..712ae45839 100644 --- a/thirdparty/rocksdb/db/range_del_aggregator.h +++ b/thirdparty/rocksdb/db/range_del_aggregator.h @@ -1,161 +1,431 @@ -// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. +// Copyright (c) 2018-present, Facebook, 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). #pragma once +#include +#include +#include #include +#include #include #include #include "db/compaction_iteration_stats.h" #include "db/dbformat.h" #include "db/pinned_iterators_manager.h" +#include "db/range_del_aggregator.h" +#include "db/range_tombstone_fragmenter.h" #include "db/version_edit.h" #include "include/rocksdb/comparator.h" #include "include/rocksdb/types.h" #include "table/internal_iterator.h" #include "table/scoped_arena_iterator.h" #include "table/table_builder.h" +#include "util/heap.h" #include "util/kv_map.h" namespace rocksdb { -// A RangeDelAggregator aggregates range deletion tombstones as they are -// encountered in memtables/SST files. It provides methods that check whether a -// key is covered by range tombstones or write the relevant tombstones to a new -// SST file. -class RangeDelAggregator { +class TruncatedRangeDelIterator { + public: + TruncatedRangeDelIterator( + std::unique_ptr iter, + const InternalKeyComparator* icmp, const InternalKey* smallest, + const InternalKey* largest); + + bool Valid() const; + + void Next(); + void Prev(); + + void InternalNext(); + + // Seeks to the tombstone with the highest viisble sequence number that covers + // target (a user key). If no such tombstone exists, the position will be at + // the earliest tombstone that ends after target. + void Seek(const Slice& target); + + // Seeks to the tombstone with the highest viisble sequence number that covers + // target (a user key). If no such tombstone exists, the position will be at + // the latest tombstone that starts before target. + void SeekForPrev(const Slice& target); + + void SeekToFirst(); + void SeekToLast(); + + ParsedInternalKey start_key() const { + return (smallest_ == nullptr || + icmp_->Compare(*smallest_, iter_->parsed_start_key()) <= 0) + ? iter_->parsed_start_key() + : *smallest_; + } + + ParsedInternalKey end_key() const { + return (largest_ == nullptr || + icmp_->Compare(iter_->parsed_end_key(), *largest_) <= 0) + ? iter_->parsed_end_key() + : *largest_; + } + + SequenceNumber seq() const { return iter_->seq(); } + + std::map> + SplitBySnapshot(const std::vector& snapshots); + + SequenceNumber upper_bound() const { return iter_->upper_bound(); } + + SequenceNumber lower_bound() const { return iter_->lower_bound(); } + + private: + std::unique_ptr iter_; + const InternalKeyComparator* icmp_; + const ParsedInternalKey* smallest_ = nullptr; + const ParsedInternalKey* largest_ = nullptr; + std::list pinned_bounds_; + + const InternalKey* smallest_ikey_; + const InternalKey* largest_ikey_; +}; + +struct SeqMaxComparator { + bool operator()(const TruncatedRangeDelIterator* a, + const TruncatedRangeDelIterator* b) const { + return a->seq() > b->seq(); + } +}; + +struct StartKeyMinComparator { + explicit StartKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {} + + bool operator()(const TruncatedRangeDelIterator* a, + const TruncatedRangeDelIterator* b) const { + return icmp->Compare(a->start_key(), b->start_key()) > 0; + } + + const InternalKeyComparator* icmp; +}; + +class ForwardRangeDelIterator { public: - // @param snapshots These are used to organize the tombstones into snapshot - // stripes, which is the seqnum range between consecutive snapshots, - // including the higher snapshot and excluding the lower one. Currently, - // this is used by ShouldDelete() to prevent deletion of keys that are - // covered by range tombstones in other snapshot stripes. This constructor - // is used for writes (flush/compaction). All DB snapshots are provided - // such that no keys are removed that are uncovered according to any DB - // snapshot. - // Note this overload does not lazily initialize Rep. - RangeDelAggregator(const InternalKeyComparator& icmp, - const std::vector& snapshots, - bool collapse_deletions = true); - - // @param upper_bound Similar to snapshots above, except with a single - // snapshot, which allows us to store the snapshot on the stack and defer - // initialization of heap-allocating members (in Rep) until the first range - // deletion is encountered. This constructor is used in case of reads (get/ - // iterator), for which only the user snapshot (upper_bound) is provided - // such that the seqnum space is divided into two stripes. Only the older - // stripe will be used by ShouldDelete(). - RangeDelAggregator(const InternalKeyComparator& icmp, - SequenceNumber upper_bound, - bool collapse_deletions = false); - - // We maintain position in the tombstone map across calls to ShouldDelete. The - // caller may wish to specify a mode to optimize positioning the iterator - // during the next call to ShouldDelete. The non-kFullScan modes are only - // available when deletion collapsing is enabled. - // - // For example, if we invoke Next() on an iterator, kForwardTraversal should - // be specified to advance one-by-one through deletions until one is found - // with its interval containing the key. This will typically be faster than - // doing a full binary search (kBinarySearch). - enum RangePositioningMode { - kFullScan, // used iff collapse_deletions_ == false - kForwardTraversal, - kBackwardTraversal, - kBinarySearch, + explicit ForwardRangeDelIterator(const InternalKeyComparator* icmp); + + bool ShouldDelete(const ParsedInternalKey& parsed); + void Invalidate(); + + void AddNewIter(TruncatedRangeDelIterator* iter, + const ParsedInternalKey& parsed) { + iter->Seek(parsed.user_key); + PushIter(iter, parsed); + assert(active_iters_.size() == active_seqnums_.size()); + } + + size_t UnusedIdx() const { return unused_idx_; } + void IncUnusedIdx() { unused_idx_++; } + + private: + using ActiveSeqSet = + std::multiset; + + struct EndKeyMinComparator { + explicit EndKeyMinComparator(const InternalKeyComparator* c) : icmp(c) {} + + bool operator()(const ActiveSeqSet::const_iterator& a, + const ActiveSeqSet::const_iterator& b) const { + return icmp->Compare((*a)->end_key(), (*b)->end_key()) > 0; + } + + const InternalKeyComparator* icmp; }; - // Returns whether the key should be deleted, which is the case when it is - // covered by a range tombstone residing in the same snapshot stripe. - // @param mode If collapse_deletions_ is true, this dictates how we will find - // the deletion whose interval contains this key. Otherwise, its - // value must be kFullScan indicating linear scan from beginning.. - bool ShouldDelete(const ParsedInternalKey& parsed, - RangePositioningMode mode = kFullScan); - bool ShouldDelete(const Slice& internal_key, - RangePositioningMode mode = kFullScan); - bool ShouldAddTombstones(bool bottommost_level = false); - - // Adds tombstones to the tombstone aggregation structure maintained by this - // object. - // @return non-OK status if any of the tombstone keys are corrupted. - Status AddTombstones(std::unique_ptr input); - - // Resets iterators maintained across calls to ShouldDelete(). This may be - // called when the tombstones change, or the owner may call explicitly, e.g., - // if it's an iterator that just seeked to an arbitrary position. The effect - // of invalidation is that the following call to ShouldDelete() will binary - // search for its tombstone. - void InvalidateTombstoneMapPositions(); - - // Writes tombstones covering a range to a table builder. - // @param extend_before_min_key If true, the range of tombstones to be added - // to the TableBuilder starts from the beginning of the key-range; - // otherwise, it starts from meta->smallest. - // @param lower_bound/upper_bound Any range deletion with [start_key, end_key) - // that overlaps the target range [*lower_bound, *upper_bound) is added to - // the builder. If lower_bound is nullptr, the target range extends - // infinitely to the left. If upper_bound is nullptr, the target range - // extends infinitely to the right. If both are nullptr, the target range - // extends infinitely in both directions, i.e., all range deletions are - // added to the builder. - // @param meta The file's metadata. We modify the begin and end keys according - // to the range tombstones added to this file such that the read path does - // not miss range tombstones that cover gaps before/after/between files in - // a level. lower_bound/upper_bound above constrain how far file boundaries - // can be extended. - // @param bottommost_level If true, we will filter out any tombstones - // belonging to the oldest snapshot stripe, because all keys potentially - // covered by this tombstone are guaranteed to have been deleted by - // compaction. - void AddToBuilder(TableBuilder* builder, const Slice* lower_bound, - const Slice* upper_bound, FileMetaData* meta, - CompactionIterationStats* range_del_out_stats = nullptr, - bool bottommost_level = false); - bool IsEmpty(); + void PushIter(TruncatedRangeDelIterator* iter, + const ParsedInternalKey& parsed) { + if (!iter->Valid()) { + // The iterator has been fully consumed, so we don't need to add it to + // either of the heaps. + return; + } + int cmp = icmp_->Compare(parsed, iter->start_key()); + if (cmp < 0) { + PushInactiveIter(iter); + } else { + PushActiveIter(iter); + } + } + + void PushActiveIter(TruncatedRangeDelIterator* iter) { + auto seq_pos = active_seqnums_.insert(iter); + active_iters_.push(seq_pos); + } + + TruncatedRangeDelIterator* PopActiveIter() { + auto active_top = active_iters_.top(); + auto iter = *active_top; + active_iters_.pop(); + active_seqnums_.erase(active_top); + return iter; + } + + void PushInactiveIter(TruncatedRangeDelIterator* iter) { + inactive_iters_.push(iter); + } + + TruncatedRangeDelIterator* PopInactiveIter() { + auto* iter = inactive_iters_.top(); + inactive_iters_.pop(); + return iter; + } + + const InternalKeyComparator* icmp_; + size_t unused_idx_; + ActiveSeqSet active_seqnums_; + BinaryHeap active_iters_; + BinaryHeap inactive_iters_; +}; + +class ReverseRangeDelIterator { + public: + explicit ReverseRangeDelIterator(const InternalKeyComparator* icmp); + + bool ShouldDelete(const ParsedInternalKey& parsed); + void Invalidate(); + + void AddNewIter(TruncatedRangeDelIterator* iter, + const ParsedInternalKey& parsed) { + iter->SeekForPrev(parsed.user_key); + PushIter(iter, parsed); + assert(active_iters_.size() == active_seqnums_.size()); + } + + size_t UnusedIdx() const { return unused_idx_; } + void IncUnusedIdx() { unused_idx_++; } private: - // Maps tombstone user start key -> tombstone object - typedef std::multimap - TombstoneMap; - // Also maintains position in TombstoneMap last seen by ShouldDelete(). The - // end iterator indicates invalidation (e.g., if AddTombstones() changes the - // underlying map). End iterator cannot be invalidated. - struct PositionalTombstoneMap { - explicit PositionalTombstoneMap(TombstoneMap _raw_map) - : raw_map(std::move(_raw_map)), iter(raw_map.end()) {} - PositionalTombstoneMap(const PositionalTombstoneMap&) = delete; - PositionalTombstoneMap(PositionalTombstoneMap&& other) - : raw_map(std::move(other.raw_map)), iter(raw_map.end()) {} - - TombstoneMap raw_map; - TombstoneMap::const_iterator iter; + using ActiveSeqSet = + std::multiset; + + struct EndKeyMaxComparator { + explicit EndKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {} + + bool operator()(const TruncatedRangeDelIterator* a, + const TruncatedRangeDelIterator* b) const { + return icmp->Compare(a->end_key(), b->end_key()) < 0; + } + + const InternalKeyComparator* icmp; }; + struct StartKeyMaxComparator { + explicit StartKeyMaxComparator(const InternalKeyComparator* c) : icmp(c) {} - // Maps snapshot seqnum -> map of tombstones that fall in that stripe, i.e., - // their seqnums are greater than the next smaller snapshot's seqnum. - typedef std::map StripeMap; + bool operator()(const ActiveSeqSet::const_iterator& a, + const ActiveSeqSet::const_iterator& b) const { + return icmp->Compare((*a)->start_key(), (*b)->start_key()) < 0; + } - struct Rep { - StripeMap stripe_map_; - PinnedIteratorsManager pinned_iters_mgr_; + const InternalKeyComparator* icmp; }; - // Initializes rep_ lazily. This aggregator object is constructed for every - // read, so expensive members should only be created when necessary, i.e., - // once the first range deletion is encountered. - void InitRep(const std::vector& snapshots); - - PositionalTombstoneMap& GetPositionalTombstoneMap(SequenceNumber seq); - Status AddTombstone(RangeTombstone tombstone); - - SequenceNumber upper_bound_; - std::unique_ptr rep_; - const InternalKeyComparator& icmp_; - // collapse range deletions so they're binary searchable - const bool collapse_deletions_; + + void PushIter(TruncatedRangeDelIterator* iter, + const ParsedInternalKey& parsed) { + if (!iter->Valid()) { + // The iterator has been fully consumed, so we don't need to add it to + // either of the heaps. + } else if (icmp_->Compare(iter->end_key(), parsed) <= 0) { + PushInactiveIter(iter); + } else { + PushActiveIter(iter); + } + } + + void PushActiveIter(TruncatedRangeDelIterator* iter) { + auto seq_pos = active_seqnums_.insert(iter); + active_iters_.push(seq_pos); + } + + TruncatedRangeDelIterator* PopActiveIter() { + auto active_top = active_iters_.top(); + auto iter = *active_top; + active_iters_.pop(); + active_seqnums_.erase(active_top); + return iter; + } + + void PushInactiveIter(TruncatedRangeDelIterator* iter) { + inactive_iters_.push(iter); + } + + TruncatedRangeDelIterator* PopInactiveIter() { + auto* iter = inactive_iters_.top(); + inactive_iters_.pop(); + return iter; + } + + const InternalKeyComparator* icmp_; + size_t unused_idx_; + ActiveSeqSet active_seqnums_; + BinaryHeap active_iters_; + BinaryHeap inactive_iters_; +}; + +enum class RangeDelPositioningMode { kForwardTraversal, kBackwardTraversal }; +class RangeDelAggregator { + public: + explicit RangeDelAggregator(const InternalKeyComparator* icmp) + : icmp_(icmp) {} + virtual ~RangeDelAggregator() {} + + virtual void AddTombstones( + std::unique_ptr input_iter, + const InternalKey* smallest = nullptr, + const InternalKey* largest = nullptr) = 0; + + bool ShouldDelete(const Slice& key, RangeDelPositioningMode mode) { + ParsedInternalKey parsed; + if (!ParseInternalKey(key, &parsed)) { + return false; + } + return ShouldDelete(parsed, mode); + } + virtual bool ShouldDelete(const ParsedInternalKey& parsed, + RangeDelPositioningMode mode) = 0; + + virtual void InvalidateRangeDelMapPositions() = 0; + + virtual bool IsEmpty() const = 0; + + bool AddFile(uint64_t file_number) { + return files_seen_.insert(file_number).second; + } + + protected: + class StripeRep { + public: + StripeRep(const InternalKeyComparator* icmp, SequenceNumber upper_bound, + SequenceNumber lower_bound) + : icmp_(icmp), + forward_iter_(icmp), + reverse_iter_(icmp), + upper_bound_(upper_bound), + lower_bound_(lower_bound) {} + + void AddTombstones(std::unique_ptr input_iter) { + iters_.push_back(std::move(input_iter)); + } + + bool IsEmpty() const { return iters_.empty(); } + + bool ShouldDelete(const ParsedInternalKey& parsed, + RangeDelPositioningMode mode); + + void Invalidate() { + InvalidateForwardIter(); + InvalidateReverseIter(); + } + + bool IsRangeOverlapped(const Slice& start, const Slice& end); + + private: + bool InStripe(SequenceNumber seq) const { + return lower_bound_ <= seq && seq <= upper_bound_; + } + + void InvalidateForwardIter() { forward_iter_.Invalidate(); } + + void InvalidateReverseIter() { reverse_iter_.Invalidate(); } + + const InternalKeyComparator* icmp_; + std::vector> iters_; + ForwardRangeDelIterator forward_iter_; + ReverseRangeDelIterator reverse_iter_; + SequenceNumber upper_bound_; + SequenceNumber lower_bound_; + }; + + const InternalKeyComparator* icmp_; + + private: + std::set files_seen_; +}; + +class ReadRangeDelAggregator : public RangeDelAggregator { + public: + ReadRangeDelAggregator(const InternalKeyComparator* icmp, + SequenceNumber upper_bound) + : RangeDelAggregator(icmp), + rep_(icmp, upper_bound, 0 /* lower_bound */) {} + ~ReadRangeDelAggregator() override {} + + using RangeDelAggregator::ShouldDelete; + void AddTombstones( + std::unique_ptr input_iter, + const InternalKey* smallest = nullptr, + const InternalKey* largest = nullptr) override; + + bool ShouldDelete(const ParsedInternalKey& parsed, + RangeDelPositioningMode mode) override; + + bool IsRangeOverlapped(const Slice& start, const Slice& end); + + void InvalidateRangeDelMapPositions() override { rep_.Invalidate(); } + + bool IsEmpty() const override { return rep_.IsEmpty(); } + + private: + StripeRep rep_; +}; + +class CompactionRangeDelAggregator : public RangeDelAggregator { + public: + CompactionRangeDelAggregator(const InternalKeyComparator* icmp, + const std::vector& snapshots) + : RangeDelAggregator(icmp), snapshots_(&snapshots) {} + ~CompactionRangeDelAggregator() override {} + + void AddTombstones( + std::unique_ptr input_iter, + const InternalKey* smallest = nullptr, + const InternalKey* largest = nullptr) override; + + using RangeDelAggregator::ShouldDelete; + bool ShouldDelete(const ParsedInternalKey& parsed, + RangeDelPositioningMode mode) override; + + bool IsRangeOverlapped(const Slice& start, const Slice& end); + + void InvalidateRangeDelMapPositions() override { + for (auto& rep : reps_) { + rep.second.Invalidate(); + } + } + + bool IsEmpty() const override { + for (const auto& rep : reps_) { + if (!rep.second.IsEmpty()) { + return false; + } + } + return true; + } + + // Creates an iterator over all the range tombstones in the aggregator, for + // use in compaction. Nullptr arguments indicate that the iterator range is + // unbounded. + // NOTE: the boundaries are used for optimization purposes to reduce the + // number of tombstones that are passed to the fragmenter; they do not + // guarantee that the resulting iterator only contains range tombstones that + // cover keys in the provided range. If required, these bounds must be + // enforced during iteration. + std::unique_ptr NewIterator( + const Slice* lower_bound = nullptr, const Slice* upper_bound = nullptr, + bool upper_bound_inclusive = false); + + private: + std::vector> parent_iters_; + std::map reps_; + + const std::vector* snapshots_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/range_del_aggregator_bench.cc b/thirdparty/rocksdb/db/range_del_aggregator_bench.cc new file mode 100644 index 0000000000..34b2f7e5db --- /dev/null +++ b/thirdparty/rocksdb/db/range_del_aggregator_bench.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2018-present, Facebook, 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 GFLAGS +#include +int main() { + fprintf(stderr, "Please install gflags to run rocksdb tools\n"); + return 1; +} +#else + +#include +#include +#include +#include +#include +#include +#include + +#include "db/range_del_aggregator.h" +#include "db/range_tombstone_fragmenter.h" +#include "rocksdb/comparator.h" +#include "rocksdb/env.h" +#include "util/coding.h" +#include "util/random.h" +#include "util/stop_watch.h" +#include "util/testutil.h" + +#include "util/gflags_compat.h" + +using GFLAGS_NAMESPACE::ParseCommandLineFlags; + +DEFINE_int32(num_range_tombstones, 1000, "number of range tombstones created"); + +DEFINE_int32(num_runs, 1000, "number of test runs"); + +DEFINE_int32(tombstone_start_upper_bound, 1000, + "exclusive upper bound on range tombstone start keys"); + +DEFINE_int32(should_delete_upper_bound, 1000, + "exclusive upper bound on keys passed to ShouldDelete"); + +DEFINE_double(tombstone_width_mean, 100.0, "average range tombstone width"); + +DEFINE_double(tombstone_width_stddev, 0.0, + "standard deviation of range tombstone width"); + +DEFINE_int32(seed, 0, "random number generator seed"); + +DEFINE_int32(should_deletes_per_run, 1, "number of ShouldDelete calls per run"); + +DEFINE_int32(add_tombstones_per_run, 1, + "number of AddTombstones calls per run"); + +namespace { + +struct Stats { + uint64_t time_add_tombstones = 0; + uint64_t time_first_should_delete = 0; + uint64_t time_rest_should_delete = 0; +}; + +std::ostream& operator<<(std::ostream& os, const Stats& s) { + std::ios fmt_holder(nullptr); + fmt_holder.copyfmt(os); + + os << std::left; + os << std::setw(25) << "AddTombstones: " + << s.time_add_tombstones / + (FLAGS_add_tombstones_per_run * FLAGS_num_runs * 1.0e3) + << " us\n"; + os << std::setw(25) << "ShouldDelete (first): " + << s.time_first_should_delete / (FLAGS_num_runs * 1.0e3) << " us\n"; + if (FLAGS_should_deletes_per_run > 1) { + os << std::setw(25) << "ShouldDelete (rest): " + << s.time_rest_should_delete / + ((FLAGS_should_deletes_per_run - 1) * FLAGS_num_runs * 1.0e3) + << " us\n"; + } + + os.copyfmt(fmt_holder); + return os; +} + +auto icmp = rocksdb::InternalKeyComparator(rocksdb::BytewiseComparator()); + +} // anonymous namespace + +namespace rocksdb { + +namespace { + +// A wrapper around RangeTombstones and the underlying data of its start and end +// keys. +struct PersistentRangeTombstone { + std::string start_key; + std::string end_key; + RangeTombstone tombstone; + + PersistentRangeTombstone(std::string start, std::string end, + SequenceNumber seq) + : start_key(std::move(start)), end_key(std::move(end)) { + tombstone = RangeTombstone(start_key, end_key, seq); + } + + PersistentRangeTombstone() = default; + + PersistentRangeTombstone(const PersistentRangeTombstone& t) { *this = t; } + + PersistentRangeTombstone& operator=(const PersistentRangeTombstone& t) { + start_key = t.start_key; + end_key = t.end_key; + tombstone = RangeTombstone(start_key, end_key, t.tombstone.seq_); + + return *this; + } + + PersistentRangeTombstone(PersistentRangeTombstone&& t) noexcept { *this = t; } + + PersistentRangeTombstone& operator=(PersistentRangeTombstone&& t) { + start_key = std::move(t.start_key); + end_key = std::move(t.end_key); + tombstone = RangeTombstone(start_key, end_key, t.tombstone.seq_); + + return *this; + } +}; + +struct TombstoneStartKeyComparator { + explicit TombstoneStartKeyComparator(const Comparator* c) : cmp(c) {} + + bool operator()(const RangeTombstone& a, const RangeTombstone& b) const { + return cmp->Compare(a.start_key_, b.start_key_) < 0; + } + + const Comparator* cmp; +}; + +std::unique_ptr MakeRangeDelIterator( + const std::vector& range_dels) { + std::vector keys, values; + for (const auto& range_del : range_dels) { + auto key_and_value = range_del.tombstone.Serialize(); + keys.push_back(key_and_value.first.Encode().ToString()); + values.push_back(key_and_value.second.ToString()); + } + return std::unique_ptr( + new test::VectorIterator(keys, values)); +} + +// convert long to a big-endian slice key +static std::string Key(int64_t val) { + std::string little_endian_key; + std::string big_endian_key; + PutFixed64(&little_endian_key, val); + assert(little_endian_key.size() == sizeof(val)); + big_endian_key.resize(sizeof(val)); + for (size_t i = 0; i < sizeof(val); ++i) { + big_endian_key[i] = little_endian_key[sizeof(val) - 1 - i]; + } + return big_endian_key; +} + +} // anonymous namespace + +} // namespace rocksdb + +int main(int argc, char** argv) { + ParseCommandLineFlags(&argc, &argv, true); + + Stats stats; + rocksdb::Random64 rnd(FLAGS_seed); + std::default_random_engine random_gen(FLAGS_seed); + std::normal_distribution normal_dist(FLAGS_tombstone_width_mean, + FLAGS_tombstone_width_stddev); + std::vector > + all_persistent_range_tombstones(FLAGS_add_tombstones_per_run); + for (int i = 0; i < FLAGS_add_tombstones_per_run; i++) { + all_persistent_range_tombstones[i] = + std::vector( + FLAGS_num_range_tombstones); + } + auto mode = rocksdb::RangeDelPositioningMode::kForwardTraversal; + + for (int i = 0; i < FLAGS_num_runs; i++) { + rocksdb::ReadRangeDelAggregator range_del_agg( + &icmp, rocksdb::kMaxSequenceNumber /* upper_bound */); + + std::vector > + fragmented_range_tombstone_lists(FLAGS_add_tombstones_per_run); + + for (auto& persistent_range_tombstones : all_persistent_range_tombstones) { + // TODO(abhimadan): consider whether creating the range tombstones right + // before AddTombstones is artificially warming the cache compared to + // real workloads. + for (int j = 0; j < FLAGS_num_range_tombstones; j++) { + uint64_t start = rnd.Uniform(FLAGS_tombstone_start_upper_bound); + uint64_t end = static_cast( + std::round(start + std::max(1.0, normal_dist(random_gen)))); + persistent_range_tombstones[j] = rocksdb::PersistentRangeTombstone( + rocksdb::Key(start), rocksdb::Key(end), j); + } + + auto range_del_iter = + rocksdb::MakeRangeDelIterator(persistent_range_tombstones); + fragmented_range_tombstone_lists.emplace_back( + new rocksdb::FragmentedRangeTombstoneList( + rocksdb::MakeRangeDelIterator(persistent_range_tombstones), + icmp)); + std::unique_ptr + fragmented_range_del_iter( + new rocksdb::FragmentedRangeTombstoneIterator( + fragmented_range_tombstone_lists.back().get(), icmp, + rocksdb::kMaxSequenceNumber)); + + rocksdb::StopWatchNano stop_watch_add_tombstones(rocksdb::Env::Default(), + true /* auto_start */); + range_del_agg.AddTombstones(std::move(fragmented_range_del_iter)); + stats.time_add_tombstones += stop_watch_add_tombstones.ElapsedNanos(); + } + + rocksdb::ParsedInternalKey parsed_key; + parsed_key.sequence = FLAGS_num_range_tombstones / 2; + parsed_key.type = rocksdb::kTypeValue; + + uint64_t first_key = rnd.Uniform(FLAGS_should_delete_upper_bound - + FLAGS_should_deletes_per_run + 1); + + for (int j = 0; j < FLAGS_should_deletes_per_run; j++) { + std::string key_string = rocksdb::Key(first_key + j); + parsed_key.user_key = key_string; + + rocksdb::StopWatchNano stop_watch_should_delete(rocksdb::Env::Default(), + true /* auto_start */); + range_del_agg.ShouldDelete(parsed_key, mode); + uint64_t call_time = stop_watch_should_delete.ElapsedNanos(); + + if (j == 0) { + stats.time_first_should_delete += call_time; + } else { + stats.time_rest_should_delete += call_time; + } + } + } + + std::cout << "=========================\n" + << "Results:\n" + << "=========================\n" + << stats; + + return 0; +} + +#endif // GFLAGS diff --git a/thirdparty/rocksdb/db/range_del_aggregator_test.cc b/thirdparty/rocksdb/db/range_del_aggregator_test.cc index 39029bd2a2..28c8129ecb 100644 --- a/thirdparty/rocksdb/db/range_del_aggregator_test.cc +++ b/thirdparty/rocksdb/db/range_del_aggregator_test.cc @@ -1,13 +1,17 @@ -// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. +// Copyright (c) 2018-present, Facebook, 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). -#include +#include "db/range_del_aggregator.h" + +#include +#include +#include #include "db/db_test_util.h" -#include "db/range_del_aggregator.h" -#include "rocksdb/comparator.h" +#include "db/dbformat.h" +#include "db/range_tombstone_fragmenter.h" #include "util/testutil.h" namespace rocksdb { @@ -16,137 +20,685 @@ class RangeDelAggregatorTest : public testing::Test {}; namespace { -struct ExpectedPoint { - Slice begin; +static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator()); + +std::unique_ptr MakeRangeDelIter( + const std::vector& range_dels) { + std::vector keys, values; + for (const auto& range_del : range_dels) { + auto key_and_value = range_del.Serialize(); + keys.push_back(key_and_value.first.Encode().ToString()); + values.push_back(key_and_value.second.ToString()); + } + return std::unique_ptr( + new test::VectorIterator(keys, values)); +} + +std::vector> +MakeFragmentedTombstoneLists( + const std::vector>& range_dels_list) { + std::vector> fragment_lists; + for (const auto& range_dels : range_dels_list) { + auto range_del_iter = MakeRangeDelIter(range_dels); + fragment_lists.emplace_back(new FragmentedRangeTombstoneList( + std::move(range_del_iter), bytewise_icmp)); + } + return fragment_lists; +} + +struct TruncatedIterScanTestCase { + ParsedInternalKey start; + ParsedInternalKey end; SequenceNumber seq; }; -enum Direction { - kForward, - kReverse, +struct TruncatedIterSeekTestCase { + Slice target; + ParsedInternalKey start; + ParsedInternalKey end; + SequenceNumber seq; + bool invalid; }; -void VerifyRangeDels(const std::vector& range_dels, - const std::vector& expected_points) { - // Test same result regardless of which order the range deletions are added. - for (Direction dir : {kForward, kReverse}) { - auto icmp = InternalKeyComparator(BytewiseComparator()); - RangeDelAggregator range_del_agg(icmp, {} /* snapshots */, true); - std::vector keys, values; - for (const auto& range_del : range_dels) { - auto key_and_value = range_del.Serialize(); - keys.push_back(key_and_value.first.Encode().ToString()); - values.push_back(key_and_value.second.ToString()); - } - if (dir == kReverse) { - std::reverse(keys.begin(), keys.end()); - std::reverse(values.begin(), values.end()); +struct ShouldDeleteTestCase { + ParsedInternalKey lookup_key; + bool result; +}; + +struct IsRangeOverlappedTestCase { + Slice start; + Slice end; + bool result; +}; + +ParsedInternalKey UncutEndpoint(const Slice& s) { + return ParsedInternalKey(s, kMaxSequenceNumber, kTypeRangeDeletion); +} + +ParsedInternalKey InternalValue(const Slice& key, SequenceNumber seq) { + return ParsedInternalKey(key, seq, kTypeValue); +} + +void VerifyIterator( + TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp, + const std::vector& expected_range_dels) { + // Test forward iteration. + iter->SeekToFirst(); + for (size_t i = 0; i < expected_range_dels.size(); i++, iter->Next()) { + ASSERT_TRUE(iter->Valid()); + EXPECT_EQ(0, icmp.Compare(iter->start_key(), expected_range_dels[i].start)); + EXPECT_EQ(0, icmp.Compare(iter->end_key(), expected_range_dels[i].end)); + EXPECT_EQ(expected_range_dels[i].seq, iter->seq()); + } + EXPECT_FALSE(iter->Valid()); + + // Test reverse iteration. + iter->SeekToLast(); + std::vector reverse_expected_range_dels( + expected_range_dels.rbegin(), expected_range_dels.rend()); + for (size_t i = 0; i < reverse_expected_range_dels.size(); + i++, iter->Prev()) { + ASSERT_TRUE(iter->Valid()); + EXPECT_EQ(0, icmp.Compare(iter->start_key(), + reverse_expected_range_dels[i].start)); + EXPECT_EQ( + 0, icmp.Compare(iter->end_key(), reverse_expected_range_dels[i].end)); + EXPECT_EQ(reverse_expected_range_dels[i].seq, iter->seq()); + } + EXPECT_FALSE(iter->Valid()); +} + +void VerifySeek(TruncatedRangeDelIterator* iter, + const InternalKeyComparator& icmp, + const std::vector& test_cases) { + for (const auto& test_case : test_cases) { + iter->Seek(test_case.target); + if (test_case.invalid) { + ASSERT_FALSE(iter->Valid()); + } else { + ASSERT_TRUE(iter->Valid()); + EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start)); + EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end)); + EXPECT_EQ(test_case.seq, iter->seq()); } - std::unique_ptr range_del_iter( - new test::VectorIterator(keys, values)); - range_del_agg.AddTombstones(std::move(range_del_iter)); - - for (const auto expected_point : expected_points) { - ParsedInternalKey parsed_key; - parsed_key.user_key = expected_point.begin; - parsed_key.sequence = expected_point.seq; - parsed_key.type = kTypeValue; - ASSERT_FALSE(range_del_agg.ShouldDelete( - parsed_key, - RangeDelAggregator::RangePositioningMode::kForwardTraversal)); - if (parsed_key.sequence > 0) { - --parsed_key.sequence; - ASSERT_TRUE(range_del_agg.ShouldDelete( - parsed_key, - RangeDelAggregator::RangePositioningMode::kForwardTraversal)); - } + } +} + +void VerifySeekForPrev( + TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp, + const std::vector& test_cases) { + for (const auto& test_case : test_cases) { + iter->SeekForPrev(test_case.target); + if (test_case.invalid) { + ASSERT_FALSE(iter->Valid()); + } else { + ASSERT_TRUE(iter->Valid()); + EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start)); + EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end)); + EXPECT_EQ(test_case.seq, iter->seq()); } } } -} // anonymous namespace +void VerifyShouldDelete(RangeDelAggregator* range_del_agg, + const std::vector& test_cases) { + for (const auto& test_case : test_cases) { + EXPECT_EQ( + test_case.result, + range_del_agg->ShouldDelete( + test_case.lookup_key, RangeDelPositioningMode::kForwardTraversal)); + } + for (auto it = test_cases.rbegin(); it != test_cases.rend(); ++it) { + const auto& test_case = *it; + EXPECT_EQ( + test_case.result, + range_del_agg->ShouldDelete( + test_case.lookup_key, RangeDelPositioningMode::kBackwardTraversal)); + } +} + +void VerifyIsRangeOverlapped( + ReadRangeDelAggregator* range_del_agg, + const std::vector& test_cases) { + for (const auto& test_case : test_cases) { + EXPECT_EQ(test_case.result, + range_del_agg->IsRangeOverlapped(test_case.start, test_case.end)); + } +} -TEST_F(RangeDelAggregatorTest, Empty) { VerifyRangeDels({}, {{"a", 0}}); } +void CheckIterPosition(const RangeTombstone& tombstone, + const FragmentedRangeTombstoneIterator* iter) { + // Test InternalIterator interface. + EXPECT_EQ(tombstone.start_key_, ExtractUserKey(iter->key())); + EXPECT_EQ(tombstone.end_key_, iter->value()); + EXPECT_EQ(tombstone.seq_, iter->seq()); -TEST_F(RangeDelAggregatorTest, SameStartAndEnd) { - VerifyRangeDels({{"a", "a", 5}}, {{" ", 0}, {"a", 0}, {"b", 0}}); + // Test FragmentedRangeTombstoneIterator interface. + EXPECT_EQ(tombstone.start_key_, iter->start_key()); + EXPECT_EQ(tombstone.end_key_, iter->end_key()); + EXPECT_EQ(tombstone.seq_, GetInternalKeySeqno(iter->key())); } -TEST_F(RangeDelAggregatorTest, Single) { - VerifyRangeDels({{"a", "b", 10}}, {{" ", 0}, {"a", 10}, {"b", 0}}); +void VerifyFragmentedRangeDels( + FragmentedRangeTombstoneIterator* iter, + const std::vector& expected_tombstones) { + iter->SeekToFirst(); + for (size_t i = 0; i < expected_tombstones.size(); i++, iter->Next()) { + ASSERT_TRUE(iter->Valid()); + CheckIterPosition(expected_tombstones[i], iter); + } + EXPECT_FALSE(iter->Valid()); } -TEST_F(RangeDelAggregatorTest, OverlapAboveLeft) { - VerifyRangeDels({{"a", "c", 10}, {"b", "d", 5}}, - {{" ", 0}, {"a", 10}, {"c", 5}, {"d", 0}}); +} // namespace + +TEST_F(RangeDelAggregatorTest, EmptyTruncatedIter) { + auto range_del_iter = MakeRangeDelIter({}); + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, + kMaxSequenceNumber)); + + TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, + nullptr); + + iter.SeekToFirst(); + ASSERT_FALSE(iter.Valid()); + + iter.SeekToLast(); + ASSERT_FALSE(iter.Valid()); } -TEST_F(RangeDelAggregatorTest, OverlapAboveRight) { - VerifyRangeDels({{"a", "c", 5}, {"b", "d", 10}}, - {{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}}); +TEST_F(RangeDelAggregatorTest, UntruncatedIter) { + auto range_del_iter = + MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, + kMaxSequenceNumber)); + + TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, + nullptr); + + VerifyIterator(&iter, bytewise_icmp, + {{UncutEndpoint("a"), UncutEndpoint("e"), 10}, + {UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {UncutEndpoint("j"), UncutEndpoint("n"), 4}}); + + VerifySeek( + &iter, bytewise_icmp, + {{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10}, + {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4}, + {"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, + {"", UncutEndpoint("a"), UncutEndpoint("e"), 10}}); + + VerifySeekForPrev( + &iter, bytewise_icmp, + {{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10}, + {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"n", UncutEndpoint("j"), UncutEndpoint("n"), 4}, + {"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}}); } -TEST_F(RangeDelAggregatorTest, OverlapAboveMiddle) { - VerifyRangeDels({{"a", "d", 5}, {"b", "c", 10}}, - {{" ", 0}, {"a", 5}, {"b", 10}, {"c", 5}, {"d", 0}}); +TEST_F(RangeDelAggregatorTest, UntruncatedIterWithSnapshot) { + auto range_del_iter = + MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, + 9 /* snapshot */)); + + TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, + nullptr); + + VerifyIterator(&iter, bytewise_icmp, + {{UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {UncutEndpoint("j"), UncutEndpoint("n"), 4}}); + + VerifySeek( + &iter, bytewise_icmp, + {{"d", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4}, + {"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, + {"", UncutEndpoint("e"), UncutEndpoint("g"), 8}}); + + VerifySeekForPrev( + &iter, bytewise_icmp, + {{"d", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, + {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"n", UncutEndpoint("j"), UncutEndpoint("n"), 4}, + {"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}}); } -TEST_F(RangeDelAggregatorTest, OverlapFully) { - VerifyRangeDels({{"a", "d", 10}, {"b", "c", 5}}, - {{" ", 0}, {"a", 10}, {"d", 0}}); +TEST_F(RangeDelAggregatorTest, TruncatedIterPartiallyCutTombstones) { + auto range_del_iter = + MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, + kMaxSequenceNumber)); + + InternalKey smallest("d", 7, kTypeValue); + InternalKey largest("m", 9, kTypeValue); + TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, + &smallest, &largest); + + VerifyIterator(&iter, bytewise_icmp, + {{InternalValue("d", 7), UncutEndpoint("e"), 10}, + {UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {UncutEndpoint("j"), InternalValue("m", 8), 4}}); + + VerifySeek( + &iter, bytewise_icmp, + {{"d", InternalValue("d", 7), UncutEndpoint("e"), 10}, + {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"ia", UncutEndpoint("j"), InternalValue("m", 8), 4}, + {"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, + {"", InternalValue("d", 7), UncutEndpoint("e"), 10}}); + + VerifySeekForPrev( + &iter, bytewise_icmp, + {{"d", InternalValue("d", 7), UncutEndpoint("e"), 10}, + {"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8}, + {"n", UncutEndpoint("j"), InternalValue("m", 8), 4}, + {"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}}); } -TEST_F(RangeDelAggregatorTest, OverlapPoint) { - VerifyRangeDels({{"a", "b", 5}, {"b", "c", 10}}, - {{" ", 0}, {"a", 5}, {"b", 10}, {"c", 0}}); +TEST_F(RangeDelAggregatorTest, TruncatedIterFullyCutTombstones) { + auto range_del_iter = + MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, + kMaxSequenceNumber)); + + InternalKey smallest("f", 7, kTypeValue); + InternalKey largest("i", 9, kTypeValue); + TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, + &smallest, &largest); + + VerifyIterator(&iter, bytewise_icmp, + {{InternalValue("f", 7), UncutEndpoint("g"), 8}}); + + VerifySeek( + &iter, bytewise_icmp, + {{"d", InternalValue("f", 7), UncutEndpoint("g"), 8}, + {"f", InternalValue("f", 7), UncutEndpoint("g"), 8}, + {"j", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}}); + + VerifySeekForPrev( + &iter, bytewise_icmp, + {{"d", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, + {"f", InternalValue("f", 7), UncutEndpoint("g"), 8}, + {"j", InternalValue("f", 7), UncutEndpoint("g"), 8}}); } -TEST_F(RangeDelAggregatorTest, SameStartKey) { - VerifyRangeDels({{"a", "c", 5}, {"a", "b", 10}}, - {{" ", 0}, {"a", 10}, {"b", 5}, {"c", 0}}); +TEST_F(RangeDelAggregatorTest, SingleIterInAggregator) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, {"c", "g", 8}}); + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(&fragment_list, bytewise_icmp, + kMaxSequenceNumber)); + + ReadRangeDelAggregator range_del_agg(&bytewise_icmp, kMaxSequenceNumber); + range_del_agg.AddTombstones(std::move(input_iter)); + + VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false}, + {InternalValue("b", 9), true}, + {InternalValue("d", 9), true}, + {InternalValue("e", 7), true}, + {InternalValue("g", 7), false}}); + + VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, + {"_", "a", true}, + {"a", "c", true}, + {"d", "f", true}, + {"g", "l", false}}); } -TEST_F(RangeDelAggregatorTest, SameEndKey) { - VerifyRangeDels({{"a", "d", 5}, {"b", "d", 10}}, - {{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}}); +TEST_F(RangeDelAggregatorTest, MultipleItersInAggregator) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "e", 10}, {"c", "g", 8}}, + {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); + + ReadRangeDelAggregator range_del_agg(&bytewise_icmp, kMaxSequenceNumber); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + kMaxSequenceNumber)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), true}, + {InternalValue("b", 19), false}, + {InternalValue("b", 9), true}, + {InternalValue("d", 9), true}, + {InternalValue("e", 7), true}, + {InternalValue("g", 7), false}, + {InternalValue("h", 24), true}, + {InternalValue("i", 24), false}, + {InternalValue("ii", 14), true}, + {InternalValue("j", 14), false}}); + + VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, + {"_", "a", true}, + {"a", "c", true}, + {"d", "f", true}, + {"g", "l", true}, + {"x", "y", false}}); } -TEST_F(RangeDelAggregatorTest, GapsBetweenRanges) { - VerifyRangeDels( - {{"a", "b", 5}, {"c", "d", 10}, {"e", "f", 15}}, - {{" ", 0}, {"a", 5}, {"b", 0}, {"c", 10}, {"d", 0}, {"e", 15}, {"f", 0}}); +TEST_F(RangeDelAggregatorTest, MultipleItersInAggregatorWithUpperBound) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "e", 10}, {"c", "g", 8}}, + {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); + + ReadRangeDelAggregator range_del_agg(&bytewise_icmp, 19); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + 19 /* snapshot */)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false}, + {InternalValue("a", 9), true}, + {InternalValue("b", 9), true}, + {InternalValue("d", 9), true}, + {InternalValue("e", 7), true}, + {InternalValue("g", 7), false}, + {InternalValue("h", 24), false}, + {InternalValue("i", 24), false}, + {InternalValue("ii", 14), true}, + {InternalValue("j", 14), false}}); + + VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, + {"_", "a", true}, + {"a", "c", true}, + {"d", "f", true}, + {"g", "l", true}, + {"x", "y", false}}); } -// Note the Cover* tests also test cases where tombstones are inserted under a -// larger one when VerifyRangeDels() runs them in reverse -TEST_F(RangeDelAggregatorTest, CoverMultipleFromLeft) { - VerifyRangeDels( - {{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"a", "f", 20}}, - {{" ", 0}, {"a", 20}, {"f", 15}, {"g", 0}}); +TEST_F(RangeDelAggregatorTest, MultipleTruncatedItersInAggregator) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}}); + std::vector> iter_bounds = { + {InternalKey("a", 4, kTypeValue), + InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)}, + {InternalKey("m", 20, kTypeValue), + InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)}, + {InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}}; + + ReadRangeDelAggregator range_del_agg(&bytewise_icmp, 19); + for (size_t i = 0; i < fragment_lists.size(); i++) { + const auto& fragment_list = fragment_lists[i]; + const auto& bounds = iter_bounds[i]; + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + 19 /* snapshot */)); + range_del_agg.AddTombstones(std::move(input_iter), &bounds.first, + &bounds.second); + } + + VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false}, + {InternalValue("a", 9), false}, + {InternalValue("a", 4), true}, + {InternalValue("m", 10), false}, + {InternalValue("m", 9), true}, + {InternalValue("x", 10), false}, + {InternalValue("x", 9), false}, + {InternalValue("x", 5), true}, + {InternalValue("z", 9), false}}); + + VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, + {"_", "a", true}, + {"a", "n", true}, + {"l", "x", true}, + {"w", "z", true}, + {"zzz", "zz", false}, + {"zz", "zzz", false}}); } -TEST_F(RangeDelAggregatorTest, CoverMultipleFromRight) { - VerifyRangeDels( - {{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"c", "h", 20}}, - {{" ", 0}, {"b", 5}, {"c", 20}, {"h", 0}}); +TEST_F(RangeDelAggregatorTest, MultipleTruncatedItersInAggregatorSameLevel) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}}); + std::vector> iter_bounds = { + {InternalKey("a", 4, kTypeValue), + InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)}, + {InternalKey("m", 20, kTypeValue), + InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)}, + {InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}}; + + ReadRangeDelAggregator range_del_agg(&bytewise_icmp, 19); + + auto add_iter_to_agg = [&](size_t i) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_lists[i].get(), + bytewise_icmp, 19 /* snapshot */)); + range_del_agg.AddTombstones(std::move(input_iter), &iter_bounds[i].first, + &iter_bounds[i].second); + }; + + add_iter_to_agg(0); + VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false}, + {InternalValue("a", 9), false}, + {InternalValue("a", 4), true}}); + + add_iter_to_agg(1); + VerifyShouldDelete(&range_del_agg, {{InternalValue("m", 10), false}, + {InternalValue("m", 9), true}}); + + add_iter_to_agg(2); + VerifyShouldDelete(&range_del_agg, {{InternalValue("x", 10), false}, + {InternalValue("x", 9), false}, + {InternalValue("x", 5), true}, + {InternalValue("z", 9), false}}); + + VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, + {"_", "a", true}, + {"a", "n", true}, + {"l", "x", true}, + {"w", "z", true}, + {"zzz", "zz", false}, + {"zz", "zzz", false}}); } -TEST_F(RangeDelAggregatorTest, CoverMultipleFully) { - VerifyRangeDels( - {{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"a", "h", 20}}, - {{" ", 0}, {"a", 20}, {"h", 0}}); +TEST_F(RangeDelAggregatorTest, CompactionAggregatorNoSnapshots) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "e", 10}, {"c", "g", 8}}, + {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); + + std::vector snapshots; + CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + kMaxSequenceNumber)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), true}, + {InternalValue("b", 19), false}, + {InternalValue("b", 9), true}, + {InternalValue("d", 9), true}, + {InternalValue("e", 7), true}, + {InternalValue("g", 7), false}, + {InternalValue("h", 24), true}, + {InternalValue("i", 24), false}, + {InternalValue("ii", 14), true}, + {InternalValue("j", 14), false}}); + + auto range_del_compaction_iter = range_del_agg.NewIterator(); + VerifyFragmentedRangeDels(range_del_compaction_iter.get(), {{"a", "b", 20}, + {"b", "c", 10}, + {"c", "e", 10}, + {"e", "g", 8}, + {"h", "i", 25}, + {"ii", "j", 15}}); } -TEST_F(RangeDelAggregatorTest, AlternateMultipleAboveBelow) { - VerifyRangeDels( - {{"b", "d", 15}, {"c", "f", 10}, {"e", "g", 20}, {"a", "h", 5}}, - {{" ", 0}, - {"a", 5}, - {"b", 15}, - {"d", 10}, - {"e", 20}, - {"g", 5}, - {"h", 0}}); +TEST_F(RangeDelAggregatorTest, CompactionAggregatorWithSnapshots) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "e", 10}, {"c", "g", 8}}, + {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); + + std::vector snapshots{9, 19}; + CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + kMaxSequenceNumber)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + VerifyShouldDelete( + &range_del_agg, + { + {InternalValue("a", 19), false}, // [10, 19] + {InternalValue("a", 9), false}, // [0, 9] + {InternalValue("b", 9), false}, // [0, 9] + {InternalValue("d", 9), false}, // [0, 9] + {InternalValue("d", 7), true}, // [0, 9] + {InternalValue("e", 7), true}, // [0, 9] + {InternalValue("g", 7), false}, // [0, 9] + {InternalValue("h", 24), true}, // [20, kMaxSequenceNumber] + {InternalValue("i", 24), false}, // [20, kMaxSequenceNumber] + {InternalValue("ii", 14), true}, // [10, 19] + {InternalValue("j", 14), false} // [10, 19] + }); + + auto range_del_compaction_iter = range_del_agg.NewIterator(); + VerifyFragmentedRangeDels(range_del_compaction_iter.get(), {{"a", "b", 20}, + {"a", "b", 10}, + {"b", "c", 10}, + {"c", "e", 10}, + {"c", "e", 8}, + {"e", "g", 8}, + {"h", "i", 25}, + {"ii", "j", 15}}); +} + +TEST_F(RangeDelAggregatorTest, CompactionAggregatorEmptyIteratorLeft) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "e", 10}, {"c", "g", 8}}, + {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); + + std::vector snapshots{9, 19}; + CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + kMaxSequenceNumber)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + Slice start("_"); + Slice end("__"); +} + +TEST_F(RangeDelAggregatorTest, CompactionAggregatorEmptyIteratorRight) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "e", 10}, {"c", "g", 8}}, + {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); + + std::vector snapshots{9, 19}; + CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + kMaxSequenceNumber)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + Slice start("p"); + Slice end("q"); + auto range_del_compaction_iter1 = + range_del_agg.NewIterator(&start, &end, false /* end_key_inclusive */); + VerifyFragmentedRangeDels(range_del_compaction_iter1.get(), {}); + + auto range_del_compaction_iter2 = + range_del_agg.NewIterator(&start, &end, true /* end_key_inclusive */); + VerifyFragmentedRangeDels(range_del_compaction_iter2.get(), {}); +} + +TEST_F(RangeDelAggregatorTest, CompactionAggregatorBoundedIterator) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "e", 10}, {"c", "g", 8}}, + {{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); + + std::vector snapshots{9, 19}; + CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + kMaxSequenceNumber)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + Slice start("bb"); + Slice end("e"); + auto range_del_compaction_iter1 = + range_del_agg.NewIterator(&start, &end, false /* end_key_inclusive */); + VerifyFragmentedRangeDels(range_del_compaction_iter1.get(), + {{"a", "c", 10}, {"c", "e", 10}, {"c", "e", 8}}); + + auto range_del_compaction_iter2 = + range_del_agg.NewIterator(&start, &end, true /* end_key_inclusive */); + VerifyFragmentedRangeDels( + range_del_compaction_iter2.get(), + {{"a", "c", 10}, {"c", "e", 10}, {"c", "e", 8}, {"e", "g", 8}}); +} + +TEST_F(RangeDelAggregatorTest, + CompactionAggregatorBoundedIteratorExtraFragments) { + auto fragment_lists = MakeFragmentedTombstoneLists( + {{{"a", "d", 10}, {"c", "g", 8}}, + {{"b", "c", 20}, {"d", "f", 30}, {"h", "i", 25}, {"ii", "j", 15}}}); + + std::vector snapshots{9, 19}; + CompactionRangeDelAggregator range_del_agg(&bytewise_icmp, snapshots); + for (const auto& fragment_list : fragment_lists) { + std::unique_ptr input_iter( + new FragmentedRangeTombstoneIterator(fragment_list.get(), bytewise_icmp, + kMaxSequenceNumber)); + range_del_agg.AddTombstones(std::move(input_iter)); + } + + Slice start("bb"); + Slice end("e"); + auto range_del_compaction_iter1 = + range_del_agg.NewIterator(&start, &end, false /* end_key_inclusive */); + VerifyFragmentedRangeDels(range_del_compaction_iter1.get(), {{"a", "b", 10}, + {"b", "c", 20}, + {"b", "c", 10}, + {"c", "d", 10}, + {"c", "d", 8}, + {"d", "f", 30}, + {"d", "f", 8}, + {"f", "g", 8}}); + + auto range_del_compaction_iter2 = + range_del_agg.NewIterator(&start, &end, true /* end_key_inclusive */); + VerifyFragmentedRangeDels(range_del_compaction_iter2.get(), {{"a", "b", 10}, + {"b", "c", 20}, + {"b", "c", 10}, + {"c", "d", 10}, + {"c", "d", 8}, + {"d", "f", 30}, + {"d", "f", 8}, + {"f", "g", 8}}); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/range_tombstone_fragmenter.cc b/thirdparty/rocksdb/db/range_tombstone_fragmenter.cc new file mode 100644 index 0000000000..f9d9f2feb4 --- /dev/null +++ b/thirdparty/rocksdb/db/range_tombstone_fragmenter.cc @@ -0,0 +1,438 @@ +// Copyright (c) 2018-present, Facebook, 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). + +#include "db/range_tombstone_fragmenter.h" + +#include +#include +#include + +#include +#include + +#include "util/autovector.h" +#include "util/kv_map.h" +#include "util/vector_iterator.h" + +namespace rocksdb { + +FragmentedRangeTombstoneList::FragmentedRangeTombstoneList( + std::unique_ptr unfragmented_tombstones, + const InternalKeyComparator& icmp, bool for_compaction, + const std::vector& snapshots) { + if (unfragmented_tombstones == nullptr) { + return; + } + bool is_sorted = true; + int num_tombstones = 0; + InternalKey pinned_last_start_key; + Slice last_start_key; + for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid(); + unfragmented_tombstones->Next(), num_tombstones++) { + if (num_tombstones > 0 && + icmp.Compare(last_start_key, unfragmented_tombstones->key()) > 0) { + is_sorted = false; + break; + } + if (unfragmented_tombstones->IsKeyPinned()) { + last_start_key = unfragmented_tombstones->key(); + } else { + pinned_last_start_key.DecodeFrom(unfragmented_tombstones->key()); + last_start_key = pinned_last_start_key.Encode(); + } + } + if (is_sorted) { + FragmentTombstones(std::move(unfragmented_tombstones), icmp, for_compaction, + snapshots); + return; + } + + // Sort the tombstones before fragmenting them. + std::vector keys, values; + keys.reserve(num_tombstones); + values.reserve(num_tombstones); + for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid(); + unfragmented_tombstones->Next()) { + keys.emplace_back(unfragmented_tombstones->key().data(), + unfragmented_tombstones->key().size()); + values.emplace_back(unfragmented_tombstones->value().data(), + unfragmented_tombstones->value().size()); + } + // VectorIterator implicitly sorts by key during construction. + auto iter = std::unique_ptr( + new VectorIterator(std::move(keys), std::move(values), &icmp)); + FragmentTombstones(std::move(iter), icmp, for_compaction, snapshots); +} + +void FragmentedRangeTombstoneList::FragmentTombstones( + std::unique_ptr unfragmented_tombstones, + const InternalKeyComparator& icmp, bool for_compaction, + const std::vector& snapshots) { + Slice cur_start_key(nullptr, 0); + auto cmp = ParsedInternalKeyComparator(&icmp); + + // Stores the end keys and sequence numbers of range tombstones with a start + // key less than or equal to cur_start_key. Provides an ordering by end key + // for use in flush_current_tombstones. + std::set cur_end_keys(cmp); + + // Given the next start key in unfragmented_tombstones, + // flush_current_tombstones writes every tombstone fragment that starts + // and ends with a key before next_start_key, and starts with a key greater + // than or equal to cur_start_key. + auto flush_current_tombstones = [&](const Slice& next_start_key) { + auto it = cur_end_keys.begin(); + bool reached_next_start_key = false; + for (; it != cur_end_keys.end() && !reached_next_start_key; ++it) { + Slice cur_end_key = it->user_key; + if (icmp.user_comparator()->Compare(cur_start_key, cur_end_key) == 0) { + // Empty tombstone. + continue; + } + if (icmp.user_comparator()->Compare(next_start_key, cur_end_key) <= 0) { + // All of the end keys in [it, cur_end_keys.end()) are after + // next_start_key, so the tombstones they represent can be used in + // fragments that start with keys greater than or equal to + // next_start_key. However, the end keys we already passed will not be + // used in any more tombstone fragments. + // + // Remove the fully fragmented tombstones and stop iteration after a + // final round of flushing to preserve the tombstones we can create more + // fragments from. + reached_next_start_key = true; + cur_end_keys.erase(cur_end_keys.begin(), it); + cur_end_key = next_start_key; + } + + // Flush a range tombstone fragment [cur_start_key, cur_end_key), which + // should not overlap with the last-flushed tombstone fragment. + assert(tombstones_.empty() || + icmp.user_comparator()->Compare(tombstones_.back().end_key, + cur_start_key) <= 0); + + // Sort the sequence numbers of the tombstones being fragmented in + // descending order, and then flush them in that order. + autovector seqnums_to_flush; + for (auto flush_it = it; flush_it != cur_end_keys.end(); ++flush_it) { + seqnums_to_flush.push_back(flush_it->sequence); + } + std::sort(seqnums_to_flush.begin(), seqnums_to_flush.end(), + std::greater()); + + size_t start_idx = tombstone_seqs_.size(); + size_t end_idx = start_idx + seqnums_to_flush.size(); + + if (for_compaction) { + // Drop all tombstone seqnums that are not preserved by a snapshot. + SequenceNumber next_snapshot = kMaxSequenceNumber; + for (auto seq : seqnums_to_flush) { + if (seq <= next_snapshot) { + // This seqnum is visible by a lower snapshot. + tombstone_seqs_.push_back(seq); + seq_set_.insert(seq); + auto upper_bound_it = + std::lower_bound(snapshots.begin(), snapshots.end(), seq); + if (upper_bound_it == snapshots.begin()) { + // This seqnum is the topmost one visible by the earliest + // snapshot. None of the seqnums below it will be visible, so we + // can skip them. + break; + } + next_snapshot = *std::prev(upper_bound_it); + } + } + end_idx = tombstone_seqs_.size(); + } else { + // The fragmentation is being done for reads, so preserve all seqnums. + tombstone_seqs_.insert(tombstone_seqs_.end(), seqnums_to_flush.begin(), + seqnums_to_flush.end()); + seq_set_.insert(seqnums_to_flush.begin(), seqnums_to_flush.end()); + } + + assert(start_idx < end_idx); + tombstones_.emplace_back(cur_start_key, cur_end_key, start_idx, end_idx); + + cur_start_key = cur_end_key; + } + if (!reached_next_start_key) { + // There is a gap between the last flushed tombstone fragment and + // the next tombstone's start key. Remove all the end keys in + // the working set, since we have fully fragmented their corresponding + // tombstones. + cur_end_keys.clear(); + } + cur_start_key = next_start_key; + }; + + pinned_iters_mgr_.StartPinning(); + + bool no_tombstones = true; + for (unfragmented_tombstones->SeekToFirst(); unfragmented_tombstones->Valid(); + unfragmented_tombstones->Next()) { + const Slice& ikey = unfragmented_tombstones->key(); + Slice tombstone_start_key = ExtractUserKey(ikey); + SequenceNumber tombstone_seq = GetInternalKeySeqno(ikey); + if (!unfragmented_tombstones->IsKeyPinned()) { + pinned_slices_.emplace_back(tombstone_start_key.data(), + tombstone_start_key.size()); + tombstone_start_key = pinned_slices_.back(); + } + no_tombstones = false; + + Slice tombstone_end_key = unfragmented_tombstones->value(); + if (!unfragmented_tombstones->IsValuePinned()) { + pinned_slices_.emplace_back(tombstone_end_key.data(), + tombstone_end_key.size()); + tombstone_end_key = pinned_slices_.back(); + } + if (!cur_end_keys.empty() && icmp.user_comparator()->Compare( + cur_start_key, tombstone_start_key) != 0) { + // The start key has changed. Flush all tombstones that start before + // this new start key. + flush_current_tombstones(tombstone_start_key); + } + cur_start_key = tombstone_start_key; + + cur_end_keys.emplace(tombstone_end_key, tombstone_seq, kTypeRangeDeletion); + } + if (!cur_end_keys.empty()) { + ParsedInternalKey last_end_key = *std::prev(cur_end_keys.end()); + flush_current_tombstones(last_end_key.user_key); + } + + if (!no_tombstones) { + pinned_iters_mgr_.PinIterator(unfragmented_tombstones.release(), + false /* arena */); + } +} + +bool FragmentedRangeTombstoneList::ContainsRange(SequenceNumber lower, + SequenceNumber upper) const { + auto seq_it = seq_set_.lower_bound(lower); + return seq_it != seq_set_.end() && *seq_it <= upper; +} + +FragmentedRangeTombstoneIterator::FragmentedRangeTombstoneIterator( + const FragmentedRangeTombstoneList* tombstones, + const InternalKeyComparator& icmp, SequenceNumber _upper_bound, + SequenceNumber _lower_bound) + : tombstone_start_cmp_(icmp.user_comparator()), + tombstone_end_cmp_(icmp.user_comparator()), + icmp_(&icmp), + ucmp_(icmp.user_comparator()), + tombstones_(tombstones), + upper_bound_(_upper_bound), + lower_bound_(_lower_bound) { + assert(tombstones_ != nullptr); + Invalidate(); +} + +FragmentedRangeTombstoneIterator::FragmentedRangeTombstoneIterator( + const std::shared_ptr& tombstones, + const InternalKeyComparator& icmp, SequenceNumber _upper_bound, + SequenceNumber _lower_bound) + : tombstone_start_cmp_(icmp.user_comparator()), + tombstone_end_cmp_(icmp.user_comparator()), + icmp_(&icmp), + ucmp_(icmp.user_comparator()), + tombstones_ref_(tombstones), + tombstones_(tombstones_ref_.get()), + upper_bound_(_upper_bound), + lower_bound_(_lower_bound) { + assert(tombstones_ != nullptr); + Invalidate(); +} + +void FragmentedRangeTombstoneIterator::SeekToFirst() { + pos_ = tombstones_->begin(); + seq_pos_ = tombstones_->seq_begin(); +} + +void FragmentedRangeTombstoneIterator::SeekToTopFirst() { + if (tombstones_->empty()) { + Invalidate(); + return; + } + pos_ = tombstones_->begin(); + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); + ScanForwardToVisibleTombstone(); +} + +void FragmentedRangeTombstoneIterator::SeekToLast() { + pos_ = std::prev(tombstones_->end()); + seq_pos_ = std::prev(tombstones_->seq_end()); +} + +void FragmentedRangeTombstoneIterator::SeekToTopLast() { + if (tombstones_->empty()) { + Invalidate(); + return; + } + pos_ = std::prev(tombstones_->end()); + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); + ScanBackwardToVisibleTombstone(); +} + +void FragmentedRangeTombstoneIterator::Seek(const Slice& target) { + if (tombstones_->empty()) { + Invalidate(); + return; + } + SeekToCoveringTombstone(target); + ScanForwardToVisibleTombstone(); +} + +void FragmentedRangeTombstoneIterator::SeekForPrev(const Slice& target) { + if (tombstones_->empty()) { + Invalidate(); + return; + } + SeekForPrevToCoveringTombstone(target); + ScanBackwardToVisibleTombstone(); +} + +void FragmentedRangeTombstoneIterator::SeekToCoveringTombstone( + const Slice& target) { + pos_ = std::upper_bound(tombstones_->begin(), tombstones_->end(), target, + tombstone_end_cmp_); + if (pos_ == tombstones_->end()) { + // All tombstones end before target. + seq_pos_ = tombstones_->seq_end(); + return; + } + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); +} + +void FragmentedRangeTombstoneIterator::SeekForPrevToCoveringTombstone( + const Slice& target) { + if (tombstones_->empty()) { + Invalidate(); + return; + } + pos_ = std::upper_bound(tombstones_->begin(), tombstones_->end(), target, + tombstone_start_cmp_); + if (pos_ == tombstones_->begin()) { + // All tombstones start after target. + Invalidate(); + return; + } + --pos_; + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); +} + +void FragmentedRangeTombstoneIterator::ScanForwardToVisibleTombstone() { + while (pos_ != tombstones_->end() && + (seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx) || + *seq_pos_ < lower_bound_)) { + ++pos_; + if (pos_ == tombstones_->end()) { + Invalidate(); + return; + } + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); + } +} + +void FragmentedRangeTombstoneIterator::ScanBackwardToVisibleTombstone() { + while (pos_ != tombstones_->end() && + (seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx) || + *seq_pos_ < lower_bound_)) { + if (pos_ == tombstones_->begin()) { + Invalidate(); + return; + } + --pos_; + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); + } +} + +void FragmentedRangeTombstoneIterator::Next() { + ++seq_pos_; + if (seq_pos_ == tombstones_->seq_iter(pos_->seq_end_idx)) { + ++pos_; + } +} + +void FragmentedRangeTombstoneIterator::TopNext() { + ++pos_; + if (pos_ == tombstones_->end()) { + return; + } + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); + ScanForwardToVisibleTombstone(); +} + +void FragmentedRangeTombstoneIterator::Prev() { + if (seq_pos_ == tombstones_->seq_begin()) { + Invalidate(); + return; + } + --seq_pos_; + if (pos_ == tombstones_->end() || + seq_pos_ == tombstones_->seq_iter(pos_->seq_start_idx - 1)) { + --pos_; + } +} + +void FragmentedRangeTombstoneIterator::TopPrev() { + if (pos_ == tombstones_->begin()) { + Invalidate(); + return; + } + --pos_; + seq_pos_ = std::lower_bound(tombstones_->seq_iter(pos_->seq_start_idx), + tombstones_->seq_iter(pos_->seq_end_idx), + upper_bound_, std::greater()); + ScanBackwardToVisibleTombstone(); +} + +bool FragmentedRangeTombstoneIterator::Valid() const { + return tombstones_ != nullptr && pos_ != tombstones_->end(); +} + +SequenceNumber FragmentedRangeTombstoneIterator::MaxCoveringTombstoneSeqnum( + const Slice& user_key) { + SeekToCoveringTombstone(user_key); + return ValidPos() && ucmp_->Compare(start_key(), user_key) <= 0 ? seq() : 0; +} + +std::map> +FragmentedRangeTombstoneIterator::SplitBySnapshot( + const std::vector& snapshots) { + std::map> + splits; + SequenceNumber lower = 0; + SequenceNumber upper; + for (size_t i = 0; i <= snapshots.size(); i++) { + if (i >= snapshots.size()) { + upper = kMaxSequenceNumber; + } else { + upper = snapshots[i]; + } + if (tombstones_->ContainsRange(lower, upper)) { + splits.emplace(upper, std::unique_ptr( + new FragmentedRangeTombstoneIterator( + tombstones_, *icmp_, upper, lower))); + } + lower = upper + 1; + } + return splits; +} + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/range_tombstone_fragmenter.h b/thirdparty/rocksdb/db/range_tombstone_fragmenter.h new file mode 100644 index 0000000000..a0b77b6777 --- /dev/null +++ b/thirdparty/rocksdb/db/range_tombstone_fragmenter.h @@ -0,0 +1,254 @@ +// Copyright (c) 2018-present, Facebook, 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). + +#pragma once + +#include +#include +#include +#include +#include + +#include "db/dbformat.h" +#include "db/pinned_iterators_manager.h" +#include "rocksdb/status.h" +#include "table/internal_iterator.h" + +namespace rocksdb { + +struct FragmentedRangeTombstoneList { + public: + // A compact representation of a "stack" of range tombstone fragments, which + // start and end at the same user keys but have different sequence numbers. + // The members seq_start_idx and seq_end_idx are intended to be parameters to + // seq_iter(). + struct RangeTombstoneStack { + RangeTombstoneStack(const Slice& start, const Slice& end, size_t start_idx, + size_t end_idx) + : start_key(start), + end_key(end), + seq_start_idx(start_idx), + seq_end_idx(end_idx) {} + + Slice start_key; + Slice end_key; + size_t seq_start_idx; + size_t seq_end_idx; + }; + FragmentedRangeTombstoneList( + std::unique_ptr unfragmented_tombstones, + const InternalKeyComparator& icmp, bool for_compaction = false, + const std::vector& snapshots = {}); + + std::vector::const_iterator begin() const { + return tombstones_.begin(); + } + + std::vector::const_iterator end() const { + return tombstones_.end(); + } + + std::vector::const_iterator seq_iter(size_t idx) const { + return std::next(tombstone_seqs_.begin(), idx); + } + + std::vector::const_iterator seq_begin() const { + return tombstone_seqs_.begin(); + } + + std::vector::const_iterator seq_end() const { + return tombstone_seqs_.end(); + } + + bool empty() const { return tombstones_.empty(); } + + // Returns true if the stored tombstones contain with one with a sequence + // number in [lower, upper]. + bool ContainsRange(SequenceNumber lower, SequenceNumber upper) const; + + private: + // Given an ordered range tombstone iterator unfragmented_tombstones, + // "fragment" the tombstones into non-overlapping pieces, and store them in + // tombstones_ and tombstone_seqs_. + void FragmentTombstones( + std::unique_ptr unfragmented_tombstones, + const InternalKeyComparator& icmp, bool for_compaction, + const std::vector& snapshots); + + std::vector tombstones_; + std::vector tombstone_seqs_; + std::set seq_set_; + std::list pinned_slices_; + PinnedIteratorsManager pinned_iters_mgr_; +}; + +// FragmentedRangeTombstoneIterator converts an InternalIterator of a range-del +// meta block into an iterator over non-overlapping tombstone fragments. The +// tombstone fragmentation process should be more efficient than the range +// tombstone collapsing algorithm in RangeDelAggregator because this leverages +// the internal key ordering already provided by the input iterator, if +// applicable (when the iterator is unsorted, a new sorted iterator is created +// before proceeding). If there are few overlaps, creating a +// FragmentedRangeTombstoneIterator should be O(n), while the RangeDelAggregator +// tombstone collapsing is always O(n log n). +class FragmentedRangeTombstoneIterator : public InternalIterator { + public: + FragmentedRangeTombstoneIterator( + const FragmentedRangeTombstoneList* tombstones, + const InternalKeyComparator& icmp, SequenceNumber upper_bound, + SequenceNumber lower_bound = 0); + FragmentedRangeTombstoneIterator( + const std::shared_ptr& tombstones, + const InternalKeyComparator& icmp, SequenceNumber upper_bound, + SequenceNumber lower_bound = 0); + + void SeekToFirst() override; + void SeekToLast() override; + + void SeekToTopFirst(); + void SeekToTopLast(); + + // NOTE: Seek and SeekForPrev do not behave in the way InternalIterator + // seeking should behave. This is OK because they are not currently used, but + // eventually FragmentedRangeTombstoneIterator should no longer implement + // InternalIterator. + // + // Seeks to the range tombstone that covers target at a seqnum in the + // snapshot. If no such tombstone exists, seek to the earliest tombstone in + // the snapshot that ends after target. + void Seek(const Slice& target) override; + // Seeks to the range tombstone that covers target at a seqnum in the + // snapshot. If no such tombstone exists, seek to the latest tombstone in the + // snapshot that starts before target. + void SeekForPrev(const Slice& target) override; + + void Next() override; + void Prev() override; + + void TopNext(); + void TopPrev(); + + bool Valid() const override; + Slice key() const override { + MaybePinKey(); + return current_start_key_.Encode(); + } + Slice value() const override { return pos_->end_key; } + bool IsKeyPinned() const override { return false; } + bool IsValuePinned() const override { return true; } + Status status() const override { return Status::OK(); } + + bool empty() const { return tombstones_->empty(); } + void Invalidate() { + pos_ = tombstones_->end(); + seq_pos_ = tombstones_->seq_end(); + } + + RangeTombstone Tombstone() const { + return RangeTombstone(start_key(), end_key(), seq()); + } + Slice start_key() const { return pos_->start_key; } + Slice end_key() const { return pos_->end_key; } + SequenceNumber seq() const { return *seq_pos_; } + ParsedInternalKey parsed_start_key() const { + return ParsedInternalKey(pos_->start_key, kMaxSequenceNumber, + kTypeRangeDeletion); + } + ParsedInternalKey parsed_end_key() const { + return ParsedInternalKey(pos_->end_key, kMaxSequenceNumber, + kTypeRangeDeletion); + } + + SequenceNumber MaxCoveringTombstoneSeqnum(const Slice& user_key); + + // Splits the iterator into n+1 iterators (where n is the number of + // snapshots), each providing a view over a "stripe" of sequence numbers. The + // iterators are keyed by the upper bound of their ranges (the provided + // snapshots + kMaxSequenceNumber). + // + // NOTE: the iterators in the returned map are no longer valid if their + // parent iterator is deleted, since they do not modify the refcount of the + // underlying tombstone list. Therefore, this map should be deleted before + // the parent iterator. + std::map> + SplitBySnapshot(const std::vector& snapshots); + + SequenceNumber upper_bound() const { return upper_bound_; } + SequenceNumber lower_bound() const { return lower_bound_; } + + private: + using RangeTombstoneStack = FragmentedRangeTombstoneList::RangeTombstoneStack; + + struct RangeTombstoneStackStartComparator { + explicit RangeTombstoneStackStartComparator(const Comparator* c) : cmp(c) {} + + bool operator()(const RangeTombstoneStack& a, + const RangeTombstoneStack& b) const { + return cmp->Compare(a.start_key, b.start_key) < 0; + } + + bool operator()(const RangeTombstoneStack& a, const Slice& b) const { + return cmp->Compare(a.start_key, b) < 0; + } + + bool operator()(const Slice& a, const RangeTombstoneStack& b) const { + return cmp->Compare(a, b.start_key) < 0; + } + + const Comparator* cmp; + }; + + struct RangeTombstoneStackEndComparator { + explicit RangeTombstoneStackEndComparator(const Comparator* c) : cmp(c) {} + + bool operator()(const RangeTombstoneStack& a, + const RangeTombstoneStack& b) const { + return cmp->Compare(a.end_key, b.end_key) < 0; + } + + bool operator()(const RangeTombstoneStack& a, const Slice& b) const { + return cmp->Compare(a.end_key, b) < 0; + } + + bool operator()(const Slice& a, const RangeTombstoneStack& b) const { + return cmp->Compare(a, b.end_key) < 0; + } + + const Comparator* cmp; + }; + + void MaybePinKey() const { + if (pos_ != tombstones_->end() && seq_pos_ != tombstones_->seq_end() && + (pinned_pos_ != pos_ || pinned_seq_pos_ != seq_pos_)) { + current_start_key_.Set(pos_->start_key, *seq_pos_, kTypeRangeDeletion); + pinned_pos_ = pos_; + pinned_seq_pos_ = seq_pos_; + } + } + + void SeekToCoveringTombstone(const Slice& key); + void SeekForPrevToCoveringTombstone(const Slice& key); + void ScanForwardToVisibleTombstone(); + void ScanBackwardToVisibleTombstone(); + bool ValidPos() const { + return Valid() && seq_pos_ != tombstones_->seq_iter(pos_->seq_end_idx); + } + + const RangeTombstoneStackStartComparator tombstone_start_cmp_; + const RangeTombstoneStackEndComparator tombstone_end_cmp_; + const InternalKeyComparator* icmp_; + const Comparator* ucmp_; + std::shared_ptr tombstones_ref_; + const FragmentedRangeTombstoneList* tombstones_; + SequenceNumber upper_bound_; + SequenceNumber lower_bound_; + std::vector::const_iterator pos_; + std::vector::const_iterator seq_pos_; + mutable std::vector::const_iterator pinned_pos_; + mutable std::vector::const_iterator pinned_seq_pos_; + mutable InternalKey current_start_key_; +}; + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/range_tombstone_fragmenter_test.cc b/thirdparty/rocksdb/db/range_tombstone_fragmenter_test.cc new file mode 100644 index 0000000000..ddd3f77417 --- /dev/null +++ b/thirdparty/rocksdb/db/range_tombstone_fragmenter_test.cc @@ -0,0 +1,552 @@ +// Copyright (c) 2018-present, Facebook, 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). + +#include "db/range_tombstone_fragmenter.h" + +#include "db/db_test_util.h" +#include "rocksdb/comparator.h" +#include "util/testutil.h" + +namespace rocksdb { + +class RangeTombstoneFragmenterTest : public testing::Test {}; + +namespace { + +static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator()); + +std::unique_ptr MakeRangeDelIter( + const std::vector& range_dels) { + std::vector keys, values; + for (const auto& range_del : range_dels) { + auto key_and_value = range_del.Serialize(); + keys.push_back(key_and_value.first.Encode().ToString()); + values.push_back(key_and_value.second.ToString()); + } + return std::unique_ptr( + new test::VectorIterator(keys, values)); +} + +void CheckIterPosition(const RangeTombstone& tombstone, + const FragmentedRangeTombstoneIterator* iter) { + // Test InternalIterator interface. + EXPECT_EQ(tombstone.start_key_, ExtractUserKey(iter->key())); + EXPECT_EQ(tombstone.end_key_, iter->value()); + EXPECT_EQ(tombstone.seq_, iter->seq()); + + // Test FragmentedRangeTombstoneIterator interface. + EXPECT_EQ(tombstone.start_key_, iter->start_key()); + EXPECT_EQ(tombstone.end_key_, iter->end_key()); + EXPECT_EQ(tombstone.seq_, GetInternalKeySeqno(iter->key())); +} + +void VerifyFragmentedRangeDels( + FragmentedRangeTombstoneIterator* iter, + const std::vector& expected_tombstones) { + iter->SeekToFirst(); + for (size_t i = 0; i < expected_tombstones.size(); i++, iter->Next()) { + ASSERT_TRUE(iter->Valid()); + CheckIterPosition(expected_tombstones[i], iter); + } + EXPECT_FALSE(iter->Valid()); +} + +void VerifyVisibleTombstones( + FragmentedRangeTombstoneIterator* iter, + const std::vector& expected_tombstones) { + iter->SeekToTopFirst(); + for (size_t i = 0; i < expected_tombstones.size(); i++, iter->TopNext()) { + ASSERT_TRUE(iter->Valid()); + CheckIterPosition(expected_tombstones[i], iter); + } + EXPECT_FALSE(iter->Valid()); +} + +struct SeekTestCase { + Slice seek_target; + RangeTombstone expected_position; + bool out_of_range; +}; + +void VerifySeek(FragmentedRangeTombstoneIterator* iter, + const std::vector& cases) { + for (const auto& testcase : cases) { + iter->Seek(testcase.seek_target); + if (testcase.out_of_range) { + ASSERT_FALSE(iter->Valid()); + } else { + ASSERT_TRUE(iter->Valid()); + CheckIterPosition(testcase.expected_position, iter); + } + } +} + +void VerifySeekForPrev(FragmentedRangeTombstoneIterator* iter, + const std::vector& cases) { + for (const auto& testcase : cases) { + iter->SeekForPrev(testcase.seek_target); + if (testcase.out_of_range) { + ASSERT_FALSE(iter->Valid()); + } else { + ASSERT_TRUE(iter->Valid()); + CheckIterPosition(testcase.expected_position, iter); + } + } +} + +struct MaxCoveringTombstoneSeqnumTestCase { + Slice user_key; + SequenceNumber result; +}; + +void VerifyMaxCoveringTombstoneSeqnum( + FragmentedRangeTombstoneIterator* iter, + const std::vector& cases) { + for (const auto& testcase : cases) { + EXPECT_EQ(testcase.result, + iter->MaxCoveringTombstoneSeqnum(testcase.user_key)); + } +} + +} // anonymous namespace + +TEST_F(RangeTombstoneFragmenterTest, NonOverlappingTombstones) { + auto range_del_iter = MakeRangeDelIter({{"a", "b", 10}, {"c", "d", 5}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + ASSERT_EQ(0, iter.lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); + VerifyFragmentedRangeDels(&iter, {{"a", "b", 10}, {"c", "d", 5}}); + VerifyMaxCoveringTombstoneSeqnum(&iter, + {{"", 0}, {"a", 10}, {"b", 0}, {"c", 5}}); +} + +TEST_F(RangeTombstoneFragmenterTest, OverlappingTombstones) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, {"c", "g", 15}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + ASSERT_EQ(0, iter.lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); + VerifyFragmentedRangeDels( + &iter, {{"a", "c", 10}, {"c", "e", 15}, {"c", "e", 10}, {"e", "g", 15}}); + VerifyMaxCoveringTombstoneSeqnum(&iter, + {{"a", 10}, {"c", 15}, {"e", 15}, {"g", 0}}); +} + +TEST_F(RangeTombstoneFragmenterTest, ContiguousTombstones) { + auto range_del_iter = MakeRangeDelIter( + {{"a", "c", 10}, {"c", "e", 20}, {"c", "e", 5}, {"e", "g", 15}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + ASSERT_EQ(0, iter.lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); + VerifyFragmentedRangeDels( + &iter, {{"a", "c", 10}, {"c", "e", 20}, {"c", "e", 5}, {"e", "g", 15}}); + VerifyMaxCoveringTombstoneSeqnum(&iter, + {{"a", 10}, {"c", 20}, {"e", 15}, {"g", 0}}); +} + +TEST_F(RangeTombstoneFragmenterTest, RepeatedStartAndEndKey) { + auto range_del_iter = + MakeRangeDelIter({{"a", "c", 10}, {"a", "c", 7}, {"a", "c", 3}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + ASSERT_EQ(0, iter.lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); + VerifyFragmentedRangeDels(&iter, + {{"a", "c", 10}, {"a", "c", 7}, {"a", "c", 3}}); + VerifyMaxCoveringTombstoneSeqnum(&iter, {{"a", 10}, {"b", 10}, {"c", 0}}); +} + +TEST_F(RangeTombstoneFragmenterTest, RepeatedStartKeyDifferentEndKeys) { + auto range_del_iter = + MakeRangeDelIter({{"a", "e", 10}, {"a", "g", 7}, {"a", "c", 3}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + ASSERT_EQ(0, iter.lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); + VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, + {"a", "c", 7}, + {"a", "c", 3}, + {"c", "e", 10}, + {"c", "e", 7}, + {"e", "g", 7}}); + VerifyMaxCoveringTombstoneSeqnum(&iter, + {{"a", 10}, {"c", 10}, {"e", 7}, {"g", 0}}); +} + +TEST_F(RangeTombstoneFragmenterTest, RepeatedStartKeyMixedEndKeys) { + auto range_del_iter = MakeRangeDelIter({{"a", "c", 30}, + {"a", "g", 20}, + {"a", "e", 10}, + {"a", "g", 7}, + {"a", "c", 3}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + ASSERT_EQ(0, iter.lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, iter.upper_bound()); + VerifyFragmentedRangeDels(&iter, {{"a", "c", 30}, + {"a", "c", 20}, + {"a", "c", 10}, + {"a", "c", 7}, + {"a", "c", 3}, + {"c", "e", 20}, + {"c", "e", 10}, + {"c", "e", 7}, + {"e", "g", 20}, + {"e", "g", 7}}); + VerifyMaxCoveringTombstoneSeqnum(&iter, + {{"a", 30}, {"c", 20}, {"e", 20}, {"g", 0}}); +} + +TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKey) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"c", "g", 8}, + {"c", "i", 6}, + {"j", "n", 4}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, + 9 /* upper_bound */); + FragmentedRangeTombstoneIterator iter3(&fragment_list, bytewise_icmp, + 7 /* upper_bound */); + FragmentedRangeTombstoneIterator iter4(&fragment_list, bytewise_icmp, + 5 /* upper_bound */); + FragmentedRangeTombstoneIterator iter5(&fragment_list, bytewise_icmp, + 3 /* upper_bound */); + for (auto* iter : {&iter1, &iter2, &iter3, &iter4, &iter5}) { + VerifyFragmentedRangeDels(iter, {{"a", "c", 10}, + {"c", "e", 10}, + {"c", "e", 8}, + {"c", "e", 6}, + {"e", "g", 8}, + {"e", "g", 6}, + {"g", "i", 6}, + {"j", "l", 4}, + {"j", "l", 2}, + {"l", "n", 4}}); + } + + ASSERT_EQ(0, iter1.lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, iter1.upper_bound()); + VerifyVisibleTombstones(&iter1, {{"a", "c", 10}, + {"c", "e", 10}, + {"e", "g", 8}, + {"g", "i", 6}, + {"j", "l", 4}, + {"l", "n", 4}}); + VerifyMaxCoveringTombstoneSeqnum( + &iter1, {{"a", 10}, {"c", 10}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}}); + + ASSERT_EQ(0, iter2.lower_bound()); + ASSERT_EQ(9, iter2.upper_bound()); + VerifyVisibleTombstones(&iter2, {{"c", "e", 8}, + {"e", "g", 8}, + {"g", "i", 6}, + {"j", "l", 4}, + {"l", "n", 4}}); + VerifyMaxCoveringTombstoneSeqnum( + &iter2, {{"a", 0}, {"c", 8}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}}); + + ASSERT_EQ(0, iter3.lower_bound()); + ASSERT_EQ(7, iter3.upper_bound()); + VerifyVisibleTombstones(&iter3, {{"c", "e", 6}, + {"e", "g", 6}, + {"g", "i", 6}, + {"j", "l", 4}, + {"l", "n", 4}}); + VerifyMaxCoveringTombstoneSeqnum( + &iter3, {{"a", 0}, {"c", 6}, {"e", 6}, {"i", 0}, {"j", 4}, {"m", 4}}); + + ASSERT_EQ(0, iter4.lower_bound()); + ASSERT_EQ(5, iter4.upper_bound()); + VerifyVisibleTombstones(&iter4, {{"j", "l", 4}, {"l", "n", 4}}); + VerifyMaxCoveringTombstoneSeqnum( + &iter4, {{"a", 0}, {"c", 0}, {"e", 0}, {"i", 0}, {"j", 4}, {"m", 4}}); + + ASSERT_EQ(0, iter5.lower_bound()); + ASSERT_EQ(3, iter5.upper_bound()); + VerifyVisibleTombstones(&iter5, {{"j", "l", 2}}); + VerifyMaxCoveringTombstoneSeqnum( + &iter5, {{"a", 0}, {"c", 0}, {"e", 0}, {"i", 0}, {"j", 2}, {"m", 0}}); +} + +TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKeyUnordered) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"j", "n", 4}, + {"c", "i", 6}, + {"c", "g", 8}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + 9 /* upper_bound */); + ASSERT_EQ(0, iter.lower_bound()); + ASSERT_EQ(9, iter.upper_bound()); + VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, + {"c", "e", 10}, + {"c", "e", 8}, + {"c", "e", 6}, + {"e", "g", 8}, + {"e", "g", 6}, + {"g", "i", 6}, + {"j", "l", 4}, + {"j", "l", 2}, + {"l", "n", 4}}); + VerifyMaxCoveringTombstoneSeqnum( + &iter, {{"a", 0}, {"c", 8}, {"e", 8}, {"i", 0}, {"j", 4}, {"m", 4}}); +} + +TEST_F(RangeTombstoneFragmenterTest, OverlapAndRepeatedStartKeyForCompaction) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"j", "n", 4}, + {"c", "i", 6}, + {"c", "g", 8}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list( + std::move(range_del_iter), bytewise_icmp, true /* for_compaction */, + {} /* snapshots */); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber /* upper_bound */); + VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, + {"c", "e", 10}, + {"e", "g", 8}, + {"g", "i", 6}, + {"j", "l", 4}, + {"l", "n", 4}}); +} + +TEST_F(RangeTombstoneFragmenterTest, + OverlapAndRepeatedStartKeyForCompactionWithSnapshot) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"j", "n", 4}, + {"c", "i", 6}, + {"c", "g", 8}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list( + std::move(range_del_iter), bytewise_icmp, true /* for_compaction */, + {20, 9} /* upper_bounds */); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber /* upper_bound */); + VerifyFragmentedRangeDels(&iter, {{"a", "c", 10}, + {"c", "e", 10}, + {"c", "e", 8}, + {"e", "g", 8}, + {"g", "i", 6}, + {"j", "l", 4}, + {"l", "n", 4}}); +} + +TEST_F(RangeTombstoneFragmenterTest, IteratorSplitNoSnapshots) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"j", "n", 4}, + {"c", "i", 6}, + {"c", "g", 8}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber /* upper_bound */); + + auto split_iters = iter.SplitBySnapshot({} /* snapshots */); + ASSERT_EQ(1, split_iters.size()); + + auto* split_iter = split_iters[kMaxSequenceNumber].get(); + ASSERT_EQ(0, split_iter->lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, split_iter->upper_bound()); + VerifyVisibleTombstones(split_iter, {{"a", "c", 10}, + {"c", "e", 10}, + {"e", "g", 8}, + {"g", "i", 6}, + {"j", "l", 4}, + {"l", "n", 4}}); +} + +TEST_F(RangeTombstoneFragmenterTest, IteratorSplitWithSnapshots) { + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"j", "n", 4}, + {"c", "i", 6}, + {"c", "g", 8}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber /* upper_bound */); + + auto split_iters = iter.SplitBySnapshot({3, 5, 7, 9} /* snapshots */); + ASSERT_EQ(5, split_iters.size()); + + auto* split_iter1 = split_iters[3].get(); + ASSERT_EQ(0, split_iter1->lower_bound()); + ASSERT_EQ(3, split_iter1->upper_bound()); + VerifyVisibleTombstones(split_iter1, {{"j", "l", 2}}); + + auto* split_iter2 = split_iters[5].get(); + ASSERT_EQ(4, split_iter2->lower_bound()); + ASSERT_EQ(5, split_iter2->upper_bound()); + VerifyVisibleTombstones(split_iter2, {{"j", "l", 4}, {"l", "n", 4}}); + + auto* split_iter3 = split_iters[7].get(); + ASSERT_EQ(6, split_iter3->lower_bound()); + ASSERT_EQ(7, split_iter3->upper_bound()); + VerifyVisibleTombstones(split_iter3, + {{"c", "e", 6}, {"e", "g", 6}, {"g", "i", 6}}); + + auto* split_iter4 = split_iters[9].get(); + ASSERT_EQ(8, split_iter4->lower_bound()); + ASSERT_EQ(9, split_iter4->upper_bound()); + VerifyVisibleTombstones(split_iter4, {{"c", "e", 8}, {"e", "g", 8}}); + + auto* split_iter5 = split_iters[kMaxSequenceNumber].get(); + ASSERT_EQ(10, split_iter5->lower_bound()); + ASSERT_EQ(kMaxSequenceNumber, split_iter5->upper_bound()); + VerifyVisibleTombstones(split_iter5, {{"a", "c", 10}, {"c", "e", 10}}); +} + +TEST_F(RangeTombstoneFragmenterTest, SeekStartKey) { + // Same tombstones as OverlapAndRepeatedStartKey. + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"c", "g", 8}, + {"c", "i", 6}, + {"j", "n", 4}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + + FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + VerifySeek( + &iter1, + {{"a", {"a", "c", 10}}, {"e", {"e", "g", 8}}, {"l", {"l", "n", 4}}}); + VerifySeekForPrev( + &iter1, + {{"a", {"a", "c", 10}}, {"e", {"e", "g", 8}}, {"l", {"l", "n", 4}}}); + + FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, + 3 /* upper_bound */); + VerifySeek(&iter2, {{"a", {"j", "l", 2}}, + {"e", {"j", "l", 2}}, + {"l", {}, true /* out of range */}}); + VerifySeekForPrev(&iter2, {{"a", {}, true /* out of range */}, + {"e", {}, true /* out of range */}, + {"l", {"j", "l", 2}}}); +} + +TEST_F(RangeTombstoneFragmenterTest, SeekCovered) { + // Same tombstones as OverlapAndRepeatedStartKey. + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"c", "g", 8}, + {"c", "i", 6}, + {"j", "n", 4}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + + FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + VerifySeek( + &iter1, + {{"b", {"a", "c", 10}}, {"f", {"e", "g", 8}}, {"m", {"l", "n", 4}}}); + VerifySeekForPrev( + &iter1, + {{"b", {"a", "c", 10}}, {"f", {"e", "g", 8}}, {"m", {"l", "n", 4}}}); + + FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, + 3 /* upper_bound */); + VerifySeek(&iter2, {{"b", {"j", "l", 2}}, + {"f", {"j", "l", 2}}, + {"m", {}, true /* out of range */}}); + VerifySeekForPrev(&iter2, {{"b", {}, true /* out of range */}, + {"f", {}, true /* out of range */}, + {"m", {"j", "l", 2}}}); +} + +TEST_F(RangeTombstoneFragmenterTest, SeekEndKey) { + // Same tombstones as OverlapAndRepeatedStartKey. + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"c", "g", 8}, + {"c", "i", 6}, + {"j", "n", 4}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + + FragmentedRangeTombstoneIterator iter1(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + VerifySeek(&iter1, {{"c", {"c", "e", 10}}, + {"g", {"g", "i", 6}}, + {"i", {"j", "l", 4}}, + {"n", {}, true /* out of range */}}); + VerifySeekForPrev(&iter1, {{"c", {"c", "e", 10}}, + {"g", {"g", "i", 6}}, + {"i", {"g", "i", 6}}, + {"n", {"l", "n", 4}}}); + + FragmentedRangeTombstoneIterator iter2(&fragment_list, bytewise_icmp, + 3 /* upper_bound */); + VerifySeek(&iter2, {{"c", {"j", "l", 2}}, + {"g", {"j", "l", 2}}, + {"i", {"j", "l", 2}}, + {"n", {}, true /* out of range */}}); + VerifySeekForPrev(&iter2, {{"c", {}, true /* out of range */}, + {"g", {}, true /* out of range */}, + {"i", {}, true /* out of range */}, + {"n", {"j", "l", 2}}}); +} + +TEST_F(RangeTombstoneFragmenterTest, SeekOutOfBounds) { + // Same tombstones as OverlapAndRepeatedStartKey. + auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, + {"c", "g", 8}, + {"c", "i", 6}, + {"j", "n", 4}, + {"j", "l", 2}}); + + FragmentedRangeTombstoneList fragment_list(std::move(range_del_iter), + bytewise_icmp); + + FragmentedRangeTombstoneIterator iter(&fragment_list, bytewise_icmp, + kMaxSequenceNumber); + VerifySeek(&iter, {{"", {"a", "c", 10}}, {"z", {}, true /* out of range */}}); + VerifySeekForPrev(&iter, + {{"", {}, true /* out of range */}, {"z", {"l", "n", 4}}}); +} + +} // namespace rocksdb + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/thirdparty/rocksdb/db/read_callback.h b/thirdparty/rocksdb/db/read_callback.h new file mode 100644 index 0000000000..52573be19d --- /dev/null +++ b/thirdparty/rocksdb/db/read_callback.h @@ -0,0 +1,55 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#include "rocksdb/types.h" + +namespace rocksdb { + +class ReadCallback { + public: + ReadCallback(SequenceNumber last_visible_seq) + : max_visible_seq_(last_visible_seq) {} + ReadCallback(SequenceNumber last_visible_seq, SequenceNumber min_uncommitted) + : max_visible_seq_(last_visible_seq), min_uncommitted_(min_uncommitted) {} + + virtual ~ReadCallback() {} + + // Will be called to see if the seq number visible; if not it moves on to + // the next seq number. + virtual bool IsVisibleFullCheck(SequenceNumber seq) = 0; + + inline bool IsVisible(SequenceNumber seq) { + assert(min_uncommitted_ > 0); + assert(min_uncommitted_ >= kMinUnCommittedSeq); + if (seq < min_uncommitted_) { // handles seq == 0 as well + assert(seq <= max_visible_seq_); + return true; + } else if (max_visible_seq_ < seq) { + assert(seq != 0); + return false; + } else { + assert(seq != 0); // already handled in the first if-then clause + return IsVisibleFullCheck(seq); + } + } + + inline SequenceNumber max_visible_seq() { return max_visible_seq_; } + + virtual void Refresh(SequenceNumber seq) { max_visible_seq_ = seq; } + + // Refer to DBIter::CanReseekToSkip + virtual bool CanReseekToSkip() { return true; } + + protected: + // The max visible seq, it is usually the snapshot but could be larger if + // transaction has its own writes written to db. + SequenceNumber max_visible_seq_ = kMaxSequenceNumber; + // Any seq less than min_uncommitted_ is committed. + const SequenceNumber min_uncommitted_ = kMinUnCommittedSeq; +}; + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/repair.cc b/thirdparty/rocksdb/db/repair.cc index 9ed326032c..7b9409a229 100644 --- a/thirdparty/rocksdb/db/repair.cc +++ b/thirdparty/rocksdb/db/repair.cc @@ -99,12 +99,14 @@ class Repairer { env_(db_options.env), env_options_(), db_options_(SanitizeOptions(dbname_, db_options)), - immutable_db_options_(db_options_), + immutable_db_options_(ImmutableDBOptions(db_options_)), icmp_(default_cf_opts.comparator), - default_cf_opts_(default_cf_opts), + default_cf_opts_( + SanitizeOptions(immutable_db_options_, default_cf_opts)), default_cf_iopts_( - ImmutableCFOptions(immutable_db_options_, default_cf_opts)), - unknown_cf_opts_(unknown_cf_opts), + ImmutableCFOptions(immutable_db_options_, default_cf_opts_)), + unknown_cf_opts_( + SanitizeOptions(immutable_db_options_, unknown_cf_opts)), create_unknown_cfs_(create_unknown_cfs), raw_table_cache_( // TableCache can be small since we expect each table to be opened @@ -116,7 +118,8 @@ class Repairer { wc_(db_options_.delayed_write_rate), vset_(dbname_, &immutable_db_options_, env_options_, raw_table_cache_.get(), &wb_, &wc_), - next_file_number_(1) { + next_file_number_(1), + db_lock_(nullptr) { for (const auto& cfd : column_families) { cf_name_to_opts_[cfd.name] = cfd.options; } @@ -161,11 +164,18 @@ class Repairer { } ~Repairer() { + if (db_lock_ != nullptr) { + env_->UnlockFile(db_lock_); + } delete table_cache_; } Status Run() { - Status status = FindFiles(); + Status status = env_->LockFile(LockFileName(dbname_), &db_lock_); + if (!status.ok()) { + return status; + } + status = FindFiles(); if (status.ok()) { // Discard older manifests and start a fresh one for (size_t i = 0; i < manifests_.size(); i++) { @@ -203,7 +213,7 @@ class Repairer { ROCKS_LOG_WARN(db_options_.info_log, "**** Repaired rocksdb %s; " "recovered %" ROCKSDB_PRIszt " files; %" PRIu64 - "bytes. " + " bytes. " "Some data may have been lost. " "****", dbname_.c_str(), tables_.size(), bytes); @@ -243,6 +253,9 @@ class Repairer { std::vector logs_; std::vector tables_; uint64_t next_file_number_; + // Lock over the persistent DB state. Non-nullptr iff successfully + // acquired. + FileLock* db_lock_; Status FindFiles() { std::vector filenames; @@ -254,14 +267,21 @@ class Repairer { } // search wal_dir if user uses a customize wal_dir - if (!db_options_.wal_dir.empty() && - db_options_.wal_dir != dbname_) { - to_search_paths.push_back(db_options_.wal_dir); + bool same = false; + Status status = env_->AreFilesSame(db_options_.wal_dir, dbname_, &same); + if (status.IsNotSupported()) { + same = db_options_.wal_dir == dbname_; + status = Status::OK(); + } else if (!status.ok()) { + return status; + } + + if (!same) { + to_search_paths.push_back(db_options_.wal_dir); } for (size_t path_id = 0; path_id < to_search_paths.size(); path_id++) { - Status status = - env_->GetChildren(to_search_paths[path_id], &filenames); + status = env_->GetChildren(to_search_paths[path_id], &filenames); if (!status.ok()) { return status; } @@ -316,7 +336,7 @@ class Repairer { Env* env; std::shared_ptr info_log; uint64_t lognum; - virtual void Corruption(size_t bytes, const Status& s) override { + void Corruption(size_t bytes, const Status& s) override { // We print error messages for corruption, but continue repairing. ROCKS_LOG_ERROR(info_log, "Log #%" PRIu64 ": dropping %d bytes; %s", lognum, static_cast(bytes), s.ToString().c_str()); @@ -325,14 +345,14 @@ class Repairer { // Open the log file std::string logname = LogFileName(db_options_.wal_dir, log); - unique_ptr lfile; + std::unique_ptr lfile; Status status = env_->NewSequentialFile( logname, &lfile, env_->OptimizeForLogRead(env_options_)); if (!status.ok()) { return status; } - unique_ptr lfile_reader( - new SequentialFileReader(std::move(lfile))); + std::unique_ptr lfile_reader( + new SequentialFileReader(std::move(lfile), logname)); // Create the log reader. LogReporter reporter; @@ -344,7 +364,7 @@ class Repairer { // propagating bad information (like overly large sequence // numbers). log::Reader reader(db_options_.info_log, std::move(lfile_reader), &reporter, - true /*enable checksum*/, 0 /*initial_offset*/, log); + true /*enable checksum*/, log); // Initialize per-column family memtables for (auto* cfd : *vset_.GetColumnFamilySet()) { @@ -390,23 +410,30 @@ class Repairer { ro.total_order_seek = true; Arena arena; ScopedArenaIterator iter(mem->NewIterator(ro, &arena)); - EnvOptions optimized_env_options = - env_->OptimizeForCompactionTableWrite(env_options_, immutable_db_options_); - int64_t _current_time = 0; status = env_->GetCurrentTime(&_current_time); // ignore error const uint64_t current_time = static_cast(_current_time); - + SnapshotChecker* snapshot_checker = DisableGCSnapshotChecker::Instance(); + + auto write_hint = cfd->CalculateSSTWriteHint(0); + std::vector> + range_del_iters; + auto range_del_iter = + mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber); + if (range_del_iter != nullptr) { + range_del_iters.emplace_back(range_del_iter); + } status = BuildTable( dbname_, env_, *cfd->ioptions(), *cfd->GetLatestMutableCFOptions(), - optimized_env_options, table_cache_, iter.get(), - std::unique_ptr(mem->NewRangeTombstoneIterator(ro)), + env_options_, table_cache_, iter.get(), std::move(range_del_iters), &meta, cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(), - {}, kMaxSequenceNumber, kNoCompression, CompressionOptions(), false, + {}, kMaxSequenceNumber, snapshot_checker, kNoCompression, + 0 /* sample_for_compression */, CompressionOptions(), false, nullptr /* internal_stats */, TableFileCreationReason::kRecovery, nullptr /* event_logger */, 0 /* job_id */, Env::IO_HIGH, - nullptr /* table_properties */, -1 /* level */, current_time); + nullptr /* table_properties */, -1 /* level */, current_time, + write_hint); ROCKS_LOG_INFO(db_options_.info_log, "Log #%" PRIu64 ": %d ops saved to Table #%" PRIu64 " %s", log, counter, meta.fd.GetNumber(), @@ -491,8 +518,9 @@ class Repairer { } if (status.ok()) { InternalIterator* iter = table_cache_->NewIterator( - ReadOptions(), env_options_, cfd->internal_comparator(), t->meta.fd, - nullptr /* range_del_agg */); + ReadOptions(), env_options_, cfd->internal_comparator(), t->meta, + nullptr /* range_del_agg */, + cfd->GetLatestMutableCFOptions()->prefix_extractor.get()); bool empty = true; ParsedInternalKey parsed; t->min_sequence = 0; @@ -541,7 +569,8 @@ class Repairer { max_sequence = tables_[i].max_sequence; } } - vset_.SetLastToBeWrittenSequence(max_sequence); + vset_.SetLastAllocatedSequence(max_sequence); + vset_.SetLastPublishedSequence(max_sequence); vset_.SetLastSequence(max_sequence); for (const auto& cf_id_and_tables : cf_id_to_tables) { @@ -560,6 +589,8 @@ class Repairer { table->meta.largest, table->min_sequence, table->max_sequence, table->meta.marked_for_compaction); } + assert(next_file_number_ > 0); + vset_.MarkFileNumberUsed(next_file_number_ - 1); mutex_.Lock(); Status status = vset_.LogAndApply( cfd, *cfd->GetLatestMutableCFOptions(), &edit, &mutex_, @@ -611,11 +642,13 @@ Status GetDefaultCFOptions( } // anonymous namespace Status RepairDB(const std::string& dbname, const DBOptions& db_options, - const std::vector& column_families) { + const std::vector& column_families + ) { ColumnFamilyOptions default_cf_opts; Status status = GetDefaultCFOptions(column_families, &default_cf_opts); if (status.ok()) { - Repairer repairer(dbname, db_options, column_families, default_cf_opts, + Repairer repairer(dbname, db_options, column_families, + default_cf_opts, ColumnFamilyOptions() /* unknown_cf_opts */, false /* create_unknown_cfs */); status = repairer.Run(); @@ -629,7 +662,8 @@ Status RepairDB(const std::string& dbname, const DBOptions& db_options, ColumnFamilyOptions default_cf_opts; Status status = GetDefaultCFOptions(column_families, &default_cf_opts); if (status.ok()) { - Repairer repairer(dbname, db_options, column_families, default_cf_opts, + Repairer repairer(dbname, db_options, + column_families, default_cf_opts, unknown_cf_opts, true /* create_unknown_cfs */); status = repairer.Run(); } @@ -639,7 +673,8 @@ Status RepairDB(const std::string& dbname, const DBOptions& db_options, Status RepairDB(const std::string& dbname, const Options& options) { DBOptions db_options(options); ColumnFamilyOptions cf_options(options); - Repairer repairer(dbname, db_options, {}, cf_options /* default_cf_opts */, + Repairer repairer(dbname, db_options, + {}, cf_options /* default_cf_opts */, cf_options /* unknown_cf_opts */, true /* create_unknown_cfs */); return repairer.Run(); diff --git a/thirdparty/rocksdb/db/repair_test.cc b/thirdparty/rocksdb/db/repair_test.cc index b267c6d168..3422532da4 100644 --- a/thirdparty/rocksdb/db/repair_test.cc +++ b/thirdparty/rocksdb/db/repair_test.cc @@ -74,7 +74,7 @@ TEST_F(RepairTest, CorruptManifest) { Close(); ASSERT_OK(env_->FileExists(manifest_path)); - CreateFile(env_, manifest_path, "blah"); + CreateFile(env_, manifest_path, "blah", false /* use_fsync */); ASSERT_OK(RepairDB(dbname_, CurrentOptions())); Reopen(CurrentOptions()); @@ -108,6 +108,23 @@ TEST_F(RepairTest, IncompleteManifest) { ASSERT_EQ(Get("key2"), "val2"); } +TEST_F(RepairTest, PostRepairSstFileNumbering) { + // Verify after a DB is repaired, new files will be assigned higher numbers + // than old files. + Put("key", "val"); + Flush(); + Put("key2", "val2"); + Flush(); + uint64_t pre_repair_file_num = dbfull()->TEST_Current_Next_FileNo(); + Close(); + + ASSERT_OK(RepairDB(dbname_, CurrentOptions())); + + Reopen(CurrentOptions()); + uint64_t post_repair_file_num = dbfull()->TEST_Current_Next_FileNo(); + ASSERT_GE(post_repair_file_num, pre_repair_file_num); +} + TEST_F(RepairTest, LostSst) { // Delete one of the SST files but preserve the manifest that refers to it, // then verify the DB is still usable for the intact SST. @@ -136,7 +153,7 @@ TEST_F(RepairTest, CorruptSst) { Flush(); auto sst_path = GetFirstSstPath(); ASSERT_FALSE(sst_path.empty()); - CreateFile(env_, sst_path, "blah"); + CreateFile(env_, sst_path, "blah", false /* use_fsync */); Close(); ASSERT_OK(RepairDB(dbname_, CurrentOptions())); @@ -296,6 +313,7 @@ TEST_F(RepairTest, RepairColumnFamilyOptions) { ASSERT_EQ(comparator_name, fname_and_props.second->comparator_name); } + Close(); // Also check comparator when it's provided via "unknown" CF options ASSERT_OK(RepairDB(dbname_, opts, {{"default", opts}}, @@ -309,6 +327,25 @@ TEST_F(RepairTest, RepairColumnFamilyOptions) { } } +TEST_F(RepairTest, DbNameContainsTrailingSlash) { + { + bool tmp; + if (env_->AreFilesSame("", "", &tmp).IsNotSupported()) { + fprintf(stderr, + "skipping RepairTest.DbNameContainsTrailingSlash due to " + "unsupported Env::AreFilesSame\n"); + return; + } + } + + Put("key", "val"); + Flush(); + Close(); + + ASSERT_OK(RepairDB(dbname_ + "/", CurrentOptions())); + Reopen(CurrentOptions()); + ASSERT_EQ(Get("key"), "val"); +} #endif // ROCKSDB_LITE } // namespace rocksdb @@ -320,7 +357,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as RepairDB is not supported in ROCKSDB_LITE\n"); return 0; } diff --git a/thirdparty/rocksdb/db/snapshot_checker.h b/thirdparty/rocksdb/db/snapshot_checker.h new file mode 100644 index 0000000000..4d29b83c4f --- /dev/null +++ b/thirdparty/rocksdb/db/snapshot_checker.h @@ -0,0 +1,61 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once +#include "rocksdb/types.h" + +namespace rocksdb { + +enum class SnapshotCheckerResult : int { + kInSnapshot = 0, + kNotInSnapshot = 1, + // In case snapshot is released and the checker has no clue whether + // the given sequence is visible to the snapshot. + kSnapshotReleased = 2, +}; + +// Callback class that control GC of duplicate keys in flush/compaction. +class SnapshotChecker { + public: + virtual ~SnapshotChecker() {} + virtual SnapshotCheckerResult CheckInSnapshot( + SequenceNumber sequence, SequenceNumber snapshot_sequence) const = 0; +}; + +class DisableGCSnapshotChecker : public SnapshotChecker { + public: + virtual ~DisableGCSnapshotChecker() {} + virtual SnapshotCheckerResult CheckInSnapshot( + SequenceNumber /*sequence*/, + SequenceNumber /*snapshot_sequence*/) const override { + // By returning kNotInSnapshot, we prevent all the values from being GCed + return SnapshotCheckerResult::kNotInSnapshot; + } + static DisableGCSnapshotChecker* Instance() { return &instance_; } + + protected: + static DisableGCSnapshotChecker instance_; + explicit DisableGCSnapshotChecker() {} +}; + +class WritePreparedTxnDB; + +// Callback class created by WritePreparedTxnDB to check if a key +// is visible by a snapshot. +class WritePreparedSnapshotChecker : public SnapshotChecker { + public: + explicit WritePreparedSnapshotChecker(WritePreparedTxnDB* txn_db); + virtual ~WritePreparedSnapshotChecker() {} + + virtual SnapshotCheckerResult CheckInSnapshot( + SequenceNumber sequence, SequenceNumber snapshot_sequence) const override; + + private: +#ifndef ROCKSDB_LITE + const WritePreparedTxnDB* const txn_db_; +#endif // !ROCKSDB_LITE +}; + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/db/snapshot_impl.h b/thirdparty/rocksdb/db/snapshot_impl.h index 7dc405931c..f2610fd18b 100644 --- a/thirdparty/rocksdb/db/snapshot_impl.h +++ b/thirdparty/rocksdb/db/snapshot_impl.h @@ -21,6 +21,10 @@ class SnapshotList; class SnapshotImpl : public Snapshot { public: SequenceNumber number_; // const after creation + // It indicates the smallest uncommitted data at the time the snapshot was + // taken. This is currently used by WritePrepared transactions to limit the + // scope of queries to IsInSnpashot. + SequenceNumber min_uncommitted_ = kMinUnCommittedSeq; virtual SequenceNumber GetSequenceNumber() const override { return number_; } @@ -45,15 +49,22 @@ class SnapshotList { list_.prev_ = &list_; list_.next_ = &list_; list_.number_ = 0xFFFFFFFFL; // placeholder marker, for debugging + // Set all the variables to make UBSAN happy. + list_.list_ = nullptr; + list_.unix_time_ = 0; + list_.is_write_conflict_boundary_ = false; count_ = 0; } + // No copy-construct. + SnapshotList(const SnapshotList&) = delete; + bool empty() const { return list_.next_ == &list_; } SnapshotImpl* oldest() const { assert(!empty()); return list_.next_; } SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; } - const SnapshotImpl* New(SnapshotImpl* s, SequenceNumber seq, - uint64_t unix_time, bool is_write_conflict_boundary) { + SnapshotImpl* New(SnapshotImpl* s, SequenceNumber seq, uint64_t unix_time, + bool is_write_conflict_boundary) { s->number_ = seq; s->unix_time_ = unix_time; s->is_write_conflict_boundary_ = is_write_conflict_boundary; @@ -75,7 +86,7 @@ class SnapshotList { } // retrieve all snapshot numbers up until max_seq. They are sorted in - // ascending order. + // ascending order (with no duplicates). std::vector GetAll( SequenceNumber* oldest_write_conflict_snapshot = nullptr, const SequenceNumber& max_seq = kMaxSequenceNumber) const { @@ -93,7 +104,10 @@ class SnapshotList { if (s->next_->number_ > max_seq) { break; } - ret.push_back(s->next_->number_); + // Avoid duplicates + if (ret.empty() || ret.back() != s->next_->number_) { + ret.push_back(s->next_->number_); + } if (oldest_write_conflict_snapshot != nullptr && *oldest_write_conflict_snapshot == kMaxSequenceNumber && @@ -108,22 +122,6 @@ class SnapshotList { return ret; } - // Whether there is an active snapshot in range [lower_bound, upper_bound). - bool HasSnapshotInRange(SequenceNumber lower_bound, - SequenceNumber upper_bound) { - if (empty()) { - return false; - } - const SnapshotImpl* s = &list_; - while (s->next_ != &list_) { - if (s->next_->number_ >= lower_bound) { - return s->next_->number_ < upper_bound; - } - s = s->next_; - } - return false; - } - // get the sequence number of the most recent snapshot SequenceNumber GetNewest() { if (empty()) { diff --git a/thirdparty/rocksdb/db/table_cache.cc b/thirdparty/rocksdb/db/table_cache.cc index b4d5cc1bb7..764c05bfa4 100644 --- a/thirdparty/rocksdb/db/table_cache.cc +++ b/thirdparty/rocksdb/db/table_cache.cc @@ -10,6 +10,7 @@ #include "db/table_cache.h" #include "db/dbformat.h" +#include "db/range_tombstone_fragmenter.h" #include "db/version_edit.h" #include "util/filename.h" @@ -30,7 +31,7 @@ namespace rocksdb { namespace { template -static void DeleteEntry(const Slice& key, void* value) { +static void DeleteEntry(const Slice& /*key*/, void* value) { T* typed_value = reinterpret_cast(value); delete typed_value; } @@ -43,6 +44,8 @@ static void UnrefEntry(void* arg1, void* arg2) { static void DeleteTableReader(void* arg1, void* arg2) { TableReader* table_reader = reinterpret_cast(arg1); + Statistics* stats = reinterpret_cast(arg2); + RecordTick(stats, NO_FILE_CLOSES); delete table_reader; } @@ -65,7 +68,10 @@ void AppendVarint64(IterKey* key, uint64_t v) { TableCache::TableCache(const ImmutableCFOptions& ioptions, const EnvOptions& env_options, Cache* const cache) - : ioptions_(ioptions), env_options_(env_options), cache_(cache) { + : ioptions_(ioptions), + env_options_(env_options), + cache_(cache), + immortal_tables_(false) { if (ioptions_.row_cache) { // If the same cache is shared by multiple instances, we need to // disambiguate its entries. @@ -88,17 +94,21 @@ Status TableCache::GetTableReader( const EnvOptions& env_options, const InternalKeyComparator& internal_comparator, const FileDescriptor& fd, bool sequential_mode, size_t readahead, bool record_read_stats, - HistogramImpl* file_read_hist, unique_ptr* table_reader, - bool skip_filters, int level, bool prefetch_index_and_filter_in_cache, - bool for_compaction) { + HistogramImpl* file_read_hist, std::unique_ptr* table_reader, + const SliceTransform* prefix_extractor, bool skip_filters, int level, + bool prefetch_index_and_filter_in_cache, bool for_compaction) { std::string fname = - TableFileName(ioptions_.db_paths, fd.GetNumber(), fd.GetPathId()); - unique_ptr file; + TableFileName(ioptions_.cf_paths, fd.GetNumber(), fd.GetPathId()); + std::unique_ptr file; Status s = ioptions_.env->NewRandomAccessFile(fname, &file, env_options); RecordTick(ioptions_.statistics, NO_FILE_OPENS); if (s.ok()) { - if (readahead > 0) { + if (readahead > 0 && !env_options.use_mmap_reads) { + // Not compatible with mmap files since ReadaheadRandomAccessFile requires + // its wrapped file's Read() to copy data into the provided scratch + // buffer, which mmap files don't use. + // TODO(ajkr): try madvise for mmap files in place of buffered readahead. file = NewReadaheadRandomAccessFile(std::move(file), readahead); } if (!sequential_mode && ioptions_.advise_random_on_open) { @@ -109,10 +119,12 @@ Status TableCache::GetTableReader( new RandomAccessFileReader( std::move(file), fname, ioptions_.env, record_read_stats ? ioptions_.statistics : nullptr, SST_READ_MICROS, - file_read_hist, ioptions_.rate_limiter, for_compaction)); + file_read_hist, ioptions_.rate_limiter, for_compaction, + ioptions_.listeners)); s = ioptions_.table_factory->NewTableReader( - TableReaderOptions(ioptions_, env_options, internal_comparator, - skip_filters, level), + TableReaderOptions(ioptions_, prefix_extractor, env_options, + internal_comparator, skip_filters, immortal_tables_, + level, fd.largest_seqno), std::move(file_reader), fd.GetFileSize(), table_reader, prefetch_index_and_filter_in_cache); TEST_SYNC_POINT("TableCache::GetTableReader:0"); @@ -130,11 +142,12 @@ void TableCache::EraseHandle(const FileDescriptor& fd, Cache::Handle* handle) { Status TableCache::FindTable(const EnvOptions& env_options, const InternalKeyComparator& internal_comparator, const FileDescriptor& fd, Cache::Handle** handle, + const SliceTransform* prefix_extractor, const bool no_io, bool record_read_stats, HistogramImpl* file_read_hist, bool skip_filters, int level, bool prefetch_index_and_filter_in_cache) { - PERF_TIMER_GUARD(find_table_nanos); + PERF_TIMER_GUARD_WITH_ENV(find_table_nanos, ioptions_.env); Status s; uint64_t number = fd.GetNumber(); Slice key = GetSliceForFileNumber(&number); @@ -146,11 +159,12 @@ Status TableCache::FindTable(const EnvOptions& env_options, if (no_io) { // Don't do IO and return a not-found status return Status::Incomplete("Table not found in table_cache, no_io is set"); } - unique_ptr table_reader; + std::unique_ptr table_reader; s = GetTableReader(env_options, internal_comparator, fd, false /* sequential mode */, 0 /* readahead */, record_read_stats, file_read_hist, &table_reader, - skip_filters, level, prefetch_index_and_filter_in_cache); + prefix_extractor, skip_filters, level, + prefetch_index_and_filter_in_cache); if (!s.ok()) { assert(table_reader == nullptr); RecordTick(ioptions_.statistics, NO_FILE_ERRORS); @@ -170,65 +184,75 @@ Status TableCache::FindTable(const EnvOptions& env_options, InternalIterator* TableCache::NewIterator( const ReadOptions& options, const EnvOptions& env_options, - const InternalKeyComparator& icomparator, const FileDescriptor& fd, - RangeDelAggregator* range_del_agg, TableReader** table_reader_ptr, - HistogramImpl* file_read_hist, bool for_compaction, Arena* arena, - bool skip_filters, int level) { + const InternalKeyComparator& icomparator, const FileMetaData& file_meta, + RangeDelAggregator* range_del_agg, const SliceTransform* prefix_extractor, + TableReader** table_reader_ptr, HistogramImpl* file_read_hist, + bool for_compaction, Arena* arena, bool skip_filters, int level, + const InternalKey* smallest_compaction_key, + const InternalKey* largest_compaction_key) { PERF_TIMER_GUARD(new_table_iterator_nanos); Status s; bool create_new_table_reader = false; TableReader* table_reader = nullptr; Cache::Handle* handle = nullptr; - if (s.ok()) { - if (table_reader_ptr != nullptr) { - *table_reader_ptr = nullptr; - } - size_t readahead = 0; - if (for_compaction) { + if (table_reader_ptr != nullptr) { + *table_reader_ptr = nullptr; + } + size_t readahead = 0; + if (for_compaction) { #ifndef NDEBUG - bool use_direct_reads_for_compaction = env_options.use_direct_reads; - TEST_SYNC_POINT_CALLBACK("TableCache::NewIterator:for_compaction", - &use_direct_reads_for_compaction); + bool use_direct_reads_for_compaction = env_options.use_direct_reads; + TEST_SYNC_POINT_CALLBACK("TableCache::NewIterator:for_compaction", + &use_direct_reads_for_compaction); #endif // !NDEBUG - if (ioptions_.new_table_reader_for_compaction_inputs) { - readahead = ioptions_.compaction_readahead_size; - create_new_table_reader = true; - } - } else { - readahead = options.readahead_size; - create_new_table_reader = readahead > 0; + if (ioptions_.new_table_reader_for_compaction_inputs) { + // get compaction_readahead_size from env_options allows us to set the + // value dynamically + readahead = env_options.compaction_readahead_size; + create_new_table_reader = true; } + } else { + readahead = options.readahead_size; + create_new_table_reader = readahead > 0; + } - if (create_new_table_reader) { - unique_ptr table_reader_unique_ptr; - s = GetTableReader( - env_options, icomparator, fd, true /* sequential_mode */, readahead, - !for_compaction /* record stats */, nullptr, &table_reader_unique_ptr, - false /* skip_filters */, level, - true /* prefetch_index_and_filter_in_cache */, for_compaction); + auto& fd = file_meta.fd; + if (create_new_table_reader) { + std::unique_ptr table_reader_unique_ptr; + s = GetTableReader( + env_options, icomparator, fd, true /* sequential_mode */, readahead, + !for_compaction /* record stats */, nullptr, &table_reader_unique_ptr, + prefix_extractor, false /* skip_filters */, level, + true /* prefetch_index_and_filter_in_cache */, for_compaction); + if (s.ok()) { + table_reader = table_reader_unique_ptr.release(); + } + } else { + table_reader = fd.table_reader; + if (table_reader == nullptr) { + s = FindTable(env_options, icomparator, fd, &handle, prefix_extractor, + options.read_tier == kBlockCacheTier /* no_io */, + !for_compaction /* record read_stats */, file_read_hist, + skip_filters, level); if (s.ok()) { - table_reader = table_reader_unique_ptr.release(); - } - } else { - table_reader = fd.table_reader; - if (table_reader == nullptr) { - s = FindTable(env_options, icomparator, fd, &handle, - options.read_tier == kBlockCacheTier /* no_io */, - !for_compaction /* record read_stats */, file_read_hist, - skip_filters, level); - if (s.ok()) { - table_reader = GetTableReaderFromHandle(handle); - } + table_reader = GetTableReaderFromHandle(handle); } } } InternalIterator* result = nullptr; if (s.ok()) { - result = table_reader->NewIterator(options, arena, skip_filters); + if (options.table_filter && + !options.table_filter(*table_reader->GetTableProperties())) { + result = NewEmptyInternalIterator(arena); + } else { + result = table_reader->NewIterator(options, prefix_extractor, arena, + skip_filters, for_compaction); + } if (create_new_table_reader) { assert(handle == nullptr); - result->RegisterCleanup(&DeleteTableReader, table_reader, nullptr); + result->RegisterCleanup(&DeleteTableReader, table_reader, + ioptions_.statistics); } else if (handle != nullptr) { result->RegisterCleanup(&UnrefEntry, cache_, handle); handle = nullptr; // prevent from releasing below @@ -242,13 +266,25 @@ InternalIterator* TableCache::NewIterator( } } if (s.ok() && range_del_agg != nullptr && !options.ignore_range_deletions) { - std::unique_ptr range_del_iter( - table_reader->NewRangeTombstoneIterator(options)); - if (range_del_iter != nullptr) { - s = range_del_iter->status(); - } - if (s.ok()) { - s = range_del_agg->AddTombstones(std::move(range_del_iter)); + if (range_del_agg->AddFile(fd.GetNumber())) { + std::unique_ptr range_del_iter( + static_cast( + table_reader->NewRangeTombstoneIterator(options))); + if (range_del_iter != nullptr) { + s = range_del_iter->status(); + } + if (s.ok()) { + const InternalKey* smallest = &file_meta.smallest; + const InternalKey* largest = &file_meta.largest; + if (smallest_compaction_key != nullptr) { + smallest = smallest_compaction_key; + } + if (largest_compaction_key != nullptr) { + largest = largest_compaction_key; + } + range_del_agg->AddTombstones(std::move(range_del_iter), smallest, + largest); + } } } @@ -257,54 +293,19 @@ InternalIterator* TableCache::NewIterator( } if (!s.ok()) { assert(result == nullptr); - result = NewErrorInternalIterator(s, arena); - } - return result; -} - -InternalIterator* TableCache::NewRangeTombstoneIterator( - const ReadOptions& options, const EnvOptions& env_options, - const InternalKeyComparator& icomparator, const FileDescriptor& fd, - HistogramImpl* file_read_hist, bool skip_filters, int level) { - Status s; - TableReader* table_reader = nullptr; - Cache::Handle* handle = nullptr; - table_reader = fd.table_reader; - if (table_reader == nullptr) { - s = FindTable(env_options, icomparator, fd, &handle, - options.read_tier == kBlockCacheTier /* no_io */, - true /* record read_stats */, file_read_hist, skip_filters, - level); - if (s.ok()) { - table_reader = GetTableReaderFromHandle(handle); - } - } - InternalIterator* result = nullptr; - if (s.ok()) { - result = table_reader->NewRangeTombstoneIterator(options); - if (result != nullptr) { - if (handle != nullptr) { - result->RegisterCleanup(&UnrefEntry, cache_, handle); - } - } - } - if (result == nullptr && handle != nullptr) { - // the range deletion block didn't exist, or there was a failure between - // getting handle and getting iterator. - ReleaseHandle(handle); - } - if (!s.ok()) { - assert(result == nullptr); - result = NewErrorInternalIterator(s); + result = NewErrorInternalIterator(s, arena); } return result; } Status TableCache::Get(const ReadOptions& options, const InternalKeyComparator& internal_comparator, - const FileDescriptor& fd, const Slice& k, - GetContext* get_context, HistogramImpl* file_read_hist, - bool skip_filters, int level) { + const FileMetaData& file_meta, const Slice& k, + GetContext* get_context, + const SliceTransform* prefix_extractor, + HistogramImpl* file_read_hist, bool skip_filters, + int level) { + auto& fd = file_meta.fd; std::string* row_cache_entry = nullptr; bool done = false; #ifndef ROCKSDB_LITE @@ -368,29 +369,29 @@ Status TableCache::Get(const ReadOptions& options, Cache::Handle* handle = nullptr; if (!done && s.ok()) { if (t == nullptr) { - s = FindTable(env_options_, internal_comparator, fd, &handle, - options.read_tier == kBlockCacheTier /* no_io */, - true /* record_read_stats */, file_read_hist, skip_filters, - level); + s = FindTable( + env_options_, internal_comparator, fd, &handle, prefix_extractor, + options.read_tier == kBlockCacheTier /* no_io */, + true /* record_read_stats */, file_read_hist, skip_filters, level); if (s.ok()) { t = GetTableReaderFromHandle(handle); } } - if (s.ok() && get_context->range_del_agg() != nullptr && + SequenceNumber* max_covering_tombstone_seq = + get_context->max_covering_tombstone_seq(); + if (s.ok() && max_covering_tombstone_seq != nullptr && !options.ignore_range_deletions) { - std::unique_ptr range_del_iter( + std::unique_ptr range_del_iter( t->NewRangeTombstoneIterator(options)); if (range_del_iter != nullptr) { - s = range_del_iter->status(); - } - if (s.ok()) { - s = get_context->range_del_agg()->AddTombstones( - std::move(range_del_iter)); + *max_covering_tombstone_seq = std::max( + *max_covering_tombstone_seq, + range_del_iter->MaxCoveringTombstoneSeqnum(ExtractUserKey(k))); } } if (s.ok()) { get_context->SetReplayLog(row_cache_entry); // nullptr if no cache. - s = t->Get(options, k, get_context, skip_filters); + s = t->Get(options, k, get_context, prefix_extractor, skip_filters); get_context->SetReplayLog(nullptr); } else if (options.read_tier == kBlockCacheTier && s.IsIncomplete()) { // Couldn't find Table in cache but treat as kFound if no_io set @@ -420,7 +421,8 @@ Status TableCache::Get(const ReadOptions& options, Status TableCache::GetTableProperties( const EnvOptions& env_options, const InternalKeyComparator& internal_comparator, const FileDescriptor& fd, - std::shared_ptr* properties, bool no_io) { + std::shared_ptr* properties, + const SliceTransform* prefix_extractor, bool no_io) { Status s; auto table_reader = fd.table_reader; // table already been pre-loaded? @@ -431,7 +433,8 @@ Status TableCache::GetTableProperties( } Cache::Handle* table_handle = nullptr; - s = FindTable(env_options, internal_comparator, fd, &table_handle, no_io); + s = FindTable(env_options, internal_comparator, fd, &table_handle, + prefix_extractor, no_io); if (!s.ok()) { return s; } @@ -444,8 +447,8 @@ Status TableCache::GetTableProperties( size_t TableCache::GetMemoryUsageByTableReader( const EnvOptions& env_options, - const InternalKeyComparator& internal_comparator, - const FileDescriptor& fd) { + const InternalKeyComparator& internal_comparator, const FileDescriptor& fd, + const SliceTransform* prefix_extractor) { Status s; auto table_reader = fd.table_reader; // table already been pre-loaded? @@ -454,7 +457,8 @@ size_t TableCache::GetMemoryUsageByTableReader( } Cache::Handle* table_handle = nullptr; - s = FindTable(env_options, internal_comparator, fd, &table_handle, true); + s = FindTable(env_options, internal_comparator, fd, &table_handle, + prefix_extractor, true); if (!s.ok()) { return 0; } diff --git a/thirdparty/rocksdb/db/table_cache.h b/thirdparty/rocksdb/db/table_cache.h index 8b65bafa3e..180ebc6bde 100644 --- a/thirdparty/rocksdb/db/table_cache.h +++ b/thirdparty/rocksdb/db/table_cache.h @@ -31,7 +31,6 @@ class Arena; struct FileDescriptor; class GetContext; class HistogramImpl; -class InternalIterator; class TableCache { public: @@ -53,16 +52,13 @@ class TableCache { InternalIterator* NewIterator( const ReadOptions& options, const EnvOptions& toptions, const InternalKeyComparator& internal_comparator, - const FileDescriptor& file_fd, RangeDelAggregator* range_del_agg, + const FileMetaData& file_meta, RangeDelAggregator* range_del_agg, + const SliceTransform* prefix_extractor = nullptr, TableReader** table_reader_ptr = nullptr, HistogramImpl* file_read_hist = nullptr, bool for_compaction = false, - Arena* arena = nullptr, bool skip_filters = false, int level = -1); - - InternalIterator* NewRangeTombstoneIterator( - const ReadOptions& options, const EnvOptions& toptions, - const InternalKeyComparator& internal_comparator, - const FileDescriptor& file_fd, HistogramImpl* file_read_hist, - bool skip_filters, int level); + Arena* arena = nullptr, bool skip_filters = false, int level = -1, + const InternalKey* smallest_compaction_key = nullptr, + const InternalKey* largest_compaction_key = nullptr); // If a seek to internal key "k" in specified file finds an entry, // call (*handle_result)(arg, found_key, found_value) repeatedly until @@ -74,9 +70,11 @@ class TableCache { // @param level The level this table is at, -1 for "not set / don't know" Status Get(const ReadOptions& options, const InternalKeyComparator& internal_comparator, - const FileDescriptor& file_fd, const Slice& k, - GetContext* get_context, HistogramImpl* file_read_hist = nullptr, - bool skip_filters = false, int level = -1); + const FileMetaData& file_meta, const Slice& k, + GetContext* get_context, + const SliceTransform* prefix_extractor = nullptr, + HistogramImpl* file_read_hist = nullptr, bool skip_filters = false, + int level = -1); // Evict any entry for the specified file number static void Evict(Cache* cache, uint64_t file_number); @@ -91,6 +89,7 @@ class TableCache { Status FindTable(const EnvOptions& toptions, const InternalKeyComparator& internal_comparator, const FileDescriptor& file_fd, Cache::Handle**, + const SliceTransform* prefix_extractor = nullptr, const bool no_io = false, bool record_read_stats = true, HistogramImpl* file_read_hist = nullptr, bool skip_filters = false, int level = -1, @@ -109,6 +108,7 @@ class TableCache { const InternalKeyComparator& internal_comparator, const FileDescriptor& file_meta, std::shared_ptr* properties, + const SliceTransform* prefix_extractor = nullptr, bool no_io = false); // Return total memory usage of the table reader of the file. @@ -116,15 +116,26 @@ class TableCache { size_t GetMemoryUsageByTableReader( const EnvOptions& toptions, const InternalKeyComparator& internal_comparator, - const FileDescriptor& fd); + const FileDescriptor& fd, + const SliceTransform* prefix_extractor = nullptr); // Release the handle from a cache void ReleaseHandle(Cache::Handle* handle); + Cache* get_cache() const { return cache_; } + // Capacity of the backing Cache that indicates inifinite TableCache capacity. // For example when max_open_files is -1 we set the backing Cache to this. static const int kInfiniteCapacity = 0x400000; + // The tables opened with this TableCache will be immortal, i.e., their + // lifetime is as long as that of the DB. + void SetTablesAreImmortal() { + if (cache_->GetCapacity() >= kInfiniteCapacity) { + immortal_tables_ = true; + } + } + private: // Build a table reader Status GetTableReader(const EnvOptions& env_options, @@ -132,7 +143,8 @@ class TableCache { const FileDescriptor& fd, bool sequential_mode, size_t readahead, bool record_read_stats, HistogramImpl* file_read_hist, - unique_ptr* table_reader, + std::unique_ptr* table_reader, + const SliceTransform* prefix_extractor = nullptr, bool skip_filters = false, int level = -1, bool prefetch_index_and_filter_in_cache = true, bool for_compaction = false); @@ -141,6 +153,7 @@ class TableCache { const EnvOptions& env_options_; Cache* const cache_; std::string row_cache_id_; + bool immortal_tables_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/table_properties_collector.cc b/thirdparty/rocksdb/db/table_properties_collector.cc index a1f4dba97b..4dbcd4cc41 100644 --- a/thirdparty/rocksdb/db/table_properties_collector.cc +++ b/thirdparty/rocksdb/db/table_properties_collector.cc @@ -11,71 +11,10 @@ namespace rocksdb { -Status InternalKeyPropertiesCollector::InternalAdd(const Slice& key, - const Slice& value, - uint64_t file_size) { - ParsedInternalKey ikey; - if (!ParseInternalKey(key, &ikey)) { - return Status::InvalidArgument("Invalid internal key"); - } - - // Note: We count both, deletions and single deletions here. - if (ikey.type == ValueType::kTypeDeletion || - ikey.type == ValueType::kTypeSingleDeletion) { - ++deleted_keys_; - } else if (ikey.type == ValueType::kTypeMerge) { - ++merge_operands_; - } - - return Status::OK(); -} - -Status InternalKeyPropertiesCollector::Finish( - UserCollectedProperties* properties) { - assert(properties); - assert(properties->find( - InternalKeyTablePropertiesNames::kDeletedKeys) == properties->end()); - assert(properties->find(InternalKeyTablePropertiesNames::kMergeOperands) == - properties->end()); - - std::string val_deleted_keys; - PutVarint64(&val_deleted_keys, deleted_keys_); - properties->insert( - {InternalKeyTablePropertiesNames::kDeletedKeys, val_deleted_keys}); - - std::string val_merge_operands; - PutVarint64(&val_merge_operands, merge_operands_); - properties->insert( - {InternalKeyTablePropertiesNames::kMergeOperands, val_merge_operands}); - - return Status::OK(); -} - -UserCollectedProperties -InternalKeyPropertiesCollector::GetReadableProperties() const { - return {{"kDeletedKeys", ToString(deleted_keys_)}, - {"kMergeOperands", ToString(merge_operands_)}}; -} - namespace { -EntryType GetEntryType(ValueType value_type) { - switch (value_type) { - case kTypeValue: - return kEntryPut; - case kTypeDeletion: - return kEntryDelete; - case kTypeSingleDeletion: - return kEntrySingleDelete; - case kTypeMerge: - return kEntryMerge; - default: - return kEntryOther; - } -} - uint64_t GetUint64Property(const UserCollectedProperties& props, - const std::string property_name, + const std::string& property_name, bool* property_present) { auto pos = props.find(property_name); if (pos == props.end()) { @@ -102,6 +41,13 @@ Status UserKeyTablePropertiesCollector::InternalAdd(const Slice& key, ikey.sequence, file_size); } +void UserKeyTablePropertiesCollector::BlockAdd( + uint64_t bLockRawBytes, uint64_t blockCompressedBytesFast, + uint64_t blockCompressedBytesSlow) { + return collector_->BlockAdd(bLockRawBytes, blockCompressedBytesFast, + blockCompressedBytesSlow); +} + Status UserKeyTablePropertiesCollector::Finish( UserCollectedProperties* properties) { return collector_->Finish(properties); @@ -112,23 +58,17 @@ UserKeyTablePropertiesCollector::GetReadableProperties() const { return collector_->GetReadableProperties(); } - -const std::string InternalKeyTablePropertiesNames::kDeletedKeys - = "rocksdb.deleted.keys"; -const std::string InternalKeyTablePropertiesNames::kMergeOperands = - "rocksdb.merge.operands"; - uint64_t GetDeletedKeys( const UserCollectedProperties& props) { bool property_present_ignored; - return GetUint64Property(props, InternalKeyTablePropertiesNames::kDeletedKeys, + return GetUint64Property(props, TablePropertiesNames::kDeletedKeys, &property_present_ignored); } uint64_t GetMergeOperands(const UserCollectedProperties& props, bool* property_present) { return GetUint64Property( - props, InternalKeyTablePropertiesNames::kMergeOperands, property_present); + props, TablePropertiesNames::kMergeOperands, property_present); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/table_properties_collector.h b/thirdparty/rocksdb/db/table_properties_collector.h index d8cd75689d..e4d6217157 100644 --- a/thirdparty/rocksdb/db/table_properties_collector.h +++ b/thirdparty/rocksdb/db/table_properties_collector.h @@ -14,11 +14,6 @@ namespace rocksdb { -struct InternalKeyTablePropertiesNames { - static const std::string kDeletedKeys; - static const std::string kMergeOperands; -}; - // Base class for internal table properties collector. class IntTblPropCollector { public: @@ -32,6 +27,10 @@ class IntTblPropCollector { virtual Status InternalAdd(const Slice& key, const Slice& value, uint64_t file_size) = 0; + virtual void BlockAdd(uint64_t blockRawBytes, + uint64_t blockCompressedBytesFast, + uint64_t blockCompressedBytesSlow) = 0; + virtual UserCollectedProperties GetReadableProperties() const = 0; virtual bool NeedCompact() const { return false; } @@ -49,39 +48,6 @@ class IntTblPropCollectorFactory { virtual const char* Name() const = 0; }; -// Collecting the statistics for internal keys. Visible only by internal -// rocksdb modules. -class InternalKeyPropertiesCollector : public IntTblPropCollector { - public: - virtual Status InternalAdd(const Slice& key, const Slice& value, - uint64_t file_size) override; - - virtual Status Finish(UserCollectedProperties* properties) override; - - virtual const char* Name() const override { - return "InternalKeyPropertiesCollector"; - } - - UserCollectedProperties GetReadableProperties() const override; - - private: - uint64_t deleted_keys_ = 0; - uint64_t merge_operands_ = 0; -}; - -class InternalKeyPropertiesCollectorFactory - : public IntTblPropCollectorFactory { - public: - virtual IntTblPropCollector* CreateIntTblPropCollector( - uint32_t column_family_id) override { - return new InternalKeyPropertiesCollector(); - } - - virtual const char* Name() const override { - return "InternalKeyPropertiesCollectorFactory"; - } -}; - // When rocksdb creates a new table, it will encode all "user keys" into // "internal keys", which contains meta information of a given entry. // @@ -98,6 +64,10 @@ class UserKeyTablePropertiesCollector : public IntTblPropCollector { virtual Status InternalAdd(const Slice& key, const Slice& value, uint64_t file_size) override; + virtual void BlockAdd(uint64_t blockRawBytes, + uint64_t blockCompressedBytesFast, + uint64_t blockCompressedBytesSlow) override; + virtual Status Finish(UserCollectedProperties* properties) override; virtual const char* Name() const override { return collector_->Name(); } diff --git a/thirdparty/rocksdb/db/table_properties_collector_test.cc b/thirdparty/rocksdb/db/table_properties_collector_test.cc index 66c66c0253..ea561e982f 100644 --- a/thirdparty/rocksdb/db/table_properties_collector_test.cc +++ b/thirdparty/rocksdb/db/table_properties_collector_test.cc @@ -28,7 +28,7 @@ namespace rocksdb { class TablePropertiesTest : public testing::Test, public testing::WithParamInterface { public: - virtual void SetUp() override { backward_mode_ = GetParam(); } + void SetUp() override { backward_mode_ = GetParam(); } bool backward_mode_; }; @@ -39,19 +39,21 @@ static const uint32_t kTestColumnFamilyId = 66; static const std::string kTestColumnFamilyName = "test_column_fam"; void MakeBuilder(const Options& options, const ImmutableCFOptions& ioptions, + const MutableCFOptions& moptions, const InternalKeyComparator& internal_comparator, const std::vector>* int_tbl_prop_collector_factories, std::unique_ptr* writable, std::unique_ptr* builder) { - unique_ptr wf(new test::StringSink); - writable->reset(new WritableFileWriter(std::move(wf), EnvOptions())); + std::unique_ptr wf(new test::StringSink); + writable->reset( + new WritableFileWriter(std::move(wf), "" /* don't care */, EnvOptions())); int unknown_level = -1; builder->reset(NewTableBuilder( - ioptions, internal_comparator, int_tbl_prop_collector_factories, - kTestColumnFamilyId, kTestColumnFamilyName, - writable->get(), options.compression, options.compression_opts, - unknown_level)); + ioptions, moptions, internal_comparator, int_tbl_prop_collector_factories, + kTestColumnFamilyId, kTestColumnFamilyName, writable->get(), + options.compression, options.sample_for_compression, + options.compression_opts, unknown_level)); } } // namespace @@ -82,8 +84,9 @@ class RegularKeysStartWithA: public TablePropertiesCollector { return Status::OK(); } - Status AddUserKey(const Slice& user_key, const Slice& value, EntryType type, - SequenceNumber seq, uint64_t file_size) override { + Status AddUserKey(const Slice& user_key, const Slice& /*value*/, + EntryType type, SequenceNumber /*seq*/, + uint64_t file_size) override { // simply asssume all user keys are not empty. if (user_key.data()[0] == 'A') { ++count_; @@ -104,7 +107,7 @@ class RegularKeysStartWithA: public TablePropertiesCollector { return Status::OK(); } - virtual UserCollectedProperties GetReadableProperties() const override { + UserCollectedProperties GetReadableProperties() const override { return UserCollectedProperties{}; } @@ -133,7 +136,7 @@ class RegularKeysStartWithABackwardCompatible return Status::OK(); } - Status Add(const Slice& user_key, const Slice& value) override { + Status Add(const Slice& user_key, const Slice& /*value*/) override { // simply asssume all user keys are not empty. if (user_key.data()[0] == 'A') { ++count_; @@ -141,7 +144,7 @@ class RegularKeysStartWithABackwardCompatible return Status::OK(); } - virtual UserCollectedProperties GetReadableProperties() const override { + UserCollectedProperties GetReadableProperties() const override { return UserCollectedProperties{}; } @@ -161,8 +164,8 @@ class RegularKeysStartWithAInternal : public IntTblPropCollector { return Status::OK(); } - Status InternalAdd(const Slice& user_key, const Slice& value, - uint64_t file_size) override { + Status InternalAdd(const Slice& user_key, const Slice& /*value*/, + uint64_t /*file_size*/) override { // simply asssume all user keys are not empty. if (user_key.data()[0] == 'A') { ++count_; @@ -170,7 +173,14 @@ class RegularKeysStartWithAInternal : public IntTblPropCollector { return Status::OK(); } - virtual UserCollectedProperties GetReadableProperties() const override { + void BlockAdd(uint64_t /* blockRawBytes */, + uint64_t /* blockCompressedBytesFast */, + uint64_t /* blockCompressedBytesSlow */) override { + // Nothing to do. + return; + } + + UserCollectedProperties GetReadableProperties() const override { return UserCollectedProperties{}; } @@ -183,7 +193,7 @@ class RegularKeysStartWithAFactory : public IntTblPropCollectorFactory, public: explicit RegularKeysStartWithAFactory(bool backward_mode) : backward_mode_(backward_mode) {} - virtual TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollector* CreateTablePropertiesCollector( TablePropertiesCollectorFactory::Context context) override { EXPECT_EQ(kTestColumnFamilyId, context.column_family_id); if (!backward_mode_) { @@ -192,8 +202,8 @@ class RegularKeysStartWithAFactory : public IntTblPropCollectorFactory, return new RegularKeysStartWithABackwardCompatible(); } } - virtual IntTblPropCollector* CreateIntTblPropCollector( - uint32_t column_family_id) override { + IntTblPropCollector* CreateIntTblPropCollector( + uint32_t /*column_family_id*/) override { return new RegularKeysStartWithAInternal(); } const char* Name() const override { return "RegularKeysStartWithA"; } @@ -203,7 +213,7 @@ class RegularKeysStartWithAFactory : public IntTblPropCollectorFactory, class FlushBlockEveryThreePolicy : public FlushBlockPolicy { public: - virtual bool Update(const Slice& key, const Slice& value) override { + bool Update(const Slice& /*key*/, const Slice& /*value*/) override { return (++count_ % 3U == 0); } @@ -220,8 +230,8 @@ class FlushBlockEveryThreePolicyFactory : public FlushBlockPolicyFactory { } FlushBlockPolicy* NewFlushBlockPolicy( - const BlockBasedTableOptions& table_options, - const BlockBuilder& data_block_builder) const override { + const BlockBasedTableOptions& /*table_options*/, + const BlockBuilder& /*data_block_builder*/) const override { return new FlushBlockEveryThreePolicy; } }; @@ -250,6 +260,7 @@ void TestCustomizedTablePropertiesCollector( std::unique_ptr builder; std::unique_ptr writer; const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); std::vector> int_tbl_prop_collector_factories; if (test_int_tbl_prop_collector) { @@ -258,7 +269,7 @@ void TestCustomizedTablePropertiesCollector( } else { GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories); } - MakeBuilder(options, ioptions, internal_comparator, + MakeBuilder(options, ioptions, moptions, internal_comparator, &int_tbl_prop_collector_factories, &writer, &builder); SequenceNumber seqNum = 0U; @@ -277,7 +288,8 @@ void TestCustomizedTablePropertiesCollector( new test::StringSource(fwf->contents()))); TableProperties* props; Status s = ReadTableProperties(fake_file_reader.get(), fwf->contents().size(), - magic_number, ioptions, &props); + magic_number, ioptions, &props, + true /* compression_type_missing */); std::unique_ptr props_guard(props); ASSERT_OK(s); @@ -395,15 +407,13 @@ void TestInternalKeyPropertiesCollector( ImmutableCFOptions ioptions(options); GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories); options.comparator = comparator; - } else { - int_tbl_prop_collector_factories.emplace_back( - new InternalKeyPropertiesCollectorFactory); } const ImmutableCFOptions ioptions(options); + MutableCFOptions moptions(options); for (int iter = 0; iter < 2; ++iter) { - MakeBuilder(options, ioptions, pikc, &int_tbl_prop_collector_factories, - &writable, &builder); + MakeBuilder(options, ioptions, moptions, pikc, + &int_tbl_prop_collector_factories, &writable, &builder); for (const auto& k : keys) { builder->Add(k.Encode(), "val"); } @@ -413,12 +423,13 @@ void TestInternalKeyPropertiesCollector( test::StringSink* fwf = static_cast(writable->writable_file()); - unique_ptr reader(test::GetRandomAccessFileReader( - new test::StringSource(fwf->contents()))); + std::unique_ptr reader( + test::GetRandomAccessFileReader( + new test::StringSource(fwf->contents()))); TableProperties* props; Status s = ReadTableProperties(reader.get(), fwf->contents().size(), magic_number, - ioptions, &props); + ioptions, &props, true /* compression_type_missing */); ASSERT_OK(s); std::unique_ptr props_guard(props); diff --git a/thirdparty/rocksdb/db/transaction_log_impl.cc b/thirdparty/rocksdb/db/transaction_log_impl.cc index e22c0c4af0..f92d563eb8 100644 --- a/thirdparty/rocksdb/db/transaction_log_impl.cc +++ b/thirdparty/rocksdb/db/transaction_log_impl.cc @@ -19,19 +19,21 @@ TransactionLogIteratorImpl::TransactionLogIteratorImpl( const std::string& dir, const ImmutableDBOptions* options, const TransactionLogIterator::ReadOptions& read_options, const EnvOptions& soptions, const SequenceNumber seq, - std::unique_ptr files, VersionSet const* const versions) + std::unique_ptr files, VersionSet const* const versions, + const bool seq_per_batch) : dir_(dir), options_(options), read_options_(read_options), soptions_(soptions), - startingSequenceNumber_(seq), + starting_sequence_number_(seq), files_(std::move(files)), started_(false), - isValid_(false), - currentFileIndex_(0), - currentBatchSeq_(0), - currentLastSeq_(0), - versions_(versions) { + is_valid_(false), + current_file_index_(0), + current_batch_seq_(0), + current_last_seq_(0), + versions_(versions), + seq_per_batch_(seq_per_batch) { assert(files_ != nullptr); assert(versions_ != nullptr); @@ -41,70 +43,68 @@ TransactionLogIteratorImpl::TransactionLogIteratorImpl( } Status TransactionLogIteratorImpl::OpenLogFile( - const LogFile* logFile, unique_ptr* file_reader) { + const LogFile* log_file, + std::unique_ptr* file_reader) { Env* env = options_->env; - unique_ptr file; + std::unique_ptr file; + std::string fname; Status s; EnvOptions optimized_env_options = env->OptimizeForLogRead(soptions_); - if (logFile->Type() == kArchivedLogFile) { - std::string fname = ArchivedLogFileName(dir_, logFile->LogNumber()); + if (log_file->Type() == kArchivedLogFile) { + fname = ArchivedLogFileName(dir_, log_file->LogNumber()); s = env->NewSequentialFile(fname, &file, optimized_env_options); } else { - std::string fname = LogFileName(dir_, logFile->LogNumber()); + fname = LogFileName(dir_, log_file->LogNumber()); s = env->NewSequentialFile(fname, &file, optimized_env_options); if (!s.ok()) { // If cannot open file in DB directory. // Try the archive dir, as it could have moved in the meanwhile. - fname = ArchivedLogFileName(dir_, logFile->LogNumber()); + fname = ArchivedLogFileName(dir_, log_file->LogNumber()); s = env->NewSequentialFile(fname, &file, optimized_env_options); } } if (s.ok()) { - file_reader->reset(new SequentialFileReader(std::move(file))); + file_reader->reset(new SequentialFileReader(std::move(file), fname)); } return s; } BatchResult TransactionLogIteratorImpl::GetBatch() { - assert(isValid_); // cannot call in a non valid state. + assert(is_valid_); // cannot call in a non valid state. BatchResult result; - result.sequence = currentBatchSeq_; - result.writeBatchPtr = std::move(currentBatch_); + result.sequence = current_batch_seq_; + result.writeBatchPtr = std::move(current_batch_); return result; } -Status TransactionLogIteratorImpl::status() { - return currentStatus_; -} +Status TransactionLogIteratorImpl::status() { return current_status_; } -bool TransactionLogIteratorImpl::Valid() { - return started_ && isValid_; -} +bool TransactionLogIteratorImpl::Valid() { return started_ && is_valid_; } bool TransactionLogIteratorImpl::RestrictedRead( Slice* record, std::string* scratch) { // Don't read if no more complete entries to read from logs - if (currentLastSeq_ >= versions_->LastSequence()) { + if (current_last_seq_ >= versions_->LastSequence()) { return false; } - return currentLogReader_->ReadRecord(record, scratch); + return current_log_reader_->ReadRecord(record, scratch); } -void TransactionLogIteratorImpl::SeekToStartSequence( - uint64_t startFileIndex, - bool strict) { +void TransactionLogIteratorImpl::SeekToStartSequence(uint64_t start_file_index, + bool strict) { std::string scratch; Slice record; started_ = false; - isValid_ = false; - if (files_->size() <= startFileIndex) { + is_valid_ = false; + if (files_->size() <= start_file_index) { return; } - Status s = OpenLogReader(files_->at(startFileIndex).get()); + Status s = + OpenLogReader(files_->at(static_cast(start_file_index)).get()); if (!s.ok()) { - currentStatus_ = s; - reporter_.Info(currentStatus_.ToString().c_str()); + current_status_ = s; + reporter_.Info(current_status_.ToString().c_str()); return; } while (RestrictedRead(&record, &scratch)) { @@ -114,21 +114,22 @@ void TransactionLogIteratorImpl::SeekToStartSequence( continue; } UpdateCurrentWriteBatch(record); - if (currentLastSeq_ >= startingSequenceNumber_) { - if (strict && currentBatchSeq_ != startingSequenceNumber_) { - currentStatus_ = Status::Corruption("Gap in sequence number. Could not " - "seek to required sequence number"); - reporter_.Info(currentStatus_.ToString().c_str()); + if (current_last_seq_ >= starting_sequence_number_) { + if (strict && current_batch_seq_ != starting_sequence_number_) { + current_status_ = Status::Corruption( + "Gap in sequence number. Could not " + "seek to required sequence number"); + reporter_.Info(current_status_.ToString().c_str()); return; } else if (strict) { reporter_.Info("Could seek required sequence number. Iterator will " "continue."); } - isValid_ = true; + is_valid_ = true; started_ = true; // set started_ as we could seek till starting sequence return; } else { - isValid_ = false; + is_valid_ = false; } } @@ -137,13 +138,15 @@ void TransactionLogIteratorImpl::SeekToStartSequence( // If strict is set, we want to seek exactly till the start sequence and it // should have been present in the file we scanned above if (strict) { - currentStatus_ = Status::Corruption("Gap in sequence number. Could not " - "seek to required sequence number"); - reporter_.Info(currentStatus_.ToString().c_str()); + current_status_ = Status::Corruption( + "Gap in sequence number. Could not " + "seek to required sequence number"); + reporter_.Info(current_status_.ToString().c_str()); } else if (files_->size() != 1) { - currentStatus_ = Status::Corruption("Start sequence was not found, " - "skipping to the next available"); - reporter_.Info(currentStatus_.ToString().c_str()); + current_status_ = Status::Corruption( + "Start sequence was not found, " + "skipping to the next available"); + reporter_.Info(current_status_.ToString().c_str()); // Let NextImpl find the next available entry. started_ remains false // because we don't want to check for gaps while moving to start sequence NextImpl(true); @@ -157,15 +160,15 @@ void TransactionLogIteratorImpl::Next() { void TransactionLogIteratorImpl::NextImpl(bool internal) { std::string scratch; Slice record; - isValid_ = false; + is_valid_ = false; if (!internal && !started_) { // Runs every time until we can seek to the start sequence return SeekToStartSequence(); } while(true) { - assert(currentLogReader_); - if (currentLogReader_->IsEOF()) { - currentLogReader_->UnmarkEOF(); + assert(current_log_reader_); + if (current_log_reader_->IsEOF()) { + current_log_reader_->UnmarkEOF(); } while (RestrictedRead(&record, &scratch)) { if (record.size() < WriteBatchInternal::kHeader) { @@ -186,20 +189,20 @@ void TransactionLogIteratorImpl::NextImpl(bool internal) { } // Open the next file - if (currentFileIndex_ < files_->size() - 1) { - ++currentFileIndex_; - Status s = OpenLogReader(files_->at(currentFileIndex_).get()); + if (current_file_index_ < files_->size() - 1) { + ++current_file_index_; + Status s = OpenLogReader(files_->at(current_file_index_).get()); if (!s.ok()) { - isValid_ = false; - currentStatus_ = s; + is_valid_ = false; + current_status_ = s; return; } } else { - isValid_ = false; - if (currentLastSeq_ == versions_->LastSequence()) { - currentStatus_ = Status::OK(); + is_valid_ = false; + if (current_last_seq_ == versions_->LastSequence()) { + current_status_ = Status::OK(); } else { - currentStatus_ = Status::Corruption("NO MORE DATA LEFT"); + current_status_ = Status::Corruption("NO MORE DATA LEFT"); } return; } @@ -207,17 +210,16 @@ void TransactionLogIteratorImpl::NextImpl(bool internal) { } bool TransactionLogIteratorImpl::IsBatchExpected( - const WriteBatch* batch, - const SequenceNumber expectedSeq) { + const WriteBatch* batch, const SequenceNumber expected_seq) { assert(batch); SequenceNumber batchSeq = WriteBatchInternal::Sequence(batch); - if (batchSeq != expectedSeq) { + if (batchSeq != expected_seq) { char buf[200]; snprintf(buf, sizeof(buf), "Discontinuity in log records. Got seq=%" PRIu64 ", Expected seq=%" PRIu64 ", Last flushed seq=%" PRIu64 ".Log iterator will reseek the correct batch.", - batchSeq, expectedSeq, versions_->LastSequence()); + batchSeq, expected_seq, versions_->LastSequence()); reporter_.Info(buf); return false; } @@ -228,44 +230,90 @@ void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) { std::unique_ptr batch(new WriteBatch()); WriteBatchInternal::SetContents(batch.get(), record); - SequenceNumber expectedSeq = currentLastSeq_ + 1; + SequenceNumber expected_seq = current_last_seq_ + 1; // If the iterator has started, then confirm that we get continuous batches - if (started_ && !IsBatchExpected(batch.get(), expectedSeq)) { + if (started_ && !IsBatchExpected(batch.get(), expected_seq)) { // Seek to the batch having expected sequence number - if (expectedSeq < files_->at(currentFileIndex_)->StartSequence()) { + if (expected_seq < files_->at(current_file_index_)->StartSequence()) { // Expected batch must lie in the previous log file // Avoid underflow. - if (currentFileIndex_ != 0) { - currentFileIndex_--; + if (current_file_index_ != 0) { + current_file_index_--; } } - startingSequenceNumber_ = expectedSeq; + starting_sequence_number_ = expected_seq; // currentStatus_ will be set to Ok if reseek succeeds - currentStatus_ = Status::NotFound("Gap in sequence numbers"); - return SeekToStartSequence(currentFileIndex_, true); + // Note: this is still ok in seq_pre_batch_ && two_write_queuesp_ mode + // that allows gaps in the WAL since it will still skip over the gap. + current_status_ = Status::NotFound("Gap in sequence numbers"); + // In seq_per_batch_ mode, gaps in the seq are possible so the strict mode + // should be disabled + return SeekToStartSequence(current_file_index_, !seq_per_batch_); } - currentBatchSeq_ = WriteBatchInternal::Sequence(batch.get()); - currentLastSeq_ = currentBatchSeq_ + - WriteBatchInternal::Count(batch.get()) - 1; + struct BatchCounter : public WriteBatch::Handler { + SequenceNumber sequence_; + BatchCounter(SequenceNumber sequence) : sequence_(sequence) {} + Status MarkNoop(bool empty_batch) override { + if (!empty_batch) { + sequence_++; + } + return Status::OK(); + } + Status MarkEndPrepare(const Slice&) override { + sequence_++; + return Status::OK(); + } + Status MarkCommit(const Slice&) override { + sequence_++; + return Status::OK(); + } + + Status PutCF(uint32_t /*cf*/, const Slice& /*key*/, + const Slice& /*val*/) override { + return Status::OK(); + } + Status DeleteCF(uint32_t /*cf*/, const Slice& /*key*/) override { + return Status::OK(); + } + Status SingleDeleteCF(uint32_t /*cf*/, const Slice& /*key*/) override { + return Status::OK(); + } + Status MergeCF(uint32_t /*cf*/, const Slice& /*key*/, + const Slice& /*val*/) override { + return Status::OK(); + } + Status MarkBeginPrepare(bool) override { return Status::OK(); } + Status MarkRollback(const Slice&) override { return Status::OK(); } + }; + + current_batch_seq_ = WriteBatchInternal::Sequence(batch.get()); + if (seq_per_batch_) { + BatchCounter counter(current_batch_seq_); + batch->Iterate(&counter); + current_last_seq_ = counter.sequence_; + } else { + current_last_seq_ = + current_batch_seq_ + WriteBatchInternal::Count(batch.get()) - 1; + } // currentBatchSeq_ can only change here - assert(currentLastSeq_ <= versions_->LastSequence()); + assert(current_last_seq_ <= versions_->LastSequence()); - currentBatch_ = std::move(batch); - isValid_ = true; - currentStatus_ = Status::OK(); + current_batch_ = std::move(batch); + is_valid_ = true; + current_status_ = Status::OK(); } -Status TransactionLogIteratorImpl::OpenLogReader(const LogFile* logFile) { - unique_ptr file; - Status s = OpenLogFile(logFile, &file); +Status TransactionLogIteratorImpl::OpenLogReader(const LogFile* log_file) { + std::unique_ptr file; + Status s = OpenLogFile(log_file, &file); if (!s.ok()) { return s; } assert(file); - currentLogReader_.reset(new log::Reader( - options_->info_log, std::move(file), &reporter_, - read_options_.verify_checksums_, 0, logFile->LogNumber())); + current_log_reader_.reset( + new log::Reader(options_->info_log, std::move(file), &reporter_, + read_options_.verify_checksums_, log_file->LogNumber())); return Status::OK(); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/transaction_log_impl.h b/thirdparty/rocksdb/db/transaction_log_impl.h index 769d8339bd..6382b61a5b 100644 --- a/thirdparty/rocksdb/db/transaction_log_impl.h +++ b/thirdparty/rocksdb/db/transaction_log_impl.h @@ -62,7 +62,8 @@ class TransactionLogIteratorImpl : public TransactionLogIterator { const std::string& dir, const ImmutableDBOptions* options, const TransactionLogIterator::ReadOptions& read_options, const EnvOptions& soptions, const SequenceNumber seqNum, - std::unique_ptr files, VersionSet const* const versions); + std::unique_ptr files, VersionSet const* const versions, + const bool seq_per_batch); virtual bool Valid() override; @@ -77,16 +78,16 @@ class TransactionLogIteratorImpl : public TransactionLogIterator { const ImmutableDBOptions* options_; const TransactionLogIterator::ReadOptions read_options_; const EnvOptions& soptions_; - SequenceNumber startingSequenceNumber_; + SequenceNumber starting_sequence_number_; std::unique_ptr files_; bool started_; - bool isValid_; // not valid when it starts of. - Status currentStatus_; - size_t currentFileIndex_; - std::unique_ptr currentBatch_; - unique_ptr currentLogReader_; - Status OpenLogFile(const LogFile* logFile, - unique_ptr* file); + bool is_valid_; // not valid when it starts of. + Status current_status_; + size_t current_file_index_; + std::unique_ptr current_batch_; + std::unique_ptr current_log_reader_; + Status OpenLogFile(const LogFile* log_file, + std::unique_ptr* file); struct LogReporter : public log::Reader::Reporter { Env* env; @@ -98,24 +99,25 @@ class TransactionLogIteratorImpl : public TransactionLogIterator { virtual void Info(const char* s) { ROCKS_LOG_INFO(info_log, "%s", s); } } reporter_; - SequenceNumber currentBatchSeq_; // sequence number at start of current batch - SequenceNumber currentLastSeq_; // last sequence in the current batch + SequenceNumber + current_batch_seq_; // sequence number at start of current batch + SequenceNumber current_last_seq_; // last sequence in the current batch // Used only to get latest seq. num // TODO(icanadi) can this be just a callback? VersionSet const* const versions_; - + const bool seq_per_batch_; // Reads from transaction log only if the writebatch record has been written bool RestrictedRead(Slice* record, std::string* scratch); // Seeks to startingSequenceNumber reading from startFileIndex in files_. // If strict is set,then must get a batch starting with startingSequenceNumber - void SeekToStartSequence(uint64_t startFileIndex = 0, bool strict = false); + void SeekToStartSequence(uint64_t start_file_index = 0, bool strict = false); // Implementation of Next. SeekToStartSequence calls it internally with // internal=true to let it find next entry even if it has to jump gaps because // the iterator may start off from the first available entry but promises to // be continuous after that void NextImpl(bool internal = false); // Check if batch is expected, else return false - bool IsBatchExpected(const WriteBatch* batch, SequenceNumber expectedSeq); + bool IsBatchExpected(const WriteBatch* batch, SequenceNumber expected_seq); // Update current batch if a continuous batch is found, else return false void UpdateCurrentWriteBatch(const Slice& record); Status OpenLogReader(const LogFile* file); diff --git a/thirdparty/rocksdb/db/version_builder.cc b/thirdparty/rocksdb/db/version_builder.cc index e8db67527e..84e4dc6579 100644 --- a/thirdparty/rocksdb/db/version_builder.cc +++ b/thirdparty/rocksdb/db/version_builder.cc @@ -35,11 +35,11 @@ namespace rocksdb { bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) { - if (a->largest_seqno != b->largest_seqno) { - return a->largest_seqno > b->largest_seqno; + if (a->fd.largest_seqno != b->fd.largest_seqno) { + return a->fd.largest_seqno > b->fd.largest_seqno; } - if (a->smallest_seqno != b->smallest_seqno) { - return a->smallest_seqno > b->smallest_seqno; + if (a->fd.smallest_seqno != b->fd.smallest_seqno) { + return a->fd.smallest_seqno > b->fd.smallest_seqno; } // Break ties by file number return a->fd.GetNumber() > b->fd.GetNumber(); @@ -66,6 +66,8 @@ class VersionBuilder::Rep { enum SortMethod { kLevel0 = 0, kLevelNon0 = 1, } sort_method; const InternalKeyComparator* internal_comparator; + FileComparator() : internal_comparator(nullptr) {} + bool operator()(FileMetaData* f1, FileMetaData* f2) const { switch (sort_method) { case kLevel0: @@ -160,22 +162,24 @@ class VersionBuilder::Rep { abort(); } - if (f2->smallest_seqno == f2->largest_seqno) { + if (f2->fd.smallest_seqno == f2->fd.largest_seqno) { // This is an external file that we ingested - SequenceNumber external_file_seqno = f2->smallest_seqno; - if (!(external_file_seqno < f1->largest_seqno || + SequenceNumber external_file_seqno = f2->fd.smallest_seqno; + if (!(external_file_seqno < f1->fd.largest_seqno || external_file_seqno == 0)) { - fprintf(stderr, "L0 file with seqno %" PRIu64 " %" PRIu64 - " vs. file with global_seqno %" PRIu64 "\n", - f1->smallest_seqno, f1->largest_seqno, + fprintf(stderr, + "L0 file with seqno %" PRIu64 " %" PRIu64 + " vs. file with global_seqno %" PRIu64 "\n", + f1->fd.smallest_seqno, f1->fd.largest_seqno, external_file_seqno); abort(); } - } else if (f1->smallest_seqno <= f2->smallest_seqno) { - fprintf(stderr, "L0 files seqno %" PRIu64 " %" PRIu64 - " vs. %" PRIu64 " %" PRIu64 "\n", - f1->smallest_seqno, f1->largest_seqno, f2->smallest_seqno, - f2->largest_seqno); + } else if (f1->fd.smallest_seqno <= f2->fd.smallest_seqno) { + fprintf(stderr, + "L0 files seqno %" PRIu64 " %" PRIu64 " vs. %" PRIu64 + " %" PRIu64 "\n", + f1->fd.smallest_seqno, f1->fd.largest_seqno, + f2->fd.smallest_seqno, f2->fd.largest_seqno); abort(); } } else { @@ -197,7 +201,7 @@ class VersionBuilder::Rep { } } - void CheckConsistencyForDeletes(VersionEdit* edit, uint64_t number, + void CheckConsistencyForDeletes(VersionEdit* /*edit*/, uint64_t number, int level) { #ifdef NDEBUG if (!base_vstorage_->force_consistency_checks()) { @@ -274,11 +278,12 @@ class VersionBuilder::Rep { auto exising = levels_[level].added_files.find(number); if (exising != levels_[level].added_files.end()) { UnrefFile(exising->second); - levels_[level].added_files.erase(number); + levels_[level].added_files.erase(exising); } } else { - if (invalid_levels_[level].count(number) > 0) { - invalid_levels_[level].erase(number); + auto exising = invalid_levels_[level].find(number); + if (exising != invalid_levels_[level].end()) { + invalid_levels_[level].erase(exising); } else { // Deleting an non-existing file on invalid level. has_invalid_levels_ = true; @@ -319,8 +324,6 @@ class VersionBuilder::Rep { // Merge the set of added files with the set of pre-existing files. // Drop any deleted files. Store the result in *v. const auto& base_files = base_vstorage_->LevelFiles(level); - auto base_iter = base_files.begin(); - auto base_end = base_files.end(); const auto& unordered_added_files = levels_[level].added_files; vstorage->Reserve(level, base_files.size() + unordered_added_files.size()); @@ -334,51 +337,92 @@ class VersionBuilder::Rep { std::sort(added_files.begin(), added_files.end(), cmp); #ifndef NDEBUG - FileMetaData* prev_file = nullptr; -#endif - + FileMetaData* prev_added_file = nullptr; for (const auto& added : added_files) { -#ifndef NDEBUG - if (level > 0 && prev_file != nullptr) { + if (level > 0 && prev_added_file != nullptr) { assert(base_vstorage_->InternalComparator()->Compare( - prev_file->smallest, added->smallest) <= 0); + prev_added_file->smallest, added->smallest) <= 0); } - prev_file = added; + prev_added_file = added; + } #endif - // Add all smaller files listed in base_ - for (auto bpos = std::upper_bound(base_iter, base_end, added, cmp); - base_iter != bpos; ++base_iter) { - MaybeAddFile(vstorage, level, *base_iter); + auto base_iter = base_files.begin(); + auto base_end = base_files.end(); + auto added_iter = added_files.begin(); + auto added_end = added_files.end(); + while (added_iter != added_end || base_iter != base_end) { + if (base_iter == base_end || + (added_iter != added_end && cmp(*added_iter, *base_iter))) { + MaybeAddFile(vstorage, level, *added_iter++); + } else { + MaybeAddFile(vstorage, level, *base_iter++); } - - MaybeAddFile(vstorage, level, added); - } - - // Add remaining base files - for (; base_iter != base_end; ++base_iter) { - MaybeAddFile(vstorage, level, *base_iter); } } CheckConsistency(vstorage); } - void LoadTableHandlers(InternalStats* internal_stats, int max_threads, - bool prefetch_index_and_filter_in_cache) { + Status LoadTableHandlers(InternalStats* internal_stats, int max_threads, + bool prefetch_index_and_filter_in_cache, + bool is_initial_load, + const SliceTransform* prefix_extractor) { assert(table_cache_ != nullptr); + + size_t table_cache_capacity = table_cache_->get_cache()->GetCapacity(); + bool always_load = (table_cache_capacity == TableCache::kInfiniteCapacity); + size_t max_load = port::kMaxSizet; + + if (!always_load) { + // If it is initial loading and not set to always laoding all the + // files, we only load up to kInitialLoadLimit files, to limit the + // time reopening the DB. + const size_t kInitialLoadLimit = 16; + size_t load_limit; + // If the table cache is not 1/4 full, we pin the table handle to + // file metadata to avoid the cache read costs when reading the file. + // The downside of pinning those files is that LRU won't be followed + // for those files. This doesn't matter much because if number of files + // of the DB excceeds table cache capacity, eventually no table reader + // will be pinned and LRU will be followed. + if (is_initial_load) { + load_limit = std::min(kInitialLoadLimit, table_cache_capacity / 4); + } else { + load_limit = table_cache_capacity / 4; + } + + size_t table_cache_usage = table_cache_->get_cache()->GetUsage(); + if (table_cache_usage >= load_limit) { + // TODO (yanqin) find a suitable status code. + return Status::OK(); + } else { + max_load = load_limit - table_cache_usage; + } + } + // std::vector> files_meta; + std::vector statuses; for (int level = 0; level < num_levels_; level++) { for (auto& file_meta_pair : levels_[level].added_files) { auto* file_meta = file_meta_pair.second; - assert(!file_meta->table_reader_handle); - files_meta.emplace_back(file_meta, level); + // If the file has been opened before, just skip it. + if (!file_meta->table_reader_handle) { + files_meta.emplace_back(file_meta, level); + statuses.emplace_back(Status::OK()); + } + if (files_meta.size() >= max_load) { + break; + } + } + if (files_meta.size() >= max_load) { + break; } } std::atomic next_file_meta_idx(0); - std::function load_handlers_func = [&]() { + std::function load_handlers_func([&]() { while (true) { size_t file_idx = next_file_meta_idx.fetch_add(1); if (file_idx >= files_meta.size()) { @@ -387,37 +431,39 @@ class VersionBuilder::Rep { auto* file_meta = files_meta[file_idx].first; int level = files_meta[file_idx].second; - table_cache_->FindTable(env_options_, - *(base_vstorage_->InternalComparator()), - file_meta->fd, &file_meta->table_reader_handle, - false /*no_io */, true /* record_read_stats */, - internal_stats->GetFileReadHist(level), false, - level, prefetch_index_and_filter_in_cache); + statuses[file_idx] = table_cache_->FindTable( + env_options_, *(base_vstorage_->InternalComparator()), + file_meta->fd, &file_meta->table_reader_handle, prefix_extractor, + false /*no_io */, true /* record_read_stats */, + internal_stats->GetFileReadHist(level), false, level, + prefetch_index_and_filter_in_cache); if (file_meta->table_reader_handle != nullptr) { // Load table_reader file_meta->fd.table_reader = table_cache_->GetTableReaderFromHandle( file_meta->table_reader_handle); } } - }; + }); - if (max_threads <= 1) { - load_handlers_func(); - } else { - std::vector threads; - for (int i = 0; i < max_threads; i++) { - threads.emplace_back(load_handlers_func); - } - - for (auto& t : threads) { - t.join(); + std::vector threads; + for (int i = 1; i < max_threads; i++) { + threads.emplace_back(load_handlers_func); + } + load_handlers_func(); + for (auto& t : threads) { + t.join(); + } + for (const auto& s : statuses) { + if (!s.ok()) { + return s; } } + return Status::OK(); } void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f) { if (levels_[level].deleted_files.count(f->fd.GetNumber()) > 0) { - // f is to-be-delected table file + // f is to-be-deleted table file vstorage->RemoveCurrentStats(f); } else { vstorage->AddFile(level, f, info_log_); @@ -452,11 +498,13 @@ void VersionBuilder::SaveTo(VersionStorageInfo* vstorage) { rep_->SaveTo(vstorage); } -void VersionBuilder::LoadTableHandlers( +Status VersionBuilder::LoadTableHandlers( InternalStats* internal_stats, int max_threads, - bool prefetch_index_and_filter_in_cache) { - rep_->LoadTableHandlers(internal_stats, max_threads, - prefetch_index_and_filter_in_cache); + bool prefetch_index_and_filter_in_cache, bool is_initial_load, + const SliceTransform* prefix_extractor) { + return rep_->LoadTableHandlers(internal_stats, max_threads, + prefetch_index_and_filter_in_cache, + is_initial_load, prefix_extractor); } void VersionBuilder::MaybeAddFile(VersionStorageInfo* vstorage, int level, diff --git a/thirdparty/rocksdb/db/version_builder.h b/thirdparty/rocksdb/db/version_builder.h index 440d4eaf6b..168301fdd6 100644 --- a/thirdparty/rocksdb/db/version_builder.h +++ b/thirdparty/rocksdb/db/version_builder.h @@ -9,6 +9,7 @@ // #pragma once #include "rocksdb/env.h" +#include "rocksdb/slice_transform.h" namespace rocksdb { @@ -32,8 +33,10 @@ class VersionBuilder { bool CheckConsistencyForNumLevels(); void Apply(VersionEdit* edit); void SaveTo(VersionStorageInfo* vstorage); - void LoadTableHandlers(InternalStats* internal_stats, int max_threads, - bool prefetch_index_and_filter_in_cache); + Status LoadTableHandlers(InternalStats* internal_stats, int max_threads, + bool prefetch_index_and_filter_in_cache, + bool is_initial_load, + const SliceTransform* prefix_extractor); void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f); private: diff --git a/thirdparty/rocksdb/db/version_builder_test.cc b/thirdparty/rocksdb/db/version_builder_test.cc index 304df2a045..514952bb5b 100644 --- a/thirdparty/rocksdb/db/version_builder_test.cc +++ b/thirdparty/rocksdb/db/version_builder_test.cc @@ -37,7 +37,7 @@ class VersionBuilderTest : public testing::Test { size_being_compacted_.resize(options_.num_levels); } - ~VersionBuilderTest() { + ~VersionBuilderTest() override { for (int i = 0; i < vstorage_.num_levels(); i++) { for (auto* f : vstorage_.LevelFiles(i)) { if (--f->refs == 0) { @@ -63,8 +63,8 @@ class VersionBuilderTest : public testing::Test { f->fd = FileDescriptor(file_number, path_id, file_size); f->smallest = GetInternalKey(smallest, smallest_seq); f->largest = GetInternalKey(largest, largest_seq); - f->smallest_seqno = smallest_seqno; - f->largest_seqno = largest_seqno; + f->fd.smallest_seqno = smallest_seqno; + f->fd.largest_seqno = largest_seqno; f->compensated_file_size = file_size; f->refs = 0; f->num_entries = num_entries; diff --git a/thirdparty/rocksdb/db/version_edit.cc b/thirdparty/rocksdb/db/version_edit.cc index b01f7bbdf7..01ec44515a 100644 --- a/thirdparty/rocksdb/db/version_edit.cc +++ b/thirdparty/rocksdb/db/version_edit.cc @@ -20,7 +20,7 @@ namespace rocksdb { // Tag numbers for serialized VersionEdit. These numbers are written to // disk and should not be changed. -enum Tag { +enum Tag : uint32_t { kComparator = 1, kLogNumber = 2, kNextFileNumber = 3, @@ -30,6 +30,7 @@ enum Tag { kNewFile = 7, // 8 was used for large value refs kPrevLogNumber = 9, + kMinLogNumberToKeep = 10, // these are new formats divergent from open source leveldb kNewFile2 = 100, @@ -39,11 +40,21 @@ enum Tag { kColumnFamilyAdd = 201, kColumnFamilyDrop = 202, kMaxColumnFamily = 203, + + kInAtomicGroup = 300, }; -enum CustomTag { +// Mask for an identified tag from the future which can be safely ignored. +uint32_t kTagSafeIgnoreMask = 1 << 13; + +enum CustomTag : uint32_t { kTerminate = 1, // The end of customized fields kNeedCompaction = 2, + // Since Manifest is not entirely currently forward-compatible, and the only + // forward-compatible part is the CutsomtTag of kNewFile, we currently encode + // kMinLogNumberToKeep as part of a CustomTag as a hack. This should be + // removed when manifest becomes forward-comptabile. + kMinLogNumberToKeepHack = 3, kPathId = 65, }; // If this bit for the custom tag is set, opening DB should fail if @@ -63,18 +74,22 @@ void VersionEdit::Clear() { last_sequence_ = 0; next_file_number_ = 0; max_column_family_ = 0; + min_log_number_to_keep_ = 0; has_comparator_ = false; has_log_number_ = false; has_prev_log_number_ = false; has_next_file_number_ = false; has_last_sequence_ = false; has_max_column_family_ = false; + has_min_log_number_to_keep_ = false; deleted_files_.clear(); new_files_.clear(); column_family_ = 0; is_column_family_add_ = 0; is_column_family_drop_ = 0; column_family_name_.clear(); + is_in_atomic_group_ = false; + remaining_entries_ = 0; } bool VersionEdit::EncodeTo(std::string* dst) const { @@ -97,19 +112,19 @@ bool VersionEdit::EncodeTo(std::string* dst) const { if (has_max_column_family_) { PutVarint32Varint32(dst, kMaxColumnFamily, max_column_family_); } - for (const auto& deleted : deleted_files_) { PutVarint32Varint32Varint64(dst, kDeletedFile, deleted.first /* level */, deleted.second /* file number */); } + bool min_log_num_written = false; for (size_t i = 0; i < new_files_.size(); i++) { const FileMetaData& f = new_files_[i].second; if (!f.smallest.Valid() || !f.largest.Valid()) { return false; } bool has_customized_fields = false; - if (f.marked_for_compaction) { + if (f.marked_for_compaction || has_min_log_number_to_keep_) { PutVarint32(dst, kNewFile4); has_customized_fields = true; } else if (f.fd.GetPathId() == 0) { @@ -127,7 +142,7 @@ bool VersionEdit::EncodeTo(std::string* dst) const { PutVarint64(dst, f.fd.GetFileSize()); PutLengthPrefixedSlice(dst, f.smallest.Encode()); PutLengthPrefixedSlice(dst, f.largest.Encode()); - PutVarint64Varint64(dst, f.smallest_seqno, f.largest_seqno); + PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno); if (has_customized_fields) { // Customized fields' format: // +-----------------------------+ @@ -165,6 +180,13 @@ bool VersionEdit::EncodeTo(std::string* dst) const { char p = static_cast(1); PutLengthPrefixedSlice(dst, Slice(&p, 1)); } + if (has_min_log_number_to_keep_ && !min_log_num_written) { + PutVarint32(dst, CustomTag::kMinLogNumberToKeepHack); + std::string varint_log_number; + PutFixed64(&varint_log_number, min_log_number_to_keep_); + PutLengthPrefixedSlice(dst, Slice(varint_log_number)); + min_log_num_written = true; + } TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields", dst); @@ -185,6 +207,11 @@ bool VersionEdit::EncodeTo(std::string* dst) const { if (is_column_family_drop_) { PutVarint32(dst, kColumnFamilyDrop); } + + if (is_in_atomic_group_) { + PutVarint32(dst, kInAtomicGroup); + PutVarint32(dst, remaining_entries_); + } return true; } @@ -198,7 +225,7 @@ static bool GetInternalKey(Slice* input, InternalKey* dst) { } } -bool VersionEdit::GetLevel(Slice* input, int* level, const char** msg) { +bool VersionEdit::GetLevel(Slice* input, int* level, const char** /*msg*/) { uint32_t v; if (GetVarint32(input, &v)) { *level = v; @@ -218,11 +245,16 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) { uint64_t number; uint32_t path_id = 0; uint64_t file_size; + SequenceNumber smallest_seqno; + SequenceNumber largest_seqno; + // Since this is the only forward-compatible part of the code, we hack new + // extension into this record. When we do, we set this boolean to distinguish + // the record from the normal NewFile records. if (GetLevel(input, &level, &msg) && GetVarint64(input, &number) && GetVarint64(input, &file_size) && GetInternalKey(input, &f.smallest) && GetInternalKey(input, &f.largest) && - GetVarint64(input, &f.smallest_seqno) && - GetVarint64(input, &f.largest_seqno)) { + GetVarint64(input, &smallest_seqno) && + GetVarint64(input, &largest_seqno)) { // See comments in VersionEdit::EncodeTo() for format of customized fields while (true) { uint32_t custom_tag; @@ -234,7 +266,7 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) { break; } if (!GetLengthPrefixedSlice(input, &field)) { - return "new-file4 custom field lenth prefixed slice error"; + return "new-file4 custom field length prefixed slice error"; } switch (custom_tag) { case kPathId: @@ -252,6 +284,14 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) { } f.marked_for_compaction = (field[0] == 1); break; + case kMinLogNumberToKeepHack: + // This is a hack to encode kMinLogNumberToKeep in a + // forward-compatible fashion. + if (!GetFixed64(&field, &min_log_number_to_keep_)) { + return "deleted log number malformatted"; + } + has_min_log_number_to_keep_ = true; + break; default: if ((custom_tag & kCustomTagNonSafeIgnoreMask) != 0) { // Should not proceed if cannot understand it @@ -263,7 +303,8 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) { } else { return "new-file4 entry"; } - f.fd = FileDescriptor(number, path_id, file_size); + f.fd = + FileDescriptor(number, path_id, file_size, smallest_seqno, largest_seqno); new_files_.push_back(std::make_pair(level, f)); return nullptr; } @@ -331,6 +372,14 @@ Status VersionEdit::DecodeFrom(const Slice& src) { } break; + case kMinLogNumberToKeep: + if (GetVarint64(&input, &min_log_number_to_keep_)) { + has_min_log_number_to_keep_ = true; + } else { + msg = "min log number to kee"; + } + break; + case kCompactPointer: if (GetLevel(&input, &level, &msg) && GetInternalKey(&input, &key)) { @@ -375,13 +424,16 @@ Status VersionEdit::DecodeFrom(const Slice& src) { case kNewFile2: { uint64_t number; uint64_t file_size; + SequenceNumber smallest_seqno; + SequenceNumber largest_seqno; if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) && GetVarint64(&input, &file_size) && GetInternalKey(&input, &f.smallest) && GetInternalKey(&input, &f.largest) && - GetVarint64(&input, &f.smallest_seqno) && - GetVarint64(&input, &f.largest_seqno)) { - f.fd = FileDescriptor(number, 0, file_size); + GetVarint64(&input, &smallest_seqno) && + GetVarint64(&input, &largest_seqno)) { + f.fd = FileDescriptor(number, 0, file_size, smallest_seqno, + largest_seqno); new_files_.push_back(std::make_pair(level, f)); } else { if (!msg) { @@ -395,13 +447,16 @@ Status VersionEdit::DecodeFrom(const Slice& src) { uint64_t number; uint32_t path_id; uint64_t file_size; + SequenceNumber smallest_seqno; + SequenceNumber largest_seqno; if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) && GetVarint32(&input, &path_id) && GetVarint64(&input, &file_size) && GetInternalKey(&input, &f.smallest) && GetInternalKey(&input, &f.largest) && - GetVarint64(&input, &f.smallest_seqno) && - GetVarint64(&input, &f.largest_seqno)) { - f.fd = FileDescriptor(number, path_id, file_size); + GetVarint64(&input, &smallest_seqno) && + GetVarint64(&input, &largest_seqno)) { + f.fd = FileDescriptor(number, path_id, file_size, smallest_seqno, + largest_seqno); new_files_.push_back(std::make_pair(level, f)); } else { if (!msg) { @@ -439,8 +494,31 @@ Status VersionEdit::DecodeFrom(const Slice& src) { is_column_family_drop_ = true; break; + case kInAtomicGroup: + is_in_atomic_group_ = true; + if (!GetVarint32(&input, &remaining_entries_)) { + if (!msg) { + msg = "remaining entries"; + } + } + break; + default: - msg = "unknown tag"; + if (tag & kTagSafeIgnoreMask) { + // Tag from future which can be safely ignored. + // The next field must be the length of the entry. + uint32_t field_len; + if (!GetVarint32(&input, &field_len) || + static_cast(field_len) > input.size()) { + if (!msg) { + msg = "safely ignoreable tag length error"; + } + } else { + input.remove_prefix(static_cast(field_len)); + } + } else { + msg = "unknown tag"; + } break; } } @@ -475,6 +553,10 @@ std::string VersionEdit::DebugString(bool hex_key) const { r.append("\n NextFileNumber: "); AppendNumberTo(&r, next_file_number_); } + if (has_min_log_number_to_keep_) { + r.append("\n MinLogNumberToKeep: "); + AppendNumberTo(&r, min_log_number_to_keep_); + } if (has_last_sequence_) { r.append("\n LastSeq: "); AppendNumberTo(&r, last_sequence_); @@ -513,6 +595,11 @@ std::string VersionEdit::DebugString(bool hex_key) const { r.append("\n MaxColumnFamily: "); AppendNumberTo(&r, max_column_family_); } + if (is_in_atomic_group_) { + r.append("\n AtomicGroup: "); + AppendNumberTo(&r, remaining_entries_); + r.append(" entries remains"); + } r.append("\n}\n"); return r; } @@ -582,6 +669,12 @@ std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const { if (has_max_column_family_) { jw << "MaxColumnFamily" << max_column_family_; } + if (has_min_log_number_to_keep_) { + jw << "MinLogNumberToKeep" << min_log_number_to_keep_; + } + if (is_in_atomic_group_) { + jw << "AtomicGroup" << remaining_entries_; + } jw.EndObject(); diff --git a/thirdparty/rocksdb/db/version_edit.h b/thirdparty/rocksdb/db/version_edit.h index 47ebf5b1c7..ee6499cdc3 100644 --- a/thirdparty/rocksdb/db/version_edit.h +++ b/thirdparty/rocksdb/db/version_edit.h @@ -27,7 +27,7 @@ const uint64_t kFileNumberMask = 0x3FFFFFFFFFFFFFFF; extern uint64_t PackFileNumberAndPathId(uint64_t number, uint64_t path_id); // A copyable structure contains information needed to read data from an SST -// file. It can contains a pointer to a table reader opened for the file, or +// file. It can contain a pointer to a table reader opened for the file, or // file number and size, which can be used to create a new table reader for it. // The behavior is undefined when a copied of the structure is used when the // file is not in any live version any more. @@ -36,18 +36,28 @@ struct FileDescriptor { TableReader* table_reader; uint64_t packed_number_and_path_id; uint64_t file_size; // File size in bytes + SequenceNumber smallest_seqno; // The smallest seqno in this file + SequenceNumber largest_seqno; // The largest seqno in this file FileDescriptor() : FileDescriptor(0, 0, 0) {} FileDescriptor(uint64_t number, uint32_t path_id, uint64_t _file_size) + : FileDescriptor(number, path_id, _file_size, kMaxSequenceNumber, 0) {} + + FileDescriptor(uint64_t number, uint32_t path_id, uint64_t _file_size, + SequenceNumber _smallest_seqno, SequenceNumber _largest_seqno) : table_reader(nullptr), packed_number_and_path_id(PackFileNumberAndPathId(number, path_id)), - file_size(_file_size) {} + file_size(_file_size), + smallest_seqno(_smallest_seqno), + largest_seqno(_largest_seqno) {} FileDescriptor& operator=(const FileDescriptor& fd) { table_reader = fd.table_reader; packed_number_and_path_id = fd.packed_number_and_path_id; file_size = fd.file_size; + smallest_seqno = fd.smallest_seqno; + largest_seqno = fd.largest_seqno; return *this; } @@ -77,8 +87,6 @@ struct FileMetaData { FileDescriptor fd; InternalKey smallest; // Smallest internal key served by table InternalKey largest; // Largest internal key served by table - SequenceNumber smallest_seqno; // The smallest seqno in this file - SequenceNumber largest_seqno; // The largest seqno in this file // Needs to be disposed when refs becomes 0. Cache::Handle* table_reader_handle; @@ -108,9 +116,7 @@ struct FileMetaData { // file. FileMetaData() - : smallest_seqno(kMaxSequenceNumber), - largest_seqno(0), - table_reader_handle(nullptr), + : table_reader_handle(nullptr), compensated_file_size(0), num_entries(0), num_deletions(0), @@ -128,8 +134,23 @@ struct FileMetaData { smallest.DecodeFrom(key); } largest.DecodeFrom(key); - smallest_seqno = std::min(smallest_seqno, seqno); - largest_seqno = std::max(largest_seqno, seqno); + fd.smallest_seqno = std::min(fd.smallest_seqno, seqno); + fd.largest_seqno = std::max(fd.largest_seqno, seqno); + } + + // Unlike UpdateBoundaries, ranges do not need to be presented in any + // particular order. + void UpdateBoundariesForRange(const InternalKey& start, + const InternalKey& end, SequenceNumber seqno, + const InternalKeyComparator& icmp) { + if (smallest.size() == 0 || icmp.Compare(start, smallest) < 0) { + smallest = start; + } + if (largest.size() == 0 || icmp.Compare(largest, end) < 0) { + largest = end; + } + fd.smallest_seqno = std::min(fd.smallest_seqno, seqno); + fd.largest_seqno = std::max(fd.largest_seqno, seqno); } }; @@ -144,6 +165,7 @@ struct FdWithKeyRange { FdWithKeyRange() : fd(), + file_metadata(nullptr), smallest_key(), largest_key() { } @@ -198,6 +220,18 @@ class VersionEdit { has_max_column_family_ = true; max_column_family_ = max_column_family; } + void SetMinLogNumberToKeep(uint64_t num) { + has_min_log_number_to_keep_ = true; + min_log_number_to_keep_ = num; + } + + bool has_log_number() { return has_log_number_; } + + uint64_t log_number() { return log_number_; } + + bool has_next_file_number() const { return has_next_file_number_; } + + uint64_t next_file_number() const { return next_file_number_; } // Add the specified file at the specified number. // REQUIRES: This version has not been saved (see VersionSet::SaveTo) @@ -209,17 +243,18 @@ class VersionEdit { bool marked_for_compaction) { assert(smallest_seqno <= largest_seqno); FileMetaData f; - f.fd = FileDescriptor(file, file_path_id, file_size); + f.fd = FileDescriptor(file, file_path_id, file_size, smallest_seqno, + largest_seqno); f.smallest = smallest; f.largest = largest; - f.smallest_seqno = smallest_seqno; - f.largest_seqno = largest_seqno; + f.fd.smallest_seqno = smallest_seqno; + f.fd.largest_seqno = largest_seqno; f.marked_for_compaction = marked_for_compaction; new_files_.emplace_back(level, std::move(f)); } void AddFile(int level, const FileMetaData& f) { - assert(f.smallest_seqno <= f.largest_seqno); + assert(f.fd.smallest_seqno <= f.fd.largest_seqno); new_files_.emplace_back(level, f); } @@ -269,10 +304,16 @@ class VersionEdit { return new_files_; } + void MarkAtomicGroup(uint32_t remaining_entries) { + is_in_atomic_group_ = true; + remaining_entries_ = remaining_entries; + } + std::string DebugString(bool hex_key = false) const; std::string DebugJSON(int edit_num, bool hex_key = false) const; private: + friend class ReactiveVersionSet; friend class VersionSet; friend class Version; @@ -284,6 +325,8 @@ class VersionEdit { uint64_t prev_log_number_; uint64_t next_file_number_; uint32_t max_column_family_; + // The most recent WAL log number that is deleted + uint64_t min_log_number_to_keep_; SequenceNumber last_sequence_; bool has_comparator_; bool has_log_number_; @@ -291,11 +334,12 @@ class VersionEdit { bool has_next_file_number_; bool has_last_sequence_; bool has_max_column_family_; + bool has_min_log_number_to_keep_; DeletedFileSet deleted_files_; std::vector> new_files_; - // Each version edit record should have column_family_id set + // Each version edit record should have column_family_ set // If it's not set, it is default (0) uint32_t column_family_; // a version edit can be either column_family add or @@ -304,6 +348,9 @@ class VersionEdit { bool is_column_family_drop_; bool is_column_family_add_; std::string column_family_name_; + + bool is_in_atomic_group_; + uint32_t remaining_entries_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/version_edit_test.cc b/thirdparty/rocksdb/db/version_edit_test.cc index 338bb36f60..64d1fd77bc 100644 --- a/thirdparty/rocksdb/db/version_edit_test.cc +++ b/thirdparty/rocksdb/db/version_edit_test.cc @@ -8,6 +8,7 @@ // found in the LICENSE file. See the AUTHORS file for names of contributors. #include "db/version_edit.h" +#include "util/coding.h" #include "util/sync_point.h" #include "util/testharness.h" @@ -181,6 +182,63 @@ TEST_F(VersionEditTest, ColumnFamilyTest) { TestEncodeDecode(edit); } +TEST_F(VersionEditTest, MinLogNumberToKeep) { + VersionEdit edit; + edit.SetMinLogNumberToKeep(13); + TestEncodeDecode(edit); + + edit.Clear(); + edit.SetMinLogNumberToKeep(23); + TestEncodeDecode(edit); +} + +TEST_F(VersionEditTest, AtomicGroupTest) { + VersionEdit edit; + edit.MarkAtomicGroup(1); + TestEncodeDecode(edit); +} + +TEST_F(VersionEditTest, IgnorableField) { + VersionEdit ve; + std::string encoded; + + // Size of ignorable field is too large + PutVarint32Varint64(&encoded, 2 /* kLogNumber */, 66); + // This is a customized ignorable tag + PutVarint32Varint64(&encoded, + 0x2710 /* A field with kTagSafeIgnoreMask set */, + 5 /* fieldlength 5 */); + encoded += "abc"; // Only fills 3 bytes, + ASSERT_NOK(ve.DecodeFrom(encoded)); + + encoded.clear(); + // Error when seeing unidentified tag that is not ignorable + PutVarint32Varint64(&encoded, 2 /* kLogNumber */, 66); + // This is a customized ignorable tag + PutVarint32Varint64(&encoded, 666 /* A field with kTagSafeIgnoreMask unset */, + 3 /* fieldlength 3 */); + encoded += "abc"; // Fill 3 bytes + PutVarint32Varint64(&encoded, 3 /* next file number */, 88); + ASSERT_NOK(ve.DecodeFrom(encoded)); + + // Safely ignore an identified but safely ignorable entry + encoded.clear(); + PutVarint32Varint64(&encoded, 2 /* kLogNumber */, 66); + // This is a customized ignorable tag + PutVarint32Varint64(&encoded, + 0x2710 /* A field with kTagSafeIgnoreMask set */, + 3 /* fieldlength 3 */); + encoded += "abc"; // Fill 3 bytes + PutVarint32Varint64(&encoded, 3 /* kNextFileNumber */, 88); + + ASSERT_OK(ve.DecodeFrom(encoded)); + + ASSERT_TRUE(ve.has_log_number()); + ASSERT_TRUE(ve.has_next_file_number()); + ASSERT_EQ(66, ve.log_number()); + ASSERT_EQ(88, ve.next_file_number()); +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/version_set.cc b/thirdparty/rocksdb/db/version_set.cc index 782ebc263e..6c7b77a900 100644 --- a/thirdparty/rocksdb/db/version_set.cc +++ b/thirdparty/rocksdb/db/version_set.cc @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -51,6 +51,7 @@ #include "util/stop_watch.h" #include "util/string_util.h" #include "util/sync_point.h" +#include "util/user_comparator_wrapper.h" namespace rocksdb { @@ -63,20 +64,39 @@ int FindFileInRange(const InternalKeyComparator& icmp, const Slice& key, uint32_t left, uint32_t right) { - while (left < right) { - uint32_t mid = (left + right) / 2; - const FdWithKeyRange& f = file_level.files[mid]; - if (icmp.InternalKeyComparator::Compare(f.largest_key, key) < 0) { - // Key at "mid.largest" is < "target". Therefore all - // files at or before "mid" are uninteresting. - left = mid + 1; - } else { - // Key at "mid.largest" is >= "target". Therefore all files - // after "mid" are uninteresting. - right = mid; + auto cmp = [&](const FdWithKeyRange& f, const Slice& k) -> bool { + return icmp.InternalKeyComparator::Compare(f.largest_key, k) < 0; + }; + const auto &b = file_level.files; + return static_cast(std::lower_bound(b + left, + b + right, key, cmp) - b); +} + +Status OverlapWithIterator(const Comparator* ucmp, + const Slice& smallest_user_key, + const Slice& largest_user_key, + InternalIterator* iter, + bool* overlap) { + InternalKey range_start(smallest_user_key, kMaxSequenceNumber, + kValueTypeForSeek); + iter->Seek(range_start.Encode()); + if (!iter->status().ok()) { + return iter->status(); + } + + *overlap = false; + if (iter->Valid()) { + ParsedInternalKey seek_result; + if (!ParseInternalKey(iter->key(), &seek_result)) { + return Status::Corruption("DB have corrupted keys"); + } + + if (ucmp->Compare(seek_result.user_key, largest_user_key) <= 0) { + *overlap = true; } } - return right; + + return iter->status(); } // Class to help choose the next file to search for the particular key. @@ -103,11 +123,15 @@ class FilePicker { #endif level_files_brief_(file_levels), is_hit_file_last_in_level_(false), + curr_file_level_(nullptr), user_key_(user_key), ikey_(ikey), file_indexer_(file_indexer), user_comparator_(user_comparator), internal_comparator_(internal_comparator) { +#ifdef NDEBUG + (void)files; +#endif // Setup member variables to search first level. search_ended_ = !PrepareNextLevel(); if (!search_ended_) { @@ -278,17 +302,28 @@ class FilePicker { // On Level-n (n>=1), files are sorted. Binary search to find the // earliest file whose largest key >= ikey. Search left bound and // right bound are used to narrow the range. - if (search_left_bound_ == search_right_bound_) { - start_index = search_left_bound_; - } else if (search_left_bound_ < search_right_bound_) { + if (search_left_bound_ <= search_right_bound_) { if (search_right_bound_ == FileIndexer::kLevelMaxIndex) { search_right_bound_ = static_cast(curr_file_level_->num_files) - 1; } + // `search_right_bound_` is an inclusive upper-bound, but since it was + // determined based on user key, it is still possible the lookup key + // falls to the right of `search_right_bound_`'s corresponding file. + // So, pass a limit one higher, which allows us to detect this case. start_index = FindFileInRange(*internal_comparator_, *curr_file_level_, ikey_, static_cast(search_left_bound_), - static_cast(search_right_bound_)); + static_cast(search_right_bound_) + 1); + if (start_index == search_right_bound_ + 1) { + // `ikey_` comes after `search_right_bound_`. The lookup key does + // not exist on this level, so let's skip this level and do a full + // binary search on the next level. + search_left_bound_ = 0; + search_right_bound_ = FileIndexer::kLevelMaxIndex; + curr_level_++; + continue; + } } else { // search_left_bound > search_right_bound, key does not exist in // this level. Since no comparison is done in this level, it will @@ -328,7 +363,11 @@ Version::~Version() { assert(f->refs > 0); f->refs--; if (f->refs <= 0) { - vset_->obsolete_files_.push_back(f); + assert(cfd_ != nullptr); + uint32_t path_id = f->fd.GetPathId(); + assert(path_id < cfd_->ioptions()->cf_paths.size()); + vset_->obsolete_files_.push_back( + ObsoleteFileInfo(f, cfd_->ioptions()->cf_paths[path_id].path)); } } } @@ -409,9 +448,9 @@ bool SomeFileOverlapsRange( // Binary search over file list uint32_t index = 0; if (smallest_user_key != nullptr) { - // Find the earliest possible internal key for smallest_user_key + // Find the leftmost possible internal key for smallest_user_key InternalKey small; - small.SetMaxPossibleForUserKey(*smallest_user_key); + small.SetMinPossibleForUserKey(*smallest_user_key); index = FindFile(icmp, file_level, small.Encode()); } @@ -425,128 +464,258 @@ bool SomeFileOverlapsRange( namespace { -// An internal iterator. For a given version/level pair, yields -// information about the files in the level. For a given entry, key() -// is the largest key that occurs in the file, and value() is an -// 16-byte value containing the file number and file size, both -// encoded using EncodeFixed64. -class LevelFileNumIterator : public InternalIterator { +class LevelIterator final : public InternalIterator { public: - LevelFileNumIterator(const InternalKeyComparator& icmp, - const LevelFilesBrief* flevel, bool should_sample) - : icmp_(icmp), + LevelIterator( + TableCache* table_cache, const ReadOptions& read_options, + const EnvOptions& env_options, const InternalKeyComparator& icomparator, + const LevelFilesBrief* flevel, const SliceTransform* prefix_extractor, + bool should_sample, HistogramImpl* file_read_hist, bool for_compaction, + bool skip_filters, int level, RangeDelAggregator* range_del_agg, + const std::vector* compaction_boundaries = + nullptr) + : table_cache_(table_cache), + read_options_(read_options), + env_options_(env_options), + icomparator_(icomparator), + user_comparator_(icomparator.user_comparator()), flevel_(flevel), - index_(static_cast(flevel->num_files)), - current_value_(0, 0, 0), // Marks as invalid - should_sample_(should_sample) {} - virtual bool Valid() const override { return index_ < flevel_->num_files; } - virtual void Seek(const Slice& target) override { - index_ = FindFile(icmp_, *flevel_, target); - } - virtual void SeekForPrev(const Slice& target) override { - SeekForPrevImpl(target, &icmp_); + prefix_extractor_(prefix_extractor), + file_read_hist_(file_read_hist), + should_sample_(should_sample), + for_compaction_(for_compaction), + skip_filters_(skip_filters), + file_index_(flevel_->num_files), + level_(level), + range_del_agg_(range_del_agg), + pinned_iters_mgr_(nullptr), + compaction_boundaries_(compaction_boundaries) { + // Empty level is not supported. + assert(flevel_ != nullptr && flevel_->num_files > 0); } - virtual void SeekToFirst() override { index_ = 0; } - virtual void SeekToLast() override { - index_ = (flevel_->num_files == 0) - ? 0 - : static_cast(flevel_->num_files) - 1; - } - virtual void Next() override { - assert(Valid()); - index_++; - } - virtual void Prev() override { - assert(Valid()); - if (index_ == 0) { - index_ = static_cast(flevel_->num_files); // Marks as invalid - } else { - index_--; - } - } + ~LevelIterator() override { delete file_iter_.Set(nullptr); } + + void Seek(const Slice& target) override; + void SeekForPrev(const Slice& target) override; + void SeekToFirst() override; + void SeekToLast() override; + void Next() override; + void Prev() override; + + bool Valid() const override { return file_iter_.Valid(); } Slice key() const override { assert(Valid()); - return flevel_->files[index_].largest_key; + return file_iter_.key(); } Slice value() const override { assert(Valid()); - - auto file_meta = flevel_->files[index_]; - if (should_sample_) { - sample_file_read_inc(file_meta.file_metadata); + return file_iter_.value(); + } + Status status() const override { + return file_iter_.iter() ? file_iter_.status() : Status::OK(); + } + void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override { + pinned_iters_mgr_ = pinned_iters_mgr; + if (file_iter_.iter()) { + file_iter_.SetPinnedItersMgr(pinned_iters_mgr); } - current_value_ = file_meta.fd; - return Slice(reinterpret_cast(¤t_value_), - sizeof(FileDescriptor)); } - virtual Status status() const override { return Status::OK(); } + bool IsKeyPinned() const override { + return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && + file_iter_.iter() && file_iter_.IsKeyPinned(); + } + bool IsValuePinned() const override { + return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && + file_iter_.iter() && file_iter_.IsValuePinned(); + } private: - const InternalKeyComparator icmp_; - const LevelFilesBrief* flevel_; - uint32_t index_; - mutable FileDescriptor current_value_; - bool should_sample_; -}; - -class LevelFileIteratorState : public TwoLevelIteratorState { - public: - // @param skip_filters Disables loading/accessing the filter block - LevelFileIteratorState(TableCache* table_cache, - const ReadOptions& read_options, - const EnvOptions& env_options, - const InternalKeyComparator& icomparator, - HistogramImpl* file_read_hist, bool for_compaction, - bool prefix_enabled, bool skip_filters, int level, - RangeDelAggregator* range_del_agg) - : TwoLevelIteratorState(prefix_enabled), - table_cache_(table_cache), - read_options_(read_options), - env_options_(env_options), - icomparator_(icomparator), - file_read_hist_(file_read_hist), - for_compaction_(for_compaction), - skip_filters_(skip_filters), - level_(level), - range_del_agg_(range_del_agg) {} + void SkipEmptyFileForward(); + void SkipEmptyFileBackward(); + void SetFileIterator(InternalIterator* iter); + void InitFileIterator(size_t new_file_index); - InternalIterator* NewSecondaryIterator(const Slice& meta_handle) override { - if (meta_handle.size() != sizeof(FileDescriptor)) { - return NewErrorInternalIterator( - Status::Corruption("FileReader invoked with unexpected value")); - } - const FileDescriptor* fd = - reinterpret_cast(meta_handle.data()); - return table_cache_->NewIterator( - read_options_, env_options_, icomparator_, *fd, range_del_agg_, - nullptr /* don't need reference to table */, file_read_hist_, - for_compaction_, nullptr /* arena */, skip_filters_, level_); + const Slice& file_smallest_key(size_t file_index) { + assert(file_index < flevel_->num_files); + return flevel_->files[file_index].smallest_key; } - bool PrefixMayMatch(const Slice& internal_key) override { - return true; + bool KeyReachedUpperBound(const Slice& internal_key) { + return read_options_.iterate_upper_bound != nullptr && + user_comparator_.Compare(ExtractUserKey(internal_key), + *read_options_.iterate_upper_bound) >= 0; } - bool KeyReachedUpperBound(const Slice& internal_key) override { - return read_options_.iterate_upper_bound != nullptr && - icomparator_.user_comparator()->Compare( - ExtractUserKey(internal_key), - *read_options_.iterate_upper_bound) >= 0; + InternalIterator* NewFileIterator() { + assert(file_index_ < flevel_->num_files); + auto file_meta = flevel_->files[file_index_]; + if (should_sample_) { + sample_file_read_inc(file_meta.file_metadata); + } + + const InternalKey* smallest_compaction_key = nullptr; + const InternalKey* largest_compaction_key = nullptr; + if (compaction_boundaries_ != nullptr) { + smallest_compaction_key = (*compaction_boundaries_)[file_index_].smallest; + largest_compaction_key = (*compaction_boundaries_)[file_index_].largest; + } + return table_cache_->NewIterator( + read_options_, env_options_, icomparator_, *file_meta.file_metadata, + range_del_agg_, prefix_extractor_, + nullptr /* don't need reference to table */, + file_read_hist_, for_compaction_, nullptr /* arena */, skip_filters_, + level_, smallest_compaction_key, largest_compaction_key); } - private: TableCache* table_cache_; const ReadOptions read_options_; const EnvOptions& env_options_; const InternalKeyComparator& icomparator_; + const UserComparatorWrapper user_comparator_; + const LevelFilesBrief* flevel_; + mutable FileDescriptor current_value_; + const SliceTransform* prefix_extractor_; + HistogramImpl* file_read_hist_; + bool should_sample_; bool for_compaction_; bool skip_filters_; + size_t file_index_; int level_; RangeDelAggregator* range_del_agg_; + IteratorWrapper file_iter_; // May be nullptr + PinnedIteratorsManager* pinned_iters_mgr_; + + // To be propagated to RangeDelAggregator in order to safely truncate range + // tombstones. + const std::vector* compaction_boundaries_; }; +void LevelIterator::Seek(const Slice& target) { + size_t new_file_index = FindFile(icomparator_, *flevel_, target); + + InitFileIterator(new_file_index); + if (file_iter_.iter() != nullptr) { + file_iter_.Seek(target); + } + SkipEmptyFileForward(); +} + +void LevelIterator::SeekForPrev(const Slice& target) { + size_t new_file_index = FindFile(icomparator_, *flevel_, target); + if (new_file_index >= flevel_->num_files) { + new_file_index = flevel_->num_files - 1; + } + + InitFileIterator(new_file_index); + if (file_iter_.iter() != nullptr) { + file_iter_.SeekForPrev(target); + SkipEmptyFileBackward(); + } +} + +void LevelIterator::SeekToFirst() { + InitFileIterator(0); + if (file_iter_.iter() != nullptr) { + file_iter_.SeekToFirst(); + } + SkipEmptyFileForward(); +} + +void LevelIterator::SeekToLast() { + InitFileIterator(flevel_->num_files - 1); + if (file_iter_.iter() != nullptr) { + file_iter_.SeekToLast(); + } + SkipEmptyFileBackward(); +} + +void LevelIterator::Next() { + assert(Valid()); + file_iter_.Next(); + SkipEmptyFileForward(); +} + +void LevelIterator::Prev() { + assert(Valid()); + file_iter_.Prev(); + SkipEmptyFileBackward(); +} + +void LevelIterator::SkipEmptyFileForward() { + while (file_iter_.iter() == nullptr || + (!file_iter_.Valid() && file_iter_.status().ok() && + !file_iter_.iter()->IsOutOfBound())) { + // Move to next file + if (file_index_ >= flevel_->num_files - 1) { + // Already at the last file + SetFileIterator(nullptr); + return; + } + if (KeyReachedUpperBound(file_smallest_key(file_index_ + 1))) { + SetFileIterator(nullptr); + return; + } + InitFileIterator(file_index_ + 1); + if (file_iter_.iter() != nullptr) { + file_iter_.SeekToFirst(); + } + } +} + +void LevelIterator::SkipEmptyFileBackward() { + while (file_iter_.iter() == nullptr || + (!file_iter_.Valid() && file_iter_.status().ok())) { + // Move to previous file + if (file_index_ == 0) { + // Already the first file + SetFileIterator(nullptr); + return; + } + InitFileIterator(file_index_ - 1); + if (file_iter_.iter() != nullptr) { + file_iter_.SeekToLast(); + } + } +} + +void LevelIterator::SetFileIterator(InternalIterator* iter) { + if (pinned_iters_mgr_ && iter) { + iter->SetPinnedItersMgr(pinned_iters_mgr_); + } + + InternalIterator* old_iter = file_iter_.Set(iter); + if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) { + pinned_iters_mgr_->PinIterator(old_iter); + } else { + delete old_iter; + } +} + +void LevelIterator::InitFileIterator(size_t new_file_index) { + if (new_file_index >= flevel_->num_files) { + file_index_ = new_file_index; + SetFileIterator(nullptr); + return; + } else { + // If the file iterator shows incomplete, we try it again if users seek + // to the same file, as this time we may go to a different data block + // which is cached in block cache. + // + if (file_iter_.iter() != nullptr && !file_iter_.status().IsIncomplete() && + new_file_index == file_index_) { + // file_iter_ is already constructed with this iterator, so + // no need to change anything + } else { + file_index_ = new_file_index; + InternalIterator* iter = NewFileIterator(); + SetFileIterator(iter); + } + } +} +} // anonymous namespace + // A wrapper of version builder which references the current version in // constructor and unref it in the destructor. // Both of the constructor and destructor need to be called inside DB Mutex. @@ -560,16 +729,14 @@ class BaseReferencedVersionBuilder { version_->Ref(); } ~BaseReferencedVersionBuilder() { - delete version_builder_; version_->Unref(); } - VersionBuilder* version_builder() { return version_builder_; } + VersionBuilder* version_builder() { return version_builder_.get(); } private: - VersionBuilder* version_builder_; + std::unique_ptr version_builder_; Version* version_; }; -} // anonymous namespace Status Version::GetTableProperties(std::shared_ptr* tp, const FileMetaData* file_meta, @@ -577,8 +744,8 @@ Status Version::GetTableProperties(std::shared_ptr* tp, auto table_cache = cfd_->table_cache(); auto ioptions = cfd_->ioptions(); Status s = table_cache->GetTableProperties( - vset_->env_options_, cfd_->internal_comparator(), file_meta->fd, - tp, true /* no io */); + env_options_, cfd_->internal_comparator(), file_meta->fd, tp, + mutable_cf_options_.prefix_extractor.get(), true /* no io */); if (s.ok()) { return s; } @@ -597,10 +764,10 @@ Status Version::GetTableProperties(std::shared_ptr* tp, file_name = *fname; } else { file_name = - TableFileName(vset_->db_options_->db_paths, file_meta->fd.GetNumber(), + TableFileName(ioptions->cf_paths, file_meta->fd.GetNumber(), file_meta->fd.GetPathId()); } - s = ioptions->env->NewRandomAccessFile(file_name, &file, vset_->env_options_); + s = ioptions->env->NewRandomAccessFile(file_name, &file, env_options_); if (!s.ok()) { return s; } @@ -609,10 +776,15 @@ Status Version::GetTableProperties(std::shared_ptr* tp, // By setting the magic number to kInvalidTableMagicNumber, we can by // pass the magic number check in the footer. std::unique_ptr file_reader( - new RandomAccessFileReader(std::move(file), file_name)); + new RandomAccessFileReader( + std::move(file), file_name, nullptr /* env */, nullptr /* stats */, + 0 /* hist_type */, nullptr /* file_read_hist */, + nullptr /* rate_limiter */, false /* for_compaction*/, + ioptions->listeners)); s = ReadTableProperties( file_reader.get(), file_meta->fd.GetFileSize(), - Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions, &raw_table_properties); + Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions, + &raw_table_properties, false /* compression_type_missing */); if (!s.ok()) { return s; } @@ -638,7 +810,7 @@ Status Version::GetPropertiesOfAllTables(TablePropertiesCollection* props, int level) { for (const auto& file_meta : storage_info_.files_[level]) { auto fname = - TableFileName(vset_->db_options_->db_paths, file_meta->fd.GetNumber(), + TableFileName(cfd_->ioptions()->cf_paths, file_meta->fd.GetNumber(), file_meta->fd.GetPathId()); // 1. If the table is already present in table cache, load table // properties from there. @@ -666,7 +838,7 @@ Status Version::GetPropertiesOfTablesInRange( false); for (const auto& file_meta : files) { auto fname = - TableFileName(vset_->db_options_->db_paths, + TableFileName(cfd_->ioptions()->cf_paths, file_meta->fd.GetNumber(), file_meta->fd.GetPathId()); if (props->count(fname) == 0) { // 1. If the table is already present in table cache, load table @@ -712,8 +884,8 @@ size_t Version::GetMemoryUsageByTableReaders() { for (auto& file_level : storage_info_.level_files_brief_) { for (size_t i = 0; i < file_level.num_files; i++) { total_usage += cfd_->table_cache()->GetMemoryUsageByTableReader( - vset_->env_options_, cfd_->internal_comparator(), - file_level.files[i].fd); + env_options_, cfd_->internal_comparator(), file_level.files[i].fd, + mutable_cf_options_.prefix_extractor.get()); } } return total_usage; @@ -738,19 +910,24 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) { for (const auto& file : vstorage->LevelFiles(level)) { uint32_t path_id = file->fd.GetPathId(); std::string file_path; - if (path_id < ioptions->db_paths.size()) { - file_path = ioptions->db_paths[path_id].path; + if (path_id < ioptions->cf_paths.size()) { + file_path = ioptions->cf_paths[path_id].path; } else { - assert(!ioptions->db_paths.empty()); - file_path = ioptions->db_paths.back().path; + assert(!ioptions->cf_paths.empty()); + file_path = ioptions->cf_paths.back().path; } - files.emplace_back( - MakeTableFileName("", file->fd.GetNumber()), file_path, - file->fd.GetFileSize(), file->smallest_seqno, file->largest_seqno, + files.emplace_back(SstFileMetaData{ + MakeTableFileName("", file->fd.GetNumber()), + file_path, + static_cast(file->fd.GetFileSize()), + file->fd.smallest_seqno, + file->fd.largest_seqno, file->smallest.user_key().ToString(), file->largest.user_key().ToString(), file->stats.num_reads_sampled.load(std::memory_order_relaxed), - file->being_compacted); + file->being_compacted}); + files.back().num_entries = file->num_entries; + files.back().num_deletions = file->num_deletions; level_size += file->fd.GetFileSize(); } cf_meta->levels.emplace_back( @@ -759,6 +936,15 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) { } } +uint64_t Version::GetSstFilesSize() { + uint64_t sst_files_size = 0; + for (int level = 0; level < storage_info_.num_levels_; level++) { + for (const auto& file_meta : storage_info_.LevelFiles(level)) { + sst_files_size += file_meta->fd.GetFileSize(); + } + } + return sst_files_size; +} uint64_t VersionStorageInfo::GetEstimatedActiveKeys() const { // Estimation will be inaccurate when: @@ -841,9 +1027,10 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options, for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) { const auto& file = storage_info_.LevelFilesBrief(0).files[i]; merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator( - read_options, soptions, cfd_->internal_comparator(), file.fd, - range_del_agg, nullptr, cfd_->internal_stats()->GetFileReadHist(0), - false, arena, false /* skip_filters */, 0 /* level */)); + read_options, soptions, cfd_->internal_comparator(), *file.file_metadata, + range_del_agg, mutable_cf_options_.prefix_extractor.get(), nullptr, + cfd_->internal_stats()->GetFileReadHist(0), false, arena, + false /* skip_filters */, 0 /* level */)); } if (should_sample) { // Count ones for every L0 files. This is done per iterator creation @@ -854,41 +1041,74 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options, sample_file_read_inc(meta); } } - } else { + } else if (storage_info_.LevelFilesBrief(level).num_files > 0) { // For levels > 0, we can use a concatenating iterator that sequentially // walks through the non-overlapping files in the level, opening them // lazily. - auto* mem = arena->AllocateAligned(sizeof(LevelFileIteratorState)); - auto* state = new (mem) - LevelFileIteratorState(cfd_->table_cache(), read_options, soptions, - cfd_->internal_comparator(), - cfd_->internal_stats()->GetFileReadHist(level), - false /* for_compaction */, - cfd_->ioptions()->prefix_extractor != nullptr, - IsFilterSkipped(level), level, range_del_agg); - mem = arena->AllocateAligned(sizeof(LevelFileNumIterator)); - auto* first_level_iter = new (mem) LevelFileNumIterator( + auto* mem = arena->AllocateAligned(sizeof(LevelIterator)); + merge_iter_builder->AddIterator(new (mem) LevelIterator( + cfd_->table_cache(), read_options, soptions, cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level), - should_sample_file_read()); - merge_iter_builder->AddIterator( - NewTwoLevelIterator(state, first_level_iter, arena, false)); + mutable_cf_options_.prefix_extractor.get(), should_sample_file_read(), + cfd_->internal_stats()->GetFileReadHist(level), + false /* for_compaction */, IsFilterSkipped(level), level, + range_del_agg)); } } -void Version::AddRangeDelIteratorsForLevel( - const ReadOptions& read_options, const EnvOptions& soptions, int level, - std::vector* range_del_iters) { - range_del_iters->clear(); - for (size_t i = 0; i < storage_info_.LevelFilesBrief(level).num_files; i++) { - const auto& file = storage_info_.LevelFilesBrief(level).files[i]; - auto* range_del_iter = cfd_->table_cache()->NewRangeTombstoneIterator( - read_options, soptions, cfd_->internal_comparator(), file.fd, - cfd_->internal_stats()->GetFileReadHist(level), - false /* skip_filters */, level); - if (range_del_iter != nullptr) { - range_del_iters->push_back(range_del_iter); +Status Version::OverlapWithLevelIterator(const ReadOptions& read_options, + const EnvOptions& env_options, + const Slice& smallest_user_key, + const Slice& largest_user_key, + int level, bool* overlap) { + assert(storage_info_.finalized_); + + auto icmp = cfd_->internal_comparator(); + auto ucmp = icmp.user_comparator(); + + Arena arena; + Status status; + ReadRangeDelAggregator range_del_agg(&icmp, + kMaxSequenceNumber /* upper_bound */); + + *overlap = false; + + if (level == 0) { + for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) { + const auto file = &storage_info_.LevelFilesBrief(0).files[i]; + if (AfterFile(ucmp, &smallest_user_key, file) || + BeforeFile(ucmp, &largest_user_key, file)) { + continue; + } + ScopedArenaIterator iter(cfd_->table_cache()->NewIterator( + read_options, env_options, cfd_->internal_comparator(), *file->file_metadata, + &range_del_agg, mutable_cf_options_.prefix_extractor.get(), nullptr, + cfd_->internal_stats()->GetFileReadHist(0), false, &arena, + false /* skip_filters */, 0 /* level */)); + status = OverlapWithIterator( + ucmp, smallest_user_key, largest_user_key, iter.get(), overlap); + if (!status.ok() || *overlap) { + break; + } } + } else if (storage_info_.LevelFilesBrief(level).num_files > 0) { + auto mem = arena.AllocateAligned(sizeof(LevelIterator)); + ScopedArenaIterator iter(new (mem) LevelIterator( + cfd_->table_cache(), read_options, env_options, + cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level), + mutable_cf_options_.prefix_extractor.get(), should_sample_file_read(), + cfd_->internal_stats()->GetFileReadHist(level), + false /* for_compaction */, IsFilterSkipped(level), level, + &range_del_agg)); + status = OverlapWithIterator( + ucmp, smallest_user_key, largest_user_key, iter.get(), overlap); + } + + if (status.ok() && *overlap == false && + range_del_agg.IsRangeOverlapped(smallest_user_key, largest_user_key)) { + *overlap = true; } + return status; } VersionStorageInfo::VersionStorageInfo( @@ -905,6 +1125,7 @@ VersionStorageInfo::VersionStorageInfo( compaction_style_(compaction_style), files_(new std::vector[num_levels_]), base_level_(num_levels_ == 1 ? -1 : 1), + level_multiplier_(0.0), files_by_compaction_pri_(num_levels_), level0_non_overlapping_(false), next_file_to_compact_by_size_(num_levels_), @@ -932,10 +1153,13 @@ VersionStorageInfo::VersionStorageInfo( current_num_non_deletions_ = ref_vstorage->current_num_non_deletions_; current_num_deletions_ = ref_vstorage->current_num_deletions_; current_num_samples_ = ref_vstorage->current_num_samples_; + oldest_snapshot_seqnum_ = ref_vstorage->oldest_snapshot_seqnum_; } } Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset, + const EnvOptions& env_opt, + const MutableCFOptions mutable_cf_options, uint64_t version_number) : env_(vset->env_), cfd_(column_family_data), @@ -959,13 +1183,16 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset, next_(this), prev_(this), refs_(0), + env_options_(env_opt), + mutable_cf_options_(mutable_cf_options), version_number_(version_number) {} void Version::Get(const ReadOptions& read_options, const LookupKey& k, PinnableSlice* value, Status* status, MergeContext* merge_context, - RangeDelAggregator* range_del_agg, bool* value_found, - bool* key_exists, SequenceNumber* seq, bool* is_blob) { + SequenceNumber* max_covering_tombstone_seq, bool* value_found, + bool* key_exists, SequenceNumber* seq, ReadCallback* callback, + bool* is_blob) { Slice ikey = k.internal_key(); Slice user_key = k.user_key(); @@ -980,8 +1207,8 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k, GetContext get_context( user_comparator(), merge_operator_, info_log_, db_statistics_, status->ok() ? GetContext::kNotFound : GetContext::kMerge, user_key, - value, value_found, merge_context, range_del_agg, this->env_, seq, - merge_operator_ ? &pinned_iters_mgr : nullptr, is_blob); + value, value_found, merge_context, max_covering_tombstone_seq, this->env_, + seq, merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob); // Pin blocks that we read to hold merge operands if (merge_operator_) { @@ -993,25 +1220,50 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k, storage_info_.num_non_empty_levels_, &storage_info_.file_indexer_, user_comparator(), internal_comparator()); FdWithKeyRange* f = fp.GetNextFile(); + while (f != nullptr) { + if (*max_covering_tombstone_seq > 0) { + // The remaining files we look at will only contain covered keys, so we + // stop here. + break; + } if (get_context.sample()) { sample_file_read_inc(f->file_metadata); } + + bool timer_enabled = + GetPerfLevel() >= PerfLevel::kEnableTimeExceptForMutex && + get_perf_context()->per_level_perf_context_enabled; + StopWatchNano timer(env_, timer_enabled /* auto_start */); *status = table_cache_->Get( - read_options, *internal_comparator(), f->fd, ikey, &get_context, + read_options, *internal_comparator(), *f->file_metadata, ikey, + &get_context, mutable_cf_options_.prefix_extractor.get(), cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()), IsFilterSkipped(static_cast(fp.GetHitFileLevel()), fp.IsHitFileLastInLevel()), fp.GetCurrentLevel()); // TODO: examine the behavior for corrupted key + if (timer_enabled) { + PERF_COUNTER_BY_LEVEL_ADD(get_from_table_nanos, timer.ElapsedNanos(), + fp.GetCurrentLevel()); + } if (!status->ok()) { return; } + // report the counters before returning + if (get_context.State() != GetContext::kNotFound && + get_context.State() != GetContext::kMerge && + db_statistics_ != nullptr) { + get_context.ReportCounters(); + } switch (get_context.State()) { case GetContext::kNotFound: // Keep searching in other files break; + case GetContext::kMerge: + // TODO: update per-level perfcontext user_key_return_count for kMerge + break; case GetContext::kFound: if (fp.GetHitFileLevel() == 0) { RecordTick(db_statistics_, GET_HIT_L0); @@ -1020,6 +1272,7 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k, } else if (fp.GetHitFileLevel() >= 2) { RecordTick(db_statistics_, GET_HIT_L2_AND_UP); } + PERF_COUNTER_BY_LEVEL_ADD(user_key_return_count, 1, fp.GetHitFileLevel()); return; case GetContext::kDeleted: // Use empty error message for speed @@ -1028,8 +1281,6 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k, case GetContext::kCorrupt: *status = Status::Corruption("corrupted key for ", user_key); return; - case GetContext::kMerge: - break; case GetContext::kBlobIndex: ROCKS_LOG_ERROR(info_log_, "Encounter unexpected blob index."); *status = Status::NotSupported( @@ -1040,6 +1291,9 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k, f = fp.GetNextFile(); } + if (db_statistics_ != nullptr) { + get_context.ReportCounters(); + } if (GetContext::kMerge == get_context.State()) { if (!merge_operator_) { *status = Status::InvalidArgument( @@ -1090,6 +1344,7 @@ void Version::PrepareApply( storage_info_.GenerateFileIndexer(); storage_info_.GenerateLevelFilesBrief(); storage_info_.GenerateLevel0NonOverlapping(); + storage_info_.GenerateBottommostFiles(); } bool Version::MaybeInitializeFileMetaData(FileMetaData* file_meta) { @@ -1109,7 +1364,7 @@ bool Version::MaybeInitializeFileMetaData(FileMetaData* file_meta) { } if (tp.get() == nullptr) return false; file_meta->num_entries = tp->num_entries; - file_meta->num_deletions = GetDeletedKeys(tp->user_collected_properties); + file_meta->num_deletions = tp->num_deletions; file_meta->raw_value_size = tp->raw_value_size; file_meta->raw_key_size = tp->raw_key_size; @@ -1328,6 +1583,7 @@ void VersionStorageInfo::EstimateCompactionBytesNeeded( namespace { uint32_t GetExpiredTtlFilesCount(const ImmutableCFOptions& ioptions, + const MutableCFOptions& mutable_cf_options, const std::vector& files) { uint32_t ttl_expired_files_count = 0; @@ -1341,8 +1597,7 @@ uint32_t GetExpiredTtlFilesCount(const ImmutableCFOptions& ioptions, auto creation_time = f->fd.table_reader->GetTableProperties()->creation_time; if (creation_time > 0 && - creation_time < - (current_time - ioptions.compaction_options_fifo.ttl)) { + creation_time < (current_time - mutable_cf_options.ttl)) { ttl_expired_files_count++; } } @@ -1389,19 +1644,19 @@ void VersionStorageInfo::ComputeCompactionScore( } if (compaction_style_ == kCompactionStyleFIFO) { - score = - static_cast(total_size) / - immutable_cf_options.compaction_options_fifo.max_table_files_size; - if (immutable_cf_options.compaction_options_fifo.allow_compaction) { + score = static_cast(total_size) / + mutable_cf_options.compaction_options_fifo.max_table_files_size; + if (mutable_cf_options.compaction_options_fifo.allow_compaction) { score = std::max( static_cast(num_sorted_runs) / mutable_cf_options.level0_file_num_compaction_trigger, score); } - if (immutable_cf_options.compaction_options_fifo.ttl > 0) { - score = std::max(static_cast(GetExpiredTtlFilesCount( - immutable_cf_options, files_[level])), - score); + if (mutable_cf_options.ttl > 0) { + score = std::max( + static_cast(GetExpiredTtlFilesCount( + immutable_cf_options, mutable_cf_options, files_[level])), + score); } } else { @@ -1446,6 +1701,10 @@ void VersionStorageInfo::ComputeCompactionScore( } } ComputeFilesMarkedForCompaction(); + ComputeBottommostFilesMarkedForCompaction(); + if (mutable_cf_options.ttl > 0) { + ComputeExpiredTtlFiles(immutable_cf_options, mutable_cf_options.ttl); + } EstimateCompactionBytesNeeded(mutable_cf_options); } @@ -1472,6 +1731,33 @@ void VersionStorageInfo::ComputeFilesMarkedForCompaction() { } } +void VersionStorageInfo::ComputeExpiredTtlFiles( + const ImmutableCFOptions& ioptions, const uint64_t ttl) { + assert(ttl > 0); + + expired_ttl_files_.clear(); + + int64_t _current_time; + auto status = ioptions.env->GetCurrentTime(&_current_time); + if (!status.ok()) { + return; + } + const uint64_t current_time = static_cast(_current_time); + + for (int level = 0; level < num_levels() - 1; level++) { + for (auto f : files_[level]) { + if (!f->being_compacted && f->fd.table_reader != nullptr && + f->fd.table_reader->GetTableProperties() != nullptr) { + auto creation_time = + f->fd.table_reader->GetTableProperties()->creation_time; + if (creation_time > 0 && creation_time < (current_time - ttl)) { + expired_ttl_files_.emplace_back(level, f); + } + } + } + } +} + namespace { // used to sort files by size @@ -1508,6 +1794,8 @@ void VersionStorageInfo::AddFile(int level, FileMetaData* f, Logger* info_log) { } assert(false); } +#else + (void)info_log; #endif f->refs++; level_files->push_back(f); @@ -1521,6 +1809,7 @@ void VersionStorageInfo::AddFile(int level, FileMetaData* f, Logger* info_log) { // 4. GenerateFileIndexer(); // 5. GenerateLevelFilesBrief(); // 6. GenerateLevel0NonOverlapping(); +// 7. GenerateBottommostFiles(); void VersionStorageInfo::SetFinalized() { finalized_ = true; #ifndef NDEBUG @@ -1597,9 +1886,9 @@ void SortFileByOverlappingRatio( next_level_it++; } - assert(file->fd.file_size != 0); + assert(file->compensated_file_size != 0); file_to_order[file->fd.GetNumber()] = - overlapping_bytes * 1024u / file->fd.file_size; + overlapping_bytes * 1024u / file->compensated_file_size; } std::sort(temp->begin(), temp->end(), @@ -1612,7 +1901,8 @@ void SortFileByOverlappingRatio( void VersionStorageInfo::UpdateFilesByCompactionPri( CompactionPri compaction_pri) { - if (compaction_style_ == kCompactionStyleFIFO || + if (compaction_style_ == kCompactionStyleNone || + compaction_style_ == kCompactionStyleFIFO || compaction_style_ == kCompactionStyleUniversal) { // don't need this return; @@ -1643,13 +1933,15 @@ void VersionStorageInfo::UpdateFilesByCompactionPri( case kOldestLargestSeqFirst: std::sort(temp.begin(), temp.end(), [](const Fsize& f1, const Fsize& f2) -> bool { - return f1.file->largest_seqno < f2.file->largest_seqno; + return f1.file->fd.largest_seqno < + f2.file->fd.largest_seqno; }); break; case kOldestSmallestSeqFirst: std::sort(temp.begin(), temp.end(), [](const Fsize& f1, const Fsize& f2) -> bool { - return f1.file->smallest_seqno < f2.file->smallest_seqno; + return f1.file->fd.smallest_seqno < + f2.file->fd.smallest_seqno; }); break; case kMinOverlappingRatio: @@ -1697,6 +1989,60 @@ void VersionStorageInfo::GenerateLevel0NonOverlapping() { } } +void VersionStorageInfo::GenerateBottommostFiles() { + assert(!finalized_); + assert(bottommost_files_.empty()); + for (size_t level = 0; level < level_files_brief_.size(); ++level) { + for (size_t file_idx = 0; file_idx < level_files_brief_[level].num_files; + ++file_idx) { + const FdWithKeyRange& f = level_files_brief_[level].files[file_idx]; + int l0_file_idx; + if (level == 0) { + l0_file_idx = static_cast(file_idx); + } else { + l0_file_idx = -1; + } + Slice smallest_user_key = ExtractUserKey(f.smallest_key); + Slice largest_user_key = ExtractUserKey(f.largest_key); + if (!RangeMightExistAfterSortedRun(smallest_user_key, largest_user_key, + static_cast(level), + l0_file_idx)) { + bottommost_files_.emplace_back(static_cast(level), + f.file_metadata); + } + } + } +} + +void VersionStorageInfo::UpdateOldestSnapshot(SequenceNumber seqnum) { + assert(seqnum >= oldest_snapshot_seqnum_); + oldest_snapshot_seqnum_ = seqnum; + if (oldest_snapshot_seqnum_ > bottommost_files_mark_threshold_) { + ComputeBottommostFilesMarkedForCompaction(); + } +} + +void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction() { + bottommost_files_marked_for_compaction_.clear(); + bottommost_files_mark_threshold_ = kMaxSequenceNumber; + for (auto& level_and_file : bottommost_files_) { + if (!level_and_file.second->being_compacted && + level_and_file.second->fd.largest_seqno != 0 && + level_and_file.second->num_deletions > 1) { + // largest_seqno might be nonzero due to containing the final key in an + // earlier compaction, whose seqnum we didn't zero out. Multiple deletions + // ensures the file really contains deleted or overwritten keys. + if (level_and_file.second->fd.largest_seqno < oldest_snapshot_seqnum_) { + bottommost_files_marked_for_compaction_.push_back(level_and_file); + } else { + bottommost_files_mark_threshold_ = + std::min(bottommost_files_mark_threshold_, + level_and_file.second->fd.largest_seqno); + } + } + } +} + void Version::Ref() { ++refs_; } @@ -1730,13 +2076,29 @@ bool VersionStorageInfo::OverlapInLevel(int level, void VersionStorageInfo::GetOverlappingInputs( int level, const InternalKey* begin, const InternalKey* end, std::vector* inputs, int hint_index, int* file_index, - bool expand_range) const { + bool expand_range, InternalKey** next_smallest) const { if (level >= num_non_empty_levels_) { // this level is empty, no overlapping inputs return; } inputs->clear(); + if (file_index) { + *file_index = -1; + } + const Comparator* user_cmp = user_comparator_; + if (level > 0) { + GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs, hint_index, + file_index, false, next_smallest); + return; + } + + if (next_smallest) { + // next_smallest key only makes sense for non-level 0, where files are + // non-overlapping + *next_smallest = nullptr; + } + Slice user_begin, user_end; if (begin != nullptr) { user_begin = begin->user_key(); @@ -1744,43 +2106,52 @@ void VersionStorageInfo::GetOverlappingInputs( if (end != nullptr) { user_end = end->user_key(); } - if (file_index) { - *file_index = -1; - } - const Comparator* user_cmp = user_comparator_; - if (begin != nullptr && end != nullptr && level > 0) { - GetOverlappingInputsRangeBinarySearch(level, user_begin, user_end, inputs, - hint_index, file_index); - return; - } - for (size_t i = 0; i < level_files_brief_[level].num_files; ) { - FdWithKeyRange* f = &(level_files_brief_[level].files[i++]); - const Slice file_start = ExtractUserKey(f->smallest_key); - const Slice file_limit = ExtractUserKey(f->largest_key); - if (begin != nullptr && user_cmp->Compare(file_limit, user_begin) < 0) { - // "f" is completely before specified range; skip it - } else if (end != nullptr && user_cmp->Compare(file_start, user_end) > 0) { - // "f" is completely after specified range; skip it - } else { - inputs->push_back(files_[level][i-1]); - if (level == 0 && expand_range) { - // Level-0 files may overlap each other. So check if the newly - // added file has expanded the range. If so, restart search. - if (begin != nullptr && user_cmp->Compare(file_start, user_begin) < 0) { - user_begin = file_start; - inputs->clear(); - i = 0; - } else if (end != nullptr - && user_cmp->Compare(file_limit, user_end) > 0) { - user_end = file_limit; - inputs->clear(); - i = 0; + // index stores the file index need to check. + std::list index; + for (size_t i = 0; i < level_files_brief_[level].num_files; i++) { + index.emplace_back(i); + } + + while (!index.empty()) { + bool found_overlapping_file = false; + auto iter = index.begin(); + while (iter != index.end()) { + FdWithKeyRange* f = &(level_files_brief_[level].files[*iter]); + const Slice file_start = ExtractUserKey(f->smallest_key); + const Slice file_limit = ExtractUserKey(f->largest_key); + if (begin != nullptr && user_cmp->Compare(file_limit, user_begin) < 0) { + // "f" is completely before specified range; skip it + iter++; + } else if (end != nullptr && + user_cmp->Compare(file_start, user_end) > 0) { + // "f" is completely after specified range; skip it + iter++; + } else { + // if overlap + inputs->emplace_back(files_[level][*iter]); + found_overlapping_file = true; + // record the first file index. + if (file_index && *file_index == -1) { + *file_index = static_cast(*iter); + } + // the related file is overlap, erase to avoid checking again. + iter = index.erase(iter); + if (expand_range) { + if (begin != nullptr && + user_cmp->Compare(file_start, user_begin) < 0) { + user_begin = file_start; + } + if (end != nullptr && user_cmp->Compare(file_limit, user_end) > 0) { + user_end = file_limit; + } } - } else if (file_index) { - *file_index = static_cast(i) - 1; } } + // if all the files left are not overlap, break + if (!found_overlapping_file) { + break; + } } } @@ -1793,27 +2164,28 @@ void VersionStorageInfo::GetOverlappingInputs( void VersionStorageInfo::GetCleanInputsWithinInterval( int level, const InternalKey* begin, const InternalKey* end, std::vector* inputs, int hint_index, int* file_index) const { - if (level >= num_non_empty_levels_) { + inputs->clear(); + if (file_index) { + *file_index = -1; + } + if (level >= num_non_empty_levels_ || level == 0 || + level_files_brief_[level].num_files == 0) { // this level is empty, no inputs within range + // also don't support clean input interval within L0 return; } - inputs->clear(); - Slice user_begin, user_end; - if (begin != nullptr) { - user_begin = begin->user_key(); - } - if (end != nullptr) { - user_end = end->user_key(); + const auto& level_files = level_files_brief_[level]; + if (begin == nullptr) { + begin = &level_files.files[0].file_metadata->smallest; } - if (file_index) { - *file_index = -1; - } - if (begin != nullptr && end != nullptr && level > 0) { - GetOverlappingInputsRangeBinarySearch(level, user_begin, user_end, inputs, - hint_index, file_index, - true /* within_interval */); + if (end == nullptr) { + end = &level_files.files[level_files.num_files - 1].file_metadata->largest; } + + GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs, + hint_index, file_index, + true /* within_interval */); } // Store in "*inputs" all files in "level" that overlap [begin,end] @@ -1824,15 +2196,15 @@ void VersionStorageInfo::GetCleanInputsWithinInterval( // within range [begin, end]. "clean" means there is a boudnary // between the files in "*inputs" and the surrounding files void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch( - int level, const Slice& user_begin, const Slice& user_end, + int level, const InternalKey* begin, const InternalKey* end, std::vector* inputs, int hint_index, int* file_index, - bool within_interval) const { + bool within_interval, InternalKey** next_smallest) const { assert(level > 0); int min = 0; int mid = 0; int max = static_cast(files_[level].size()) - 1; bool foundOverlap = false; - const Comparator* user_cmp = user_comparator_; + auto user_cmp = user_comparator_; // if the caller already knows the index of a file that has overlap, // then we can skip the binary search. @@ -1844,15 +2216,15 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch( while (!foundOverlap && min <= max) { mid = (min + max)/2; FdWithKeyRange* f = &(level_files_brief_[level].files[mid]); - const Slice file_start = ExtractUserKey(f->smallest_key); - const Slice file_limit = ExtractUserKey(f->largest_key); - if ((!within_interval && user_cmp->Compare(file_limit, user_begin) < 0) || - (within_interval && user_cmp->Compare(file_start, user_begin) < 0)) { + auto& smallest = f->file_metadata->smallest; + auto& largest = f->file_metadata->largest; + if ((!within_interval && sstableKeyCompare(user_cmp, begin, largest) > 0) || + (within_interval && sstableKeyCompare(user_cmp, begin, smallest) > 0)) { min = mid + 1; } else if ((!within_interval && - user_cmp->Compare(user_end, file_start) < 0) || + sstableKeyCompare(user_cmp, smallest, end) > 0) || (within_interval && - user_cmp->Compare(user_end, file_limit) < 0)) { + sstableKeyCompare(user_cmp, largest, end) > 0)) { max = mid - 1; } else { foundOverlap = true; @@ -1862,6 +2234,9 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch( // If there were no overlapping files, return immediately. if (!foundOverlap) { + if (next_smallest) { + *next_smallest = nullptr; + } return; } // returns the index where an overlap is found @@ -1871,17 +2246,26 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch( int start_index, end_index; if (within_interval) { - ExtendFileRangeWithinInterval(level, user_begin, user_end, mid, &start_index, - &end_index); + ExtendFileRangeWithinInterval(level, begin, end, mid, + &start_index, &end_index); } else { - ExtendFileRangeOverlappingInterval(level, user_begin, user_end, mid, + ExtendFileRangeOverlappingInterval(level, begin, end, mid, &start_index, &end_index); + assert(end_index >= start_index); } - assert(end_index >= start_index); // insert overlapping files into vector for (int i = start_index; i <= end_index; i++) { inputs->push_back(files_[level][i]); } + + if (next_smallest != nullptr) { + // Provide the next key outside the range covered by inputs + if (++end_index < static_cast(files_[level].size())) { + **next_smallest = files_[level][end_index]->smallest; + } else { + *next_smallest = nullptr; + } + } } // Store in *start_index and *end_index the range of all files in @@ -1891,33 +2275,41 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch( // and forward to find all overlapping files. // Use FileLevel in searching, make it faster void VersionStorageInfo::ExtendFileRangeOverlappingInterval( - int level, const Slice& user_begin, const Slice& user_end, + int level, const InternalKey* begin, const InternalKey* end, unsigned int mid_index, int* start_index, int* end_index) const { - const Comparator* user_cmp = user_comparator_; + auto user_cmp = user_comparator_; const FdWithKeyRange* files = level_files_brief_[level].files; #ifndef NDEBUG { // assert that the file at mid_index overlaps with the range assert(mid_index < level_files_brief_[level].num_files); const FdWithKeyRange* f = &files[mid_index]; - const Slice fstart = ExtractUserKey(f->smallest_key); - const Slice flimit = ExtractUserKey(f->largest_key); - if (user_cmp->Compare(fstart, user_begin) >= 0) { - assert(user_cmp->Compare(fstart, user_end) <= 0); + auto& smallest = f->file_metadata->smallest; + auto& largest = f->file_metadata->largest; + if (sstableKeyCompare(user_cmp, begin, smallest) <= 0) { + assert(sstableKeyCompare(user_cmp, smallest, end) <= 0); } else { - assert(user_cmp->Compare(flimit, user_begin) >= 0); + // fprintf(stderr, "ExtendFileRangeOverlappingInterval\n%s - %s\n%s - %s\n%d %d\n", + // begin ? begin->DebugString().c_str() : "(null)", + // end ? end->DebugString().c_str() : "(null)", + // smallest->DebugString().c_str(), + // largest->DebugString().c_str(), + // sstableKeyCompare(user_cmp, smallest, begin), + // sstableKeyCompare(user_cmp, largest, begin)); + assert(sstableKeyCompare(user_cmp, begin, largest) <= 0); } } #endif *start_index = mid_index + 1; *end_index = mid_index; - int count __attribute__((unused)) = 0; + int count __attribute__((__unused__)); + count = 0; // check backwards from 'mid' to lower indices for (int i = mid_index; i >= 0 ; i--) { const FdWithKeyRange* f = &files[i]; - const Slice file_limit = ExtractUserKey(f->largest_key); - if (user_cmp->Compare(file_limit, user_begin) >= 0) { + auto& largest = f->file_metadata->largest; + if (sstableKeyCompare(user_cmp, begin, largest) <= 0) { *start_index = i; assert((count++, true)); } else { @@ -1928,8 +2320,8 @@ void VersionStorageInfo::ExtendFileRangeOverlappingInterval( for (unsigned int i = mid_index+1; i < level_files_brief_[level].num_files; i++) { const FdWithKeyRange* f = &files[i]; - const Slice file_start = ExtractUserKey(f->smallest_key); - if (user_cmp->Compare(file_start, user_end) <= 0) { + auto& smallest = f->file_metadata->smallest; + if (sstableKeyCompare(user_cmp, smallest, end) <= 0) { assert((count++, true)); *end_index = i; } else { @@ -1947,39 +2339,36 @@ void VersionStorageInfo::ExtendFileRangeOverlappingInterval( // the clean range required. // Use FileLevel in searching, make it faster void VersionStorageInfo::ExtendFileRangeWithinInterval( - int level, const Slice& user_begin, const Slice& user_end, + int level, const InternalKey* begin, const InternalKey* end, unsigned int mid_index, int* start_index, int* end_index) const { assert(level != 0); - const Comparator* user_cmp = user_comparator_; + auto* user_cmp = user_comparator_; const FdWithKeyRange* files = level_files_brief_[level].files; #ifndef NDEBUG { // assert that the file at mid_index is within the range assert(mid_index < level_files_brief_[level].num_files); const FdWithKeyRange* f = &files[mid_index]; - const Slice fstart = ExtractUserKey(f->smallest_key); - const Slice flimit = ExtractUserKey(f->largest_key); - assert(user_cmp->Compare(fstart, user_begin) >= 0 && - user_cmp->Compare(flimit, user_end) <= 0); + auto& smallest = f->file_metadata->smallest; + auto& largest = f->file_metadata->largest; + assert(sstableKeyCompare(user_cmp, begin, smallest) <= 0 && + sstableKeyCompare(user_cmp, largest, end) <= 0); } #endif - ExtendFileRangeOverlappingInterval(level, user_begin, user_end, mid_index, + ExtendFileRangeOverlappingInterval(level, begin, end, mid_index, start_index, end_index); int left = *start_index; int right = *end_index; // shrink from left to right while (left <= right) { - const Slice& first_key_in_range = ExtractUserKey(files[left].smallest_key); - if (user_cmp->Compare(first_key_in_range, user_begin) < 0) { + auto& smallest = files[left].file_metadata->smallest; + if (sstableKeyCompare(user_cmp, begin, smallest) > 0) { left++; continue; } if (left > 0) { // If not first file - const Slice& last_key_before = - ExtractUserKey(files[left - 1].largest_key); - if (user_cmp->Equal(first_key_in_range, last_key_before)) { - // The first user key in range overlaps with the previous file's last - // key + auto& largest = files[left - 1].file_metadata->largest; + if (sstableKeyCompare(user_cmp, smallest, largest) == 0) { left++; continue; } @@ -1988,16 +2377,15 @@ void VersionStorageInfo::ExtendFileRangeWithinInterval( } // shrink from right to left while (left <= right) { - const Slice last_key_in_range = ExtractUserKey(files[right].largest_key); - if (user_cmp->Compare(last_key_in_range, user_end) > 0) { + auto& largest = files[right].file_metadata->largest; + if (sstableKeyCompare(user_cmp, largest, end) > 0) { right--; continue; } if (right < static_cast(level_files_brief_[level].num_files) - 1) { // If not the last file - const Slice first_key_after = - ExtractUserKey(files[right + 1].smallest_key); - if (user_cmp->Equal(last_key_in_range, first_key_after)) { + auto& smallest = files[right + 1].file_metadata->smallest; + if (sstableKeyCompare(user_cmp, smallest, largest) == 0) { // The last user key in range overlaps with the next file's first key right--; continue; @@ -2021,9 +2409,12 @@ const char* VersionStorageInfo::LevelSummary( int len = 0; if (compaction_style_ == kCompactionStyleLevel && num_levels() > 1) { assert(base_level_ < static_cast(level_max_bytes_.size())); - len = snprintf(scratch->buffer, sizeof(scratch->buffer), - "base level %d max bytes base %" PRIu64 " ", base_level_, - level_max_bytes_[base_level_]); + if (level_multiplier_ != 0.0) { + len = snprintf( + scratch->buffer, sizeof(scratch->buffer), + "base level %d level multiplier %.2f max bytes base %" PRIu64 " ", + base_level_, level_multiplier_, level_max_bytes_[base_level_]); + } } len += snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, "files["); @@ -2058,7 +2449,7 @@ const char* VersionStorageInfo::LevelFileSummary(FileSummaryStorage* scratch, AppendHumanBytes(f->fd.GetFileSize(), sztxt, sizeof(sztxt)); int ret = snprintf(scratch->buffer + len, sz, "#%" PRIu64 "(seq=%" PRIu64 ",sz=%s,%d) ", - f->fd.GetNumber(), f->smallest_seqno, sztxt, + f->fd.GetNumber(), f->fd.smallest_seqno, sztxt, static_cast(f->being_compacted)); if (ret < 0 || ret >= sz) break; @@ -2159,7 +2550,13 @@ void VersionStorageInfo::CalculateBaseBytes(const ImmutableCFOptions& ioptions, // No compaction from L1+ needs to be scheduled. base_level_ = num_levels_ - 1; } else { - uint64_t base_bytes_max = options.max_bytes_for_level_base; + uint64_t l0_size = 0; + for (const auto& f : files_[0]) { + l0_size += f->fd.GetFileSize(); + } + + uint64_t base_bytes_max = + std::max(options.max_bytes_for_level_base, l0_size); uint64_t base_bytes_min = static_cast( base_bytes_max / options.max_bytes_for_level_multiplier); @@ -2199,11 +2596,33 @@ void VersionStorageInfo::CalculateBaseBytes(const ImmutableCFOptions& ioptions, } } + level_multiplier_ = options.max_bytes_for_level_multiplier; + assert(base_level_size > 0); + if (l0_size > base_level_size && + (l0_size > options.max_bytes_for_level_base || + static_cast(files_[0].size() / 2) >= + options.level0_file_num_compaction_trigger)) { + // We adjust the base level according to actual L0 size, and adjust + // the level multiplier accordingly, when: + // 1. the L0 size is larger than level size base, or + // 2. number of L0 files reaches twice the L0->L1 compaction trigger + // We don't do this otherwise to keep the LSM-tree structure stable + // unless the L0 compation is backlogged. + base_level_size = l0_size; + if (base_level_ == num_levels_ - 1) { + level_multiplier_ = 1.0; + } else { + level_multiplier_ = std::pow( + static_cast(max_level_size) / + static_cast(base_level_size), + 1.0 / static_cast(num_levels_ - base_level_ - 1)); + } + } + uint64_t level_size = base_level_size; for (int i = base_level_; i < num_levels_; i++) { if (i > base_level_) { - level_size = MultiplyCheckOverflow( - level_size, options.max_bytes_for_level_multiplier); + level_size = MultiplyCheckOverflow(level_size, level_multiplier_); } // Don't set any level below base_bytes_max. Otherwise, the LSM can // assume an hourglass shape where L1+ sizes are smaller than L0. This @@ -2249,6 +2668,36 @@ uint64_t VersionStorageInfo::EstimateLiveDataSize() const { return size; } +bool VersionStorageInfo::RangeMightExistAfterSortedRun( + const Slice& smallest_user_key, const Slice& largest_user_key, + int last_level, int last_l0_idx) { + assert((last_l0_idx != -1) == (last_level == 0)); + // TODO(ajkr): this preserves earlier behavior where we considered an L0 file + // bottommost only if it's the oldest L0 file and there are no files on older + // levels. It'd be better to consider it bottommost if there's no overlap in + // older levels/files. + if (last_level == 0 && + last_l0_idx != static_cast(LevelFiles(0).size() - 1)) { + return true; + } + + // Checks whether there are files living beyond the `last_level`. If lower + // levels have files, it checks for overlap between [`smallest_key`, + // `largest_key`] and those files. Bottomlevel optimizations can be made if + // there are no files in lower levels or if there is no overlap with the files + // in the lower levels. + for (int level = last_level + 1; level < num_levels(); level++) { + // The range is not in the bottommost level if there are files in lower + // levels when the `last_level` is 0 or if there are files in lower levels + // which overlap with [`smallest_key`, `largest_key`]. + if (files_[level].size() > 0 && + (last_level == 0 || + OverlapInLevel(level, &smallest_user_key, &largest_user_key))) { + return true; + } + } + return false; +} void Version::AddLiveFiles(std::vector* live) { for (int level = 0; level < storage_info_.num_levels(); level++) { @@ -2303,35 +2752,41 @@ struct VersionSet::ManifestWriter { bool done; InstrumentedCondVar cv; ColumnFamilyData* cfd; + const MutableCFOptions mutable_cf_options; const autovector& edit_list; explicit ManifestWriter(InstrumentedMutex* mu, ColumnFamilyData* _cfd, + const MutableCFOptions& cf_options, const autovector& e) - : done(false), cv(mu), cfd(_cfd), edit_list(e) {} + : done(false), + cv(mu), + cfd(_cfd), + mutable_cf_options(cf_options), + edit_list(e) {} }; VersionSet::VersionSet(const std::string& dbname, - const ImmutableDBOptions* db_options, + const ImmutableDBOptions* _db_options, const EnvOptions& storage_options, Cache* table_cache, WriteBufferManager* write_buffer_manager, WriteController* write_controller) : column_family_set_( - new ColumnFamilySet(dbname, db_options, storage_options, table_cache, + new ColumnFamilySet(dbname, _db_options, storage_options, table_cache, write_buffer_manager, write_controller)), - env_(db_options->env), + env_(_db_options->env), dbname_(dbname), - db_options_(db_options), + db_options_(_db_options), next_file_number_(2), manifest_file_number_(0), // Filled by Recover() + options_file_number_(0), pending_manifest_file_number_(0), last_sequence_(0), - last_to_be_written_sequence_(0), + last_allocated_sequence_(0), + last_published_sequence_(0), prev_log_number_(0), current_version_number_(0), manifest_file_size_(0), - env_options_(storage_options), - env_options_compactions_( - env_->OptimizeForCompactionTableRead(env_options_, *db_options_)) {} + env_options_(storage_options) {} void CloseTables(void* ptr, size_t) { TableReader* table_reader = reinterpret_cast(ptr); @@ -2344,12 +2799,12 @@ VersionSet::~VersionSet() { Cache* table_cache = column_family_set_->get_table_cache(); table_cache->ApplyToAllCacheEntries(&CloseTables, false /* thread_safe */); column_family_set_.reset(); - for (auto file : obsolete_files_) { - if (file->table_reader_handle) { - table_cache->Release(file->table_reader_handle); - TableCache::Evict(table_cache, file->fd.GetNumber()); + for (auto& file : obsolete_files_) { + if (file.metadata->table_reader_handle) { + table_cache->Release(file.metadata->table_reader_handle); + TableCache::Evict(table_cache, file.metadata->fd.GetNumber()); } - delete file; + file.DeleteMetadata(); } obsolete_files_.clear(); } @@ -2382,95 +2837,161 @@ void VersionSet::AppendVersion(ColumnFamilyData* column_family_data, v->next_->prev_ = v; } -Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data, - const MutableCFOptions& mutable_cf_options, - const autovector& edit_list, - InstrumentedMutex* mu, Directory* db_directory, - bool new_descriptor_log, - const ColumnFamilyOptions* new_cf_options) { - mu->AssertHeld(); - // num of edits - auto num_edits = edit_list.size(); - if (num_edits == 0) { - return Status::OK(); - } else if (num_edits > 1) { -#ifndef NDEBUG - // no group commits for column family add or drop - for (auto& edit : edit_list) { - assert(!edit->IsColumnFamilyManipulation()); - } -#endif - } +Status VersionSet::ProcessManifestWrites( + std::deque& writers, InstrumentedMutex* mu, + Directory* db_directory, bool new_descriptor_log, + const ColumnFamilyOptions* new_cf_options) { + assert(!writers.empty()); + ManifestWriter& first_writer = writers.front(); + ManifestWriter* last_writer = &first_writer; - // column_family_data can be nullptr only if this is column_family_add. - // in that case, we also need to specify ColumnFamilyOptions - if (column_family_data == nullptr) { - assert(num_edits == 1); - assert(edit_list[0]->is_column_family_add_); - assert(new_cf_options != nullptr); - } - - // queue our request - ManifestWriter w(mu, column_family_data, edit_list); - manifest_writers_.push_back(&w); - while (!w.done && &w != manifest_writers_.front()) { - w.cv.Wait(); - } - if (w.done) { - return w.status; - } - if (column_family_data != nullptr && column_family_data->IsDropped()) { - // if column family is dropped by the time we get here, no need to write - // anything to the manifest - manifest_writers_.pop_front(); - // Notify new head of write queue - if (!manifest_writers_.empty()) { - manifest_writers_.front()->cv.Signal(); - } - // we steal this code to also inform about cf-drop - return Status::ShutdownInProgress(); - } + assert(!manifest_writers_.empty()); + assert(manifest_writers_.front() == &first_writer); autovector batch_edits; - Version* v = nullptr; - std::unique_ptr builder_guard(nullptr); - - // process all requests in the queue - ManifestWriter* last_writer = &w; - assert(!manifest_writers_.empty()); - assert(manifest_writers_.front() == &w); - if (w.edit_list.front()->IsColumnFamilyManipulation()) { - // no group commits for column family add or drop - LogAndApplyCFHelper(w.edit_list.front()); - batch_edits.push_back(w.edit_list.front()); + autovector versions; + autovector mutable_cf_options_ptrs; + std::vector> builder_guards; + + if (first_writer.edit_list.front()->IsColumnFamilyManipulation()) { + // No group commits for column family add or drop + LogAndApplyCFHelper(first_writer.edit_list.front()); + batch_edits.push_back(first_writer.edit_list.front()); } else { - v = new Version(column_family_data, this, current_version_number_++); - builder_guard.reset(new BaseReferencedVersionBuilder(column_family_data)); - auto* builder = builder_guard->version_builder(); - for (const auto& writer : manifest_writers_) { - if (writer->edit_list.front()->IsColumnFamilyManipulation() || - writer->cfd->GetID() != column_family_data->GetID()) { + auto it = manifest_writers_.cbegin(); + size_t group_start = std::numeric_limits::max(); + while (it != manifest_writers_.cend()) { + if ((*it)->edit_list.front()->IsColumnFamilyManipulation()) { // no group commits for column family add or drop - // also, group commits across column families are not supported break; } - last_writer = writer; - for (const auto& edit : writer->edit_list) { - LogAndApplyHelper(column_family_data, builder, v, edit, mu); - batch_edits.push_back(edit); + last_writer = *(it++); + assert(last_writer != nullptr); + assert(last_writer->cfd != nullptr); + if (last_writer->cfd->IsDropped()) { + // If we detect a dropped CF at this point, and the corresponding + // version edits belong to an atomic group, then we need to find out + // the preceding version edits in the same atomic group, and update + // their `remaining_entries_` member variable because we are NOT going + // to write the version edits' of dropped CF to the MANIFEST. If we + // don't update, then Recover can report corrupted atomic group because + // the `remaining_entries_` do not match. + if (!batch_edits.empty()) { + if (batch_edits.back()->is_in_atomic_group_ && + batch_edits.back()->remaining_entries_ > 0) { + assert(group_start < batch_edits.size()); + const auto& edit_list = last_writer->edit_list; + size_t k = 0; + while (k < edit_list.size()) { + if (!edit_list[k]->is_in_atomic_group_) { + break; + } else if (edit_list[k]->remaining_entries_ == 0) { + ++k; + break; + } + ++k; + } + for (auto i = group_start; i < batch_edits.size(); ++i) { + assert(static_cast(k) <= + batch_edits.back()->remaining_entries_); + batch_edits[i]->remaining_entries_ -= static_cast(k); + } + } + } + continue; + } + // We do a linear search on versions because versions is small. + // TODO(yanqin) maybe consider unordered_map + Version* version = nullptr; + VersionBuilder* builder = nullptr; + for (int i = 0; i != static_cast(versions.size()); ++i) { + uint32_t cf_id = last_writer->cfd->GetID(); + if (versions[i]->cfd()->GetID() == cf_id) { + version = versions[i]; + assert(!builder_guards.empty() && + builder_guards.size() == versions.size()); + builder = builder_guards[i]->version_builder(); + TEST_SYNC_POINT_CALLBACK( + "VersionSet::ProcessManifestWrites:SameColumnFamily", &cf_id); + break; + } + } + if (version == nullptr) { + version = new Version(last_writer->cfd, this, env_options_, + last_writer->mutable_cf_options, + current_version_number_++); + versions.push_back(version); + mutable_cf_options_ptrs.push_back(&last_writer->mutable_cf_options); + builder_guards.emplace_back( + new BaseReferencedVersionBuilder(last_writer->cfd)); + builder = builder_guards.back()->version_builder(); + } + assert(builder != nullptr); // make checker happy + for (const auto& e : last_writer->edit_list) { + if (e->is_in_atomic_group_) { + if (batch_edits.empty() || !batch_edits.back()->is_in_atomic_group_ || + (batch_edits.back()->is_in_atomic_group_ && + batch_edits.back()->remaining_entries_ == 0)) { + group_start = batch_edits.size(); + } + } else if (group_start != std::numeric_limits::max()) { + group_start = std::numeric_limits::max(); + } + LogAndApplyHelper(last_writer->cfd, builder, e, mu); + batch_edits.push_back(e); + } + } + for (int i = 0; i < static_cast(versions.size()); ++i) { + assert(!builder_guards.empty() && + builder_guards.size() == versions.size()); + auto* builder = builder_guards[i]->version_builder(); + builder->SaveTo(versions[i]->storage_info()); + } + } + +#ifndef NDEBUG + // Verify that version edits of atomic groups have correct + // remaining_entries_. + size_t k = 0; + while (k < batch_edits.size()) { + while (k < batch_edits.size() && !batch_edits[k]->is_in_atomic_group_) { + ++k; + } + if (k == batch_edits.size()) { + break; + } + size_t i = k; + while (i < batch_edits.size()) { + if (!batch_edits[i]->is_in_atomic_group_) { + break; + } + assert(i - k + batch_edits[i]->remaining_entries_ == + batch_edits[k]->remaining_entries_); + if (batch_edits[i]->remaining_entries_ == 0) { + ++i; + break; } + ++i; } - builder->SaveTo(v->storage_info()); + assert(batch_edits[i - 1]->is_in_atomic_group_); + assert(0 == batch_edits[i - 1]->remaining_entries_); + std::vector tmp; + for (size_t j = k; j != i; ++j) { + tmp.emplace_back(batch_edits[j]); + } + TEST_SYNC_POINT_CALLBACK( + "VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", &tmp); + k = i; } +#endif // NDEBUG - // Initialize new descriptor log file if necessary by creating - // a temporary file that contains a snapshot of the current version. uint64_t new_manifest_file_size = 0; Status s; assert(pending_manifest_file_number_ == 0); if (!descriptor_log_ || manifest_file_size_ > db_options_->max_manifest_file_size) { + TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest"); pending_manifest_file_number_ = NewFileNumber(); batch_edits.back()->SetNextFile(next_file_number_.load()); new_descriptor_log = true; @@ -2479,70 +3000,87 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data, } if (new_descriptor_log) { - // if we're writing out new snapshot make sure to persist max column family + // if we are writing out new snapshot make sure to persist max column + // family. if (column_family_set_->GetMaxColumnFamily() > 0) { - w.edit_list.front()->SetMaxColumnFamily( + first_writer.edit_list.front()->SetMaxColumnFamily( column_family_set_->GetMaxColumnFamily()); } } - // Unlock during expensive operations. New writes cannot get here - // because &w is ensuring that all new writes get queued. { - + EnvOptions opt_env_opts = env_->OptimizeForManifestWrite(env_options_); mu->Unlock(); TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifest"); - if (!w.edit_list.front()->IsColumnFamilyManipulation() && - this->GetColumnFamilySet()->get_table_cache()->GetCapacity() == - TableCache::kInfiniteCapacity) { - // unlimited table cache. Pre-load table handle now. - // Need to do it out of the mutex. - builder_guard->version_builder()->LoadTableHandlers( - column_family_data->internal_stats(), - column_family_data->ioptions()->optimize_filters_for_hits, - true /* prefetch_index_and_filter_in_cache */); + if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) { + for (int i = 0; i < static_cast(versions.size()); ++i) { + assert(!builder_guards.empty() && + builder_guards.size() == versions.size()); + assert(!mutable_cf_options_ptrs.empty() && + builder_guards.size() == versions.size()); + ColumnFamilyData* cfd = versions[i]->cfd_; + builder_guards[i]->version_builder()->LoadTableHandlers( + cfd->internal_stats(), cfd->ioptions()->optimize_filters_for_hits, + true /* prefetch_index_and_filter_in_cache */, + false /* is_initial_load */, + mutable_cf_options_ptrs[i]->prefix_extractor.get()); + } } // This is fine because everything inside of this block is serialized -- // only one thread can be here at the same time if (new_descriptor_log) { - // create manifest file + // create new manifest file ROCKS_LOG_INFO(db_options_->info_log, "Creating manifest %" PRIu64 "\n", pending_manifest_file_number_); - unique_ptr descriptor_file; - EnvOptions opt_env_opts = env_->OptimizeForManifestWrite(env_options_); - s = NewWritableFile( - env_, DescriptorFileName(dbname_, pending_manifest_file_number_), - &descriptor_file, opt_env_opts); + std::string descriptor_fname = + DescriptorFileName(dbname_, pending_manifest_file_number_); + std::unique_ptr descriptor_file; + s = NewWritableFile(env_, descriptor_fname, &descriptor_file, + opt_env_opts); if (s.ok()) { descriptor_file->SetPreallocationBlockSize( db_options_->manifest_preallocation_size); - unique_ptr file_writer( - new WritableFileWriter(std::move(descriptor_file), opt_env_opts)); + std::unique_ptr file_writer(new WritableFileWriter( + std::move(descriptor_file), descriptor_fname, opt_env_opts, env_, + nullptr, db_options_->listeners)); descriptor_log_.reset( new log::Writer(std::move(file_writer), 0, false)); s = WriteSnapshot(descriptor_log_.get()); } } - if (!w.edit_list.front()->IsColumnFamilyManipulation()) { - // This is cpu-heavy operations, which should be called outside mutex. - v->PrepareApply(mutable_cf_options, true); + if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) { + for (int i = 0; i < static_cast(versions.size()); ++i) { + versions[i]->PrepareApply(*mutable_cf_options_ptrs[i], true); + } } - // Write new record to MANIFEST log + // Write new records to MANIFEST log if (s.ok()) { +#ifndef NDEBUG + size_t idx = 0; +#endif for (auto& e : batch_edits) { std::string record; if (!e->EncodeTo(&record)) { - s = Status::Corruption( - "Unable to Encode VersionEdit:" + e->DebugString(true)); + s = Status::Corruption("Unable to encode VersionEdit:" + + e->DebugString(true)); break; } TEST_KILL_RANDOM("VersionSet::LogAndApply:BeforeAddRecord", rocksdb_kill_odds * REDUCE_ODDS2); +#ifndef NDEBUG + if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) { + TEST_SYNC_POINT( + "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0"); + TEST_SYNC_POINT( + "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1"); + } + ++idx; +#endif /* !NDEBUG */ s = descriptor_log_->AddRecord(record); if (!s.ok()) { break; @@ -2552,7 +3090,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data, s = SyncManifest(env_, db_options_, descriptor_log_->file()); } if (!s.ok()) { - ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write: %s\n", + ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write %s\n", s.ToString().c_str()); } } @@ -2562,6 +3100,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data, if (s.ok() && new_descriptor_log) { s = SetCurrentFile(env_, dbname_, pending_manifest_file_number_, db_directory); + TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:AfterNewManifest"); } if (s.ok()) { @@ -2569,7 +3108,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data, new_manifest_file_size = descriptor_log_->file()->GetFileSize(); } - if (w.edit_list.front()->is_column_family_drop_) { + if (first_writer.edit_list.front()->is_column_family_drop_) { TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:0"); TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:1"); TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:2"); @@ -2580,88 +3119,207 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data, mu->Lock(); } - // Append the old mainfest file to the obsolete_manifests_ list to be deleted + // Append the old manifest file to the obsolete_manifest_ list to be deleted // by PurgeObsoleteFiles later. if (s.ok() && new_descriptor_log) { obsolete_manifests_.emplace_back( DescriptorFileName("", manifest_file_number_)); } - // Install the new version + // Install the new versions if (s.ok()) { - if (w.edit_list.front()->is_column_family_add_) { - // no group commit on column family add + if (first_writer.edit_list.front()->is_column_family_add_) { assert(batch_edits.size() == 1); assert(new_cf_options != nullptr); - CreateColumnFamily(*new_cf_options, w.edit_list.front()); - } else if (w.edit_list.front()->is_column_family_drop_) { + CreateColumnFamily(*new_cf_options, first_writer.edit_list.front()); + } else if (first_writer.edit_list.front()->is_column_family_drop_) { assert(batch_edits.size() == 1); - column_family_data->SetDropped(); - if (column_family_data->Unref()) { - delete column_family_data; + first_writer.cfd->SetDropped(); + if (first_writer.cfd->Unref()) { + delete first_writer.cfd; } } else { - uint64_t max_log_number_in_batch = 0; + // Each version in versions corresponds to a column family. + // For each column family, update its log number indicating that logs + // with number smaller than this should be ignored. + for (const auto version : versions) { + uint64_t max_log_number_in_batch = 0; + uint32_t cf_id = version->cfd_->GetID(); + for (const auto& e : batch_edits) { + if (e->has_log_number_ && e->column_family_ == cf_id) { + max_log_number_in_batch = + std::max(max_log_number_in_batch, e->log_number_); + } + } + if (max_log_number_in_batch != 0) { + assert(version->cfd_->GetLogNumber() <= max_log_number_in_batch); + version->cfd_->SetLogNumber(max_log_number_in_batch); + } + } + + uint64_t last_min_log_number_to_keep = 0; for (auto& e : batch_edits) { - if (e->has_log_number_) { - max_log_number_in_batch = - std::max(max_log_number_in_batch, e->log_number_); + if (e->has_min_log_number_to_keep_) { + last_min_log_number_to_keep = + std::max(last_min_log_number_to_keep, e->min_log_number_to_keep_); } } - if (max_log_number_in_batch != 0) { - assert(column_family_data->GetLogNumber() <= max_log_number_in_batch); - column_family_data->SetLogNumber(max_log_number_in_batch); + + if (last_min_log_number_to_keep != 0) { + // Should only be set in 2PC mode. + MarkMinLogNumberToKeep2PC(last_min_log_number_to_keep); } - AppendVersion(column_family_data, v); - } + for (int i = 0; i < static_cast(versions.size()); ++i) { + ColumnFamilyData* cfd = versions[i]->cfd_; + AppendVersion(cfd, versions[i]); + } + } manifest_file_number_ = pending_manifest_file_number_; manifest_file_size_ = new_manifest_file_size; - prev_log_number_ = w.edit_list.front()->prev_log_number_; + prev_log_number_ = first_writer.edit_list.front()->prev_log_number_; } else { std::string version_edits; for (auto& e : batch_edits) { - version_edits = version_edits + "\n" + e->DebugString(true); + version_edits += ("\n" + e->DebugString(true)); + } + ROCKS_LOG_ERROR(db_options_->info_log, + "Error in committing version edit to MANIFEST: %s", + version_edits.c_str()); + for (auto v : versions) { + delete v; } - ROCKS_LOG_ERROR( - db_options_->info_log, - "[%s] Error in committing version edit to MANIFEST: %s", - column_family_data ? column_family_data->GetName().c_str() : "", - version_edits.c_str()); - delete v; if (new_descriptor_log) { - ROCKS_LOG_INFO(db_options_->info_log, "Deleting manifest %" PRIu64 - " current manifest %" PRIu64 "\n", + ROCKS_LOG_INFO(db_options_->info_log, + "Deleting manifest %" PRIu64 " current manifest %" PRIu64 + "\n", manifest_file_number_, pending_manifest_file_number_); descriptor_log_.reset(); env_->DeleteFile( DescriptorFileName(dbname_, pending_manifest_file_number_)); } } + pending_manifest_file_number_ = 0; // wake up all the waiting writers while (true) { ManifestWriter* ready = manifest_writers_.front(); manifest_writers_.pop_front(); - if (ready != &w) { - ready->status = s; - ready->done = true; + bool need_signal = true; + for (const auto& w : writers) { + if (&w == ready) { + need_signal = false; + break; + } + } + ready->status = s; + ready->done = true; + if (need_signal) { ready->cv.Signal(); } - if (ready == last_writer) break; + if (ready == last_writer) { + break; + } } - // Notify new head of write queue if (!manifest_writers_.empty()) { manifest_writers_.front()->cv.Signal(); } return s; } +// 'datas' is gramatically incorrect. We still use this notation to indicate +// that this variable represents a collection of column_family_data. +Status VersionSet::LogAndApply( + const autovector& column_family_datas, + const autovector& mutable_cf_options_list, + const autovector>& edit_lists, + InstrumentedMutex* mu, Directory* db_directory, bool new_descriptor_log, + const ColumnFamilyOptions* new_cf_options) { + mu->AssertHeld(); + int num_edits = 0; + for (const auto& elist : edit_lists) { + num_edits += static_cast(elist.size()); + } + if (num_edits == 0) { + return Status::OK(); + } else if (num_edits > 1) { +#ifndef NDEBUG + for (const auto& edit_list : edit_lists) { + for (const auto& edit : edit_list) { + assert(!edit->IsColumnFamilyManipulation()); + } + } +#endif /* ! NDEBUG */ + } + + int num_cfds = static_cast(column_family_datas.size()); + if (num_cfds == 1 && column_family_datas[0] == nullptr) { + assert(edit_lists.size() == 1 && edit_lists[0].size() == 1); + assert(edit_lists[0][0]->is_column_family_add_); + assert(new_cf_options != nullptr); + } + std::deque writers; + if (num_cfds > 0) { + assert(static_cast(num_cfds) == mutable_cf_options_list.size()); + assert(static_cast(num_cfds) == edit_lists.size()); + } + for (int i = 0; i < num_cfds; ++i) { + writers.emplace_back(mu, column_family_datas[i], + *mutable_cf_options_list[i], edit_lists[i]); + manifest_writers_.push_back(&writers[i]); + } + assert(!writers.empty()); + ManifestWriter& first_writer = writers.front(); + while (!first_writer.done && &first_writer != manifest_writers_.front()) { + first_writer.cv.Wait(); + } + if (first_writer.done) { + // All non-CF-manipulation operations can be grouped together and committed + // to MANIFEST. They should all have finished. The status code is stored in + // the first manifest writer. +#ifndef NDEBUG + for (const auto& writer : writers) { + assert(writer.done); + } +#endif /* !NDEBUG */ + return first_writer.status; + } + + int num_undropped_cfds = 0; + for (auto cfd : column_family_datas) { + // if cfd == nullptr, it is a column family add. + if (cfd == nullptr || !cfd->IsDropped()) { + ++num_undropped_cfds; + } + } + if (0 == num_undropped_cfds) { + // TODO (yanqin) maybe use a different status code to denote column family + // drop other than OK and ShutdownInProgress + for (int i = 0; i != num_cfds; ++i) { + manifest_writers_.pop_front(); + } + // Notify new head of manifest write queue. + if (!manifest_writers_.empty()) { + manifest_writers_.front()->cv.Signal(); + } + return Status::ShutdownInProgress(); + } + + return ProcessManifestWrites(writers, mu, db_directory, new_descriptor_log, + new_cf_options); +} + void VersionSet::LogAndApplyCFHelper(VersionEdit* edit) { assert(edit->IsColumnFamilyManipulation()); edit->SetNextFile(next_file_number_.load()); - edit->SetLastSequence(last_sequence_); + // The log might have data that is not visible to memtbale and hence have not + // updated the last_sequence_ yet. It is also possible that the log has is + // expecting some new data that is not written yet. Since LastSequence is an + // upper bound on the sequence, it is ok to record + // last_allocated_sequence_ as the last sequence. + edit->SetLastSequence(db_options_->two_write_queues ? last_allocated_sequence_ + : last_sequence_); if (edit->is_column_family_drop_) { // if we drop column family, we have to make sure to save max column family, // so that we don't reuse existing ID @@ -2670,8 +3328,11 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit) { } void VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd, - VersionBuilder* builder, Version* v, - VersionEdit* edit, InstrumentedMutex* mu) { + VersionBuilder* builder, VersionEdit* edit, + InstrumentedMutex* mu) { +#ifdef NDEBUG + (void)cfd; +#endif mu->AssertHeld(); assert(!edit->IsColumnFamilyManipulation()); @@ -2684,11 +3345,181 @@ void VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd, edit->SetPrevLogNumber(prev_log_number_); } edit->SetNextFile(next_file_number_.load()); - edit->SetLastSequence(last_sequence_); + // The log might have data that is not visible to memtbale and hence have not + // updated the last_sequence_ yet. It is also possible that the log has is + // expecting some new data that is not written yet. Since LastSequence is an + // upper bound on the sequence, it is ok to record + // last_allocated_sequence_ as the last sequence. + edit->SetLastSequence(db_options_->two_write_queues ? last_allocated_sequence_ + : last_sequence_); builder->Apply(edit); } +Status VersionSet::ApplyOneVersionEditToBuilder( + VersionEdit& edit, + const std::unordered_map& name_to_options, + std::unordered_map& column_families_not_found, + std::unordered_map>& + builders, + bool* have_log_number, uint64_t* log_number, bool* have_prev_log_number, + uint64_t* previous_log_number, bool* have_next_file, uint64_t* next_file, + bool* have_last_sequence, SequenceNumber* last_sequence, + uint64_t* min_log_number_to_keep, uint32_t* max_column_family) { + // Not found means that user didn't supply that column + // family option AND we encountered column family add + // record. Once we encounter column family drop record, + // we will delete the column family from + // column_families_not_found. + bool cf_in_not_found = (column_families_not_found.find(edit.column_family_) != + column_families_not_found.end()); + // in builders means that user supplied that column family + // option AND that we encountered column family add record + bool cf_in_builders = builders.find(edit.column_family_) != builders.end(); + + // they can't both be true + assert(!(cf_in_not_found && cf_in_builders)); + + ColumnFamilyData* cfd = nullptr; + + if (edit.is_column_family_add_) { + if (cf_in_builders || cf_in_not_found) { + return Status::Corruption( + "Manifest adding the same column family twice: " + + edit.column_family_name_); + } + auto cf_options = name_to_options.find(edit.column_family_name_); + if (cf_options == name_to_options.end()) { + column_families_not_found.insert( + {edit.column_family_, edit.column_family_name_}); + } else { + cfd = CreateColumnFamily(cf_options->second, &edit); + cfd->set_initialized(); + builders.insert(std::make_pair( + edit.column_family_, std::unique_ptr( + new BaseReferencedVersionBuilder(cfd)))); + } + } else if (edit.is_column_family_drop_) { + if (cf_in_builders) { + auto builder = builders.find(edit.column_family_); + assert(builder != builders.end()); + builders.erase(builder); + cfd = column_family_set_->GetColumnFamily(edit.column_family_); + assert(cfd != nullptr); + if (cfd->Unref()) { + delete cfd; + cfd = nullptr; + } else { + // who else can have reference to cfd!? + assert(false); + } + } else if (cf_in_not_found) { + column_families_not_found.erase(edit.column_family_); + } else { + return Status::Corruption( + "Manifest - dropping non-existing column family"); + } + } else if (!cf_in_not_found) { + if (!cf_in_builders) { + return Status::Corruption( + "Manifest record referencing unknown column family"); + } + + cfd = column_family_set_->GetColumnFamily(edit.column_family_); + // this should never happen since cf_in_builders is true + assert(cfd != nullptr); + + // if it is not column family add or column family drop, + // then it's a file add/delete, which should be forwarded + // to builder + auto builder = builders.find(edit.column_family_); + assert(builder != builders.end()); + builder->second->version_builder()->Apply(&edit); + } + return ExtractInfoFromVersionEdit( + cfd, edit, have_log_number, log_number, have_prev_log_number, + previous_log_number, have_next_file, next_file, have_last_sequence, + last_sequence, min_log_number_to_keep, max_column_family); +} + +Status VersionSet::ExtractInfoFromVersionEdit( + ColumnFamilyData* cfd, const VersionEdit& edit, bool* have_log_number, + uint64_t* log_number, bool* have_prev_log_number, + uint64_t* previous_log_number, bool* have_next_file, uint64_t* next_file, + bool* have_last_sequence, SequenceNumber* last_sequence, + uint64_t* min_log_number_to_keep, uint32_t* max_column_family) { + if (cfd != nullptr) { + if (edit.has_log_number_) { + if (cfd->GetLogNumber() > edit.log_number_) { + ROCKS_LOG_WARN( + db_options_->info_log, + "MANIFEST corruption detected, but ignored - Log numbers in " + "records NOT monotonically increasing"); + } else { + cfd->SetLogNumber(edit.log_number_); + *have_log_number = true; + *log_number = edit.log_number_; + } + } + if (edit.has_comparator_ && + edit.comparator_ != cfd->user_comparator()->Name()) { + return Status::InvalidArgument( + cfd->user_comparator()->Name(), + "does not match existing comparator " + edit.comparator_); + } + } + + if (edit.has_prev_log_number_) { + *previous_log_number = edit.prev_log_number_; + *have_prev_log_number = true; + } + + if (edit.has_next_file_number_) { + *next_file = edit.next_file_number_; + *have_next_file = true; + } + + if (edit.has_max_column_family_) { + *max_column_family = edit.max_column_family_; + } + + if (edit.has_min_log_number_to_keep_) { + *min_log_number_to_keep = + std::max(*min_log_number_to_keep, edit.min_log_number_to_keep_); + } + + if (edit.has_last_sequence_) { + *last_sequence = edit.last_sequence_; + *have_last_sequence = true; + } + return Status::OK(); +} + +Status VersionSet::GetCurrentManifestPath(std::string* manifest_path) { + assert(manifest_path != nullptr); + std::string fname; + Status s = ReadFileToString(env_, CurrentFileName(dbname_), &fname); + if (!s.ok()) { + return s; + } + if (fname.empty() || fname.back() != '\n') { + return Status::Corruption("CURRENT file does not end with newline"); + } + // remove the trailing '\n' + fname.resize(fname.size() - 1); + FileType type; + bool parse_ok = ParseFileName(fname, &manifest_file_number_, &type); + if (!parse_ok || type != kDescriptorFile) { + return Status::Corruption("CURRENT file corrupted"); + } + *manifest_path = dbname_; + if (dbname_.back() != '/') { + manifest_path->push_back('/'); + } + *manifest_path += fname; + return Status::OK(); +} + Status VersionSet::Recover( const std::vector& column_families, bool read_only) { @@ -2702,43 +3533,28 @@ Status VersionSet::Recover( std::unordered_map column_families_not_found; // Read "CURRENT" file, which contains a pointer to the current manifest file - std::string manifest_filename; - Status s = ReadFileToString( - env_, CurrentFileName(dbname_), &manifest_filename - ); + std::string manifest_path; + Status s = GetCurrentManifestPath(&manifest_path); if (!s.ok()) { return s; } - if (manifest_filename.empty() || - manifest_filename.back() != '\n') { - return Status::Corruption("CURRENT file does not end with newline"); - } - // remove the trailing '\n' - manifest_filename.resize(manifest_filename.size() - 1); - FileType type; - bool parse_ok = - ParseFileName(manifest_filename, &manifest_file_number_, &type); - if (!parse_ok || type != kDescriptorFile) { - return Status::Corruption("CURRENT file corrupted"); - } ROCKS_LOG_INFO(db_options_->info_log, "Recovering from manifest file: %s\n", - manifest_filename.c_str()); + manifest_path.c_str()); - manifest_filename = dbname_ + "/" + manifest_filename; - unique_ptr manifest_file_reader; + std::unique_ptr manifest_file_reader; { - unique_ptr manifest_file; - s = env_->NewSequentialFile(manifest_filename, &manifest_file, + std::unique_ptr manifest_file; + s = env_->NewSequentialFile(manifest_path, &manifest_file, env_->OptimizeForManifestRead(env_options_)); if (!s.ok()) { return s; } manifest_file_reader.reset( - new SequentialFileReader(std::move(manifest_file))); + new SequentialFileReader(std::move(manifest_file), manifest_path)); } uint64_t current_manifest_file_size; - s = env_->GetFileSize(manifest_filename, ¤t_manifest_file_size); + s = env_->GetFileSize(manifest_path, ¤t_manifest_file_size); if (!s.ok()) { return s; } @@ -2752,7 +3568,9 @@ Status VersionSet::Recover( uint64_t log_number = 0; uint64_t previous_log_number = 0; uint32_t max_column_family = 0; - std::unordered_map builders; + uint64_t min_log_number_to_keep = 0; + std::unordered_map> + builders; // add default column family auto default_cf_iter = cf_name_to_options.find(kDefaultColumnFamilyName); @@ -2767,15 +3585,19 @@ Status VersionSet::Recover( // In recovery, nobody else can access it, so it's fine to set it to be // initialized earlier. default_cfd->set_initialized(); - builders.insert({0, new BaseReferencedVersionBuilder(default_cfd)}); + builders.insert( + std::make_pair(0, std::unique_ptr( + new BaseReferencedVersionBuilder(default_cfd)))); { VersionSet::LogReporter reporter; reporter.status = &s; - log::Reader reader(NULL, std::move(manifest_file_reader), &reporter, - true /*checksum*/, 0 /*initial_offset*/, 0); + log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter, + true /* checksum */, 0 /* log_number */); Slice record; std::string scratch; + std::vector replay_buffer; + size_t num_entries_decoded = 0; while (reader.ReadRecord(&record, &scratch) && s.ok()) { VersionEdit edit; s = edit.DecodeFrom(record); @@ -2783,118 +3605,55 @@ Status VersionSet::Recover( break; } - // Not found means that user didn't supply that column - // family option AND we encountered column family add - // record. Once we encounter column family drop record, - // we will delete the column family from - // column_families_not_found. - bool cf_in_not_found = - column_families_not_found.find(edit.column_family_) != - column_families_not_found.end(); - // in builders means that user supplied that column family - // option AND that we encountered column family add record - bool cf_in_builders = - builders.find(edit.column_family_) != builders.end(); - - // they can't both be true - assert(!(cf_in_not_found && cf_in_builders)); - - ColumnFamilyData* cfd = nullptr; - - if (edit.is_column_family_add_) { - if (cf_in_builders || cf_in_not_found) { - s = Status::Corruption( - "Manifest adding the same column family twice"); - break; - } - auto cf_options = cf_name_to_options.find(edit.column_family_name_); - if (cf_options == cf_name_to_options.end()) { - column_families_not_found.insert( - {edit.column_family_, edit.column_family_name_}); - } else { - cfd = CreateColumnFamily(cf_options->second, &edit); - cfd->set_initialized(); - builders.insert( - {edit.column_family_, new BaseReferencedVersionBuilder(cfd)}); - } - } else if (edit.is_column_family_drop_) { - if (cf_in_builders) { - auto builder = builders.find(edit.column_family_); - assert(builder != builders.end()); - delete builder->second; - builders.erase(builder); - cfd = column_family_set_->GetColumnFamily(edit.column_family_); - if (cfd->Unref()) { - delete cfd; - cfd = nullptr; - } else { - // who else can have reference to cfd!? - assert(false); - } - } else if (cf_in_not_found) { - column_families_not_found.erase(edit.column_family_); - } else { - s = Status::Corruption( - "Manifest - dropping non-existing column family"); - break; + if (edit.is_in_atomic_group_) { + if (replay_buffer.empty()) { + replay_buffer.resize(edit.remaining_entries_ + 1); + TEST_SYNC_POINT_CALLBACK("VersionSet::Recover:FirstInAtomicGroup", + &edit); } - } else if (!cf_in_not_found) { - if (!cf_in_builders) { - s = Status::Corruption( - "Manifest record referencing unknown column family"); + ++num_entries_decoded; + if (num_entries_decoded + edit.remaining_entries_ != + static_cast(replay_buffer.size())) { + TEST_SYNC_POINT_CALLBACK( + "VersionSet::Recover:IncorrectAtomicGroupSize", &edit); + s = Status::Corruption("corrupted atomic group"); break; } - - cfd = column_family_set_->GetColumnFamily(edit.column_family_); - // this should never happen since cf_in_builders is true - assert(cfd != nullptr); - - // if it is not column family add or column family drop, - // then it's a file add/delete, which should be forwarded - // to builder - auto builder = builders.find(edit.column_family_); - assert(builder != builders.end()); - builder->second->version_builder()->Apply(&edit); - } - - if (cfd != nullptr) { - if (edit.has_log_number_) { - if (cfd->GetLogNumber() > edit.log_number_) { - ROCKS_LOG_WARN( - db_options_->info_log, - "MANIFEST corruption detected, but ignored - Log numbers in " - "records NOT monotonically increasing"); - } else { - cfd->SetLogNumber(edit.log_number_); - have_log_number = true; + replay_buffer[num_entries_decoded - 1] = std::move(edit); + if (num_entries_decoded == replay_buffer.size()) { + TEST_SYNC_POINT_CALLBACK("VersionSet::Recover:LastInAtomicGroup", + &edit); + for (auto& e : replay_buffer) { + s = ApplyOneVersionEditToBuilder( + e, cf_name_to_options, column_families_not_found, builders, + &have_log_number, &log_number, &have_prev_log_number, + &previous_log_number, &have_next_file, &next_file, + &have_last_sequence, &last_sequence, &min_log_number_to_keep, + &max_column_family); + if (!s.ok()) { + break; + } } + replay_buffer.clear(); + num_entries_decoded = 0; } - if (edit.has_comparator_ && - edit.comparator_ != cfd->user_comparator()->Name()) { - s = Status::InvalidArgument( - cfd->user_comparator()->Name(), - "does not match existing comparator " + edit.comparator_); + TEST_SYNC_POINT("VersionSet::Recover:AtomicGroup"); + } else { + if (!replay_buffer.empty()) { + TEST_SYNC_POINT_CALLBACK( + "VersionSet::Recover:AtomicGroupMixedWithNormalEdits", &edit); + s = Status::Corruption("corrupted atomic group"); break; } + s = ApplyOneVersionEditToBuilder( + edit, cf_name_to_options, column_families_not_found, builders, + &have_log_number, &log_number, &have_prev_log_number, + &previous_log_number, &have_next_file, &next_file, + &have_last_sequence, &last_sequence, &min_log_number_to_keep, + &max_column_family); } - - if (edit.has_prev_log_number_) { - previous_log_number = edit.prev_log_number_; - have_prev_log_number = true; - } - - if (edit.has_next_file_number_) { - next_file = edit.next_file_number_; - have_next_file = true; - } - - if (edit.has_max_column_family_) { - max_column_family = edit.max_column_family_; - } - - if (edit.has_last_sequence_) { - last_sequence = edit.last_sequence_; - have_last_sequence = true; + if (!s.ok()) { + break; } } } @@ -2914,8 +3673,11 @@ Status VersionSet::Recover( column_family_set_->UpdateMaxColumnFamily(max_column_family); - MarkFileNumberUsedDuringRecovery(previous_log_number); - MarkFileNumberUsedDuringRecovery(log_number); + // When reading DB generated using old release, min_log_number_to_keep=0. + // All log files will be scanned for potential prepare entries. + MarkMinLogNumberToKeep2PC(min_log_number_to_keep); + MarkFileNumberUsed(previous_log_number); + MarkFileNumberUsed(log_number); } // there were some column families in the MANIFEST that weren't specified @@ -2948,21 +3710,25 @@ Status VersionSet::Recover( if (cfd->IsDropped()) { continue; } + if (read_only) { + cfd->table_cache()->SetTablesAreImmortal(); + } assert(cfd->initialized()); auto builders_iter = builders.find(cfd->GetID()); assert(builders_iter != builders.end()); - auto* builder = builders_iter->second->version_builder(); - - if (GetColumnFamilySet()->get_table_cache()->GetCapacity() == - TableCache::kInfiniteCapacity) { - // unlimited table cache. Pre-load table handle now. - // Need to do it out of the mutex. - builder->LoadTableHandlers( - cfd->internal_stats(), db_options_->max_file_opening_threads, - false /* prefetch_index_and_filter_in_cache */); - } + auto builder = builders_iter->second->version_builder(); - Version* v = new Version(cfd, this, current_version_number_++); + // unlimited table cache. Pre-load table handle now. + // Need to do it out of the mutex. + builder->LoadTableHandlers( + cfd->internal_stats(), db_options_->max_file_opening_threads, + false /* prefetch_index_and_filter_in_cache */, + true /* is_initial_load */, + cfd->GetLatestMutableCFOptions()->prefix_extractor.get()); + + Version* v = new Version(cfd, this, env_options_, + *cfd->GetLatestMutableCFOptions(), + current_version_number_++); builder->SaveTo(v->storage_info()); // Install recovered version @@ -2973,36 +3739,34 @@ Status VersionSet::Recover( manifest_file_size_ = current_manifest_file_size; next_file_number_.store(next_file + 1); - last_to_be_written_sequence_ = last_sequence; + last_allocated_sequence_ = last_sequence; + last_published_sequence_ = last_sequence; last_sequence_ = last_sequence; prev_log_number_ = previous_log_number; ROCKS_LOG_INFO( db_options_->info_log, "Recovered from manifest file:%s succeeded," - "manifest_file_number is %lu, next_file_number is %lu, " - "last_sequence is %lu, log_number is %lu," - "prev_log_number is %lu," - "max_column_family is %u\n", - manifest_filename.c_str(), (unsigned long)manifest_file_number_, - (unsigned long)next_file_number_.load(), (unsigned long)last_sequence_, - (unsigned long)log_number, (unsigned long)prev_log_number_, - column_family_set_->GetMaxColumnFamily()); + "manifest_file_number is %" PRIu64 ", next_file_number is %" PRIu64 + ", last_sequence is %" PRIu64 ", log_number is %" PRIu64 + ",prev_log_number is %" PRIu64 ",max_column_family is %" PRIu32 + ",min_log_number_to_keep is %" PRIu64 "\n", + manifest_path.c_str(), manifest_file_number_, + next_file_number_.load(), last_sequence_.load(), log_number, + prev_log_number_, column_family_set_->GetMaxColumnFamily(), + min_log_number_to_keep_2pc()); for (auto cfd : *column_family_set_) { if (cfd->IsDropped()) { continue; } ROCKS_LOG_INFO(db_options_->info_log, - "Column family [%s] (ID %u), log number is %" PRIu64 "\n", + "Column family [%s] (ID %" PRIu32 + "), log number is %" PRIu64 "\n", cfd->GetName().c_str(), cfd->GetID(), cfd->GetLogNumber()); } } - for (auto& builder : builders) { - delete builder.second; - } - return s; } @@ -3024,14 +3788,14 @@ Status VersionSet::ListColumnFamilies(std::vector* column_families, std::string dscname = dbname + "/" + current; - unique_ptr file_reader; + std::unique_ptr file_reader; { - unique_ptr file; - s = env->NewSequentialFile(dscname, &file, soptions); - if (!s.ok()) { - return s; + std::unique_ptr file; + s = env->NewSequentialFile(dscname, &file, soptions); + if (!s.ok()) { + return s; } - file_reader.reset(new SequentialFileReader(std::move(file))); + file_reader.reset(new SequentialFileReader(std::move(file), dscname)); } std::map column_family_names; @@ -3039,8 +3803,8 @@ Status VersionSet::ListColumnFamilies(std::vector* column_families, column_family_names.insert({0, kDefaultColumnFamilyName}); VersionSet::LogReporter reporter; reporter.status = &s; - log::Reader reader(NULL, std::move(file_reader), &reporter, true /*checksum*/, - 0 /*initial_offset*/, 0); + log::Reader reader(nullptr, std::move(file_reader), &reporter, + true /* checksum */, 0 /* log_number */); Slice record; std::string scratch; while (reader.ReadRecord(&record, &scratch) && s.ok()) { @@ -3166,16 +3930,16 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname, Status VersionSet::DumpManifest(Options& options, std::string& dscname, bool verbose, bool hex, bool json) { // Open the specified manifest file. - unique_ptr file_reader; + std::unique_ptr file_reader; Status s; { - unique_ptr file; + std::unique_ptr file; s = options.env->NewSequentialFile( dscname, &file, env_->OptimizeForManifestRead(env_options_)); if (!s.ok()) { return s; } - file_reader.reset(new SequentialFileReader(std::move(file))); + file_reader.reset(new SequentialFileReader(std::move(file), dscname)); } bool have_prev_log_number = false; @@ -3186,7 +3950,8 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, uint64_t previous_log_number = 0; int count = 0; std::unordered_map comparators; - std::unordered_map builders; + std::unordered_map> + builders; // add default column family VersionEdit default_cf_edit; @@ -3194,13 +3959,15 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, default_cf_edit.SetColumnFamily(0); ColumnFamilyData* default_cfd = CreateColumnFamily(ColumnFamilyOptions(options), &default_cf_edit); - builders.insert({0, new BaseReferencedVersionBuilder(default_cfd)}); + builders.insert( + std::make_pair(0, std::unique_ptr( + new BaseReferencedVersionBuilder(default_cfd)))); { VersionSet::LogReporter reporter; reporter.status = &s; - log::Reader reader(NULL, std::move(file_reader), &reporter, - true /*checksum*/, 0 /*initial_offset*/, 0); + log::Reader reader(nullptr, std::move(file_reader), &reporter, + true /* checksum */, 0 /* log_number */); Slice record; std::string scratch; while (reader.ReadRecord(&record, &scratch) && s.ok()) { @@ -3235,8 +4002,9 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, } cfd = CreateColumnFamily(ColumnFamilyOptions(options), &edit); cfd->set_initialized(); - builders.insert( - {edit.column_family_, new BaseReferencedVersionBuilder(cfd)}); + builders.insert(std::make_pair( + edit.column_family_, std::unique_ptr( + new BaseReferencedVersionBuilder(cfd)))); } else if (edit.is_column_family_drop_) { if (!cf_in_builders) { s = Status::Corruption( @@ -3244,7 +4012,6 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, break; } auto builder_iter = builders.find(edit.column_family_); - delete builder_iter->second; builders.erase(builder_iter); comparators.erase(edit.column_family_); cfd = column_family_set_->GetColumnFamily(edit.column_family_); @@ -3275,6 +4042,7 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, cfd->SetLogNumber(edit.log_number_); } + if (edit.has_prev_log_number_) { previous_log_number = edit.prev_log_number_; have_prev_log_number = true; @@ -3293,6 +4061,10 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, if (edit.has_max_column_family_) { column_family_set_->UpdateMaxColumnFamily(edit.max_column_family_); } + + if (edit.has_min_log_number_to_keep_) { + MarkMinLogNumberToKeep2PC(edit.min_log_number_to_keep_); + } } } file_reader.reset(); @@ -3320,13 +4092,16 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, assert(builders_iter != builders.end()); auto builder = builders_iter->second->version_builder(); - Version* v = new Version(cfd, this, current_version_number_++); + Version* v = new Version(cfd, this, env_options_, + *cfd->GetLatestMutableCFOptions(), + current_version_number_++); builder->SaveTo(v->storage_info()); v->PrepareApply(*cfd->GetLatestMutableCFOptions(), false); - printf("--------------- Column family \"%s\" (ID %u) --------------\n", - cfd->GetName().c_str(), (unsigned int)cfd->GetID()); - printf("log number: %lu\n", (unsigned long)cfd->GetLogNumber()); + printf("--------------- Column family \"%s\" (ID %" PRIu32 + ") --------------\n", + cfd->GetName().c_str(), cfd->GetID()); + printf("log number: %" PRIu64 "\n", cfd->GetLogNumber()); auto comparator = comparators.find(cfd->GetID()); if (comparator != comparators.end()) { printf("comparator: %s\n", comparator->second.c_str()); @@ -3337,36 +4112,41 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname, delete v; } - // Free builders - for (auto& builder : builders) { - delete builder.second; - } - next_file_number_.store(next_file + 1); - last_to_be_written_sequence_ = last_sequence; + last_allocated_sequence_ = last_sequence; + last_published_sequence_ = last_sequence; last_sequence_ = last_sequence; prev_log_number_ = previous_log_number; - printf( - "next_file_number %lu last_sequence " - "%lu prev_log_number %lu max_column_family %u\n", - (unsigned long)next_file_number_.load(), (unsigned long)last_sequence, - (unsigned long)previous_log_number, - column_family_set_->GetMaxColumnFamily()); + printf("next_file_number %" PRIu64 " last_sequence %" PRIu64 + " prev_log_number %" PRIu64 " max_column_family %" PRIu32 + " min_log_number_to_keep " + "%" PRIu64 "\n", + next_file_number_.load(), last_sequence, previous_log_number, + column_family_set_->GetMaxColumnFamily(), + min_log_number_to_keep_2pc()); } return s; } #endif // ROCKSDB_LITE -void VersionSet::MarkFileNumberUsedDuringRecovery(uint64_t number) { - // only called during recovery which is single threaded, so this works because - // there can't be concurrent calls +void VersionSet::MarkFileNumberUsed(uint64_t number) { + // only called during recovery and repair which are single threaded, so this + // works because there can't be concurrent calls if (next_file_number_.load(std::memory_order_relaxed) <= number) { next_file_number_.store(number + 1, std::memory_order_relaxed); } } +// Called only either from ::LogAndApply which is protected by mutex or during +// recovery which is single-threaded. +void VersionSet::MarkMinLogNumberToKeep2PC(uint64_t number) { + if (min_log_number_to_keep_2pc_.load(std::memory_order_relaxed) < number) { + min_log_number_to_keep_2pc_.store(number, std::memory_order_relaxed); + } +} + Status VersionSet::WriteSnapshot(log::Writer* log) { // TODO: Break up into multiple records to reduce memory usage on recovery? @@ -3412,7 +4192,7 @@ Status VersionSet::WriteSnapshot(log::Writer* log) { cfd->current()->storage_info()->LevelFiles(level)) { edit.AddFile(level, f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(), f->smallest, f->largest, - f->smallest_seqno, f->largest_seqno, + f->fd.smallest_seqno, f->fd.largest_seqno, f->marked_for_compaction); } } @@ -3532,8 +4312,9 @@ uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f, // approximate offset of "key" within the table. TableReader* table_reader_ptr; InternalIterator* iter = v->cfd_->table_cache()->NewIterator( - ReadOptions(), env_options_, v->cfd_->internal_comparator(), f.fd, - nullptr /* range_del_agg */, &table_reader_ptr); + ReadOptions(), v->env_options_, v->cfd_->internal_comparator(), + *f.file_metadata, nullptr /* range_del_agg */, + v->GetMutableCFOptions().prefix_extractor.get(), &table_reader_ptr); if (table_reader_ptr != nullptr) { result = table_reader_ptr->ApproximateOffsetOf(key); } @@ -3585,7 +4366,8 @@ void VersionSet::AddLiveFiles(std::vector* live_list) { } InternalIterator* VersionSet::MakeInputIterator( - const Compaction* c, RangeDelAggregator* range_del_agg) { + const Compaction* c, RangeDelAggregator* range_del_agg, + const EnvOptions& env_options_compactions) { auto cfd = c->column_family_data(); ReadOptions read_options; read_options.verify_checksums = true; @@ -3610,26 +4392,25 @@ InternalIterator* VersionSet::MakeInputIterator( const LevelFilesBrief* flevel = c->input_levels(which); for (size_t i = 0; i < flevel->num_files; i++) { list[num++] = cfd->table_cache()->NewIterator( - read_options, env_options_compactions_, - cfd->internal_comparator(), flevel->files[i].fd, range_del_agg, + read_options, env_options_compactions, cfd->internal_comparator(), + *flevel->files[i].file_metadata, range_del_agg, + c->mutable_cf_options()->prefix_extractor.get(), nullptr /* table_reader_ptr */, nullptr /* no per level latency histogram */, true /* for_compaction */, nullptr /* arena */, - false /* skip_filters */, (int)which /* level */); + false /* skip_filters */, static_cast(which) /* level */); } } else { // Create concatenating iterator for the files from this level - list[num++] = NewTwoLevelIterator( - new LevelFileIteratorState( - cfd->table_cache(), read_options, env_options_compactions_, - cfd->internal_comparator(), - nullptr /* no per level latency histogram */, - true /* for_compaction */, false /* prefix enabled */, - false /* skip_filters */, (int)which /* level */, - range_del_agg), - new LevelFileNumIterator(cfd->internal_comparator(), - c->input_levels(which), - false /* don't sample compaction */)); + list[num++] = new LevelIterator( + cfd->table_cache(), read_options, env_options_compactions, + cfd->internal_comparator(), c->input_levels(which), + c->mutable_cf_options()->prefix_extractor.get(), + false /* should_sample */, + nullptr /* no per level latency histogram */, + true /* for_compaction */, false /* skip_filters */, + static_cast(which) /* level */, range_del_agg, + c->boundaries(which)); } } } @@ -3686,6 +4467,8 @@ bool VersionSet::VerifyCompactionFileConsistency(Compaction* c) { } } } +#else + (void)c; #endif return true; // everything good } @@ -3724,36 +4507,41 @@ void VersionSet::GetLiveFilesMetaData(std::vector* metadata) { LiveFileMetaData filemetadata; filemetadata.column_family_name = cfd->GetName(); uint32_t path_id = file->fd.GetPathId(); - if (path_id < db_options_->db_paths.size()) { - filemetadata.db_path = db_options_->db_paths[path_id].path; + if (path_id < cfd->ioptions()->cf_paths.size()) { + filemetadata.db_path = cfd->ioptions()->cf_paths[path_id].path; } else { - assert(!db_options_->db_paths.empty()); - filemetadata.db_path = db_options_->db_paths.back().path; + assert(!cfd->ioptions()->cf_paths.empty()); + filemetadata.db_path = cfd->ioptions()->cf_paths.back().path; } filemetadata.name = MakeTableFileName("", file->fd.GetNumber()); filemetadata.level = level; - filemetadata.size = file->fd.GetFileSize(); + filemetadata.size = static_cast(file->fd.GetFileSize()); filemetadata.smallestkey = file->smallest.user_key().ToString(); filemetadata.largestkey = file->largest.user_key().ToString(); - filemetadata.smallest_seqno = file->smallest_seqno; - filemetadata.largest_seqno = file->largest_seqno; + filemetadata.smallest_seqno = file->fd.smallest_seqno; + filemetadata.largest_seqno = file->fd.largest_seqno; + filemetadata.num_reads_sampled = file->stats.num_reads_sampled.load( + std::memory_order_relaxed); + filemetadata.being_compacted = file->being_compacted; + filemetadata.num_entries = file->num_entries; + filemetadata.num_deletions = file->num_deletions; metadata->push_back(filemetadata); } } } } -void VersionSet::GetObsoleteFiles(std::vector* files, +void VersionSet::GetObsoleteFiles(std::vector* files, std::vector* manifest_filenames, uint64_t min_pending_output) { assert(manifest_filenames->empty()); obsolete_manifests_.swap(*manifest_filenames); - std::vector pending_files; - for (auto f : obsolete_files_) { - if (f->fd.GetNumber() < min_pending_output) { - files->push_back(f); + std::vector pending_files; + for (auto& f : obsolete_files_) { + if (f.metadata->fd.GetNumber() < min_pending_output) { + files->push_back(std::move(f)); } else { - pending_files.push_back(f); + pending_files.push_back(std::move(f)); } } obsolete_files_.swap(pending_files); @@ -3763,7 +4551,9 @@ ColumnFamilyData* VersionSet::CreateColumnFamily( const ColumnFamilyOptions& cf_options, VersionEdit* edit) { assert(edit->is_column_family_add_); - Version* dummy_versions = new Version(nullptr, this); + MutableCFOptions dummy_cf_options; + Version* dummy_versions = + new Version(nullptr, this, env_options_, dummy_cf_options); // Ref() dummy version once so that later we can call Unref() to delete it // by avoiding calling "delete" explicitly (~Version is private) dummy_versions->Ref(); @@ -3771,7 +4561,9 @@ ColumnFamilyData* VersionSet::CreateColumnFamily( edit->column_family_name_, edit->column_family_, dummy_versions, cf_options); - Version* v = new Version(new_cfd, this, current_version_number_++); + Version* v = new Version(new_cfd, this, env_options_, + *new_cfd->GetLatestMutableCFOptions(), + current_version_number_++); // Fill level target base information. v->storage_info()->CalculateBaseBytes(*new_cfd->ioptions(), @@ -3811,4 +4603,405 @@ uint64_t VersionSet::GetTotalSstFilesSize(Version* dummy_versions) { return total_files_size; } +ReactiveVersionSet::ReactiveVersionSet(const std::string& dbname, + const ImmutableDBOptions* _db_options, + const EnvOptions& _env_options, + Cache* table_cache, + WriteBufferManager* write_buffer_manager, + WriteController* write_controller) + : VersionSet(dbname, _db_options, _env_options, table_cache, + write_buffer_manager, write_controller) {} + +ReactiveVersionSet::~ReactiveVersionSet() {} + +Status ReactiveVersionSet::Recover( + const std::vector& column_families, + std::unique_ptr* manifest_reader, + std::unique_ptr* manifest_reporter, + std::unique_ptr* manifest_reader_status) { + assert(manifest_reader != nullptr); + assert(manifest_reporter != nullptr); + assert(manifest_reader_status != nullptr); + + std::unordered_map cf_name_to_options; + for (const auto& cf : column_families) { + cf_name_to_options.insert({cf.name, cf.options}); + } + + // add default column family + auto default_cf_iter = cf_name_to_options.find(kDefaultColumnFamilyName); + if (default_cf_iter == cf_name_to_options.end()) { + return Status::InvalidArgument("Default column family not specified"); + } + VersionEdit default_cf_edit; + default_cf_edit.AddColumnFamily(kDefaultColumnFamilyName); + default_cf_edit.SetColumnFamily(0); + ColumnFamilyData* default_cfd = + CreateColumnFamily(default_cf_iter->second, &default_cf_edit); + // In recovery, nobody else can access it, so it's fine to set it to be + // initialized earlier. + default_cfd->set_initialized(); + + bool have_log_number = false; + bool have_prev_log_number = false; + bool have_next_file = false; + bool have_last_sequence = false; + uint64_t next_file = 0; + uint64_t last_sequence = 0; + uint64_t log_number = 0; + uint64_t previous_log_number = 0; + uint32_t max_column_family = 0; + uint64_t min_log_number_to_keep = 0; + std::unordered_map> + builders; + std::unordered_map column_families_not_found; + builders.insert( + std::make_pair(0, std::unique_ptr( + new BaseReferencedVersionBuilder(default_cfd)))); + + manifest_reader_status->reset(new Status()); + manifest_reporter->reset(new LogReporter()); + static_cast(manifest_reporter->get())->status = + manifest_reader_status->get(); + Status s = MaybeSwitchManifest(manifest_reporter->get(), manifest_reader); + log::Reader* reader = manifest_reader->get(); + + int retry = 0; + while (s.ok() && retry < 1) { + assert(reader != nullptr); + Slice record; + std::string scratch; + while (s.ok() && reader->ReadRecord(&record, &scratch)) { + VersionEdit edit; + s = edit.DecodeFrom(record); + if (!s.ok()) { + break; + } + s = ApplyOneVersionEditToBuilder( + edit, cf_name_to_options, column_families_not_found, builders, + &have_log_number, &log_number, &have_prev_log_number, + &previous_log_number, &have_next_file, &next_file, + &have_last_sequence, &last_sequence, &min_log_number_to_keep, + &max_column_family); + } + if (s.ok()) { + bool enough = have_next_file && have_log_number && have_last_sequence; + if (enough) { + for (const auto& cf : column_families) { + auto cfd = column_family_set_->GetColumnFamily(cf.name); + if (cfd == nullptr) { + enough = false; + break; + } + } + } + if (enough) { + for (const auto& cf : column_families) { + auto cfd = column_family_set_->GetColumnFamily(cf.name); + assert(cfd != nullptr); + if (!cfd->IsDropped()) { + auto builder_iter = builders.find(cfd->GetID()); + assert(builder_iter != builders.end()); + auto builder = builder_iter->second->version_builder(); + assert(builder != nullptr); + s = builder->LoadTableHandlers( + cfd->internal_stats(), db_options_->max_file_opening_threads, + false /* prefetch_index_and_filter_in_cache */, + true /* is_initial_load */, + cfd->GetLatestMutableCFOptions()->prefix_extractor.get()); + if (!s.ok()) { + enough = false; + if (s.IsPathNotFound()) { + s = Status::OK(); + } + break; + } + } + } + } + if (enough) { + break; + } + } + ++retry; + } + + if (s.ok()) { + if (!have_prev_log_number) { + previous_log_number = 0; + } + column_family_set_->UpdateMaxColumnFamily(max_column_family); + + MarkMinLogNumberToKeep2PC(min_log_number_to_keep); + MarkFileNumberUsed(previous_log_number); + MarkFileNumberUsed(log_number); + + for (auto cfd : *column_family_set_) { + assert(builders.count(cfd->GetID()) > 0); + auto builder = builders[cfd->GetID()]->version_builder(); + if (!builder->CheckConsistencyForNumLevels()) { + s = Status::InvalidArgument( + "db has more levels than options.num_levels"); + break; + } + } + } + + if (s.ok()) { + for (auto cfd : *column_family_set_) { + if (cfd->IsDropped()) { + continue; + } + assert(cfd->initialized()); + auto builders_iter = builders.find(cfd->GetID()); + assert(builders_iter != builders.end()); + auto* builder = builders_iter->second->version_builder(); + + Version* v = new Version(cfd, this, env_options_, + *cfd->GetLatestMutableCFOptions(), + current_version_number_++); + builder->SaveTo(v->storage_info()); + + // Install recovered version + v->PrepareApply(*cfd->GetLatestMutableCFOptions(), + !(db_options_->skip_stats_update_on_db_open)); + AppendVersion(cfd, v); + } + next_file_number_.store(next_file + 1); + last_allocated_sequence_ = last_sequence; + last_published_sequence_ = last_sequence; + last_sequence_ = last_sequence; + prev_log_number_ = previous_log_number; + for (auto cfd : *column_family_set_) { + if (cfd->IsDropped()) { + continue; + } + ROCKS_LOG_INFO(db_options_->info_log, + "Column family [%s] (ID %u), log number is %" PRIu64 "\n", + cfd->GetName().c_str(), cfd->GetID(), cfd->GetLogNumber()); + } + } + return s; +} + +Status ReactiveVersionSet::ReadAndApply( + InstrumentedMutex* mu, + std::unique_ptr* manifest_reader, + std::unordered_set* cfds_changed) { + assert(manifest_reader != nullptr); + assert(cfds_changed != nullptr); + mu->AssertHeld(); + + Status s; + bool have_log_number = false; + bool have_prev_log_number = false; + bool have_next_file = false; + bool have_last_sequence = false; + uint64_t next_file = 0; + uint64_t last_sequence = 0; + uint64_t log_number = 0; + uint64_t previous_log_number = 0; + uint32_t max_column_family = 0; + uint64_t min_log_number_to_keep = 0; + + while (s.ok()) { + Slice record; + std::string scratch; + log::Reader* reader = manifest_reader->get(); + std::string old_manifest_path = reader->file()->file_name(); + while (reader->ReadRecord(&record, &scratch)) { + VersionEdit edit; + s = edit.DecodeFrom(record); + if (!s.ok()) { + break; + } + ColumnFamilyData* cfd = + column_family_set_->GetColumnFamily(edit.column_family_); + // If we cannot find this column family in our column family set, then it + // may be a new column family created by the primary after the secondary + // starts. Ignore it for now. + if (nullptr == cfd) { + continue; + } + if (active_version_builders_.find(edit.column_family_) == + active_version_builders_.end()) { + std::unique_ptr builder_guard( + new BaseReferencedVersionBuilder(cfd)); + active_version_builders_.insert( + std::make_pair(edit.column_family_, std::move(builder_guard))); + } + s = ApplyOneVersionEditToBuilder( + edit, &have_log_number, &log_number, &have_prev_log_number, + &previous_log_number, &have_next_file, &next_file, + &have_last_sequence, &last_sequence, &min_log_number_to_keep, + &max_column_family); + if (!s.ok()) { + break; + } + auto builder_iter = active_version_builders_.find(edit.column_family_); + assert(builder_iter != active_version_builders_.end()); + auto builder = builder_iter->second->version_builder(); + assert(builder != nullptr); + s = builder->LoadTableHandlers( + cfd->internal_stats(), db_options_->max_file_opening_threads, + false /* prefetch_index_and_filter_in_cache */, + false /* is_initial_load */, + cfd->GetLatestMutableCFOptions()->prefix_extractor.get()); + TEST_SYNC_POINT_CALLBACK( + "ReactiveVersionSet::ReadAndApply:AfterLoadTableHandlers", &s); + if (!s.ok() && !s.IsPathNotFound()) { + break; + } else if (s.IsPathNotFound()) { + s = Status::OK(); + } else { // s.ok() == true + auto version = new Version(cfd, this, env_options_, + *cfd->GetLatestMutableCFOptions(), + current_version_number_++); + builder->SaveTo(version->storage_info()); + version->PrepareApply(*cfd->GetLatestMutableCFOptions(), true); + AppendVersion(cfd, version); + active_version_builders_.erase(builder_iter); + if (cfds_changed->count(cfd) == 0) { + cfds_changed->insert(cfd); + } + } + if (have_next_file) { + next_file_number_.store(next_file + 1); + } + if (have_last_sequence) { + last_allocated_sequence_ = last_sequence; + last_published_sequence_ = last_sequence; + last_sequence_ = last_sequence; + } + if (have_prev_log_number) { + prev_log_number_ = previous_log_number; + MarkFileNumberUsed(previous_log_number); + } + if (have_log_number) { + MarkFileNumberUsed(log_number); + } + column_family_set_->UpdateMaxColumnFamily(max_column_family); + MarkMinLogNumberToKeep2PC(min_log_number_to_keep); + } + // It's possible that: + // 1) s.IsCorruption(), indicating the current MANIFEST is corrupted. + // 2) we have finished reading the current MANIFEST. + // 3) we have encountered an IOError reading the current MANIFEST. + // We need to look for the next MANIFEST and start from there. If we cannot + // find the next MANIFEST, we should exit the loop. + s = MaybeSwitchManifest(reader->GetReporter(), manifest_reader); + reader = manifest_reader->get(); + if (s.ok() && reader->file()->file_name() == old_manifest_path) { + break; + } + } + + if (s.ok()) { + for (auto cfd : *column_family_set_) { + auto builder_iter = active_version_builders_.find(cfd->GetID()); + if (builder_iter == active_version_builders_.end()) { + continue; + } + auto builder = builder_iter->second->version_builder(); + if (!builder->CheckConsistencyForNumLevels()) { + s = Status::InvalidArgument( + "db has more levels than options.num_levels"); + break; + } + } + } + return s; +} + +Status ReactiveVersionSet::ApplyOneVersionEditToBuilder( + VersionEdit& edit, bool* have_log_number, uint64_t* log_number, + bool* have_prev_log_number, uint64_t* previous_log_number, + bool* have_next_file, uint64_t* next_file, bool* have_last_sequence, + SequenceNumber* last_sequence, uint64_t* min_log_number_to_keep, + uint32_t* max_column_family) { + ColumnFamilyData* cfd = nullptr; + Status status; + if (edit.is_column_family_add_) { + // TODO (yanqin) for now the secondary ignores column families created + // after Open. This also simplifies handling of switching to a new MANIFEST + // and processing the snapshot of the system at the beginning of the + // MANIFEST. + return Status::OK(); + } else if (edit.is_column_family_drop_) { + cfd = column_family_set_->GetColumnFamily(edit.column_family_); + // Drop a CF created by primary after secondary starts? Then ignore + if (cfd == nullptr) { + return Status::OK(); + } + // Drop the column family by setting it to be 'dropped' without destroying + // the column family handle. + cfd->SetDropped(); + if (cfd->Unref()) { + delete cfd; + cfd = nullptr; + } + } else { + cfd = column_family_set_->GetColumnFamily(edit.column_family_); + // Operation on a CF created after Open? Then ignore + if (cfd == nullptr) { + return Status::OK(); + } + auto builder_iter = active_version_builders_.find(edit.column_family_); + assert(builder_iter != active_version_builders_.end()); + auto builder = builder_iter->second->version_builder(); + assert(builder != nullptr); + builder->Apply(&edit); + } + return ExtractInfoFromVersionEdit( + cfd, edit, have_log_number, log_number, have_prev_log_number, + previous_log_number, have_next_file, next_file, have_last_sequence, + last_sequence, min_log_number_to_keep, max_column_family); +} + +Status ReactiveVersionSet::MaybeSwitchManifest( + log::Reader::Reporter* reporter, + std::unique_ptr* manifest_reader) { + assert(manifest_reader != nullptr); + Status s; + do { + std::string manifest_path; + s = GetCurrentManifestPath(&manifest_path); + std::unique_ptr manifest_file; + if (s.ok()) { + if (nullptr == manifest_reader->get() || + manifest_reader->get()->file()->file_name() != manifest_path) { + TEST_SYNC_POINT( + "ReactiveVersionSet::MaybeSwitchManifest:" + "AfterGetCurrentManifestPath:0"); + TEST_SYNC_POINT( + "ReactiveVersionSet::MaybeSwitchManifest:" + "AfterGetCurrentManifestPath:1"); + s = env_->NewSequentialFile( + manifest_path, &manifest_file, + env_->OptimizeForManifestRead(env_options_)); + } else { + // No need to switch manifest. + break; + } + } + std::unique_ptr manifest_file_reader; + if (s.ok()) { + manifest_file_reader.reset( + new SequentialFileReader(std::move(manifest_file), manifest_path)); + manifest_reader->reset(new log::FragmentBufferedReader( + nullptr, std::move(manifest_file_reader), reporter, + true /* checksum */, 0 /* log_number */)); + ROCKS_LOG_INFO(db_options_->info_log, "Switched to new manifest: %s\n", + manifest_path.c_str()); + // TODO (yanqin) every time we switch to a new MANIFEST, we clear the + // active_version_builders_ map because we choose to construct the + // versions from scratch, thanks to the first part of each MANIFEST + // written by VersionSet::WriteSnapshot. This is not necessary, but we + // choose this at present for the sake of simplicity. + active_version_builders_.clear(); + } + } while (s.IsPathNotFound()); + return s; +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/version_set.h b/thirdparty/rocksdb/db/version_set.h index 5862dea335..8b50dca76e 100644 --- a/thirdparty/rocksdb/db/version_set.h +++ b/thirdparty/rocksdb/db/version_set.h @@ -35,6 +35,7 @@ #include "db/file_indexer.h" #include "db/log_reader.h" #include "db/range_del_aggregator.h" +#include "db/read_callback.h" #include "db/table_cache.h" #include "db/version_builder.h" #include "db/version_edit.h" @@ -51,7 +52,6 @@ class Writer; } class Compaction; -class InternalIterator; class LogBuffer; class LookupKey; class MemTable; @@ -114,7 +114,7 @@ class VersionStorageInfo { // Update the accumulated stats from a file-meta. void UpdateAccumulatedStats(FileMetaData* file_meta); - // Decrease the current stat form a to-be-delected file-meta + // Decrease the current stat from a to-be-deleted file-meta void RemoveCurrentStats(FileMetaData* file_meta); void ComputeCompensatedSizes(); @@ -134,6 +134,23 @@ class VersionStorageInfo { // ComputeCompactionScore() void ComputeFilesMarkedForCompaction(); + // This computes ttl_expired_files_ and is called by + // ComputeCompactionScore() + void ComputeExpiredTtlFiles(const ImmutableCFOptions& ioptions, + const uint64_t ttl); + + // This computes bottommost_files_marked_for_compaction_ and is called by + // ComputeCompactionScore() or UpdateOldestSnapshot(). + // + // Among bottommost files (assumes they've already been computed), marks the + // ones that have keys that would be eliminated if recompacted, according to + // the seqnum of the oldest existing snapshot. Must be called every time + // oldest snapshot changes as that is when bottom-level files can become + // eligible for compaction. + // + // REQUIRES: DB mutex held + void ComputeBottommostFilesMarkedForCompaction(); + // Generate level_files_brief_ from files_ void GenerateLevelFilesBrief(); // Sort all files for this version based on their file size and @@ -146,6 +163,16 @@ class VersionStorageInfo { return level0_non_overlapping_; } + // Check whether each file in this version is bottommost (i.e., nothing in its + // key-range could possibly exist in an older file/level). + // REQUIRES: This version has not been saved + void GenerateBottommostFiles(); + + // Updates the oldest snapshot and related internal state, like the bottommost + // files marked for compaction. + // REQUIRES: DB mutex held + void UpdateOldestSnapshot(SequenceNumber oldest_snapshot_seqnum); + int MaxInputLevel() const; int MaxOutputLevel(bool allow_ingest_behind) const; @@ -161,9 +188,11 @@ class VersionStorageInfo { std::vector* inputs, int hint_index = -1, // index of overlap file int* file_index = nullptr, // return index of overlap file - bool expand_range = true) // if set, returns files which overlap the - const; // range and overlap each other. If false, + bool expand_range = true, // if set, returns files which overlap the + // range and overlap each other. If false, // then just files intersecting the range + InternalKey** next_smallest = nullptr) // if non-null, returns the + const; // smallest key of next file not included void GetCleanInputsWithinInterval( int level, const InternalKey* begin, // nullptr means before all keys const InternalKey* end, // nullptr means after all keys @@ -173,31 +202,32 @@ class VersionStorageInfo { const; void GetOverlappingInputsRangeBinarySearch( - int level, // level > 0 - const Slice& begin, // nullptr means before all keys - const Slice& end, // nullptr means after all keys + int level, // level > 0 + const InternalKey* begin, // nullptr means before all keys + const InternalKey* end, // nullptr means after all keys std::vector* inputs, int hint_index, // index of overlap file int* file_index, // return index of overlap file - bool within_interval = false) // if set, force the inputs within interval - const; + bool within_interval = false, // if set, force the inputs within interval + InternalKey** next_smallest = nullptr) // if non-null, returns the + const; // smallest key of next file not included void ExtendFileRangeOverlappingInterval( int level, - const Slice& begin, // nullptr means before all keys - const Slice& end, // nullptr means after all keys - unsigned int index, // start extending from this index - int* startIndex, // return the startIndex of input range - int* endIndex) // return the endIndex of input range + const InternalKey* begin, // nullptr means before all keys + const InternalKey* end, // nullptr means after all keys + unsigned int index, // start extending from this index + int* startIndex, // return the startIndex of input range + int* endIndex) // return the endIndex of input range const; void ExtendFileRangeWithinInterval( int level, - const Slice& begin, // nullptr means before all keys - const Slice& end, // nullptr means after all keys - unsigned int index, // start extending from this index - int* startIndex, // return the startIndex of input range - int* endIndex) // return the endIndex of input range + const InternalKey* begin, // nullptr means before all keys + const InternalKey* end, // nullptr means after all keys + unsigned int index, // start extending from this index + int* startIndex, // return the startIndex of input range + int* endIndex) // return the endIndex of input range const; // Returns true iff some file in the specified level overlaps @@ -263,7 +293,23 @@ class VersionStorageInfo { return files_marked_for_compaction_; } + // REQUIRES: This version has been saved (see VersionSet::SaveTo) + // REQUIRES: DB mutex held during access + const autovector>& ExpiredTtlFiles() const { + assert(finalized_); + return expired_ttl_files_; + } + + // REQUIRES: This version has been saved (see VersionSet::SaveTo) + // REQUIRES: DB mutex held during access + const autovector>& + BottommostFilesMarkedForCompaction() const { + assert(finalized_); + return bottommost_files_marked_for_compaction_; + } + int base_level() const { return base_level_; } + double level_multiplier() const { return level_multiplier_; } // REQUIRES: lock is held // Set the index that is used to offset into files_by_compaction_pri_ to find @@ -356,6 +402,20 @@ class VersionStorageInfo { bool force_consistency_checks() const { return force_consistency_checks_; } + SequenceNumber bottommost_files_mark_threshold() const { + return bottommost_files_mark_threshold_; + } + + // Returns whether any key in [`smallest_key`, `largest_key`] could appear in + // an older L0 file than `last_l0_idx` or in a greater level than `last_level` + // + // @param last_level Level after which we check for overlap + // @param last_l0_idx If `last_level == 0`, index of L0 file after which we + // check for overlap; otherwise, must be -1 + bool RangeMightExistAfterSortedRun(const Slice& smallest_user_key, + const Slice& largest_user_key, + int last_level, int last_l0_idx); + private: const InternalKeyComparator* internal_comparator_; const Comparator* user_comparator_; @@ -380,6 +440,8 @@ class VersionStorageInfo { // be empty. -1 if it is not level-compaction so it's not applicable. int base_level_; + double level_multiplier_; + // A list for the same set of files that are stored in files_, // but files in each level are now sorted based on file // size. The file with the largest size is at the front. @@ -405,6 +467,30 @@ class VersionStorageInfo { // ComputeCompactionScore() autovector> files_marked_for_compaction_; + autovector> expired_ttl_files_; + + // These files are considered bottommost because none of their keys can exist + // at lower levels. They are not necessarily all in the same level. The marked + // ones are eligible for compaction because they contain duplicate key + // versions that are no longer protected by snapshot. These variables are + // protected by DB mutex and are calculated in `GenerateBottommostFiles()` and + // `ComputeBottommostFilesMarkedForCompaction()`. + autovector> bottommost_files_; + autovector> + bottommost_files_marked_for_compaction_; + + // Threshold for needing to mark another bottommost file. Maintain it so we + // can quickly check when releasing a snapshot whether more bottommost files + // became eligible for compaction. It's defined as the min of the max nonzero + // seqnums of unmarked bottommost files. + SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber; + + // Monotonically increases as we release old snapshots. Zero indicates no + // snapshots have been released yet. When no snapshots remain we set it to the + // current seqnum, which needs to be protected as a snapshot can still be + // created that references it. + SequenceNumber oldest_snapshot_seqnum_ = 0; + // Level that should be compacted next and its compaction score. // Score < 1 means compaction is not strictly needed. These fields // are initialized by Finalize(). @@ -428,7 +514,7 @@ class VersionStorageInfo { uint64_t accumulated_num_deletions_; // current number of non_deletion entries uint64_t current_num_non_deletions_; - // current number of delection entries + // current number of deletion entries uint64_t current_num_deletions_; // current number of file samples uint64_t current_num_samples_; @@ -462,9 +548,10 @@ class Version { MergeIteratorBuilder* merger_iter_builder, int level, RangeDelAggregator* range_del_agg); - void AddRangeDelIteratorsForLevel( - const ReadOptions& read_options, const EnvOptions& soptions, int level, - std::vector* range_del_iters); + Status OverlapWithLevelIterator(const ReadOptions&, const EnvOptions&, + const Slice& smallest_user_key, + const Slice& largest_user_key, + int level, bool* overlap); // Lookup the value for key. If found, store it in *val and // return OK. Else return a non-OK status. @@ -484,8 +571,9 @@ class Version { // REQUIRES: lock is not held void Get(const ReadOptions&, const LookupKey& key, PinnableSlice* value, Status* status, MergeContext* merge_context, - RangeDelAggregator* range_del_agg, bool* value_found = nullptr, - bool* key_exists = nullptr, SequenceNumber* seq = nullptr, + SequenceNumber* max_covering_tombstone_seq, + bool* value_found = nullptr, bool* key_exists = nullptr, + SequenceNumber* seq = nullptr, ReadCallback* callback = nullptr, bool* is_blob = nullptr); // Loads some stats information from files. Call without mutex held. It needs @@ -506,13 +594,13 @@ class Version { // Return a human readable string that describes this version's contents. std::string DebugString(bool hex = false, bool print_stats = false) const; - // Returns the version nuber of this version + // Returns the version number of this version uint64_t GetVersionNumber() const { return version_number_; } // REQUIRES: lock is held // On success, "tp" will contains the table properties of the file // specified in "file_meta". If the file name of "file_meta" is - // known ahread, passing it by a non-null "fname" can save a + // known ahead, passing it by a non-null "fname" can save a // file-name conversion. Status GetTableProperties(std::shared_ptr* tp, const FileMetaData* file_meta, @@ -521,14 +609,14 @@ class Version { // REQUIRES: lock is held // On success, *props will be populated with all SSTables' table properties. // The keys of `props` are the sst file name, the values of `props` are the - // tables' propertis, represented as shared_ptr. + // tables' properties, represented as std::shared_ptr. Status GetPropertiesOfAllTables(TablePropertiesCollection* props); Status GetPropertiesOfAllTables(TablePropertiesCollection* props, int level); Status GetPropertiesOfTablesInRange(const Range* range, std::size_t n, TablePropertiesCollection* props) const; // REQUIRES: lock is held - // On success, "tp" will contains the aggregated table property amoug + // On success, "tp" will contains the aggregated table property among // the table properties of all sst files in this version. Status GetAggregatedTableProperties( std::shared_ptr* tp, int level = -1); @@ -554,8 +642,13 @@ class Version { void GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta); + uint64_t GetSstFilesSize(); + + MutableCFOptions GetMutableCFOptions() { return mutable_cf_options_; } + private: Env* env_; + friend class ReactiveVersionSet; friend class VersionSet; const InternalKeyComparator* internal_comparator() const { @@ -576,7 +669,7 @@ class Version { bool IsFilterSkipped(int level, bool is_file_last_in_level = false); // The helper function of UpdateAccumulatedStats, which may fill the missing - // fields of file_mata from its associated TableProperties. + // fields of file_meta from its associated TableProperties. // Returns true if it does initialize FileMetaData. bool MaybeInitializeFileMetaData(FileMetaData* file_meta); @@ -600,12 +693,15 @@ class Version { Version* next_; // Next version in linked list Version* prev_; // Previous version in linked list int refs_; // Number of live refs to this version + const EnvOptions env_options_; + const MutableCFOptions mutable_cf_options_; // A version number that uniquely represents this version. This is // used for debugging and logging purposes only. uint64_t version_number_; - Version(ColumnFamilyData* cfd, VersionSet* vset, uint64_t version_number = 0); + Version(ColumnFamilyData* cfd, VersionSet* vset, const EnvOptions& env_opt, + MutableCFOptions mutable_cf_options, uint64_t version_number = 0); ~Version(); @@ -614,13 +710,45 @@ class Version { void operator=(const Version&); }; +struct ObsoleteFileInfo { + FileMetaData* metadata; + std::string path; + + ObsoleteFileInfo() noexcept : metadata(nullptr) {} + ObsoleteFileInfo(FileMetaData* f, const std::string& file_path) + : metadata(f), path(file_path) {} + + ObsoleteFileInfo(const ObsoleteFileInfo&) = delete; + ObsoleteFileInfo& operator=(const ObsoleteFileInfo&) = delete; + + ObsoleteFileInfo(ObsoleteFileInfo&& rhs) noexcept : + ObsoleteFileInfo() { + *this = std::move(rhs); + } + + ObsoleteFileInfo& operator=(ObsoleteFileInfo&& rhs) noexcept { + path = std::move(rhs.path); + metadata = rhs.metadata; + rhs.metadata = nullptr; + + return *this; + } + + void DeleteMetadata() { + delete metadata; + metadata = nullptr; + } +}; + +class BaseReferencedVersionBuilder; + class VersionSet { public: VersionSet(const std::string& dbname, const ImmutableDBOptions* db_options, const EnvOptions& env_options, Cache* table_cache, WriteBufferManager* write_buffer_manager, WriteController* write_controller); - ~VersionSet(); + virtual ~VersionSet(); // Apply *edit to the current version to form a new descriptor that // is both saved to persistent state and installed as the new @@ -634,9 +762,15 @@ class VersionSet { InstrumentedMutex* mu, Directory* db_directory = nullptr, bool new_descriptor_log = false, const ColumnFamilyOptions* column_family_options = nullptr) { + autovector cfds; + cfds.emplace_back(column_family_data); + autovector mutable_cf_options_list; + mutable_cf_options_list.emplace_back(&mutable_cf_options); + autovector> edit_lists; autovector edit_list; - edit_list.push_back(edit); - return LogAndApply(column_family_data, mutable_cf_options, edit_list, mu, + edit_list.emplace_back(edit); + edit_lists.emplace_back(edit_list); + return LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu, db_directory, new_descriptor_log, column_family_options); } // The batch version. If edit_list.size() > 1, caller must ensure that @@ -646,7 +780,29 @@ class VersionSet { const MutableCFOptions& mutable_cf_options, const autovector& edit_list, InstrumentedMutex* mu, Directory* db_directory = nullptr, bool new_descriptor_log = false, - const ColumnFamilyOptions* column_family_options = nullptr); + const ColumnFamilyOptions* column_family_options = nullptr) { + autovector cfds; + cfds.emplace_back(column_family_data); + autovector mutable_cf_options_list; + mutable_cf_options_list.emplace_back(&mutable_cf_options); + autovector> edit_lists; + edit_lists.emplace_back(edit_list); + return LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu, + db_directory, new_descriptor_log, column_family_options); + } + + // The across-multi-cf batch version. If edit_lists contain more than + // 1 version edits, caller must ensure that no edit in the []list is column + // family manipulation. + virtual Status LogAndApply( + const autovector& cfds, + const autovector& mutable_cf_options_list, + const autovector>& edit_lists, + InstrumentedMutex* mu, Directory* db_directory = nullptr, + bool new_descriptor_log = false, + const ColumnFamilyOptions* new_cf_options = nullptr); + + Status GetCurrentManifestPath(std::string* manifest_filename); // Recover the last saved descriptor from persistent storage. // If read_only == true, Recover() will not complain if some column families @@ -691,52 +847,87 @@ class VersionSet { uint64_t current_next_file_number() const { return next_file_number_.load(); } + uint64_t min_log_number_to_keep_2pc() const { + return min_log_number_to_keep_2pc_.load(); + } + // Allocate and return a new file number uint64_t NewFileNumber() { return next_file_number_.fetch_add(1); } + // Fetch And Add n new file number + uint64_t FetchAddFileNumber(uint64_t n) { + return next_file_number_.fetch_add(n); + } + // Return the last sequence number. uint64_t LastSequence() const { return last_sequence_.load(std::memory_order_acquire); } // Note: memory_order_acquire must be sufficient. - uint64_t LastToBeWrittenSequence() const { - return last_to_be_written_sequence_.load(std::memory_order_seq_cst); + uint64_t LastAllocatedSequence() const { + return last_allocated_sequence_.load(std::memory_order_seq_cst); + } + + // Note: memory_order_acquire must be sufficient. + uint64_t LastPublishedSequence() const { + return last_published_sequence_.load(std::memory_order_seq_cst); } // Set the last sequence number to s. void SetLastSequence(uint64_t s) { assert(s >= last_sequence_); - // Last visible seqeunce must always be less than last written seq - assert(!db_options_->concurrent_prepare || - s <= last_to_be_written_sequence_); + // Last visible sequence must always be less than last written seq + assert(!db_options_->two_write_queues || s <= last_allocated_sequence_); last_sequence_.store(s, std::memory_order_release); } // Note: memory_order_release must be sufficient - void SetLastToBeWrittenSequence(uint64_t s) { - assert(s >= last_to_be_written_sequence_); - last_to_be_written_sequence_.store(s, std::memory_order_seq_cst); + void SetLastPublishedSequence(uint64_t s) { + assert(s >= last_published_sequence_); + last_published_sequence_.store(s, std::memory_order_seq_cst); } // Note: memory_order_release must be sufficient - uint64_t FetchAddLastToBeWrittenSequence(uint64_t s) { - return last_to_be_written_sequence_.fetch_add(s, std::memory_order_seq_cst); + void SetLastAllocatedSequence(uint64_t s) { + assert(s >= last_allocated_sequence_); + last_allocated_sequence_.store(s, std::memory_order_seq_cst); + } + + // Note: memory_order_release must be sufficient + uint64_t FetchAddLastAllocatedSequence(uint64_t s) { + return last_allocated_sequence_.fetch_add(s, std::memory_order_seq_cst); } // Mark the specified file number as used. - // REQUIRED: this is only called during single-threaded recovery - void MarkFileNumberUsedDuringRecovery(uint64_t number); + // REQUIRED: this is only called during single-threaded recovery or repair. + void MarkFileNumberUsed(uint64_t number); + + // Mark the specified log number as deleted + // REQUIRED: this is only called during single-threaded recovery or repair, or + // from ::LogAndApply where the global mutex is held. + void MarkMinLogNumberToKeep2PC(uint64_t number); // Return the log file number for the log file that is currently // being compacted, or zero if there is no such log file. uint64_t prev_log_number() const { return prev_log_number_; } - // Returns the minimum log number such that all - // log numbers less than or equal to it can be deleted - uint64_t MinLogNumber() const { + // Returns the minimum log number which still has data not flushed to any SST + // file. + // In non-2PC mode, all the log numbers smaller than this number can be safely + // deleted. + uint64_t MinLogNumberWithUnflushedData() const { + return PreComputeMinLogNumberWithUnflushedData(nullptr); + } + // Returns the minimum log number which still has data not flushed to any SST + // file, except data from `cfd_to_skip`. + uint64_t PreComputeMinLogNumberWithUnflushedData( + const ColumnFamilyData* cfd_to_skip) const { uint64_t min_log_num = std::numeric_limits::max(); for (auto cfd : *column_family_set_) { + if (cfd == cfd_to_skip) { + continue; + } // It's safe to ignore dropped column families here: // cfd->IsDropped() becomes true after the drop is persisted in MANIFEST. if (min_log_num > cfd->GetLogNumber() && !cfd->IsDropped()) { @@ -748,8 +939,9 @@ class VersionSet { // Create an iterator that reads over the compaction inputs for "*c". // The caller should delete the iterator when no longer needed. - InternalIterator* MakeInputIterator(const Compaction* c, - RangeDelAggregator* range_del_agg); + InternalIterator* MakeInputIterator( + const Compaction* c, RangeDelAggregator* range_del_agg, + const EnvOptions& env_options_compactions); // Add all files listed in any live version to *live. void AddLiveFiles(std::vector* live_list); @@ -775,26 +967,33 @@ class VersionSet { // This function doesn't support leveldb SST filenames void GetLiveFilesMetaData(std::vector *metadata); - void GetObsoleteFiles(std::vector* files, + void GetObsoleteFiles(std::vector* files, std::vector* manifest_filenames, uint64_t min_pending_output); ColumnFamilySet* GetColumnFamilySet() { return column_family_set_.get(); } const EnvOptions& env_options() { return env_options_; } + void ChangeEnvOptions(const MutableDBOptions& new_options) { + env_options_.writable_file_max_buffer_size = + new_options.writable_file_max_buffer_size; + } + + const ImmutableDBOptions* db_options() const { return db_options_; } static uint64_t GetNumLiveVersions(Version* dummy_versions); static uint64_t GetTotalSstFilesSize(Version* dummy_versions); - private: + protected: struct ManifestWriter; friend class Version; friend class DBImpl; + friend class DBImplReadOnly; struct LogReporter : public log::Reader::Reporter { Status* status; - virtual void Corruption(size_t bytes, const Status& s) override { + virtual void Corruption(size_t /*bytes*/, const Status& s) override { if (this->status->ok()) *this->status = s; } }; @@ -814,23 +1013,57 @@ class VersionSet { ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options, VersionEdit* edit); + // REQUIRES db mutex + Status ApplyOneVersionEditToBuilder( + VersionEdit& edit, + const std::unordered_map& name_to_opts, + std::unordered_map& column_families_not_found, + std::unordered_map< + uint32_t, std::unique_ptr>& builders, + bool* have_log_number, uint64_t* log_number, bool* have_prev_log_number, + uint64_t* previous_log_number, bool* have_next_file, uint64_t* next_file, + bool* have_last_sequence, SequenceNumber* last_sequence, + uint64_t* min_log_number_to_keep, uint32_t* max_column_family); + + Status ExtractInfoFromVersionEdit( + ColumnFamilyData* cfd, const VersionEdit& edit, bool* have_log_number, + uint64_t* log_number, bool* have_prev_log_number, + uint64_t* previous_log_number, bool* have_next_file, uint64_t* next_file, + bool* have_last_sequence, SequenceNumber* last_sequence, + uint64_t* min_log_number_to_keep, uint32_t* max_column_family); + std::unique_ptr column_family_set_; Env* const env_; const std::string dbname_; const ImmutableDBOptions* const db_options_; std::atomic next_file_number_; + // Any log number equal or lower than this should be ignored during recovery, + // and is qualified for being deleted in 2PC mode. In non-2PC mode, this + // number is ignored. + std::atomic min_log_number_to_keep_2pc_ = {0}; uint64_t manifest_file_number_; uint64_t options_file_number_; uint64_t pending_manifest_file_number_; - // The last seq visible to reads + // The last seq visible to reads. It normally indicates the last sequence in + // the memtable but when using two write queues it could also indicate the + // last sequence in the WAL visible to reads. std::atomic last_sequence_; - // The last seq with which a writer has written/will write. - std::atomic last_to_be_written_sequence_; + // The last seq that is already allocated. It is applicable only when we have + // two write queues. In that case seq might or might not have appreated in + // memtable but it is expected to appear in the WAL. + // We have last_sequence <= last_allocated_sequence_ + std::atomic last_allocated_sequence_; + // The last allocated sequence that is also published to the readers. This is + // applicable only when last_seq_same_as_publish_seq_ is not set. Otherwise + // last_sequence_ also indicates the last published seq. + // We have last_sequence <= last_published_sequence_ <= + // last_allocated_sequence_ + std::atomic last_published_sequence_; uint64_t prev_log_number_; // 0 or backing store for memtable being compacted // Opened lazily - unique_ptr descriptor_log_; + std::unique_ptr descriptor_log_; // generates a increasing version number for every new version uint64_t current_version_number_; @@ -841,23 +1074,83 @@ class VersionSet { // Current size of manifest file uint64_t manifest_file_size_; - std::vector obsolete_files_; + std::vector obsolete_files_; std::vector obsolete_manifests_; // env options for all reads and writes except compactions - const EnvOptions& env_options_; - - // env options used for compactions. This is a copy of - // env_options_ but with readaheads set to readahead_compactions_. - const EnvOptions env_options_compactions_; + EnvOptions env_options_; + private: // No copying allowed VersionSet(const VersionSet&); void operator=(const VersionSet&); + // REQUIRES db mutex at beginning. may release and re-acquire db mutex + Status ProcessManifestWrites(std::deque& writers, + InstrumentedMutex* mu, Directory* db_directory, + bool new_descriptor_log, + const ColumnFamilyOptions* new_cf_options); + void LogAndApplyCFHelper(VersionEdit* edit); - void LogAndApplyHelper(ColumnFamilyData* cfd, VersionBuilder* b, Version* v, + void LogAndApplyHelper(ColumnFamilyData* cfd, VersionBuilder* b, VersionEdit* edit, InstrumentedMutex* mu); }; +class ReactiveVersionSet : public VersionSet { + public: + ReactiveVersionSet(const std::string& dbname, + const ImmutableDBOptions* _db_options, + const EnvOptions& _env_options, Cache* table_cache, + WriteBufferManager* write_buffer_manager, + WriteController* write_controller); + + ~ReactiveVersionSet() override; + + Status ReadAndApply( + InstrumentedMutex* mu, + std::unique_ptr* manifest_reader, + std::unordered_set* cfds_changed); + + Status Recover(const std::vector& column_families, + std::unique_ptr* manifest_reader, + std::unique_ptr* manifest_reporter, + std::unique_ptr* manifest_reader_status); + + protected: + using VersionSet::ApplyOneVersionEditToBuilder; + + // REQUIRES db mutex + Status ApplyOneVersionEditToBuilder( + VersionEdit& edit, bool* have_log_number, uint64_t* log_number, + bool* have_prev_log_number, uint64_t* previous_log_number, + bool* have_next_file, uint64_t* next_file, bool* have_last_sequence, + SequenceNumber* last_sequence, uint64_t* min_log_number_to_keep, + uint32_t* max_column_family); + + Status MaybeSwitchManifest( + log::Reader::Reporter* reporter, + std::unique_ptr* manifest_reader); + + private: + std::unordered_map> + active_version_builders_; + + using VersionSet::LogAndApply; + using VersionSet::Recover; + + Status LogAndApply( + const autovector& /*cfds*/, + const autovector& /*mutable_cf_options_list*/, + const autovector>& /*edit_lists*/, + InstrumentedMutex* /*mu*/, Directory* /*db_directory*/, + bool /*new_descriptor_log*/, + const ColumnFamilyOptions* /*new_cf_option*/) override { + return Status::NotSupported("not supported in reactive mode"); + } + + // No copy allowed + ReactiveVersionSet(const ReactiveVersionSet&); + ReactiveVersionSet& operator=(const ReactiveVersionSet&); +}; + } // namespace rocksdb diff --git a/thirdparty/rocksdb/db/version_set_test.cc b/thirdparty/rocksdb/db/version_set_test.cc index 625d459226..43924a3add 100644 --- a/thirdparty/rocksdb/db/version_set_test.cc +++ b/thirdparty/rocksdb/db/version_set_test.cc @@ -8,7 +8,10 @@ // found in the LICENSE file. See the AUTHORS file for names of contributors. #include "db/version_set.h" +#include "db/log_writer.h" +#include "table/mock_table.h" #include "util/logging.h" +#include "util/string_util.h" #include "util/testharness.h" #include "util/testutil.h" @@ -22,7 +25,7 @@ class GenerateLevelFilesBriefTest : public testing::Test { GenerateLevelFilesBriefTest() { } - ~GenerateLevelFilesBriefTest() { + ~GenerateLevelFilesBriefTest() override { for (size_t i = 0; i < files_.size(); i++) { delete files_[i]; } @@ -76,7 +79,7 @@ class CountingLogger : public Logger { public: CountingLogger() : log_count(0) {} using Logger::Logv; - virtual void Logv(const char* format, va_list ap) override { log_count++; } + void Logv(const char* /*format*/, va_list /*ap*/) override { log_count++; } int log_count; }; @@ -112,7 +115,7 @@ class VersionStorageInfoTest : public testing::Test { mutable_cf_options_(options_), vstorage_(&icmp_, ucmp_, 6, kCompactionStyleLevel, nullptr, false) {} - ~VersionStorageInfoTest() { + ~VersionStorageInfoTest() override { for (int i = 0; i < vstorage_.num_levels(); i++) { for (auto* f : vstorage_.LevelFiles(i)) { if (--f->refs == 0) { @@ -135,6 +138,35 @@ class VersionStorageInfoTest : public testing::Test { f->num_deletions = 0; vstorage_.AddFile(level, f); } + + void Add(int level, uint32_t file_number, const InternalKey& smallest, + const InternalKey& largest, uint64_t file_size = 0) { + assert(level < vstorage_.num_levels()); + FileMetaData* f = new FileMetaData; + f->fd = FileDescriptor(file_number, 0, file_size); + f->smallest = smallest; + f->largest = largest; + f->compensated_file_size = file_size; + f->refs = 0; + f->num_entries = 0; + f->num_deletions = 0; + vstorage_.AddFile(level, f); + } + + std::string GetOverlappingFiles(int level, const InternalKey& begin, + const InternalKey& end) { + std::vector inputs; + vstorage_.GetOverlappingInputs(level, &begin, &end, &inputs); + + std::string result; + for (size_t i = 0; i < inputs.size(); ++i) { + if (i > 0) { + result += ","; + } + AppendNumberTo(&result, inputs[i]->fd.GetNumber()); + } + return result; + } }; TEST_F(VersionStorageInfoTest, MaxBytesForLevelStatic) { @@ -234,6 +266,93 @@ TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicLargeLevel) { ASSERT_EQ(0, logger_->log_count); } +TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_1) { + ioptions_.level_compaction_dynamic_level_bytes = true; + mutable_cf_options_.max_bytes_for_level_base = 40000; + mutable_cf_options_.max_bytes_for_level_multiplier = 5; + mutable_cf_options_.level0_file_num_compaction_trigger = 2; + + Add(0, 1U, "1", "2", 10000U); + Add(0, 2U, "1", "2", 10000U); + Add(0, 3U, "1", "2", 10000U); + + Add(5, 4U, "1", "2", 1286250U); + Add(4, 5U, "1", "2", 200000U); + Add(3, 6U, "1", "2", 40000U); + Add(2, 7U, "1", "2", 8000U); + + vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_); + ASSERT_EQ(0, logger_->log_count); + ASSERT_EQ(2, vstorage_.base_level()); + // level multiplier should be 3.5 + ASSERT_EQ(vstorage_.level_multiplier(), 5.0); + // Level size should be around 30,000, 105,000, 367,500 + ASSERT_EQ(40000U, vstorage_.MaxBytesForLevel(2)); + ASSERT_EQ(51450U, vstorage_.MaxBytesForLevel(3)); + ASSERT_EQ(257250U, vstorage_.MaxBytesForLevel(4)); +} + +TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_2) { + ioptions_.level_compaction_dynamic_level_bytes = true; + mutable_cf_options_.max_bytes_for_level_base = 10000; + mutable_cf_options_.max_bytes_for_level_multiplier = 5; + mutable_cf_options_.level0_file_num_compaction_trigger = 2; + + Add(0, 11U, "1", "2", 10000U); + Add(0, 12U, "1", "2", 10000U); + Add(0, 13U, "1", "2", 10000U); + + Add(5, 4U, "1", "2", 1286250U); + Add(4, 5U, "1", "2", 200000U); + Add(3, 6U, "1", "2", 40000U); + Add(2, 7U, "1", "2", 8000U); + + vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_); + ASSERT_EQ(0, logger_->log_count); + ASSERT_EQ(2, vstorage_.base_level()); + // level multiplier should be 3.5 + ASSERT_LT(vstorage_.level_multiplier(), 3.6); + ASSERT_GT(vstorage_.level_multiplier(), 3.4); + // Level size should be around 30,000, 105,000, 367,500 + ASSERT_EQ(30000U, vstorage_.MaxBytesForLevel(2)); + ASSERT_LT(vstorage_.MaxBytesForLevel(3), 110000U); + ASSERT_GT(vstorage_.MaxBytesForLevel(3), 100000U); + ASSERT_LT(vstorage_.MaxBytesForLevel(4), 370000U); + ASSERT_GT(vstorage_.MaxBytesForLevel(4), 360000U); +} + +TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_3) { + ioptions_.level_compaction_dynamic_level_bytes = true; + mutable_cf_options_.max_bytes_for_level_base = 10000; + mutable_cf_options_.max_bytes_for_level_multiplier = 5; + mutable_cf_options_.level0_file_num_compaction_trigger = 2; + + Add(0, 11U, "1", "2", 5000U); + Add(0, 12U, "1", "2", 5000U); + Add(0, 13U, "1", "2", 5000U); + Add(0, 14U, "1", "2", 5000U); + Add(0, 15U, "1", "2", 5000U); + Add(0, 16U, "1", "2", 5000U); + + Add(5, 4U, "1", "2", 1286250U); + Add(4, 5U, "1", "2", 200000U); + Add(3, 6U, "1", "2", 40000U); + Add(2, 7U, "1", "2", 8000U); + + vstorage_.CalculateBaseBytes(ioptions_, mutable_cf_options_); + ASSERT_EQ(0, logger_->log_count); + ASSERT_EQ(2, vstorage_.base_level()); + // level multiplier should be 3.5 + ASSERT_LT(vstorage_.level_multiplier(), 3.6); + ASSERT_GT(vstorage_.level_multiplier(), 3.4); + // Level size should be around 30,000, 105,000, 367,500 + ASSERT_EQ(30000U, vstorage_.MaxBytesForLevel(2)); + ASSERT_LT(vstorage_.MaxBytesForLevel(3), 110000U); + ASSERT_GT(vstorage_.MaxBytesForLevel(3), 100000U); + ASSERT_LT(vstorage_.MaxBytesForLevel(4), 370000U); + ASSERT_GT(vstorage_.MaxBytesForLevel(4), 360000U); +} + TEST_F(VersionStorageInfoTest, EstimateLiveDataSize) { // Test whether the overlaps are detected as expected Add(1, 1U, "4", "7", 1U); // Perfect overlap with last level @@ -257,6 +376,40 @@ TEST_F(VersionStorageInfoTest, EstimateLiveDataSize2) { ASSERT_EQ(4U, vstorage_.EstimateLiveDataSize()); } +TEST_F(VersionStorageInfoTest, GetOverlappingInputs) { + // Two files that overlap at the range deletion tombstone sentinel. + Add(1, 1U, {"a", 0, kTypeValue}, {"b", kMaxSequenceNumber, kTypeRangeDeletion}, 1); + Add(1, 2U, {"b", 0, kTypeValue}, {"c", 0, kTypeValue}, 1); + // Two files that overlap at the same user key. + Add(1, 3U, {"d", 0, kTypeValue}, {"e", kMaxSequenceNumber, kTypeValue}, 1); + Add(1, 4U, {"e", 0, kTypeValue}, {"f", 0, kTypeValue}, 1); + // Two files that do not overlap. + Add(1, 5U, {"g", 0, kTypeValue}, {"h", 0, kTypeValue}, 1); + Add(1, 6U, {"i", 0, kTypeValue}, {"j", 0, kTypeValue}, 1); + vstorage_.UpdateNumNonEmptyLevels(); + vstorage_.GenerateLevelFilesBrief(); + + ASSERT_EQ("1,2", GetOverlappingFiles( + 1, {"a", 0, kTypeValue}, {"b", 0, kTypeValue})); + ASSERT_EQ("1", GetOverlappingFiles( + 1, {"a", 0, kTypeValue}, {"b", kMaxSequenceNumber, kTypeRangeDeletion})); + ASSERT_EQ("2", GetOverlappingFiles( + 1, {"b", kMaxSequenceNumber, kTypeValue}, {"c", 0, kTypeValue})); + ASSERT_EQ("3,4", GetOverlappingFiles( + 1, {"d", 0, kTypeValue}, {"e", 0, kTypeValue})); + ASSERT_EQ("3", GetOverlappingFiles( + 1, {"d", 0, kTypeValue}, {"e", kMaxSequenceNumber, kTypeRangeDeletion})); + ASSERT_EQ("3,4", GetOverlappingFiles( + 1, {"e", kMaxSequenceNumber, kTypeValue}, {"f", 0, kTypeValue})); + ASSERT_EQ("3,4", GetOverlappingFiles( + 1, {"e", 0, kTypeValue}, {"f", 0, kTypeValue})); + ASSERT_EQ("5", GetOverlappingFiles( + 1, {"g", 0, kTypeValue}, {"h", 0, kTypeValue})); + ASSERT_EQ("6", GetOverlappingFiles( + 1, {"i", 0, kTypeValue}, {"j", 0, kTypeValue})); +} + + class FindLevelFileTest : public testing::Test { public: LevelFilesBrief file_level_; @@ -265,8 +418,7 @@ class FindLevelFileTest : public testing::Test { FindLevelFileTest() : disjoint_sorted_files_(true) { } - ~FindLevelFileTest() { - } + ~FindLevelFileTest() override {} void LevelFileInit(size_t num = 0) { char* mem = arena_.AllocateAligned(num * sizeof(FdWithKeyRange)); @@ -450,6 +602,493 @@ TEST_F(FindLevelFileTest, LevelOverlappingFiles) { ASSERT_TRUE(Overlaps("600", "700")); } +class VersionSetTestBase { + public: + const static std::string kColumnFamilyName1; + const static std::string kColumnFamilyName2; + const static std::string kColumnFamilyName3; + + VersionSetTestBase() + : env_(Env::Default()), + dbname_(test::PerThreadDBPath("version_set_test")), + db_options_(), + mutable_cf_options_(cf_options_), + table_cache_(NewLRUCache(50000, 16)), + write_buffer_manager_(db_options_.db_write_buffer_size), + versions_(new VersionSet(dbname_, &db_options_, env_options_, + table_cache_.get(), &write_buffer_manager_, + &write_controller_)), + shutting_down_(false), + mock_table_factory_(std::make_shared()) { + EXPECT_OK(env_->CreateDirIfMissing(dbname_)); + db_options_.db_paths.emplace_back(dbname_, + std::numeric_limits::max()); + } + + void PrepareManifest(std::vector* column_families, + SequenceNumber* last_seqno, + std::unique_ptr* log_writer) { + assert(column_families != nullptr); + assert(last_seqno != nullptr); + assert(log_writer != nullptr); + VersionEdit new_db; + new_db.SetLogNumber(0); + new_db.SetNextFile(2); + new_db.SetLastSequence(0); + + const std::vector cf_names = { + kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2, + kColumnFamilyName3}; + const int kInitialNumOfCfs = static_cast(cf_names.size()); + autovector new_cfs; + uint64_t last_seq = 1; + uint32_t cf_id = 1; + for (int i = 1; i != kInitialNumOfCfs; ++i) { + VersionEdit new_cf; + new_cf.AddColumnFamily(cf_names[i]); + new_cf.SetColumnFamily(cf_id++); + new_cf.SetLogNumber(0); + new_cf.SetNextFile(2); + new_cf.SetLastSequence(last_seq++); + new_cfs.emplace_back(new_cf); + } + *last_seqno = last_seq; + + const std::string manifest = DescriptorFileName(dbname_, 1); + std::unique_ptr file; + Status s = env_->NewWritableFile( + manifest, &file, env_->OptimizeForManifestWrite(env_options_)); + ASSERT_OK(s); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(file), manifest, env_options_)); + { + log_writer->reset(new log::Writer(std::move(file_writer), 0, false)); + std::string record; + new_db.EncodeTo(&record); + s = (*log_writer)->AddRecord(record); + for (const auto& e : new_cfs) { + record.clear(); + e.EncodeTo(&record); + s = (*log_writer)->AddRecord(record); + ASSERT_OK(s); + } + } + ASSERT_OK(s); + + cf_options_.table_factory = mock_table_factory_; + for (const auto& cf_name : cf_names) { + column_families->emplace_back(cf_name, cf_options_); + } + } + + // Create DB with 3 column families. + void NewDB() { + std::vector column_families; + SequenceNumber last_seqno; + std::unique_ptr log_writer; + + PrepareManifest(&column_families, &last_seqno, &log_writer); + log_writer.reset(); + // Make "CURRENT" file point to the new manifest file. + Status s = SetCurrentFile(env_, dbname_, 1, nullptr); + ASSERT_OK(s); + + EXPECT_OK(versions_->Recover(column_families, false)); + EXPECT_EQ(column_families.size(), + versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); + } + + Env* env_; + const std::string dbname_; + EnvOptions env_options_; + ImmutableDBOptions db_options_; + ColumnFamilyOptions cf_options_; + MutableCFOptions mutable_cf_options_; + std::shared_ptr table_cache_; + WriteController write_controller_; + WriteBufferManager write_buffer_manager_; + std::shared_ptr versions_; + InstrumentedMutex mutex_; + std::atomic shutting_down_; + std::shared_ptr mock_table_factory_; +}; + +const std::string VersionSetTestBase::kColumnFamilyName1 = "alice"; +const std::string VersionSetTestBase::kColumnFamilyName2 = "bob"; +const std::string VersionSetTestBase::kColumnFamilyName3 = "charles"; + +class VersionSetTest : public VersionSetTestBase, public testing::Test { + public: + VersionSetTest() : VersionSetTestBase() {} +}; + +TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) { + NewDB(); + const int kGroupSize = 5; + autovector edits; + for (int i = 0; i != kGroupSize; ++i) { + edits.emplace_back(VersionEdit()); + } + autovector cfds; + autovector all_mutable_cf_options; + autovector> edit_lists; + for (int i = 0; i != kGroupSize; ++i) { + cfds.emplace_back(versions_->GetColumnFamilySet()->GetDefault()); + all_mutable_cf_options.emplace_back(&mutable_cf_options_); + autovector edit_list; + edit_list.emplace_back(&edits[i]); + edit_lists.emplace_back(edit_list); + } + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + int count = 0; + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::ProcessManifestWrites:SameColumnFamily", [&](void* arg) { + uint32_t* cf_id = reinterpret_cast(arg); + EXPECT_EQ(0, *cf_id); + ++count; + }); + SyncPoint::GetInstance()->EnableProcessing(); + mutex_.Lock(); + Status s = + versions_->LogAndApply(cfds, all_mutable_cf_options, edit_lists, &mutex_); + mutex_.Unlock(); + EXPECT_OK(s); + EXPECT_EQ(kGroupSize - 1, count); +} + +TEST_F(VersionSetTest, HandleValidAtomicGroup) { + std::vector column_families; + SequenceNumber last_seqno; + std::unique_ptr log_writer; + PrepareManifest(&column_families, &last_seqno, &log_writer); + + // Append multiple version edits that form an atomic group + const int kAtomicGroupSize = 3; + std::vector edits(kAtomicGroupSize); + int remaining = kAtomicGroupSize; + for (size_t i = 0; i != edits.size(); ++i) { + edits[i].SetLogNumber(0); + edits[i].SetNextFile(2); + edits[i].MarkAtomicGroup(--remaining); + edits[i].SetLastSequence(last_seqno++); + } + Status s; + for (const auto& edit : edits) { + std::string record; + edit.EncodeTo(&record); + s = log_writer->AddRecord(record); + ASSERT_OK(s); + } + log_writer.reset(); + + s = SetCurrentFile(env_, dbname_, 1, nullptr); + ASSERT_OK(s); + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + + bool first_in_atomic_group = false; + bool last_in_atomic_group = false; + + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::Recover:FirstInAtomicGroup", [&](void* arg) { + VersionEdit* e = reinterpret_cast(arg); + EXPECT_EQ(edits.front().DebugString(), + e->DebugString()); // compare based on value + first_in_atomic_group = true; + }); + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::Recover:LastInAtomicGroup", [&](void* arg) { + VersionEdit* e = reinterpret_cast(arg); + EXPECT_EQ(edits.back().DebugString(), + e->DebugString()); // compare based on value + EXPECT_TRUE(first_in_atomic_group); + last_in_atomic_group = true; + }); + SyncPoint::GetInstance()->EnableProcessing(); + + EXPECT_OK(versions_->Recover(column_families, false)); + EXPECT_EQ(column_families.size(), + versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); + EXPECT_TRUE(first_in_atomic_group); + EXPECT_TRUE(last_in_atomic_group); +} + +TEST_F(VersionSetTest, HandleIncompleteTrailingAtomicGroup) { + std::vector column_families; + SequenceNumber last_seqno; + std::unique_ptr log_writer; + PrepareManifest(&column_families, &last_seqno, &log_writer); + + // Append multiple version edits that form an atomic group + const int kAtomicGroupSize = 4; + const int kNumberOfPersistedVersionEdits = kAtomicGroupSize - 1; + std::vector edits(kNumberOfPersistedVersionEdits); + int remaining = kAtomicGroupSize; + for (size_t i = 0; i != edits.size(); ++i) { + edits[i].SetLogNumber(0); + edits[i].SetNextFile(2); + edits[i].MarkAtomicGroup(--remaining); + edits[i].SetLastSequence(last_seqno++); + } + Status s; + for (const auto& edit : edits) { + std::string record; + edit.EncodeTo(&record); + s = log_writer->AddRecord(record); + ASSERT_OK(s); + } + log_writer.reset(); + + s = SetCurrentFile(env_, dbname_, 1, nullptr); + ASSERT_OK(s); + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + + bool first_in_atomic_group = false; + bool last_in_atomic_group = false; + size_t num = 0; + + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::Recover:FirstInAtomicGroup", [&](void* arg) { + VersionEdit* e = reinterpret_cast(arg); + EXPECT_EQ(edits.front().DebugString(), + e->DebugString()); // compare based on value + first_in_atomic_group = true; + }); + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::Recover:LastInAtomicGroup", + [&](void* /* arg */) { last_in_atomic_group = true; }); + SyncPoint::GetInstance()->SetCallBack("VersionSet::Recover:AtomicGroup", + [&](void* /* arg */) { ++num; }); + SyncPoint::GetInstance()->EnableProcessing(); + + EXPECT_OK(versions_->Recover(column_families, false)); + EXPECT_EQ(column_families.size(), + versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); + EXPECT_TRUE(first_in_atomic_group); + EXPECT_FALSE(last_in_atomic_group); + EXPECT_EQ(kNumberOfPersistedVersionEdits, num); +} + +TEST_F(VersionSetTest, HandleCorruptedAtomicGroup) { + std::vector column_families; + SequenceNumber last_seqno; + std::unique_ptr log_writer; + PrepareManifest(&column_families, &last_seqno, &log_writer); + + // Append multiple version edits that form an atomic group + const int kAtomicGroupSize = 4; + std::vector edits(kAtomicGroupSize); + int remaining = kAtomicGroupSize; + for (size_t i = 0; i != edits.size(); ++i) { + edits[i].SetLogNumber(0); + edits[i].SetNextFile(2); + if (i != (kAtomicGroupSize / 2)) { + edits[i].MarkAtomicGroup(--remaining); + } + edits[i].SetLastSequence(last_seqno++); + } + Status s; + for (const auto& edit : edits) { + std::string record; + edit.EncodeTo(&record); + s = log_writer->AddRecord(record); + ASSERT_OK(s); + } + log_writer.reset(); + + s = SetCurrentFile(env_, dbname_, 1, nullptr); + ASSERT_OK(s); + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + + bool mixed = false; + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::Recover:AtomicGroupMixedWithNormalEdits", [&](void* arg) { + VersionEdit* e = reinterpret_cast(arg); + EXPECT_EQ(edits[kAtomicGroupSize / 2].DebugString(), e->DebugString()); + mixed = true; + }); + SyncPoint::GetInstance()->EnableProcessing(); + EXPECT_NOK(versions_->Recover(column_families, false)); + EXPECT_EQ(column_families.size(), + versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); + EXPECT_TRUE(mixed); +} + +TEST_F(VersionSetTest, HandleIncorrectAtomicGroupSize) { + std::vector column_families; + SequenceNumber last_seqno; + std::unique_ptr log_writer; + PrepareManifest(&column_families, &last_seqno, &log_writer); + + // Append multiple version edits that form an atomic group + const int kAtomicGroupSize = 4; + std::vector edits(kAtomicGroupSize); + int remaining = kAtomicGroupSize; + for (size_t i = 0; i != edits.size(); ++i) { + edits[i].SetLogNumber(0); + edits[i].SetNextFile(2); + if (i != 1) { + edits[i].MarkAtomicGroup(--remaining); + } else { + edits[i].MarkAtomicGroup(remaining--); + } + edits[i].SetLastSequence(last_seqno++); + } + Status s; + for (const auto& edit : edits) { + std::string record; + edit.EncodeTo(&record); + s = log_writer->AddRecord(record); + ASSERT_OK(s); + } + log_writer.reset(); + + s = SetCurrentFile(env_, dbname_, 1, nullptr); + ASSERT_OK(s); + + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + + bool incorrect_group_size = false; + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::Recover:IncorrectAtomicGroupSize", [&](void* arg) { + VersionEdit* e = reinterpret_cast(arg); + EXPECT_EQ(edits[1].DebugString(), e->DebugString()); + incorrect_group_size = true; + }); + SyncPoint::GetInstance()->EnableProcessing(); + EXPECT_NOK(versions_->Recover(column_families, false)); + EXPECT_EQ(column_families.size(), + versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); + EXPECT_TRUE(incorrect_group_size); +} + +class VersionSetTestDropOneCF : public VersionSetTestBase, + public testing::TestWithParam { + public: + VersionSetTestDropOneCF() : VersionSetTestBase() {} +}; + +// This test simulates the following execution sequence +// Time thread1 bg_flush_thr +// | Prepare version edits (e1,e2,e3) for atomic +// | flush cf1, cf2, cf3 +// | Enqueue e to drop cfi +// | to manifest_writers_ +// | Enqueue (e1,e2,e3) to manifest_writers_ +// | +// | Apply e, +// | cfi.IsDropped() is true +// | Apply (e1,e2,e3), +// | since cfi.IsDropped() == true, we need to +// | drop ei and write the rest to MANIFEST. +// V +// +// Repeat the test for i = 1, 2, 3 to simulate dropping the first, middle and +// last column family in an atomic group. +TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) { + std::vector column_families; + SequenceNumber last_seqno; + std::unique_ptr log_writer; + PrepareManifest(&column_families, &last_seqno, &log_writer); + Status s = SetCurrentFile(env_, dbname_, 1, nullptr); + ASSERT_OK(s); + + EXPECT_OK(versions_->Recover(column_families, false /* read_only */)); + EXPECT_EQ(column_families.size(), + versions_->GetColumnFamilySet()->NumberOfColumnFamilies()); + + const int kAtomicGroupSize = 3; + const std::vector non_default_cf_names = { + kColumnFamilyName1, kColumnFamilyName2, kColumnFamilyName3}; + + // Drop one column family + VersionEdit drop_cf_edit; + drop_cf_edit.DropColumnFamily(); + const std::string cf_to_drop_name(GetParam()); + auto cfd_to_drop = + versions_->GetColumnFamilySet()->GetColumnFamily(cf_to_drop_name); + ASSERT_NE(nullptr, cfd_to_drop); + // Increase its refcount because cfd_to_drop is used later, and we need to + // prevent it from being deleted. + cfd_to_drop->Ref(); + drop_cf_edit.SetColumnFamily(cfd_to_drop->GetID()); + mutex_.Lock(); + s = versions_->LogAndApply(cfd_to_drop, + *cfd_to_drop->GetLatestMutableCFOptions(), + &drop_cf_edit, &mutex_); + mutex_.Unlock(); + ASSERT_OK(s); + + std::vector edits(kAtomicGroupSize); + uint32_t remaining = kAtomicGroupSize; + size_t i = 0; + autovector cfds; + autovector mutable_cf_options_list; + autovector> edit_lists; + for (const auto& cf_name : non_default_cf_names) { + auto cfd = (cf_name != cf_to_drop_name) + ? versions_->GetColumnFamilySet()->GetColumnFamily(cf_name) + : cfd_to_drop; + ASSERT_NE(nullptr, cfd); + cfds.push_back(cfd); + mutable_cf_options_list.emplace_back(cfd->GetLatestMutableCFOptions()); + edits[i].SetColumnFamily(cfd->GetID()); + edits[i].SetLogNumber(0); + edits[i].SetNextFile(2); + edits[i].MarkAtomicGroup(--remaining); + edits[i].SetLastSequence(last_seqno++); + autovector tmp_edits; + tmp_edits.push_back(&edits[i]); + edit_lists.emplace_back(tmp_edits); + ++i; + } + int called = 0; + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + SyncPoint::GetInstance()->SetCallBack( + "VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", [&](void* arg) { + std::vector* tmp_edits = + reinterpret_cast*>(arg); + EXPECT_EQ(kAtomicGroupSize - 1, tmp_edits->size()); + for (const auto e : *tmp_edits) { + bool found = false; + for (const auto& e2 : edits) { + if (&e2 == e) { + found = true; + break; + } + } + ASSERT_TRUE(found); + } + ++called; + }); + SyncPoint::GetInstance()->EnableProcessing(); + mutex_.Lock(); + s = versions_->LogAndApply(cfds, mutable_cf_options_list, edit_lists, + &mutex_); + mutex_.Unlock(); + ASSERT_OK(s); + ASSERT_EQ(1, called); + if (cfd_to_drop->Unref()) { + delete cfd_to_drop; + cfd_to_drop = nullptr; + } +} + +INSTANTIATE_TEST_CASE_P( + AtomicGroup, VersionSetTestDropOneCF, + testing::Values(VersionSetTestBase::kColumnFamilyName1, + VersionSetTestBase::kColumnFamilyName2, + VersionSetTestBase::kColumnFamilyName3)); + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/db/wal_manager.cc b/thirdparty/rocksdb/db/wal_manager.cc index 4a9ecbfdd8..62511819e4 100644 --- a/thirdparty/rocksdb/db/wal_manager.cc +++ b/thirdparty/rocksdb/db/wal_manager.cc @@ -29,6 +29,7 @@ #include "util/cast_util.h" #include "util/coding.h" #include "util/file_reader_writer.h" +#include "util/file_util.h" #include "util/filename.h" #include "util/logging.h" #include "util/mutexlock.h" @@ -39,6 +40,15 @@ namespace rocksdb { #ifndef ROCKSDB_LITE +Status WalManager::DeleteFile(const std::string& fname, uint64_t number) { + auto s = env_->DeleteFile(db_options_.wal_dir + "/" + fname); + if (s.ok()) { + MutexLock l(&read_first_record_cache_mutex_); + read_first_record_cache_.erase(number); + } + return s; +} + Status WalManager::GetSortedWalFiles(VectorLogPtr& files) { // First get sorted files in db dir, then get sorted files from archived // dir, to avoid a race condition where a log file is moved to archived @@ -115,7 +125,7 @@ Status WalManager::GetUpdatesSince( } iter->reset(new TransactionLogIteratorImpl( db_options_.wal_dir, &db_options_, read_options, env_options_, seq, - std::move(wal_files), version_set)); + std::move(wal_files), version_set, seq_per_batch_)); return (*iter)->status(); } @@ -181,7 +191,7 @@ void WalManager::PurgeObsoleteWALFiles() { continue; } if (now_seconds - file_m_time > db_options_.wal_ttl_seconds) { - s = env_->DeleteFile(file_path); + s = DeleteDBFile(&db_options_, file_path, archival_dir, false); if (!s.ok()) { ROCKS_LOG_WARN(db_options_.info_log, "Can't delete file: %s: %s", file_path.c_str(), s.ToString().c_str()); @@ -207,7 +217,7 @@ void WalManager::PurgeObsoleteWALFiles() { log_file_size = std::max(log_file_size, file_size); ++log_files_num; } else { - s = env_->DeleteFile(file_path); + s = DeleteDBFile(&db_options_, file_path, archival_dir, false); if (!s.ok()) { ROCKS_LOG_WARN(db_options_.info_log, "Unable to delete file: %s: %s", file_path.c_str(), @@ -228,7 +238,7 @@ void WalManager::PurgeObsoleteWALFiles() { } size_t const files_keep_num = - db_options_.wal_size_limit_mb * 1024 * 1024 / log_file_size; + static_cast(db_options_.wal_size_limit_mb * 1024 * 1024 / log_file_size); if (log_files_num <= files_keep_num) { return; } @@ -246,7 +256,8 @@ void WalManager::PurgeObsoleteWALFiles() { for (size_t i = 0; i < files_del_num; ++i) { std::string const file_path = archived_logs[i]->PathName(); - s = env_->DeleteFile(db_options_.wal_dir + "/" + file_path); + s = DeleteDBFile(&db_options_, db_options_.wal_dir + "/" + file_path, + db_options_.wal_dir, false); if (!s.ok()) { ROCKS_LOG_WARN(db_options_.info_log, "Unable to delete file: %s: %s", file_path.c_str(), s.ToString().c_str()); @@ -343,7 +354,7 @@ Status WalManager::RetainProbableWalFiles(VectorLogPtr& all_logs, // Binary Search. avoid opening all files. while (end >= start) { int64_t mid = start + (end - start) / 2; // Avoid overflow. - SequenceNumber current_seq_num = all_logs.at(mid)->StartSequence(); + SequenceNumber current_seq_num = all_logs.at(static_cast(mid))->StartSequence(); if (current_seq_num == target) { end = mid; break; @@ -354,7 +365,7 @@ Status WalManager::RetainProbableWalFiles(VectorLogPtr& all_logs, } } // end could be -ve. - size_t start_index = std::max(static_cast(0), end); + size_t start_index = static_cast(std::max(static_cast(0), end)); // The last wal file is always included all_logs.erase(all_logs.begin(), all_logs.begin() + start_index); return Status::OK(); @@ -420,7 +431,7 @@ Status WalManager::ReadFirstLine(const std::string& fname, Status* status; bool ignore_error; // true if db_options_.paranoid_checks==false - virtual void Corruption(size_t bytes, const Status& s) override { + void Corruption(size_t bytes, const Status& s) override { ROCKS_LOG_WARN(info_log, "[WalManager] %s%s: dropping %d bytes; %s", (this->ignore_error ? "(ignoring error) " : ""), fname, static_cast(bytes), s.ToString().c_str()); @@ -434,8 +445,8 @@ Status WalManager::ReadFirstLine(const std::string& fname, std::unique_ptr file; Status status = env_->NewSequentialFile( fname, &file, env_->OptimizeForLogRead(env_options_)); - unique_ptr file_reader( - new SequentialFileReader(std::move(file))); + std::unique_ptr file_reader( + new SequentialFileReader(std::move(file), fname)); if (!status.ok()) { return status; @@ -448,7 +459,7 @@ Status WalManager::ReadFirstLine(const std::string& fname, reporter.status = &status; reporter.ignore_error = !db_options_.paranoid_checks; log::Reader reader(db_options_.info_log, std::move(file_reader), &reporter, - true /*checksum*/, 0 /*initial_offset*/, number); + true /*checksum*/, number); std::string scratch; Slice record; diff --git a/thirdparty/rocksdb/db/wal_manager.h b/thirdparty/rocksdb/db/wal_manager.h index aa62d793bc..6caf1640c0 100644 --- a/thirdparty/rocksdb/db/wal_manager.h +++ b/thirdparty/rocksdb/db/wal_manager.h @@ -31,11 +31,12 @@ namespace rocksdb { class WalManager { public: WalManager(const ImmutableDBOptions& db_options, - const EnvOptions& env_options) + const EnvOptions& env_options, const bool seq_per_batch = false) : db_options_(db_options), env_options_(env_options), env_(db_options.env), - purge_wal_files_last_run_(0) {} + purge_wal_files_last_run_(0), + seq_per_batch_(seq_per_batch) {} Status GetSortedWalFiles(VectorLogPtr& files); @@ -48,6 +49,8 @@ class WalManager { void ArchiveWALFile(const std::string& fname, uint64_t number); + Status DeleteFile(const std::string& fname, uint64_t number); + Status TEST_ReadFirstRecord(const WalFileType type, const uint64_t number, SequenceNumber* sequence) { return ReadFirstRecord(type, number, sequence); @@ -86,6 +89,8 @@ class WalManager { // last time when PurgeObsoleteWALFiles ran. uint64_t purge_wal_files_last_run_; + bool seq_per_batch_; + // obsolete files will be deleted every this seconds if ttl deletion is // enabled and archive size_limit is disabled. static const uint64_t kDefaultIntervalToDeleteObsoleteWAL = 600; diff --git a/thirdparty/rocksdb/db/wal_manager_test.cc b/thirdparty/rocksdb/db/wal_manager_test.cc index 9f5cf273d2..379f12f52a 100644 --- a/thirdparty/rocksdb/db/wal_manager_test.cc +++ b/thirdparty/rocksdb/db/wal_manager_test.cc @@ -32,7 +32,7 @@ class WalManagerTest : public testing::Test { public: WalManagerTest() : env_(new MockEnv(Env::Default())), - dbname_(test::TmpDir() + "/wal_manager_test"), + dbname_(test::PerThreadDBPath("wal_manager_test")), db_options_(), table_cache_(NewLRUCache(50000, 16)), write_buffer_manager_(db_options_.db_write_buffer_size), @@ -67,18 +67,19 @@ class WalManagerTest : public testing::Test { batch.Put(key, value); WriteBatchInternal::SetSequence(&batch, seq); current_log_writer_->AddRecord(WriteBatchInternal::Contents(&batch)); - versions_->SetLastToBeWrittenSequence(seq); + versions_->SetLastAllocatedSequence(seq); + versions_->SetLastPublishedSequence(seq); versions_->SetLastSequence(seq); } // NOT thread safe - void RollTheLog(bool archived) { + void RollTheLog(bool /*archived*/) { current_log_number_++; std::string fname = ArchivedLogFileName(dbname_, current_log_number_); - unique_ptr file; + std::unique_ptr file; ASSERT_OK(env_->NewWritableFile(fname, &file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(file), env_options_)); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(file), fname, env_options_)); current_log_writer_.reset(new log::Writer(std::move(file_writer), 0, false)); } @@ -93,7 +94,7 @@ class WalManagerTest : public testing::Test { std::unique_ptr OpenTransactionLogIter( const SequenceNumber seq) { - unique_ptr iter; + std::unique_ptr iter; Status status = wal_manager_->GetUpdatesSince( seq, &iter, TransactionLogIterator::ReadOptions(), versions_.get()); EXPECT_OK(status); @@ -117,7 +118,7 @@ class WalManagerTest : public testing::Test { TEST_F(WalManagerTest, ReadFirstRecordCache) { Init(); std::string path = dbname_ + "/000001.log"; - unique_ptr file; + std::unique_ptr file; ASSERT_OK(env_->NewWritableFile(path, &file, EnvOptions())); SequenceNumber s; @@ -128,8 +129,8 @@ TEST_F(WalManagerTest, ReadFirstRecordCache) { wal_manager_->TEST_ReadFirstRecord(kAliveLogFile, 1 /* number */, &s)); ASSERT_EQ(s, 0U); - unique_ptr file_writer( - new WritableFileWriter(std::move(file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(file), path, EnvOptions())); log::Writer writer(std::move(file_writer), 1, db_options_.recycle_log_file_num > 0); WriteBatch batch; @@ -302,7 +303,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as WalManager is not supported in ROCKSDB_LITE\n"); return 0; } diff --git a/thirdparty/rocksdb/db/write_batch.cc b/thirdparty/rocksdb/db/write_batch.cc index 76fc94844a..30480f64e1 100644 --- a/thirdparty/rocksdb/db/write_batch.cc +++ b/thirdparty/rocksdb/db/write_batch.cc @@ -15,15 +15,19 @@ // kTypeValue varstring varstring // kTypeDeletion varstring // kTypeSingleDeletion varstring +// kTypeRangeDeletion varstring varstring // kTypeMerge varstring varstring // kTypeColumnFamilyValue varint32 varstring varstring -// kTypeColumnFamilyDeletion varint32 varstring varstring -// kTypeColumnFamilySingleDeletion varint32 varstring varstring +// kTypeColumnFamilyDeletion varint32 varstring +// kTypeColumnFamilySingleDeletion varint32 varstring +// kTypeColumnFamilyRangeDeletion varint32 varstring varstring // kTypeColumnFamilyMerge varint32 varstring varstring // kTypeBeginPrepareXID varstring // kTypeEndPrepareXID // kTypeCommitXID varstring // kTypeRollbackXID varstring +// kTypeBeginPersistedPrepareXID varstring +// kTypeBeginUnprepareXID varstring // kTypeNoop // varstring := // len: varint32 @@ -49,7 +53,9 @@ #include "monitoring/statistics.h" #include "rocksdb/merge_operator.h" #include "util/coding.h" +#include "util/duplicate_detector.h" #include "util/string_util.h" +#include "util/util.h" namespace rocksdb { @@ -68,6 +74,7 @@ enum ContentFlags : uint32_t { HAS_ROLLBACK = 1 << 8, HAS_DELETE_RANGE = 1 << 9, HAS_BLOB_INDEX = 1 << 10, + HAS_BEGIN_UNPREPARE = 1 << 11, }; struct BatchContentClassifier : public WriteBatch::Handler { @@ -103,8 +110,11 @@ struct BatchContentClassifier : public WriteBatch::Handler { return Status::OK(); } - Status MarkBeginPrepare() override { + Status MarkBeginPrepare(bool unprepare) override { content_flags |= ContentFlags::HAS_BEGIN_PREPARE; + if (unprepare) { + content_flags |= ContentFlags::HAS_BEGIN_UNPREPARE; + } return Status::OK(); } @@ -143,6 +153,12 @@ WriteBatch::WriteBatch(const std::string& rep) max_bytes_(0), rep_(rep) {} +WriteBatch::WriteBatch(std::string&& rep) + : save_points_(nullptr), + content_flags_(ContentFlags::DEFERRED), + max_bytes_(0), + rep_(std::move(rep)) {} + WriteBatch::WriteBatch(const WriteBatch& src) : save_points_(src.save_points_), wal_term_point_(src.wal_term_point_), @@ -150,7 +166,7 @@ WriteBatch::WriteBatch(const WriteBatch& src) max_bytes_(src.max_bytes_), rep_(src.rep_) {} -WriteBatch::WriteBatch(WriteBatch&& src) +WriteBatch::WriteBatch(WriteBatch&& src) noexcept : save_points_(std::move(src.save_points_)), wal_term_point_(std::move(src.wal_term_point_)), content_flags_(src.content_flags_.load(std::memory_order_relaxed)), @@ -177,7 +193,7 @@ WriteBatch::~WriteBatch() { delete save_points_; } WriteBatch::Handler::~Handler() { } -void WriteBatch::Handler::LogData(const Slice& blob) { +void WriteBatch::Handler::LogData(const Slice& /*blob*/) { // If the user has not specified something to do with blobs, then we ignore // them. } @@ -292,7 +308,7 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag, if (!GetVarint32(input, column_family)) { return Status::Corruption("bad WriteBatch Put"); } - // intentional fallthrough + FALLTHROUGH_INTENDED; case kTypeValue: if (!GetLengthPrefixedSlice(input, key) || !GetLengthPrefixedSlice(input, value)) { @@ -304,7 +320,7 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag, if (!GetVarint32(input, column_family)) { return Status::Corruption("bad WriteBatch Delete"); } - // intentional fallthrough + FALLTHROUGH_INTENDED; case kTypeDeletion: case kTypeSingleDeletion: if (!GetLengthPrefixedSlice(input, key)) { @@ -315,7 +331,7 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag, if (!GetVarint32(input, column_family)) { return Status::Corruption("bad WriteBatch DeleteRange"); } - // intentional fallthrough + FALLTHROUGH_INTENDED; case kTypeRangeDeletion: // for range delete, "key" is begin_key, "value" is end_key if (!GetLengthPrefixedSlice(input, key) || @@ -327,7 +343,7 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag, if (!GetVarint32(input, column_family)) { return Status::Corruption("bad WriteBatch Merge"); } - // intentional fallthrough + FALLTHROUGH_INTENDED; case kTypeMerge: if (!GetLengthPrefixedSlice(input, key) || !GetLengthPrefixedSlice(input, value)) { @@ -338,7 +354,7 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag, if (!GetVarint32(input, column_family)) { return Status::Corruption("bad WriteBatch BlobIndex"); } - // intentional fallthrough + FALLTHROUGH_INTENDED; case kTypeBlobIndex: if (!GetLengthPrefixedSlice(input, key) || !GetLengthPrefixedSlice(input, value)) { @@ -353,6 +369,11 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag, break; case kTypeNoop: case kTypeBeginPrepareXID: + // This indicates that the prepared batch is also persisted in the db. + // This is used in WritePreparedTxn + case kTypeBeginPersistedPrepareXID: + // This is used in WriteUnpreparedTxn + case kTypeBeginUnprepareXID: break; case kTypeEndPrepareXID: if (!GetLengthPrefixedSlice(input, xid)) { @@ -383,16 +404,43 @@ Status WriteBatch::Iterate(Handler* handler) const { input.remove_prefix(WriteBatchInternal::kHeader); Slice key, value, blob, xid; + // Sometimes a sub-batch starts with a Noop. We want to exclude such Noops as + // the batch boundary symbols otherwise we would mis-count the number of + // batches. We do that by checking whether the accumulated batch is empty + // before seeing the next Noop. + bool empty_batch = true; int found = 0; Status s; - while (s.ok() && !input.empty() && handler->Continue()) { - char tag = 0; - uint32_t column_family = 0; // default - - s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value, - &blob, &xid); - if (!s.ok()) { - return s; + char tag = 0; + uint32_t column_family = 0; // default + bool last_was_try_again = false; + bool handler_continue = true; + while (((s.ok() && !input.empty()) || UNLIKELY(s.IsTryAgain()))) { + handler_continue = handler->Continue(); + if (!handler_continue) { + break; + } + + if (LIKELY(!s.IsTryAgain())) { + last_was_try_again = false; + tag = 0; + column_family = 0; // default + + s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value, + &blob, &xid); + if (!s.ok()) { + return s; + } + } else { + assert(s.IsTryAgain()); + assert(!last_was_try_again); // to detect infinite loop bugs + if (UNLIKELY(last_was_try_again)) { + return Status::Corruption( + "two consecutive TryAgain in WriteBatch handler; this is either a " + "software bug or data corruption."); + } + last_was_try_again = true; + s = Status::OK(); } switch (tag) { @@ -401,67 +449,137 @@ Status WriteBatch::Iterate(Handler* handler) const { assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_PUT)); s = handler->PutCF(column_family, key, value); - found++; + if (LIKELY(s.ok())) { + empty_batch = false; + found++; + } break; case kTypeColumnFamilyDeletion: case kTypeDeletion: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_DELETE)); s = handler->DeleteCF(column_family, key); - found++; + if (LIKELY(s.ok())) { + empty_batch = false; + found++; + } break; case kTypeColumnFamilySingleDeletion: case kTypeSingleDeletion: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_SINGLE_DELETE)); s = handler->SingleDeleteCF(column_family, key); - found++; + if (LIKELY(s.ok())) { + empty_batch = false; + found++; + } break; case kTypeColumnFamilyRangeDeletion: case kTypeRangeDeletion: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_DELETE_RANGE)); s = handler->DeleteRangeCF(column_family, key, value); - found++; + if (LIKELY(s.ok())) { + empty_batch = false; + found++; + } break; case kTypeColumnFamilyMerge: case kTypeMerge: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_MERGE)); s = handler->MergeCF(column_family, key, value); - found++; + if (LIKELY(s.ok())) { + empty_batch = false; + found++; + } break; case kTypeColumnFamilyBlobIndex: case kTypeBlobIndex: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_BLOB_INDEX)); s = handler->PutBlobIndexCF(column_family, key, value); - found++; + if (LIKELY(s.ok())) { + found++; + } break; case kTypeLogData: handler->LogData(blob); + // A batch might have nothing but LogData. It is still a batch. + empty_batch = false; break; case kTypeBeginPrepareXID: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_BEGIN_PREPARE)); handler->MarkBeginPrepare(); + empty_batch = false; + if (!handler->WriteAfterCommit()) { + s = Status::NotSupported( + "WriteCommitted txn tag when write_after_commit_ is disabled (in " + "WritePrepared/WriteUnprepared mode). If it is not due to " + "corruption, the WAL must be emptied before changing the " + "WritePolicy."); + } + if (handler->WriteBeforePrepare()) { + s = Status::NotSupported( + "WriteCommitted txn tag when write_before_prepare_ is enabled " + "(in WriteUnprepared mode). If it is not due to corruption, the " + "WAL must be emptied before changing the WritePolicy."); + } + break; + case kTypeBeginPersistedPrepareXID: + assert(content_flags_.load(std::memory_order_relaxed) & + (ContentFlags::DEFERRED | ContentFlags::HAS_BEGIN_PREPARE)); + handler->MarkBeginPrepare(); + empty_batch = false; + if (handler->WriteAfterCommit()) { + s = Status::NotSupported( + "WritePrepared/WriteUnprepared txn tag when write_after_commit_ " + "is enabled (in default WriteCommitted mode). If it is not due " + "to corruption, the WAL must be emptied before changing the " + "WritePolicy."); + } + break; + case kTypeBeginUnprepareXID: + assert(content_flags_.load(std::memory_order_relaxed) & + (ContentFlags::DEFERRED | ContentFlags::HAS_BEGIN_UNPREPARE)); + handler->MarkBeginPrepare(true /* unprepared */); + empty_batch = false; + if (handler->WriteAfterCommit()) { + s = Status::NotSupported( + "WriteUnprepared txn tag when write_after_commit_ is enabled (in " + "default WriteCommitted mode). If it is not due to corruption, " + "the WAL must be emptied before changing the WritePolicy."); + } + if (!handler->WriteBeforePrepare()) { + s = Status::NotSupported( + "WriteUnprepared txn tag when write_before_prepare_ is disabled " + "(in WriteCommitted/WritePrepared mode). If it is not due to " + "corruption, the WAL must be emptied before changing the " + "WritePolicy."); + } break; case kTypeEndPrepareXID: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_END_PREPARE)); handler->MarkEndPrepare(xid); + empty_batch = true; break; case kTypeCommitXID: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_COMMIT)); handler->MarkCommit(xid); + empty_batch = true; break; case kTypeRollbackXID: assert(content_flags_.load(std::memory_order_relaxed) & (ContentFlags::DEFERRED | ContentFlags::HAS_ROLLBACK)); handler->MarkRollback(xid); + empty_batch = true; break; case kTypeNoop: + handler->MarkNoop(empty_batch); + empty_batch = true; break; default: return Status::Corruption("unknown WriteBatch tag"); @@ -470,13 +588,21 @@ Status WriteBatch::Iterate(Handler* handler) const { if (!s.ok()) { return s; } - if (found != WriteBatchInternal::Count(this)) { + if (handler_continue && found != WriteBatchInternal::Count(this)) { return Status::Corruption("WriteBatch has wrong count"); } else { return Status::OK(); } } +bool WriteBatchInternal::IsLatestPersistentState(const WriteBatch* b) { + return b->is_latest_persistent_state_; +} + +void WriteBatchInternal::SetAsLastestPersistentState(WriteBatch* b) { + b->is_latest_persistent_state_ = true; +} + int WriteBatchInternal::Count(const WriteBatch* b) { return DecodeFixed32(b->rep_.data() + 8); } @@ -493,12 +619,19 @@ void WriteBatchInternal::SetSequence(WriteBatch* b, SequenceNumber seq) { EncodeFixed64(&b->rep_[0], seq); } -size_t WriteBatchInternal::GetFirstOffset(WriteBatch* b) { +size_t WriteBatchInternal::GetFirstOffset(WriteBatch* /*b*/) { return WriteBatchInternal::kHeader; } Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id, const Slice& key, const Slice& value) { + if (key.size() > size_t{port::kMaxUint32}) { + return Status::InvalidArgument("key is too large"); + } + if (value.size() > size_t{port::kMaxUint32}) { + return Status::InvalidArgument("value is too large"); + } + LocalSavePoint save(b); WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1); if (column_family_id == 0) { @@ -521,8 +654,33 @@ Status WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key, value); } +Status WriteBatchInternal::CheckSlicePartsLength(const SliceParts& key, + const SliceParts& value) { + size_t total_key_bytes = 0; + for (int i = 0; i < key.num_parts; ++i) { + total_key_bytes += key.parts[i].size(); + } + if (total_key_bytes >= size_t{port::kMaxUint32}) { + return Status::InvalidArgument("key is too large"); + } + + size_t total_value_bytes = 0; + for (int i = 0; i < value.num_parts; ++i) { + total_value_bytes += value.parts[i].size(); + } + if (total_value_bytes >= size_t{port::kMaxUint32}) { + return Status::InvalidArgument("value is too large"); + } + return Status::OK(); +} + Status WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id, const SliceParts& key, const SliceParts& value) { + Status s = CheckSlicePartsLength(key, value); + if (!s.ok()) { + return s; + } + LocalSavePoint save(b); WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1); if (column_family_id == 0) { @@ -550,7 +708,9 @@ Status WriteBatchInternal::InsertNoop(WriteBatch* b) { return Status::OK(); } -Status WriteBatchInternal::MarkEndPrepare(WriteBatch* b, const Slice& xid) { +Status WriteBatchInternal::MarkEndPrepare(WriteBatch* b, const Slice& xid, + bool write_after_commit, + bool unprepared_batch) { // a manually constructed batch can only contain one prepare section assert(b->rep_[12] == static_cast(kTypeNoop)); @@ -562,13 +722,21 @@ Status WriteBatchInternal::MarkEndPrepare(WriteBatch* b, const Slice& xid) { } // rewrite noop as begin marker - b->rep_[12] = static_cast(kTypeBeginPrepareXID); + b->rep_[12] = static_cast( + write_after_commit ? kTypeBeginPrepareXID + : (unprepared_batch ? kTypeBeginUnprepareXID + : kTypeBeginPersistedPrepareXID)); b->rep_.push_back(static_cast(kTypeEndPrepareXID)); PutLengthPrefixedSlice(&b->rep_, xid); b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) | ContentFlags::HAS_END_PREPARE | ContentFlags::HAS_BEGIN_PREPARE, std::memory_order_relaxed); + if (unprepared_batch) { + b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) | + ContentFlags::HAS_BEGIN_UNPREPARE, + std::memory_order_relaxed); + } return Status::OK(); } @@ -736,6 +904,13 @@ Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family, Status WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id, const Slice& key, const Slice& value) { + if (key.size() > size_t{port::kMaxUint32}) { + return Status::InvalidArgument("key is too large"); + } + if (value.size() > size_t{port::kMaxUint32}) { + return Status::InvalidArgument("value is too large"); + } + LocalSavePoint save(b); WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1); if (column_family_id == 0) { @@ -761,6 +936,11 @@ Status WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key, Status WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id, const SliceParts& key, const SliceParts& value) { + Status s = CheckSlicePartsLength(key, value); + if (!s.ok()) { + return s; + } + LocalSavePoint save(b); WriteBatchInternal::SetCount(b, WriteBatchInternal::Count(b) + 1); if (column_family_id == 0) { @@ -873,14 +1053,25 @@ class MemTableInserter : public WriteBatch::Handler { // a map is too expensive in the Write() path as they // cause memory allocations though unused. // Make creation optional but do not incur - // unique_ptr additional allocation - using - MemPostInfoMap = std::map; - using - PostMapType = std::aligned_storage::type; + // std::unique_ptr additional allocation + using MemPostInfoMap = std::map; + using PostMapType = std::aligned_storage::type; PostMapType mem_post_info_map_; // current recovered transaction we are rebuilding (recovery) WriteBatch* rebuilding_trx_; + SequenceNumber rebuilding_trx_seq_; + // Increase seq number once per each write batch. Otherwise increase it once + // per key. + bool seq_per_batch_; + // Whether the memtable write will be done only after the commit + bool write_after_commit_; + // Whether memtable write can be done before prepare + bool write_before_prepare_; + // Whether this batch was unprepared or not + bool unprepared_batch_; + using DupDetector = std::aligned_storage::type; + DupDetector duplicate_detector_; + bool dup_dectector_on_; MemPostInfoMap& GetPostMap() { assert(concurrent_memtable_writes_); @@ -891,38 +1082,87 @@ class MemTableInserter : public WriteBatch::Handler { return *reinterpret_cast(&mem_post_info_map_); } -public: + bool IsDuplicateKeySeq(uint32_t column_family_id, const Slice& key) { + assert(!write_after_commit_); + assert(rebuilding_trx_ != nullptr); + if (!dup_dectector_on_) { + new (&duplicate_detector_) DuplicateDetector(db_); + dup_dectector_on_ = true; + } + return reinterpret_cast + (&duplicate_detector_)->IsDuplicateKeySeq(column_family_id, key, sequence_); + } + + protected: + bool WriteBeforePrepare() const override { return write_before_prepare_; } + bool WriteAfterCommit() const override { return write_after_commit_; } + + public: // cf_mems should not be shared with concurrent inserters - MemTableInserter(SequenceNumber _sequence, ColumnFamilyMemTables* cf_mems, - FlushScheduler* flush_scheduler, - bool ignore_missing_column_families, - uint64_t recovering_log_number, DB* db, - bool concurrent_memtable_writes, - bool* has_valid_writes = nullptr) - : sequence_(_sequence), - cf_mems_(cf_mems), - flush_scheduler_(flush_scheduler), - ignore_missing_column_families_(ignore_missing_column_families), - recovering_log_number_(recovering_log_number), - log_number_ref_(0), - db_(reinterpret_cast(db)), - concurrent_memtable_writes_(concurrent_memtable_writes), - post_info_created_(false), - has_valid_writes_(has_valid_writes), - rebuilding_trx_(nullptr) { - assert(cf_mems_); - } - - ~MemTableInserter() { + MemTableInserter(SequenceNumber _sequence, ColumnFamilyMemTables* cf_mems, + FlushScheduler* flush_scheduler, + bool ignore_missing_column_families, + uint64_t recovering_log_number, DB* db, + bool concurrent_memtable_writes, + bool* has_valid_writes = nullptr, bool seq_per_batch = false, + bool batch_per_txn = true) + : sequence_(_sequence), + cf_mems_(cf_mems), + flush_scheduler_(flush_scheduler), + ignore_missing_column_families_(ignore_missing_column_families), + recovering_log_number_(recovering_log_number), + log_number_ref_(0), + db_(reinterpret_cast(db)), + concurrent_memtable_writes_(concurrent_memtable_writes), + post_info_created_(false), + has_valid_writes_(has_valid_writes), + rebuilding_trx_(nullptr), + rebuilding_trx_seq_(0), + seq_per_batch_(seq_per_batch), + // Write after commit currently uses one seq per key (instead of per + // batch). So seq_per_batch being false indicates write_after_commit + // approach. + write_after_commit_(!seq_per_batch), + // WriteUnprepared can write WriteBatches per transaction, so + // batch_per_txn being false indicates write_before_prepare. + write_before_prepare_(!batch_per_txn), + unprepared_batch_(false), + duplicate_detector_(), + dup_dectector_on_(false) { + assert(cf_mems_); + } + + ~MemTableInserter() override { + if (dup_dectector_on_) { + reinterpret_cast + (&duplicate_detector_)->~DuplicateDetector(); + } if (post_info_created_) { reinterpret_cast (&mem_post_info_map_)->~MemPostInfoMap(); } + delete rebuilding_trx_; } MemTableInserter(const MemTableInserter&) = delete; MemTableInserter& operator=(const MemTableInserter&) = delete; + // The batch seq is regularly restarted; In normal mode it is set when + // MemTableInserter is constructed in the write thread and in recovery mode it + // is set when a batch, which is tagged with seq, is read from the WAL. + // Within a sequenced batch, which could be a merge of multiple batches, we + // have two policies to advance the seq: i) seq_per_key (default) and ii) + // seq_per_batch. To implement the latter we need to mark the boundary between + // the individual batches. The approach is this: 1) Use the terminating + // markers to indicate the boundary (kTypeEndPrepareXID, kTypeCommitXID, + // kTypeRollbackXID) 2) Terminate a batch with kTypeNoop in the absence of a + // natural boundary marker. + void MaybeAdvanceSeq(bool batch_boundry = false) { + if (batch_boundry == seq_per_batch_) { + sequence_++; + } + } + void set_log_number_ref(uint64_t log) { log_number_ref_ = log; } SequenceNumber sequence() const { return sequence_; } @@ -980,26 +1220,46 @@ class MemTableInserter : public WriteBatch::Handler { Status PutCFImpl(uint32_t column_family_id, const Slice& key, const Slice& value, ValueType value_type) { - if (rebuilding_trx_ != nullptr) { + // optimize for non-recovery mode + if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) { WriteBatchInternal::Put(rebuilding_trx_, column_family_id, key, value); return Status::OK(); + // else insert the values to the memtable right away } Status seek_status; - if (!SeekToColumnFamily(column_family_id, &seek_status)) { - ++sequence_; + if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) { + bool batch_boundry = false; + if (rebuilding_trx_ != nullptr) { + assert(!write_after_commit_); + // The CF is probably flushed and hence no need for insert but we still + // need to keep track of the keys for upcoming rollback/commit. + WriteBatchInternal::Put(rebuilding_trx_, column_family_id, key, value); + batch_boundry = IsDuplicateKeySeq(column_family_id, key); + } + MaybeAdvanceSeq(batch_boundry); return seek_status; } + Status ret_status; MemTable* mem = cf_mems_->GetMemTable(); auto* moptions = mem->GetImmutableMemTableOptions(); + // inplace_update_support is inconsistent with snapshots, and therefore with + // any kind of transactions including the ones that use seq_per_batch + assert(!seq_per_batch_ || !moptions->inplace_update_support); if (!moptions->inplace_update_support) { - mem->Add(sequence_, value_type, key, value, concurrent_memtable_writes_, - get_post_process_info(mem)); + bool mem_res = + mem->Add(sequence_, value_type, key, value, + concurrent_memtable_writes_, get_post_process_info(mem)); + if (UNLIKELY(!mem_res)) { + assert(seq_per_batch_); + ret_status = Status::TryAgain("key+seq exists"); + const bool BATCH_BOUNDRY = true; + MaybeAdvanceSeq(BATCH_BOUNDRY); + } } else if (moptions->inplace_callback == nullptr) { assert(!concurrent_memtable_writes_); mem->Update(sequence_, key, value); - RecordTick(moptions->statistics, NUMBER_KEYS_UPDATED); } else { assert(!concurrent_memtable_writes_); if (mem->UpdateCallback(sequence_, key, value)) { @@ -1008,6 +1268,9 @@ class MemTableInserter : public WriteBatch::Handler { SnapshotImpl read_from_snapshot; read_from_snapshot.number_ = sequence_; ReadOptions ropts; + // it's going to be overwritten for sure, so no point caching data block + // containing the old version + ropts.fill_cache = false; ropts.snapshot = &read_from_snapshot; std::string prev_value; @@ -1029,82 +1292,151 @@ class MemTableInserter : public WriteBatch::Handler { value, &merged_value); if (status == UpdateStatus::UPDATED_INPLACE) { // prev_value is updated in-place with final value. - mem->Add(sequence_, value_type, key, Slice(prev_buffer, prev_size)); + bool mem_res __attribute__((__unused__)); + mem_res = mem->Add( + sequence_, value_type, key, Slice(prev_buffer, prev_size)); + assert(mem_res); RecordTick(moptions->statistics, NUMBER_KEYS_WRITTEN); } else if (status == UpdateStatus::UPDATED) { // merged_value contains the final value. - mem->Add(sequence_, value_type, key, Slice(merged_value)); + bool mem_res __attribute__((__unused__)); + mem_res = + mem->Add(sequence_, value_type, key, Slice(merged_value)); + assert(mem_res); RecordTick(moptions->statistics, NUMBER_KEYS_WRITTEN); } } } - // Since all Puts are logged in trasaction logs (if enabled), always bump + // optimize for non-recovery mode + if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) { + assert(!write_after_commit_); + // If the ret_status is TryAgain then let the next try to add the ky to + // the rebuilding transaction object. + WriteBatchInternal::Put(rebuilding_trx_, column_family_id, key, value); + } + // Since all Puts are logged in transaction logs (if enabled), always bump // sequence number. Even if the update eventually fails and does not result // in memtable add/update. - sequence_++; + MaybeAdvanceSeq(); CheckMemtableFull(); - return Status::OK(); + return ret_status; } - virtual Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status PutCF(uint32_t column_family_id, const Slice& key, + const Slice& value) override { return PutCFImpl(column_family_id, key, value, kTypeValue); } - Status DeleteImpl(uint32_t column_family_id, const Slice& key, + Status DeleteImpl(uint32_t /*column_family_id*/, const Slice& key, const Slice& value, ValueType delete_type) { + Status ret_status; MemTable* mem = cf_mems_->GetMemTable(); - mem->Add(sequence_, delete_type, key, value, concurrent_memtable_writes_, - get_post_process_info(mem)); - sequence_++; + bool mem_res = + mem->Add(sequence_, delete_type, key, value, + concurrent_memtable_writes_, get_post_process_info(mem)); + if (UNLIKELY(!mem_res)) { + assert(seq_per_batch_); + ret_status = Status::TryAgain("key+seq exists"); + const bool BATCH_BOUNDRY = true; + MaybeAdvanceSeq(BATCH_BOUNDRY); + } + MaybeAdvanceSeq(); CheckMemtableFull(); - return Status::OK(); + return ret_status; } - virtual Status DeleteCF(uint32_t column_family_id, - const Slice& key) override { - if (rebuilding_trx_ != nullptr) { + Status DeleteCF(uint32_t column_family_id, const Slice& key) override { + // optimize for non-recovery mode + if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) { WriteBatchInternal::Delete(rebuilding_trx_, column_family_id, key); return Status::OK(); + // else insert the values to the memtable right away } Status seek_status; - if (!SeekToColumnFamily(column_family_id, &seek_status)) { - ++sequence_; + if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) { + bool batch_boundry = false; + if (rebuilding_trx_ != nullptr) { + assert(!write_after_commit_); + // The CF is probably flushed and hence no need for insert but we still + // need to keep track of the keys for upcoming rollback/commit. + WriteBatchInternal::Delete(rebuilding_trx_, column_family_id, key); + batch_boundry = IsDuplicateKeySeq(column_family_id, key); + } + MaybeAdvanceSeq(batch_boundry); return seek_status; } - return DeleteImpl(column_family_id, key, Slice(), kTypeDeletion); + auto ret_status = DeleteImpl(column_family_id, key, Slice(), kTypeDeletion); + // optimize for non-recovery mode + if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) { + assert(!write_after_commit_); + // If the ret_status is TryAgain then let the next try to add the ky to + // the rebuilding transaction object. + WriteBatchInternal::Delete(rebuilding_trx_, column_family_id, key); + } + return ret_status; } - virtual Status SingleDeleteCF(uint32_t column_family_id, - const Slice& key) override { - if (rebuilding_trx_ != nullptr) { + Status SingleDeleteCF(uint32_t column_family_id, const Slice& key) override { + // optimize for non-recovery mode + if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) { WriteBatchInternal::SingleDelete(rebuilding_trx_, column_family_id, key); return Status::OK(); + // else insert the values to the memtable right away } Status seek_status; - if (!SeekToColumnFamily(column_family_id, &seek_status)) { - ++sequence_; + if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) { + bool batch_boundry = false; + if (rebuilding_trx_ != nullptr) { + assert(!write_after_commit_); + // The CF is probably flushed and hence no need for insert but we still + // need to keep track of the keys for upcoming rollback/commit. + WriteBatchInternal::SingleDelete(rebuilding_trx_, column_family_id, + key); + batch_boundry = IsDuplicateKeySeq(column_family_id, key); + } + MaybeAdvanceSeq(batch_boundry); return seek_status; } - return DeleteImpl(column_family_id, key, Slice(), kTypeSingleDeletion); + auto ret_status = + DeleteImpl(column_family_id, key, Slice(), kTypeSingleDeletion); + // optimize for non-recovery mode + if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) { + assert(!write_after_commit_); + // If the ret_status is TryAgain then let the next try to add the ky to + // the rebuilding transaction object. + WriteBatchInternal::SingleDelete(rebuilding_trx_, column_family_id, key); + } + return ret_status; } - virtual Status DeleteRangeCF(uint32_t column_family_id, - const Slice& begin_key, - const Slice& end_key) override { - if (rebuilding_trx_ != nullptr) { + Status DeleteRangeCF(uint32_t column_family_id, const Slice& begin_key, + const Slice& end_key) override { + // optimize for non-recovery mode + if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) { WriteBatchInternal::DeleteRange(rebuilding_trx_, column_family_id, begin_key, end_key); return Status::OK(); + // else insert the values to the memtable right away } Status seek_status; - if (!SeekToColumnFamily(column_family_id, &seek_status)) { - ++sequence_; + if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) { + bool batch_boundry = false; + if (rebuilding_trx_ != nullptr) { + assert(!write_after_commit_); + // The CF is probably flushed and hence no need for insert but we still + // need to keep track of the keys for upcoming rollback/commit. + WriteBatchInternal::DeleteRange(rebuilding_trx_, column_family_id, + begin_key, end_key); + // TODO(myabandeh): when transactional DeleteRange support is added, + // check if end_key must also be added. + batch_boundry = IsDuplicateKeySeq(column_family_id, begin_key); + } + MaybeAdvanceSeq(batch_boundry); return seek_status; } if (db_ != nullptr) { @@ -1121,23 +1453,45 @@ class MemTableInserter : public WriteBatch::Handler { } } - return DeleteImpl(column_family_id, begin_key, end_key, kTypeRangeDeletion); + auto ret_status = + DeleteImpl(column_family_id, begin_key, end_key, kTypeRangeDeletion); + // optimize for non-recovery mode + if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) { + assert(!write_after_commit_); + // If the ret_status is TryAgain then let the next try to add the ky to + // the rebuilding transaction object. + WriteBatchInternal::DeleteRange(rebuilding_trx_, column_family_id, + begin_key, end_key); + } + return ret_status; } - virtual Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status MergeCF(uint32_t column_family_id, const Slice& key, + const Slice& value) override { assert(!concurrent_memtable_writes_); - if (rebuilding_trx_ != nullptr) { + // optimize for non-recovery mode + if (UNLIKELY(write_after_commit_ && rebuilding_trx_ != nullptr)) { WriteBatchInternal::Merge(rebuilding_trx_, column_family_id, key, value); return Status::OK(); + // else insert the values to the memtable right away } Status seek_status; - if (!SeekToColumnFamily(column_family_id, &seek_status)) { - ++sequence_; + if (UNLIKELY(!SeekToColumnFamily(column_family_id, &seek_status))) { + bool batch_boundry = false; + if (rebuilding_trx_ != nullptr) { + assert(!write_after_commit_); + // The CF is probably flushed and hence no need for insert but we still + // need to keep track of the keys for upcoming rollback/commit. + WriteBatchInternal::Merge(rebuilding_trx_, column_family_id, key, + value); + batch_boundry = IsDuplicateKeySeq(column_family_id, key); + } + MaybeAdvanceSeq(batch_boundry); return seek_status; } + Status ret_status; MemTable* mem = cf_mems_->GetMemTable(); auto* moptions = mem->GetImmutableMemTableOptions(); bool perform_merge = false; @@ -1193,22 +1547,41 @@ class MemTableInserter : public WriteBatch::Handler { perform_merge = false; } else { // 3) Add value to memtable - mem->Add(sequence_, kTypeValue, key, new_value); + bool mem_res = mem->Add(sequence_, kTypeValue, key, new_value); + if (UNLIKELY(!mem_res)) { + assert(seq_per_batch_); + ret_status = Status::TryAgain("key+seq exists"); + const bool BATCH_BOUNDRY = true; + MaybeAdvanceSeq(BATCH_BOUNDRY); + } } } if (!perform_merge) { // Add merge operator to memtable - mem->Add(sequence_, kTypeMerge, key, value); + bool mem_res = mem->Add(sequence_, kTypeMerge, key, value); + if (UNLIKELY(!mem_res)) { + assert(seq_per_batch_); + ret_status = Status::TryAgain("key+seq exists"); + const bool BATCH_BOUNDRY = true; + MaybeAdvanceSeq(BATCH_BOUNDRY); + } } - sequence_++; + // optimize for non-recovery mode + if (UNLIKELY(!ret_status.IsTryAgain() && rebuilding_trx_ != nullptr)) { + assert(!write_after_commit_); + // If the ret_status is TryAgain then let the next try to add the ky to + // the rebuilding transaction object. + WriteBatchInternal::Merge(rebuilding_trx_, column_family_id, key, value); + } + MaybeAdvanceSeq(); CheckMemtableFull(); - return Status::OK(); + return ret_status; } - virtual Status PutBlobIndexCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status PutBlobIndexCF(uint32_t column_family_id, const Slice& key, + const Slice& value) override { // Same as PutCF except for value type. return PutCFImpl(column_family_id, key, value, kTypeBlobIndex); } @@ -1226,7 +1599,9 @@ class MemTableInserter : public WriteBatch::Handler { } } - Status MarkBeginPrepare() override { + // The write batch handler calls MarkBeginPrepare with unprepare set to true + // if it encounters the kTypeBeginUnprepareXID marker. + Status MarkBeginPrepare(bool unprepare) override { assert(rebuilding_trx_ == nullptr); assert(db_); @@ -1241,14 +1616,15 @@ class MemTableInserter : public WriteBatch::Handler { // we are now iterating through a prepared section rebuilding_trx_ = new WriteBatch(); + rebuilding_trx_seq_ = sequence_; + // We only call MarkBeginPrepare once per batch, and unprepared_batch_ + // is initialized to false by default. + assert(!unprepared_batch_); + unprepared_batch_ = unprepare; + if (has_valid_writes_ != nullptr) { *has_valid_writes_ = true; } - } else { - // in non-recovery we ignore prepare markers - // and insert the values directly. making sure we have a - // log for each insertion to reference. - assert(log_number_ref_ > 0); } return Status::OK(); @@ -1260,14 +1636,33 @@ class MemTableInserter : public WriteBatch::Handler { if (recovering_log_number_ != 0) { assert(db_->allow_2pc()); + size_t batch_cnt = + write_after_commit_ + ? 0 // 0 will disable further checks + : static_cast(sequence_ - rebuilding_trx_seq_ + 1); db_->InsertRecoveredTransaction(recovering_log_number_, name.ToString(), - rebuilding_trx_); + rebuilding_trx_, rebuilding_trx_seq_, + batch_cnt, unprepared_batch_); rebuilding_trx_ = nullptr; } else { assert(rebuilding_trx_ == nullptr); - assert(log_number_ref_ > 0); } + const bool batch_boundry = true; + MaybeAdvanceSeq(batch_boundry); + + return Status::OK(); + } + Status MarkNoop(bool empty_batch) override { + // A hack in pessimistic transaction could result into a noop at the start + // of the write batch, that should be ignored. + if (!empty_batch) { + // In the absence of Prepare markers, a kTypeNoop tag indicates the end of + // a batch. This happens when write batch commits skipping the prepare + // phase. + const bool batch_boundry = true; + MaybeAdvanceSeq(batch_boundry); + } return Status::OK(); } @@ -1282,17 +1677,23 @@ class MemTableInserter : public WriteBatch::Handler { // and commit. auto trx = db_->GetRecoveredTransaction(name.ToString()); - // the log contaiting the prepared section may have + // the log containing the prepared section may have // been released in the last incarnation because the // data was flushed to L0 if (trx != nullptr) { // at this point individual CF lognumbers will prevent // duplicate re-insertion of values. assert(log_number_ref_ == 0); - // all insertes must reference this trx log number - log_number_ref_ = trx->log_number_; - s = trx->batch_->Iterate(this); - log_number_ref_ = 0; + if (write_after_commit_) { + // write_after_commit_ can only have one batch in trx. + assert(trx->batches_.size() == 1); + const auto& batch_info = trx->batches_.begin()->second; + // all inserts must reference this trx log number + log_number_ref_ = batch_info.log_number_; + s = batch_info.batch_->Iterate(this); + log_number_ref_ = 0; + } + // else the values are already inserted before the commit if (s.ok()) { db_->DeleteRecoveredTransaction(name.ToString()); @@ -1302,8 +1703,13 @@ class MemTableInserter : public WriteBatch::Handler { } } } else { - // in non recovery we simply ignore this tag + // When writes are not delayed until commit, there is no disconnect + // between a memtable write and the WAL that supports it. So the commit + // need not reference any log as the only log to which it depends. + assert(!write_after_commit_ || log_number_ref_ > 0); } + const bool batch_boundry = true; + MaybeAdvanceSeq(batch_boundry); return s; } @@ -1324,6 +1730,9 @@ class MemTableInserter : public WriteBatch::Handler { // in non recovery we simply ignore this tag } + const bool batch_boundry = true; + MaybeAdvanceSeq(batch_boundry); + return Status::OK(); } @@ -1342,45 +1751,56 @@ class MemTableInserter : public WriteBatch::Handler { // 2) During Write(), in a single-threaded write thread // 3) During Write(), in a concurrent context where memtables has been cloned // The reason is that it calls memtables->Seek(), which has a stateful cache -Status WriteBatchInternal::InsertInto(WriteThread::WriteGroup& write_group, - SequenceNumber sequence, - ColumnFamilyMemTables* memtables, - FlushScheduler* flush_scheduler, - bool ignore_missing_column_families, - uint64_t recovery_log_number, DB* db, - bool concurrent_memtable_writes) { - MemTableInserter inserter(sequence, memtables, flush_scheduler, - ignore_missing_column_families, recovery_log_number, - db, concurrent_memtable_writes); +Status WriteBatchInternal::InsertInto( + WriteThread::WriteGroup& write_group, SequenceNumber sequence, + ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler, + bool ignore_missing_column_families, uint64_t recovery_log_number, DB* db, + bool concurrent_memtable_writes, bool seq_per_batch, bool batch_per_txn) { + MemTableInserter inserter( + sequence, memtables, flush_scheduler, ignore_missing_column_families, + recovery_log_number, db, concurrent_memtable_writes, + nullptr /*has_valid_writes*/, seq_per_batch, batch_per_txn); for (auto w : write_group) { + if (w->CallbackFailed()) { + continue; + } + w->sequence = inserter.sequence(); if (!w->ShouldWriteToMemtable()) { + // In seq_per_batch_ mode this advances the seq by one. + inserter.MaybeAdvanceSeq(true); continue; } SetSequence(w->batch, inserter.sequence()); - w->sequence = inserter.sequence(); inserter.set_log_number_ref(w->log_ref); w->status = w->batch->Iterate(&inserter); if (!w->status.ok()) { return w->status; } + assert(!seq_per_batch || w->batch_cnt != 0); + assert(!seq_per_batch || inserter.sequence() - w->sequence == w->batch_cnt); } return Status::OK(); } -Status WriteBatchInternal::InsertInto(WriteThread::Writer* writer, - SequenceNumber sequence, - ColumnFamilyMemTables* memtables, - FlushScheduler* flush_scheduler, - bool ignore_missing_column_families, - uint64_t log_number, DB* db, - bool concurrent_memtable_writes) { +Status WriteBatchInternal::InsertInto( + WriteThread::Writer* writer, SequenceNumber sequence, + ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler, + bool ignore_missing_column_families, uint64_t log_number, DB* db, + bool concurrent_memtable_writes, bool seq_per_batch, size_t batch_cnt, + bool batch_per_txn) { +#ifdef NDEBUG + (void)batch_cnt; +#endif assert(writer->ShouldWriteToMemtable()); - MemTableInserter inserter(sequence, memtables, flush_scheduler, - ignore_missing_column_families, log_number, db, - concurrent_memtable_writes); + MemTableInserter inserter( + sequence, memtables, flush_scheduler, ignore_missing_column_families, + log_number, db, concurrent_memtable_writes, nullptr /*has_valid_writes*/, + seq_per_batch, batch_per_txn); SetSequence(writer->batch, sequence); inserter.set_log_number_ref(writer->log_ref); Status s = writer->batch->Iterate(&inserter); + assert(!seq_per_batch || batch_cnt != 0); + assert(!seq_per_batch || inserter.sequence() - sequence == batch_cnt); if (concurrent_memtable_writes) { inserter.PostProcess(); } @@ -1391,13 +1811,15 @@ Status WriteBatchInternal::InsertInto( const WriteBatch* batch, ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler, bool ignore_missing_column_families, uint64_t log_number, DB* db, bool concurrent_memtable_writes, - SequenceNumber* last_seq_used, bool* has_valid_writes) { + SequenceNumber* next_seq, bool* has_valid_writes, bool seq_per_batch, + bool batch_per_txn) { MemTableInserter inserter(Sequence(batch), memtables, flush_scheduler, ignore_missing_column_families, log_number, db, - concurrent_memtable_writes, has_valid_writes); + concurrent_memtable_writes, has_valid_writes, + seq_per_batch, batch_per_txn); Status s = batch->Iterate(&inserter); - if (last_seq_used != nullptr) { - *last_seq_used = inserter.sequence(); + if (next_seq != nullptr) { + *next_seq = inserter.sequence(); } if (concurrent_memtable_writes) { inserter.PostProcess(); diff --git a/thirdparty/rocksdb/db/write_batch_internal.h b/thirdparty/rocksdb/db/write_batch_internal.h index 2408686f12..bae62bf031 100644 --- a/thirdparty/rocksdb/db/write_batch_internal.h +++ b/thirdparty/rocksdb/db/write_batch_internal.h @@ -102,7 +102,9 @@ class WriteBatchInternal { static Status PutBlobIndex(WriteBatch* batch, uint32_t column_family_id, const Slice& key, const Slice& value); - static Status MarkEndPrepare(WriteBatch* batch, const Slice& xid); + static Status MarkEndPrepare(WriteBatch* batch, const Slice& xid, + const bool write_after_commit = true, + const bool unprepared_batch = false); static Status MarkRollback(WriteBatch* batch, const Slice& xid); @@ -116,10 +118,10 @@ class WriteBatchInternal { // Set the count for the number of entries in the batch. static void SetCount(WriteBatch* batch, int n); - // Return the seqeunce number for the start of this batch. + // Return the sequence number for the start of this batch. static SequenceNumber Sequence(const WriteBatch* batch); - // Store the specified number as the seqeunce number for the start of + // Store the specified number as the sequence number for the start of // this batch. static void SetSequence(WriteBatch* batch, SequenceNumber seq); @@ -137,6 +139,9 @@ class WriteBatchInternal { static Status SetContents(WriteBatch* batch, const Slice& contents); + static Status CheckSlicePartsLength(const SliceParts& key, + const SliceParts& value); + // Inserts batches[i] into memtable, for i in 0..num_batches-1 inclusive. // // If ignore_missing_column_families == true. WriteBatch @@ -154,31 +159,31 @@ class WriteBatchInternal { // // Under concurrent use, the caller is responsible for making sure that // the memtables object itself is thread-local. - static Status InsertInto(WriteThread::WriteGroup& write_group, - SequenceNumber sequence, - ColumnFamilyMemTables* memtables, - FlushScheduler* flush_scheduler, - bool ignore_missing_column_families = false, - uint64_t log_number = 0, DB* db = nullptr, - bool concurrent_memtable_writes = false); + static Status InsertInto( + WriteThread::WriteGroup& write_group, SequenceNumber sequence, + ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler, + bool ignore_missing_column_families = false, uint64_t log_number = 0, + DB* db = nullptr, bool concurrent_memtable_writes = false, + bool seq_per_batch = false, bool batch_per_txn = true); // Convenience form of InsertInto when you have only one batch - // last_seq_used returns the last sequnce number used in a MemTable insert - static Status InsertInto(const WriteBatch* batch, - ColumnFamilyMemTables* memtables, - FlushScheduler* flush_scheduler, - bool ignore_missing_column_families = false, - uint64_t log_number = 0, DB* db = nullptr, - bool concurrent_memtable_writes = false, - SequenceNumber* last_seq_used = nullptr, - bool* has_valid_writes = nullptr); + // next_seq returns the seq after last sequence number used in MemTable insert + static Status InsertInto( + const WriteBatch* batch, ColumnFamilyMemTables* memtables, + FlushScheduler* flush_scheduler, + bool ignore_missing_column_families = false, uint64_t log_number = 0, + DB* db = nullptr, bool concurrent_memtable_writes = false, + SequenceNumber* next_seq = nullptr, bool* has_valid_writes = nullptr, + bool seq_per_batch = false, bool batch_per_txn = true); static Status InsertInto(WriteThread::Writer* writer, SequenceNumber sequence, ColumnFamilyMemTables* memtables, FlushScheduler* flush_scheduler, bool ignore_missing_column_families = false, uint64_t log_number = 0, DB* db = nullptr, - bool concurrent_memtable_writes = false); + bool concurrent_memtable_writes = false, + bool seq_per_batch = false, size_t batch_cnt = 0, + bool batch_per_txn = true); static Status Append(WriteBatch* dst, const WriteBatch* src, const bool WAL_only = false); @@ -186,6 +191,11 @@ class WriteBatchInternal { // Returns the byte size of appending a WriteBatch with ByteSize // leftByteSize and a WriteBatch with ByteSize rightByteSize static size_t AppendedByteSize(size_t leftByteSize, size_t rightByteSize); + + // This write batch includes the latest state that should be persisted. Such + // state meant to be used only during recovery. + static void SetAsLastestPersistentState(WriteBatch* b); + static bool IsLatestPersistentState(const WriteBatch* b); }; // LocalSavePoint is similar to a scope guard diff --git a/thirdparty/rocksdb/db/write_batch_test.cc b/thirdparty/rocksdb/db/write_batch_test.cc index 4584793abe..322bd8945b 100644 --- a/thirdparty/rocksdb/db/write_batch_test.cc +++ b/thirdparty/rocksdb/db/write_batch_test.cc @@ -18,7 +18,6 @@ #include "rocksdb/utilities/write_batch_with_index.h" #include "rocksdb/write_buffer_manager.h" #include "table/scoped_arena_iterator.h" -#include "util/logging.h" #include "util/string_util.h" #include "util/testharness.h" @@ -52,7 +51,8 @@ static std::string PrintContents(WriteBatch* b) { iter = mem->NewIterator(ReadOptions(), &arena); arena_iter_guard.set(iter); } else { - iter = mem->NewRangeTombstoneIterator(ReadOptions()); + iter = mem->NewRangeTombstoneIterator(ReadOptions(), + kMaxSequenceNumber /* read_seq */); iter_guard.reset(iter); } if (iter == nullptr) { @@ -236,8 +236,8 @@ TEST_F(WriteBatchTest, SingleDeletion) { namespace { struct TestHandler : public WriteBatch::Handler { std::string seen; - virtual Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status PutCF(uint32_t column_family_id, const Slice& key, + const Slice& value) override { if (column_family_id == 0) { seen += "Put(" + key.ToString() + ", " + value.ToString() + ")"; } else { @@ -246,8 +246,7 @@ namespace { } return Status::OK(); } - virtual Status DeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status DeleteCF(uint32_t column_family_id, const Slice& key) override { if (column_family_id == 0) { seen += "Delete(" + key.ToString() + ")"; } else { @@ -256,8 +255,8 @@ namespace { } return Status::OK(); } - virtual Status SingleDeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status SingleDeleteCF(uint32_t column_family_id, + const Slice& key) override { if (column_family_id == 0) { seen += "SingleDelete(" + key.ToString() + ")"; } else { @@ -266,9 +265,8 @@ namespace { } return Status::OK(); } - virtual Status DeleteRangeCF(uint32_t column_family_id, - const Slice& begin_key, - const Slice& end_key) override { + Status DeleteRangeCF(uint32_t column_family_id, const Slice& begin_key, + const Slice& end_key) override { if (column_family_id == 0) { seen += "DeleteRange(" + begin_key.ToString() + ", " + end_key.ToString() + ")"; @@ -278,8 +276,8 @@ namespace { } return Status::OK(); } - virtual Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status MergeCF(uint32_t column_family_id, const Slice& key, + const Slice& value) override { if (column_family_id == 0) { seen += "Merge(" + key.ToString() + ", " + value.ToString() + ")"; } else { @@ -288,22 +286,27 @@ namespace { } return Status::OK(); } - virtual void LogData(const Slice& blob) override { + void LogData(const Slice& blob) override { seen += "LogData(" + blob.ToString() + ")"; } - virtual Status MarkBeginPrepare() override { - seen += "MarkBeginPrepare()"; + Status MarkBeginPrepare(bool unprepare) override { + seen += + "MarkBeginPrepare(" + std::string(unprepare ? "true" : "false") + ")"; return Status::OK(); } - virtual Status MarkEndPrepare(const Slice& xid) override { + Status MarkEndPrepare(const Slice& xid) override { seen += "MarkEndPrepare(" + xid.ToString() + ")"; return Status::OK(); } - virtual Status MarkCommit(const Slice& xid) override { + Status MarkNoop(bool empty_batch) override { + seen += "MarkNoop(" + std::string(empty_batch ? "true" : "false") + ")"; + return Status::OK(); + } + Status MarkCommit(const Slice& xid) override { seen += "MarkCommit(" + xid.ToString() + ")"; return Status::OK(); } - virtual Status MarkRollback(const Slice& xid) override { + Status MarkRollback(const Slice& xid) override { seen += "MarkRollback(" + xid.ToString() + ")"; return Status::OK(); } @@ -400,7 +403,7 @@ TEST_F(WriteBatchTest, PrepareCommit) { TestHandler handler; batch.Iterate(&handler); ASSERT_EQ( - "MarkBeginPrepare()" + "MarkBeginPrepare(false)" "Put(k1, v1)" "Put(k2, v2)" "MarkEndPrepare(xid1)" @@ -415,7 +418,7 @@ TEST_F(WriteBatchTest, PrepareCommit) { TEST_F(WriteBatchTest, DISABLED_ManyUpdates) { // Insert key and value of 3GB and push total batch size to 12GB. static const size_t kKeyValueSize = 4u; - static const uint32_t kNumUpdates = 3 << 30; + static const uint32_t kNumUpdates = uint32_t(3 << 30); std::string raw(kKeyValueSize, 'A'); WriteBatch batch(kNumUpdates * (4 + kKeyValueSize * 2) + 1024u); char c = 'A'; @@ -434,8 +437,8 @@ TEST_F(WriteBatchTest, DISABLED_ManyUpdates) { struct NoopHandler : public WriteBatch::Handler { uint32_t num_seen = 0; char expected_char = 'A'; - virtual Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status PutCF(uint32_t /*column_family_id*/, const Slice& key, + const Slice& value) override { EXPECT_EQ(kKeyValueSize, key.size()); EXPECT_EQ(kKeyValueSize, value.size()); EXPECT_EQ(expected_char, key[0]); @@ -449,23 +452,23 @@ TEST_F(WriteBatchTest, DISABLED_ManyUpdates) { ++num_seen; return Status::OK(); } - virtual Status DeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status DeleteCF(uint32_t /*column_family_id*/, + const Slice& /*key*/) override { ADD_FAILURE(); return Status::OK(); } - virtual Status SingleDeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status SingleDeleteCF(uint32_t /*column_family_id*/, + const Slice& /*key*/) override { ADD_FAILURE(); return Status::OK(); } - virtual Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status MergeCF(uint32_t /*column_family_id*/, const Slice& /*key*/, + const Slice& /*value*/) override { ADD_FAILURE(); return Status::OK(); } - virtual void LogData(const Slice& blob) override { ADD_FAILURE(); } - virtual bool Continue() override { return num_seen < kNumUpdates; } + void LogData(const Slice& /*blob*/) override { ADD_FAILURE(); } + bool Continue() override { return num_seen < kNumUpdates; } } handler; batch.Iterate(&handler); @@ -489,8 +492,8 @@ TEST_F(WriteBatchTest, DISABLED_LargeKeyValue) { struct NoopHandler : public WriteBatch::Handler { int num_seen = 0; - virtual Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status PutCF(uint32_t /*column_family_id*/, const Slice& key, + const Slice& value) override { EXPECT_EQ(kKeyValueSize, key.size()); EXPECT_EQ(kKeyValueSize, value.size()); EXPECT_EQ('A' + num_seen, key[0]); @@ -500,23 +503,23 @@ TEST_F(WriteBatchTest, DISABLED_LargeKeyValue) { ++num_seen; return Status::OK(); } - virtual Status DeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status DeleteCF(uint32_t /*column_family_id*/, + const Slice& /*key*/) override { ADD_FAILURE(); return Status::OK(); } - virtual Status SingleDeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status SingleDeleteCF(uint32_t /*column_family_id*/, + const Slice& /*key*/) override { ADD_FAILURE(); return Status::OK(); } - virtual Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status MergeCF(uint32_t /*column_family_id*/, const Slice& /*key*/, + const Slice& /*value*/) override { ADD_FAILURE(); return Status::OK(); } - virtual void LogData(const Slice& blob) override { ADD_FAILURE(); } - virtual bool Continue() override { return num_seen < 2; } + void LogData(const Slice& /*blob*/) override { ADD_FAILURE(); } + bool Continue() override { return num_seen < 2; } } handler; batch.Iterate(&handler); @@ -528,31 +531,30 @@ TEST_F(WriteBatchTest, Continue) { struct Handler : public TestHandler { int num_seen = 0; - virtual Status PutCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status PutCF(uint32_t column_family_id, const Slice& key, + const Slice& value) override { ++num_seen; return TestHandler::PutCF(column_family_id, key, value); } - virtual Status DeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status DeleteCF(uint32_t column_family_id, const Slice& key) override { ++num_seen; return TestHandler::DeleteCF(column_family_id, key); } - virtual Status SingleDeleteCF(uint32_t column_family_id, - const Slice& key) override { + Status SingleDeleteCF(uint32_t column_family_id, + const Slice& key) override { ++num_seen; return TestHandler::SingleDeleteCF(column_family_id, key); } - virtual Status MergeCF(uint32_t column_family_id, const Slice& key, - const Slice& value) override { + Status MergeCF(uint32_t column_family_id, const Slice& key, + const Slice& value) override { ++num_seen; return TestHandler::MergeCF(column_family_id, key, value); } - virtual void LogData(const Slice& blob) override { + void LogData(const Slice& blob) override { ++num_seen; TestHandler::LogData(blob); } - virtual bool Continue() override { return num_seen < 5; } + bool Continue() override { return num_seen < 5; } } handler; batch.Put(Slice("k1"), Slice("v1")); diff --git a/thirdparty/rocksdb/db/write_callback_test.cc b/thirdparty/rocksdb/db/write_callback_test.cc index d2bf30a093..cb880560ef 100644 --- a/thirdparty/rocksdb/db/write_callback_test.cc +++ b/thirdparty/rocksdb/db/write_callback_test.cc @@ -29,7 +29,7 @@ class WriteCallbackTest : public testing::Test { string dbname; WriteCallbackTest() { - dbname = test::TmpDir() + "/write_callback_testdb"; + dbname = test::PerThreadDBPath("write_callback_testdb"); } }; @@ -54,9 +54,7 @@ class WriteCallbackTestWriteCallback1 : public WriteCallback { class WriteCallbackTestWriteCallback2 : public WriteCallback { public: - Status Callback(DB *db) override { - return Status::Busy(); - } + Status Callback(DB* /*db*/) override { return Status::Busy(); } bool AllowWriteBatching() override { return true; } }; @@ -74,7 +72,7 @@ class MockWriteCallback : public WriteCallback { was_called_.store(other.was_called_.load()); } - Status Callback(DB* db) override { + Status Callback(DB* /*db*/) override { was_called_.store(true); if (should_fail_) { return Status::Busy(); @@ -126,6 +124,7 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) { {false, false, true, false, true}, }; + for (auto& seq_per_batch : {true, false}) { for (auto& two_queues : {true, false}) { for (auto& allow_parallel : {true, false}) { for (auto& allow_batching : {true, false}) { @@ -136,14 +135,34 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) { options.create_if_missing = true; options.allow_concurrent_memtable_write = allow_parallel; options.enable_pipelined_write = enable_pipelined_write; - options.concurrent_prepare = two_queues; + options.two_write_queues = two_queues; + if (options.enable_pipelined_write && seq_per_batch) { + // This combination is not supported + continue; + } + if (options.enable_pipelined_write && options.two_write_queues) { + // This combination is not supported + continue; + } ReadOptions read_options; DB* db; DBImpl* db_impl; DestroyDB(dbname, options); - ASSERT_OK(DB::Open(options, dbname, &db)); + + DBOptions db_options(options); + ColumnFamilyOptions cf_options(options); + std::vector column_families; + column_families.push_back( + ColumnFamilyDescriptor(kDefaultColumnFamilyName, cf_options)); + std::vector handles; + auto open_s = + DBImpl::Open(db_options, dbname, column_families, &handles, + &db, seq_per_batch, true /* batch_per_txn */); + ASSERT_OK(open_s); + assert(handles.size() == 1); + delete handles[0]; db_impl = dynamic_cast(db); ASSERT_TRUE(db_impl); @@ -259,16 +278,41 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) { string sval(10, my_key); write_op.Put(skey, sval); - if (!write_op.callback_.should_fail_) { + if (!write_op.callback_.should_fail_ && !seq_per_batch) { seq.fetch_add(1); } } + if (!write_op.callback_.should_fail_ && seq_per_batch) { + seq.fetch_add(1); + } WriteOptions woptions; woptions.disableWAL = !enable_WAL; woptions.sync = enable_WAL; - Status s = db_impl->WriteWithCallback( - woptions, &write_op.write_batch_, &write_op.callback_); + Status s; + if (seq_per_batch) { + class PublishSeqCallback : public PreReleaseCallback { + public: + PublishSeqCallback(DBImpl* db_impl_in) + : db_impl_(db_impl_in) {} + Status Callback(SequenceNumber last_seq, bool /*not used*/, + uint64_t) override { + db_impl_->SetLastPublishedSequence(last_seq); + return Status::OK(); + } + DBImpl* db_impl_; + } publish_seq_callback(db_impl); + // seq_per_batch requires a natural batch separator or Noop + WriteBatchInternal::InsertNoop(&write_op.write_batch_); + const size_t ONE_BATCH = 1; + s = db_impl->WriteImpl( + woptions, &write_op.write_batch_, &write_op.callback_, + nullptr, 0, false, nullptr, ONE_BATCH, + two_queues ? &publish_seq_callback : nullptr); + } else { + s = db_impl->WriteWithCallback( + woptions, &write_op.write_batch_, &write_op.callback_); + } if (write_op.callback_.should_fail_) { ASSERT_TRUE(s.IsBusy()); @@ -305,7 +349,7 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) { } } - ASSERT_EQ(seq.load(), db_impl->GetLatestSequenceNumber()); + ASSERT_EQ(seq.load(), db_impl->TEST_GetLastVisibleSequence()); delete db; DestroyDB(dbname, options); @@ -316,6 +360,7 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) { } } } +} TEST_F(WriteCallbackTest, WriteCallBackTest) { Options options; @@ -388,7 +433,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as WriteWithCallback is not supported in ROCKSDB_LITE\n"); return 0; diff --git a/thirdparty/rocksdb/db/write_controller_test.cc b/thirdparty/rocksdb/db/write_controller_test.cc index a1fe3fa27e..55feb00a33 100644 --- a/thirdparty/rocksdb/db/write_controller_test.cc +++ b/thirdparty/rocksdb/db/write_controller_test.cc @@ -18,7 +18,7 @@ class TimeSetEnv : public EnvWrapper { public: explicit TimeSetEnv() : EnvWrapper(nullptr) {} uint64_t now_micros_ = 6666; - virtual uint64_t NowNanos() override { return now_micros_ * std::milli::den; } + uint64_t NowNanos() override { return now_micros_ * std::milli::den; } }; TEST_F(WriteControllerTest, ChangeDelayRateTest) { diff --git a/thirdparty/rocksdb/db/write_thread.cc b/thirdparty/rocksdb/db/write_thread.cc index 2d3b34602c..835992c8fc 100644 --- a/thirdparty/rocksdb/db/write_thread.cc +++ b/thirdparty/rocksdb/db/write_thread.cc @@ -7,6 +7,7 @@ #include #include #include "db/column_family.h" +#include "monitoring/perf_context_imp.h" #include "port/port.h" #include "util/random.h" #include "util/sync_point.h" @@ -23,7 +24,10 @@ WriteThread::WriteThread(const ImmutableDBOptions& db_options) enable_pipelined_write_(db_options.enable_pipelined_write), newest_writer_(nullptr), newest_memtable_writer_(nullptr), - last_sequence_(0) {} + last_sequence_(0), + write_stall_dummy_(), + stall_mu_(), + stall_cv_(&stall_mu_) {} uint8_t WriteThread::BlockingAwaitState(Writer* w, uint8_t goal_mask) { // We're going to block. Lazily create the mutex. We guarantee @@ -73,6 +77,10 @@ uint8_t WriteThread::AwaitState(Writer* w, uint8_t goal_mask, port::AsmVolatilePause(); } + // This is below the fast path, so that the stat is zero when all writes are + // from the same thread. + PERF_TIMER_GUARD(write_thread_wait_nanos); + // If we're only going to end up waiting a short period of time, // it can be a lot more efficient to call std::this_thread::yield() // in a loop than to block in StateMutex(). For reference, on my 4.0 @@ -173,6 +181,7 @@ uint8_t WriteThread::AwaitState(Writer* w, uint8_t goal_mask, } if ((state & goal_mask) == 0) { + TEST_SYNC_POINT_CALLBACK("WriteThread::AwaitState:BlockingWaiting", w); state = BlockingAwaitState(w, goal_mask); } @@ -214,6 +223,28 @@ bool WriteThread::LinkOne(Writer* w, std::atomic* newest_writer) { assert(w->state == STATE_INIT); Writer* writers = newest_writer->load(std::memory_order_relaxed); while (true) { + // If write stall in effect, and w->no_slowdown is not true, + // block here until stall is cleared. If its true, then return + // immediately + if (writers == &write_stall_dummy_) { + if (w->no_slowdown) { + w->status = Status::Incomplete("Write stall"); + SetState(w, STATE_COMPLETED); + return false; + } + // Since no_slowdown is false, wait here to be notified of the write + // stall clearing + { + MutexLock lock(&stall_mu_); + writers = newest_writer->load(std::memory_order_relaxed); + if (writers == &write_stall_dummy_) { + stall_cv_.Wait(); + // Load newest_writers_ again since it may have changed + writers = newest_writer->load(std::memory_order_relaxed); + continue; + } + } + } w->link_older = writers; if (newest_writer->compare_exchange_weak(writers, w)) { return (writers == nullptr); @@ -258,6 +289,17 @@ void WriteThread::CreateMissingNewerLinks(Writer* head) { } } +WriteThread::Writer* WriteThread::FindNextLeader(Writer* from, + Writer* boundary) { + assert(from != nullptr && from != boundary); + Writer* current = from; + while (current->link_older != boundary) { + current = current->link_older; + assert(current != nullptr); + } + return current; +} + void WriteThread::CompleteLeader(WriteGroup& write_group) { assert(write_group.size > 0); Writer* leader = write_group.leader; @@ -287,12 +329,44 @@ void WriteThread::CompleteFollower(Writer* w, WriteGroup& write_group) { SetState(w, STATE_COMPLETED); } +void WriteThread::BeginWriteStall() { + LinkOne(&write_stall_dummy_, &newest_writer_); + + // Walk writer list until w->write_group != nullptr. The current write group + // will not have a mix of slowdown/no_slowdown, so its ok to stop at that + // point + Writer* w = write_stall_dummy_.link_older; + Writer* prev = &write_stall_dummy_; + while (w != nullptr && w->write_group == nullptr) { + if (w->no_slowdown) { + prev->link_older = w->link_older; + w->status = Status::Incomplete("Write stall"); + SetState(w, STATE_COMPLETED); + w = prev->link_older; + } else { + prev = w; + w = w->link_older; + } + } +} + +void WriteThread::EndWriteStall() { + MutexLock lock(&stall_mu_); + + assert(newest_writer_.load(std::memory_order_relaxed) == &write_stall_dummy_); + newest_writer_.exchange(write_stall_dummy_.link_older); + + // Wake up writers + stall_cv_.SignalAll(); +} + static WriteThread::AdaptationContext jbg_ctx("JoinBatchGroup"); void WriteThread::JoinBatchGroup(Writer* w) { TEST_SYNC_POINT_CALLBACK("WriteThread::JoinBatchGroup:Start", w); assert(w->batch != nullptr); bool linked_as_leader = LinkOne(w, &newest_writer_); + if (linked_as_leader) { SetState(w, STATE_GROUP_LEADER); } @@ -313,6 +387,7 @@ void WriteThread::JoinBatchGroup(Writer* w) { * 3.2) an existing memtable writer group leader tell us to finish memtable * writes in parallel. */ + TEST_SYNC_POINT_CALLBACK("WriteThread::JoinBatchGroup:BeganWaiting", w); AwaitState(w, STATE_GROUP_LEADER | STATE_MEMTABLE_WRITER_LEADER | STATE_PARALLEL_MEMTABLE_WRITER | STATE_COMPLETED, &jbg_ctx); @@ -394,6 +469,7 @@ size_t WriteThread::EnterAsBatchGroupLeader(Writer* leader, write_group->last_writer = w; write_group->size++; } + TEST_SYNC_POINT_CALLBACK("WriteThread::EnterAsBatchGroupLeader:End", w); return size; } @@ -455,7 +531,8 @@ void WriteThread::EnterAsMemTableWriter(Writer* leader, last_writer->sequence + WriteBatchInternal::Count(last_writer->batch) - 1; } -void WriteThread::ExitAsMemTableWriter(Writer* self, WriteGroup& write_group) { +void WriteThread::ExitAsMemTableWriter(Writer* /*self*/, + WriteGroup& write_group) { Writer* leader = write_group.leader; Writer* last_writer = write_group.last_writer; @@ -532,6 +609,11 @@ void WriteThread::ExitAsBatchGroupLeader(WriteGroup& write_group, Writer* last_writer = write_group.last_writer; assert(leader->link_older == nullptr); + // Propagate memtable write error to the whole group. + if (status.ok() && !write_group.status.ok()) { + status = write_group.status; + } + if (enable_pipelined_write_) { // Notify writers don't write to memtable to exit. for (Writer* w = last_writer; w != leader;) { @@ -545,21 +627,49 @@ void WriteThread::ExitAsBatchGroupLeader(WriteGroup& write_group, if (!leader->ShouldWriteToMemtable()) { CompleteLeader(write_group); } + + Writer* next_leader = nullptr; + + // Look for next leader before we call LinkGroup. If there isn't + // pending writers, place a dummy writer at the tail of the queue + // so we know the boundary of the current write group. + Writer dummy; + Writer* expected = last_writer; + bool has_dummy = newest_writer_.compare_exchange_strong(expected, &dummy); + if (!has_dummy) { + // We find at least one pending writer when we insert dummy. We search + // for next leader from there. + next_leader = FindNextLeader(expected, last_writer); + assert(next_leader != nullptr && next_leader != last_writer); + } + // Link the ramaining of the group to memtable writer list. + // + // We have to link our group to memtable writer queue before wake up the + // next leader or set newest_writer_ to null, otherwise the next leader + // can run ahead of us and link to memtable writer queue before we do. if (write_group.size > 0) { if (LinkGroup(write_group, &newest_memtable_writer_)) { // The leader can now be different from current writer. SetState(write_group.leader, STATE_MEMTABLE_WRITER_LEADER); } } - // Reset newest_writer_ and wake up the next leader. - Writer* newest_writer = last_writer; - if (!newest_writer_.compare_exchange_strong(newest_writer, nullptr)) { - Writer* next_leader = newest_writer; - while (next_leader->link_older != last_writer) { - next_leader = next_leader->link_older; - assert(next_leader != nullptr); + + // If we have inserted dummy in the queue, remove it now and check if there + // are pending writer join the queue since we insert the dummy. If so, + // look for next leader again. + if (has_dummy) { + assert(next_leader == nullptr); + expected = &dummy; + bool has_pending_writer = + !newest_writer_.compare_exchange_strong(expected, nullptr); + if (has_pending_writer) { + next_leader = FindNextLeader(expected, &dummy); + assert(next_leader != nullptr && next_leader != &dummy); } + } + + if (next_leader != nullptr) { next_leader->link_older = nullptr; SetState(next_leader, STATE_GROUP_LEADER); } diff --git a/thirdparty/rocksdb/db/write_thread.h b/thirdparty/rocksdb/db/write_thread.h index 57ce71e08f..dc9c22ff87 100644 --- a/thirdparty/rocksdb/db/write_thread.h +++ b/thirdparty/rocksdb/db/write_thread.h @@ -14,6 +14,8 @@ #include #include +#include "db/dbformat.h" +#include "db/pre_release_callback.h" #include "db/write_callback.h" #include "monitoring/instrumented_mutex.h" #include "rocksdb/options.h" @@ -116,6 +118,8 @@ class WriteThread { bool no_slowdown; bool disable_wal; bool disable_memtable; + size_t batch_cnt; // if non-zero, number of sub-batches in the write batch + PreReleaseCallback* pre_release_callback; uint64_t log_used; // log number that this batch was inserted into uint64_t log_ref; // log number that memtable insert should reference WriteCallback* callback; @@ -123,8 +127,9 @@ class WriteThread { std::atomic state; // write under StateMutex() or pre-link WriteGroup* write_group; SequenceNumber sequence; // the sequence number to use for the first key - Status status; // status of memtable inserter + Status status; Status callback_status; // status returned by callback->Callback() + std::aligned_storage::type state_mutex_bytes; std::aligned_storage::type state_cv_bytes; Writer* link_older; // read/write only before linking, or as leader @@ -136,28 +141,36 @@ class WriteThread { no_slowdown(false), disable_wal(false), disable_memtable(false), + batch_cnt(0), + pre_release_callback(nullptr), log_used(0), log_ref(0), callback(nullptr), made_waitable(false), state(STATE_INIT), write_group(nullptr), + sequence(kMaxSequenceNumber), link_older(nullptr), link_newer(nullptr) {} Writer(const WriteOptions& write_options, WriteBatch* _batch, - WriteCallback* _callback, uint64_t _log_ref, bool _disable_memtable) + WriteCallback* _callback, uint64_t _log_ref, bool _disable_memtable, + size_t _batch_cnt = 0, + PreReleaseCallback* _pre_release_callback = nullptr) : batch(_batch), sync(write_options.sync), no_slowdown(write_options.no_slowdown), disable_wal(write_options.disableWAL), disable_memtable(_disable_memtable), + batch_cnt(_batch_cnt), + pre_release_callback(_pre_release_callback), log_used(0), log_ref(_log_ref), callback(_callback), made_waitable(false), state(STATE_INIT), write_group(nullptr), + sequence(kMaxSequenceNumber), link_older(nullptr), link_newer(nullptr) {} @@ -329,6 +342,13 @@ class WriteThread { return last_sequence_; } + // Insert a dummy writer at the tail of the write queue to indicate a write + // stall, and fail any writers in the queue with no_slowdown set to true + void BeginWriteStall(); + + // Remove the dummy writer and wake up waiting writers + void EndWriteStall(); + private: // See AwaitState. const uint64_t max_yield_usec_; @@ -352,6 +372,17 @@ class WriteThread { // is not necessary visible to reads because the writer can be ongoing. SequenceNumber last_sequence_; + // A dummy writer to indicate a write stall condition. This will be inserted + // at the tail of the writer queue by the leader, so newer writers can just + // check for this and bail + Writer write_stall_dummy_; + + // Mutex and condvar for writers to block on a write stall. During a write + // stall, writers with no_slowdown set to false will wait on this rather + // on the writer queue + port::Mutex stall_mu_; + port::CondVar stall_cv_; + // Waits for w->state & goal_mask using w->StateMutex(). Returns // the state that satisfies goal_mask. uint8_t BlockingAwaitState(Writer* w, uint8_t goal_mask); @@ -379,6 +410,10 @@ class WriteThread { // concurrently with itself. void CreateMissingNewerLinks(Writer* head); + // Starting from a pending writer, follow link_older to search for next + // leader, until we hit boundary. + Writer* FindNextLeader(Writer* pending_writer, Writer* boundary); + // Set the leader in write_group to completed state and remove it from the // write group. void CompleteLeader(WriteGroup& write_group); diff --git a/thirdparty/rocksdb/defs.bzl b/thirdparty/rocksdb/defs.bzl new file mode 100644 index 0000000000..4468cebdd2 --- /dev/null +++ b/thirdparty/rocksdb/defs.bzl @@ -0,0 +1,31 @@ +load("@fbcode_macros//build_defs:cpp_binary.bzl", "cpp_binary") +load("@fbcode_macros//build_defs:custom_unittest.bzl", "custom_unittest") + +def test_binary( + test_name, + test_cc, + parallelism, + rocksdb_arch_preprocessor_flags, + rocksdb_compiler_flags, + rocksdb_preprocessor_flags, + rocksdb_external_deps): + TEST_RUNNER = native.package_name() + "/buckifier/rocks_test_runner.sh" + + ttype = "gtest" if parallelism == "parallel" else "simple" + test_bin = test_name + "_bin" + + cpp_binary( + name = test_bin, + srcs = [test_cc], + arch_preprocessor_flags = rocksdb_arch_preprocessor_flags, + compiler_flags = rocksdb_compiler_flags, + preprocessor_flags = rocksdb_preprocessor_flags, + deps = [":rocksdb_test_lib"], + external_deps = rocksdb_external_deps, + ) + + custom_unittest( + name = test_name, + command = [TEST_RUNNER, "$(location :{})".format(test_bin)], + type = ttype, + ) diff --git a/thirdparty/rocksdb/docs/.gitignore b/thirdparty/rocksdb/docs/.gitignore new file mode 100644 index 0000000000..e48dc98be8 --- /dev/null +++ b/thirdparty/rocksdb/docs/.gitignore @@ -0,0 +1,9 @@ +.DS_STORE +_site/ +*.swo +*.swp +_site +.sass-cache +*.psd +*~ + diff --git a/thirdparty/rocksdb/docs/CNAME b/thirdparty/rocksdb/docs/CNAME new file mode 100644 index 0000000000..827d1c0ed1 --- /dev/null +++ b/thirdparty/rocksdb/docs/CNAME @@ -0,0 +1 @@ +rocksdb.org \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/CONTRIBUTING.md b/thirdparty/rocksdb/docs/CONTRIBUTING.md new file mode 100644 index 0000000000..2c5842fb45 --- /dev/null +++ b/thirdparty/rocksdb/docs/CONTRIBUTING.md @@ -0,0 +1,115 @@ +This provides guidance on how to contribute various content to `rocksdb.org`. + +## Getting started + +You should only have to do these one time. + +- Rename this file to `CONTRIBUTING.md`. +- Rename `EXAMPLE-README-FOR-RUNNING-DOCS.md` to `README.md` (replacing the existing `README.md` that came with the template). +- Rename `EXAMPLE-LICENSE` to `LICENSE`. +- Review the [template information](./TEMPLATE-INFORMATION.md). +- Review `./_config.yml`. +- Make sure you update `title`, `description`, `tagline` and `gacode` (Google Analytics) in `./_config.yml`. + +## Basic Structure + +Most content is written in markdown. You name the file `something.md`, then have a header that looks like this: + +``` +--- +docid: getting-started +title: Getting started with ProjectName +layout: docs +permalink: /docs/getting-started.html +--- +``` + +Customize these values for each document, blog post, etc. + +> The filename of the `.md` file doesn't actually matter; what is important is the `docid` being unique and the `permalink` correct and unique too). + +## Landing page + +Modify `index.md` with your new or updated content. + +If you want a `GridBlock` as part of your content, you can do so directly with HTML: + +``` +
+ + +
+
+

More information

+

+ Stuff here +

+
+
+
+``` + +or with a combination of changing `./_data/features.yml` and adding some Liquid to `index.md`, such as: + +``` +{% include content/gridblocks.html data_source=site.data.features imagealign="bottom"%} +``` + +## Blog + +To modify a blog post, edit the appopriate markdown file in `./_posts/`. + +Adding a new blog post is a four-step process. + +> Some posts have a `permalink` and `comments` in the blog post YAML header. You will not need these for new blog posts. These are an artifact of migrating the blog from Wordpress to gh-pages. + +1. Create your blog post in `./_posts/` in markdown (file extension `.md` or `.markdown`). See current posts in that folder or `./doc-type-examples/2016-04-07-blog-post-example.md` for an example of the YAML format. **If the `./_posts` directory does not exist, create it**. + - You can add a `` tag in the middle of your post such that you show only the excerpt above that tag in the main `/blog` index on your page. +1. If you have not authored a blog post before, modify the `./_data/authors.yml` file with the `author` id you used in your blog post, along with your full name and Facebook ID to get your profile picture. +1. [Run the site locally](./README.md) to test your changes. It will be at `http://127.0.0.1/blog/your-new-blog-post-title.html` +1. Push your changes to GitHub. + +## Docs + +To modify docs, edit the appropriate markdown file in `./_docs/`. + +To add docs to the site.... + +1. Add your markdown file to the `./_docs/` folder. See `./doc-type-examples/docs-hello-world.md` for an example of the YAML header format. **If the `./_docs/` directory does not exist, create it**. + - You can use folders in the `./_docs/` directory to organize your content if you want. +1. Update `_data/nav_docs.yml` to add your new document to the navigation bar. Use the `docid` you put in your doc markdown in as the `id` in the `_data/nav_docs.yml` file. +1. [Run the site locally](./README.md) to test your changes. It will be at `http://127.0.0.1/docs/your-new-doc-permalink.html` +1. Push your changes to GitHub. + +## Header Bar + +To modify the header bar, change `./_data/nav.yml`. + +## Top Level Page + +To modify a top-level page, edit the appropriate markdown file in `./top-level/` + +If you want a top-level page (e.g., http://your-site.com/top-level.html) -- not in `/blog/` or `/docs/`.... + +1. Create a markdown file in the root `./top-level/`. See `./doc-type-examples/top-level-example.md` for more information. +1. If you want a visible link to that file, update `_data/nav.yml` to add a link to your new top-level document in the header bar. + + > This is not necessary if you just want to have a page that is linked to from another page, but not exposed as direct link to the user. + +1. [Run the site locally](./README.md) to test your changes. It will be at `http://127.0.0.1/your-top-level-page-permalink.html` +1. Push your changes to GitHub. + +## Other Changes + +- CSS: `./css/main.css` or `./_sass/*.scss`. +- Images: `./static/images/[docs | posts]/....` +- Main Blog post HTML: `./_includes/post.html` +- Main Docs HTML: `./_includes/doc.html` diff --git a/thirdparty/rocksdb/docs/Gemfile b/thirdparty/rocksdb/docs/Gemfile new file mode 100644 index 0000000000..93dc8b0d7f --- /dev/null +++ b/thirdparty/rocksdb/docs/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gem 'github-pages', '~> 104' diff --git a/thirdparty/rocksdb/docs/Gemfile.lock b/thirdparty/rocksdb/docs/Gemfile.lock new file mode 100644 index 0000000000..78dc919a98 --- /dev/null +++ b/thirdparty/rocksdb/docs/Gemfile.lock @@ -0,0 +1,146 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (4.2.7) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.4.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + colorator (1.1.0) + concurrent-ruby (1.0.5) + ethon (0.11.0) + ffi (>= 1.3.0) + execjs (2.7.0) + faraday (0.15.2) + multipart-post (>= 1.2, < 3) + ffi (1.9.25) + forwardable-extended (2.6.0) + gemoji (2.1.0) + github-pages (104) + activesupport (= 4.2.7) + github-pages-health-check (= 1.2.0) + jekyll (>= 3.8.4) + jekyll-avatar (= 0.4.2) + jekyll-coffeescript (= 1.0.1) + jekyll-feed (= 0.8.0) + jekyll-gist (= 1.4.0) + jekyll-github-metadata (= 2.2.0) + jekyll-mentions (= 1.2.0) + jekyll-paginate (= 1.1.0) + jekyll-redirect-from (= 0.11.0) + jekyll-sass-converter (= 1.3.0) + jekyll-seo-tag (= 2.1.0) + jekyll-sitemap (= 0.12.0) + jekyll-swiss (= 0.4.0) + jemoji (= 0.7.0) + kramdown (= 1.11.1) + liquid (= 3.0.6) + listen (= 3.0.6) + mercenary (~> 0.3) + minima (= 2.0.0) + rouge (= 1.11.1) + terminal-table (~> 1.4) + github-pages-health-check (1.2.0) + addressable (~> 2.3) + net-dns (~> 0.8) + octokit (~> 4.0) + public_suffix (~> 1.4) + typhoeus (~> 0.7) + html-pipeline (2.4.2) + activesupport (>= 2) + nokogiri (~> 1.8.2) + i18n (0.7.0) + jekyll (3.8.4) + addressable (~> 2.4) + colorator (~> 1.0) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 1.1) + kramdown (~> 1.3) + liquid (~> 3.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (~> 1.7) + safe_yaml (~> 1.0) + jekyll-avatar (0.4.2) + jekyll (~> 3.0) + jekyll-coffeescript (1.0.1) + coffee-script (~> 2.2) + jekyll-feed (0.8.0) + jekyll (~> 3.3) + jekyll-gist (1.4.0) + octokit (~> 4.2) + jekyll-github-metadata (2.2.0) + jekyll (~> 3.1) + octokit (~> 4.0, != 4.4.0) + jekyll-mentions (1.2.0) + activesupport (~> 4.0) + html-pipeline (~> 2.3) + jekyll (~> 3.0) + jekyll-paginate (1.1.0) + jekyll-redirect-from (0.11.0) + jekyll (>= 2.0) + jekyll-sass-converter (1.3.0) + sass (~> 3.2) + jekyll-seo-tag (2.1.0) + jekyll (~> 3.3) + jekyll-sitemap (0.12.0) + jekyll (~> 3.3) + jekyll-swiss (0.4.0) + jekyll-watch (1.5.0) + listen (~> 3.0, < 3.1) + jemoji (0.7.0) + activesupport (~> 4.0) + gemoji (~> 2.0) + html-pipeline (~> 2.2) + jekyll (>= 3.0) + json (1.8.3) + kramdown (1.11.1) + liquid (3.0.6) + listen (3.0.6) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9.7) + mercenary (0.3.6) + mini_portile2 (2.3.0) + minima (2.0.0) + minitest (5.9.1) + multipart-post (2.0.0) + net-dns (0.8.0) + nokogiri (~> 1.8.2) + mini_portile2 (~> 2.3.0) + octokit (4.4.1) + sawyer (~> 0.7.0, >= 0.5.3) + pathutil (0.14.0) + forwardable-extended (~> 2.6) + public_suffix (1.5.3) + rb-fsevent (0.9.8) + rb-inotify (0.9.7) + ffi (>= 0.5.0) + rouge (1.11.1) + safe_yaml (1.0.4) + sass (3.4.22) + sawyer (0.7.0) + addressable (>= 2.3.5, < 2.5) + faraday (~> 0.8, < 0.10) + terminal-table (1.7.3) + unicode-display_width (~> 1.1.1) + thread_safe (0.3.5) + typhoeus (0.8.0) + ethon (>= 0.8.0) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unicode-display_width (1.1.1) + +PLATFORMS + ruby + +DEPENDENCIES + github-pages (~> 104) + +BUNDLED WITH + 1.13.1 diff --git a/thirdparty/rocksdb/docs/LICENSE-DOCUMENTATION b/thirdparty/rocksdb/docs/LICENSE-DOCUMENTATION new file mode 100644 index 0000000000..1f255c9f37 --- /dev/null +++ b/thirdparty/rocksdb/docs/LICENSE-DOCUMENTATION @@ -0,0 +1,385 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + +b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + +c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + +d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + +e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + +f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + +g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + +h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + +i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + +j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + +k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + +Section 2 -- Scope. + +a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + +b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + +a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + +a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + +b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + +c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + +a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + +b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + +c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + +Section 6 -- Term and Termination. + +a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + +b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + +c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + +d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + +Section 7 -- Other Terms and Conditions. + +a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + +b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + +Section 8 -- Interpretation. + +a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + +b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + +c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + +d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public licenses. +Notwithstanding, Creative Commons may elect to apply one of its public +licenses to material it publishes and in those instances will be +considered the "Licensor." Except for the limited purpose of indicating +that material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the public +licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/thirdparty/rocksdb/docs/README.md b/thirdparty/rocksdb/docs/README.md new file mode 100644 index 0000000000..0ae8978bcb --- /dev/null +++ b/thirdparty/rocksdb/docs/README.md @@ -0,0 +1,80 @@ +## User Documentation for rocksdb.org + +This directory will contain the user and feature documentation for RocksDB. The documentation will be hosted on GitHub pages. + +### Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md) for details on how to add or modify content. + +### Run the Site Locally + +The requirements for running a GitHub pages site locally is described in [GitHub help](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/#requirements). The steps below summarize these steps. + +> If you have run the site before, you can start with step 1 and then move on to step 5. + +1. Ensure that you are in the `/docs` directory in your local RocksDB clone (i.e., the same directory where this `README.md` exists). The below RubyGems commands, etc. must be run from there. + +1. Make sure you have Ruby and [RubyGems](https://rubygems.org/) installed. + + > Ruby >= 2.2 is required for the gems. On the latest versions of Mac OS X, Ruby 2.0 is the + > default. Use `brew install ruby` (or your preferred upgrade mechanism) to install a newer + > version of Ruby for your Mac OS X system. + +1. Make sure you have [Bundler](http://bundler.io/) installed. + + ``` + # may require sudo + gem install bundler + ``` +1. Install the project's dependencies + + ``` + # run this in the 'docs' directory + bundle install + ``` + + > If you get an error when installing `nokogiri`, you may be running into the problem described + > in [this nokogiri issue](https://github.com/sparklemotion/nokogiri/issues/1483). You can + > either `brew uninstall xz` (and then `brew install xz` after the bundle is installed) or + > `xcode-select --install` (although this may not work if you have already installed command + > line tools). + +1. Run Jekyll's server. + + - On first runs or for structural changes to the documentation (e.g., new sidebar menu item), do a full build. + + ``` + bundle exec jekyll serve + ``` + + - For content changes only, you can use `--incremental` for faster builds. + + ``` + bundle exec jekyll serve --incremental + ``` + + > We use `bundle exec` instead of running straight `jekyll` because `bundle exec` will always use the version of Jekyll from our `Gemfile`. Just running `jekyll` will use the system version and may not necessarily be compatible. + + - To run using an actual IP address, you can use `--host=0.0.0.0` + + ``` + bundle exec jekyll serve --host=0.0.0.0 + ``` + + This will allow you to use the IP address associated with your machine in the URL. That way you could share it with other people. + + e.g., on a Mac, you can your IP address with something like `ifconfig | grep "inet " | grep -v 127.0.0.1`. + +1. Either of commands in the previous step will serve up the site on your local device at http://127.0.0.1:4000/ or http://localhost:4000. + +### Updating the Bundle + +The site depends on Github Pages and the installed bundle is based on the `github-pages` gem. +Occasionally that gem might get updated with new or changed functionality. If that is the case, +you can run: + +``` +bundle update +``` + +to get the latest packages for the installation. diff --git a/thirdparty/rocksdb/docs/TEMPLATE-INFORMATION.md b/thirdparty/rocksdb/docs/TEMPLATE-INFORMATION.md new file mode 100644 index 0000000000..9175bc0c29 --- /dev/null +++ b/thirdparty/rocksdb/docs/TEMPLATE-INFORMATION.md @@ -0,0 +1,17 @@ +## Template Details + +First, go through `_config.yml` and adjust the available settings to your project's standard. When you make changes here, you'll have to kill the `jekyll serve` instance and restart it to see those changes, but that's only the case with the config file. + +Next, update some image assets - you'll want to update `favicon.png`, `logo.svg`, and `og_image.png` (used for Like button stories and Shares on Facbeook) in the `static` folder with your own logos. + +Next, if you're going to have docs on your site, keep the `_docs` and `docs` folders, if not, you can safely remove them (or you can safely leave them and not include them in your navigation - Jekyll renders all of this before a client views the site anyway, so there's no performance hit from just leaving it there for a future expansion). + +Same thing with a blog section, either keep or delete the `_posts` and `blog` folders. + +You can customize your homepage in three parts - the first in the homepage header, which is mostly automatically derived from the elements you insert into your config file. However, you can also specify a series of 'promotional' elements in `_data/promo.yml`. You can read that file for more information. + +The second place for your homepage is in `index.md` which contains the bulk of the main content below the header. This is all markdown if you want, but you can use HTML and Jekyll's template tags (called Liquid) in there too. Checkout this folder's index.md for an example of one common template tag that we use on our sites called gridblocks. + +The third and last place is in the `_data/powered_by.yml` and `_data/powered_by_highlight.yml` files. Both these files combine to create a section on the homepage that is intended to show a list of companies or apps that are using your project. The `powered_by_highlight` file is a list of curated companies/apps that you want to show as a highlight at the top of this section, including their logos in whatever format you want. The `powered_by` file is a more open list that is just text links to the companies/apps and can be updated via Pull Request by the community. If you don't want these sections on your homepage, just empty out both files and leave them blank. + +The last thing you'll want to do is setup your top level navigation bar. You can do this by editing `nav.yml` and keeping the existing title/href/category structure used there. Although the nav is responsive and fairly flexible design-wise, no more than 5 or 6 nav items is recommended. diff --git a/thirdparty/rocksdb/docs/_config.yml b/thirdparty/rocksdb/docs/_config.yml new file mode 100644 index 0000000000..2e5cee097f --- /dev/null +++ b/thirdparty/rocksdb/docs/_config.yml @@ -0,0 +1,85 @@ +# Site settings +permalink: /blog/:year/:month/:day/:title.html +title: RocksDB +tagline: A persistent key-value store for fast storage environments +description: > + RocksDB is an embeddable persistent key-value store for fast storage. +fbappid: "1615782811974223" +gacode: "UA-49459723-1" +# baseurl determines the subpath of your site. For example if you're using an +# organisation.github.io/reponame/ basic site URL, then baseurl would be set +# as "/reponame" but leave blank if you have a top-level domain URL as it is +# now set to "" by default as discussed in: +# http://jekyllrb.com/news/2016/10/06/jekyll-3-3-is-here/ +baseurl: "" + +# the base hostname & protocol for your site +# If baseurl is set, then the absolute url for your site would be url/baseurl +# This was also be set to the right thing automatically for local development +# https://github.com/blog/2277-what-s-new-in-github-pages-with-jekyll-3-3 +# http://jekyllrb.com/news/2016/10/06/jekyll-3-3-is-here/ +url: "http://rocksdb.org" + +# Note: There are new filters in Jekyll 3.3 to help with absolute and relative urls +# absolute_url +# relative_url +# So you will see these used throughout the Jekyll code in this template. +# no more need for | prepend: site.url | prepend: site.baseurl +# http://jekyllrb.com/news/2016/10/06/jekyll-3-3-is-here/ +#https://github.com/blog/2277-what-s-new-in-github-pages-with-jekyll-3-3 + +# The GitHub repo for your project +ghrepo: "facebook/rocksdb" + +# Use these color settings to determine your colour scheme for the site. +color: + # primary should be a vivid color that reflects the project's brand + primary: "#2a2a2a" + # secondary should be a subtle light or dark color used on page backgrounds + secondary: "#f9f9f9" + # Use the following to specify whether the previous two colours are 'light' + # or 'dark' and therefore what colors can be overlaid on them + primary-overlay: "dark" + secondary-overlay: "light" + +#Uncomment this if you want to enable Algolia doc search with your own values +#searchconfig: +# apikey: "" +# indexname: "" + +# Blog posts are builtin to Jekyll by default, with the `_posts` directory. +# Here you can specify other types of documentation. The names here are `docs` +# and `top-level`. This means their content will be in `_docs` and `_top-level`. +# The permalink format is also given. +# http://ben.balter.com/2015/02/20/jekyll-collections/ +collections: + docs: + output: true + permalink: /docs/:name/ + top-level: + output: true + permalink: :name.html + +# DO NOT ADJUST BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE CHANGING + +markdown: kramdown +kramdown: + input: GFM + syntax_highlighter: rouge + + syntax_highlighter_opts: + css_class: 'rougeHighlight' + span: + line_numbers: false + block: + line_numbers: true + start_line: 1 + +sass: + style: :compressed + +redcarpet: + extensions: [with_toc_data] + +gems: + - jekyll-redirect-from diff --git a/thirdparty/rocksdb/docs/_data/authors.yml b/thirdparty/rocksdb/docs/_data/authors.yml new file mode 100644 index 0000000000..13225be9df --- /dev/null +++ b/thirdparty/rocksdb/docs/_data/authors.yml @@ -0,0 +1,70 @@ +icanadi: + full_name: Igor Canadi + fbid: 706165749 + +xjin: + full_name: Xing Jin + fbid: 100000739847320 + +leijin: + full_name: Lei Jin + fbid: 634570164 + +yhciang: + full_name: Yueh-Hsuan Chiang + fbid: 1619020986 + +radheshyam: + full_name: Radheshyam Balasundaram + fbid: 800837305 + +zagfox: + full_name: Feng Zhu + fbid: 100006493823622 + +lgalanis: + full_name: Leonidas Galanis + fbid: 8649950 + +sdong: + full_name: Siying Dong + fbid: 9805119 + +dmitrism: + full_name: Dmitri Smirnov + +rven2: + full_name: Venkatesh Radhakrishnan + fbid: 100008352697325 + +yiwu: + full_name: Yi Wu + fbid: 100000476362039 + +maysamyabandeh: + full_name: Maysam Yabandeh + fbid: 100003482360101 + +IslamAbdelRahman: + full_name: Islam AbdelRahman + fbid: 642759407 + +ajkr: + full_name: Andrew Kryczka + fbid: 568694102 + +abhimadan: + full_name: Abhishek Madan + fbid: 1850247869 + +sagar0: + full_name: Sagar Vemuri + fbid: 2419111 + +lightmark: + full_name: Aaron Gao + fbid: 1351549072 + +fgwu: + full_name: Fenggang Wu + fbid: 100002297362180 diff --git a/thirdparty/rocksdb/docs/_data/features.yml b/thirdparty/rocksdb/docs/_data/features.yml new file mode 100644 index 0000000000..d692c1849d --- /dev/null +++ b/thirdparty/rocksdb/docs/_data/features.yml @@ -0,0 +1,19 @@ +- title: High Performance + text: | + RocksDB uses a log structured database engine, written entirely in C++, for maximum performance. Keys and values are just arbitrarily-sized byte streams. + image: images/promo-performance.svg + +- title: Optimized for Fast Storage + text: | + RocksDB is optimized for fast, low latency storage such as flash drives and high-speed disk drives. RocksDB exploits the full potential of high read/write rates offered by flash or RAM. + image: images/promo-flash.svg + +- title: Adaptable + text: | + RocksDB is adaptable to different workloads. From database storage engines such as [MyRocks](https://github.com/facebook/mysql-5.6) to [application data caching](http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html) to embedded workloads, RocksDB can be used for a variety of data needs. + image: images/promo-adapt.svg + +- title: Basic and Advanced Database Operations + text: | + RocksDB provides basic operations such as opening and closing a database, reading and writing to more advanced operations such as merging and compaction filters. + image: images/promo-operations.svg diff --git a/thirdparty/rocksdb/docs/_data/nav.yml b/thirdparty/rocksdb/docs/_data/nav.yml new file mode 100644 index 0000000000..108de02545 --- /dev/null +++ b/thirdparty/rocksdb/docs/_data/nav.yml @@ -0,0 +1,30 @@ +- title: Docs + href: /docs/ + category: docs + +- title: GitHub + href: https://github.com/facebook/rocksdb/ + category: external + +- title: API (C++) + href: https://github.com/facebook/rocksdb/tree/master/include/rocksdb + category: external + +- title: API (Java) + href: https://github.com/facebook/rocksdb/tree/master/java/src/main/java/org/rocksdb + category: external + +- title: Support + href: /support.html + category: support + +- title: Blog + href: /blog/ + category: blog + +- title: Facebook + href: https://www.facebook.com/groups/rocksdb.dev/ + category: external + +# Use external for external links not associated with the paths of the current site. +# If a category is external, site urls, for example, are not prepended to the href, etc.. diff --git a/thirdparty/rocksdb/docs/_data/nav_docs.yml b/thirdparty/rocksdb/docs/_data/nav_docs.yml new file mode 100644 index 0000000000..8cdfd2d04d --- /dev/null +++ b/thirdparty/rocksdb/docs/_data/nav_docs.yml @@ -0,0 +1,3 @@ +- title: Quick Start + items: + - id: getting-started diff --git a/thirdparty/rocksdb/docs/_data/powered_by.yml b/thirdparty/rocksdb/docs/_data/powered_by.yml new file mode 100644 index 0000000000..a780cfe401 --- /dev/null +++ b/thirdparty/rocksdb/docs/_data/powered_by.yml @@ -0,0 +1 @@ +# Fill in later if desired diff --git a/thirdparty/rocksdb/docs/_data/powered_by_highlight.yml b/thirdparty/rocksdb/docs/_data/powered_by_highlight.yml new file mode 100644 index 0000000000..a780cfe401 --- /dev/null +++ b/thirdparty/rocksdb/docs/_data/powered_by_highlight.yml @@ -0,0 +1 @@ +# Fill in later if desired diff --git a/thirdparty/rocksdb/docs/_data/promo.yml b/thirdparty/rocksdb/docs/_data/promo.yml new file mode 100644 index 0000000000..9a72aa844c --- /dev/null +++ b/thirdparty/rocksdb/docs/_data/promo.yml @@ -0,0 +1,6 @@ +# This file determines the list of promotional elements added to the header of \ +# your site's homepage. Full list of plugins are shown + +- type: button + href: docs/getting-started.html + text: Get Started diff --git a/thirdparty/rocksdb/docs/_docs/faq.md b/thirdparty/rocksdb/docs/_docs/faq.md new file mode 100644 index 0000000000..0887a0987f --- /dev/null +++ b/thirdparty/rocksdb/docs/_docs/faq.md @@ -0,0 +1,48 @@ +--- +docid: support-faq +title: FAQ +layout: docs +permalink: /docs/support/faq.html +--- + +Here is an ever-growing list of frequently asked questions around RocksDB + +## What is RocksDB? + +RocksDB is an embeddable persistent key-value store for fast storage. RocksDB can also be the foundation for a client-server database but our current focus is on embedded workloads. + +RocksDB builds on [LevelDB](https://code.google.com/p/leveldb/) to be scalable to run on servers with many CPU cores, to efficiently use fast storage, to support IO-bound, in-memory and write-once workloads, and to be flexible to allow for innovation. + +For the latest details, watch [Mark Callaghan’s and Igor Canadi’s talk at CMU on 10/2015](https://scs.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=f4e0eb37-ae18-468f-9248-cb73edad3e56). [Dhruba Borthakur’s introductory talk](https://github.com/facebook/rocksdb/blob/gh-pages-old/intro.pdf?raw=true) from the Data @ Scale 2013 conference provides some perspective about how RocksDB has evolved. + +## How does performance compare? + +We benchmarked LevelDB and found that it was unsuitable for our server workloads. The [benchmark results](http://leveldb.googlecode.com/svn/trunk/doc/benchmark.html) look awesome at first sight, but we quickly realized that those results were for a database whose size was smaller than the size of RAM on the test machine – where the entire database could fit in the OS page cache. When we performed the same benchmarks on a database that was at least 5 times larger than main memory, the performance results were dismal. + +By contrast, we’ve published the [RocksDB benchmark results](https://github.com/facebook/rocksdb/wiki/Performance-Benchmarks) for server side workloads on Flash. We also measured the performance of LevelDB on these server-workload benchmarks and found that RocksDB solidly outperforms LevelDB for these IO bound workloads. We found that LevelDB’s single-threaded compaction process was insufficient to drive server workloads. We saw frequent write-stalls with LevelDB that caused 99-percentile latency to be tremendously large. We found that mmap-ing a file into the OS cache introduced performance bottlenecks for reads. We could not make LevelDB consume all the IOs offered by the underlying Flash storage. + +## What is RocksDB suitable for? + +RocksDB can be used by applications that need low latency database accesses. Possibilities include: + +* A user-facing application that stores the viewing history and state of users of a website. +* A spam detection application that needs fast access to big data sets. +* A graph-search query that needs to scan a data set in realtime. +* A cache data from Hadoop, thereby allowing applications to query Hadoop data in realtime. +* A message-queue that supports a high number of inserts and deletes. + +## How big is RocksDB adoption? + +RocksDB is an embedded storage engine that is used in a number of backend systems at Facebook. In the Facebook newsfeed’s backend, it replaced another internal storage engine called Centrifuge and is one of the many components used. ZippyDB, a distributed key value store service used by Facebook products relies RocksDB. Details on ZippyDB are in [Muthu Annamalai’s talk at Data@Scale in Seattle](https://youtu.be/DfiN7pG0D0k). Dragon, a distributed graph query engine part of the social graph infrastructure, is using RocksDB to store data. Parse has been running [MongoDB on RocksDB in production](http://blog.parse.com/announcements/mongodb-rocksdb-parse/) since early 2015. + +RocksDB is proving to be a useful component for a lot of other groups in the industry. For a list of projects currently using RocksDB, take a look at our USERS.md list on github. + +## How good is RocksDB as a database storage engine? + +Our engineering team at Facebook firmly believes that RocksDB has great potential as storage engine for databases. It has been proven in production with MongoDB: [MongoRocks](https://github.com/mongodb-partners/mongo-rocks) is the RocksDB based storage engine for MongoDB. + +[MyRocks](https://code.facebook.com/posts/190251048047090/myrocks-a-space-and-write-optimized-mysql-database/) is the RocksDB based storage engine for MySQL. Using RocksDB we have managed to achieve 2x better compression and 10x less write amplification for our benchmarks compared to our existing MySQL setup. Given our current results, work is currently underway to develop MyRocks into a production ready solution for web-scale MySQL workloads. Follow along on [GitHub](https://github.com/facebook/mysql-5.6)! + +## Why is RocksDB open sourced? + +We are open sourcing this project on [GitHub](http://github.com/facebook/rocksdb) because we think it will be useful beyond Facebook. We are hoping that software programmers and database developers will use, enhance, and customize RocksDB for their use-cases. We would also like to engage with the academic community on topics related to efficiency for modern database algorithms. diff --git a/thirdparty/rocksdb/docs/_docs/getting-started.md b/thirdparty/rocksdb/docs/_docs/getting-started.md new file mode 100644 index 0000000000..8b01dfefd4 --- /dev/null +++ b/thirdparty/rocksdb/docs/_docs/getting-started.md @@ -0,0 +1,78 @@ +--- +docid: getting-started +title: Getting started +layout: docs +permalink: /docs/getting-started.html +--- + +## Overview + +The RocksDB library provides a persistent key value store. Keys and values are arbitrary byte arrays. The keys are ordered within the key value store according to a user-specified comparator function. + +The library is maintained by the Facebook Database Engineering Team, and is based on [LevelDB](https://github.com/google/leveldb), by Sanjay Ghemawat and Jeff Dean at Google. + +This overview gives some simple examples of how RocksDB is used. For the story of why RocksDB was created in the first place, see [Dhruba Borthakur’s introductory talk](https://github.com/facebook/rocksdb/blob/gh-pages-old/intro.pdf?raw=true) from the Data @ Scale 2013 conference. + +## Opening A Database + +A rocksdb database has a name which corresponds to a file system directory. All of the contents of database are stored in this directory. The following example shows how to open a database, creating it if necessary: + +```c++ +#include +#include "rocksdb/db.h" + +rocksdb::DB* db; +rocksdb::Options options; +options.create_if_missing = true; +rocksdb::Status status = + rocksdb::DB::Open(options, "/tmp/testdb", &db); +assert(status.ok()); +... +``` + +If you want to raise an error if the database already exists, add the following line before the rocksdb::DB::Open call: + +```c++ +options.error_if_exists = true; +``` + +## Status + +You may have noticed the `rocksdb::Status` type above. Values of this type are returned by most functions in RocksDB that may encounter +an error. You can check if such a result is ok, and also print an associated error message: + +```c++ +rocksdb::Status s = ...; +if (!s.ok()) cerr << s.ToString() << endl; +``` + +## Closing A Database + +When you are done with a database, just delete the database object. For example: + +```c++ +/* open the db as described above */ +/* do something with db */ +delete db; +``` + +## Reads And Writes + +The database provides Put, Delete, and Get methods to modify/query the database. For example, the following code moves the value stored under `key1` to `key2`. + +```c++ +std::string value; +rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &value); +if (s.ok()) s = db->Put(rocksdb::WriteOptions(), key2, value); +if (s.ok()) s = db->Delete(rocksdb::WriteOptions(), key1); +``` + +## Further documentation + +These are just simple examples of how RocksDB is used. The full documentation is currently on the [GitHub wiki](https://github.com/facebook/rocksdb/wiki). + +Here are some specific details about the RocksDB implementation: + +- [Architecture Guide](https://github.com/facebook/rocksdb/wiki/Rocksdb-Architecture-Guide) +- [Format of an immutable Table file](https://github.com/facebook/rocksdb/wiki/Rocksdb-Table-Format) +- [Format of a log file](https://github.com/facebook/rocksdb/wiki/Write-Ahead-Log-File-Format) diff --git a/thirdparty/rocksdb/docs/_includes/blog_pagination.html b/thirdparty/rocksdb/docs/_includes/blog_pagination.html new file mode 100644 index 0000000000..6a1f33436e --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/blog_pagination.html @@ -0,0 +1,28 @@ + +{% if paginator.total_pages > 1 %} +
+ +
+{% endif %} diff --git a/thirdparty/rocksdb/docs/_includes/content/gridblocks.html b/thirdparty/rocksdb/docs/_includes/content/gridblocks.html new file mode 100644 index 0000000000..49c5e5917d --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/content/gridblocks.html @@ -0,0 +1,5 @@ +
+{% for item in {{include.data_source}} %} + {% include content/items/gridblock.html item=item layout=include.layout imagealign=include.imagealign align=include.align %} +{% endfor %} +
\ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/content/items/gridblock.html b/thirdparty/rocksdb/docs/_includes/content/items/gridblock.html new file mode 100644 index 0000000000..58c9e7fdaf --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/content/items/gridblock.html @@ -0,0 +1,37 @@ +{% if include.layout == "fourColumn" %} + {% assign layout = "fourByGridBlock" %} +{% else %} + {% assign layout = "twoByGridBlock" %} +{% endif %} + +{% if include.imagealign == "side" %} + {% assign imagealign = "imageAlignSide" %} +{% else %} + {% if item.image %} + {% assign imagealign = "imageAlignTop" %} + {% else %} + {% assign imagealign = "" %} + {% endif %} +{% endif %} + +{% if include.align == "right" %} + {% assign align = "alignRight" %} +{% elsif include.align == "center" %} + {% assign align = "alignCenter" %} +{% else %} + {% assign align = "alignLeft" %} +{% endif %} + +
+ {% if item.image %} +
+ {{ item.title }} +
+ {% endif %} +
+

{{ item.title }}

+ {% if item.text %} + {{ item.text | markdownify }} + {% endif %} +
+
diff --git a/thirdparty/rocksdb/docs/_includes/doc.html b/thirdparty/rocksdb/docs/_includes/doc.html new file mode 100644 index 0000000000..a7950004ec --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/doc.html @@ -0,0 +1,25 @@ +
+
+

{% if include.truncate %}{{ page.title }}{% else %}{{ page.title }}{% endif %}

+
+ +
+ {% if include.truncate %} + {% if page.content contains '' %} + {{ page.content | split:'' | first }} + + {% else %} + {{ page.content }} + {% endif %} + {% else %} + {{ content }} + +

Edit on GitHub

+ {% endif %} +
+ {% include doc_paging.html %} +
diff --git a/thirdparty/rocksdb/docs/_includes/doc_paging.html b/thirdparty/rocksdb/docs/_includes/doc_paging.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/thirdparty/rocksdb/docs/_includes/footer.html b/thirdparty/rocksdb/docs/_includes/footer.html new file mode 100644 index 0000000000..dd9494aeb5 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/footer.html @@ -0,0 +1,33 @@ +
+ +
+ diff --git a/thirdparty/rocksdb/docs/_includes/head.html b/thirdparty/rocksdb/docs/_includes/head.html new file mode 100644 index 0000000000..10845ec1d5 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/head.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + {% if site.searchconfig %} + + {% endif %} + + {% if page.title %}{{ page.title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %} + + + + + diff --git a/thirdparty/rocksdb/docs/_includes/header.html b/thirdparty/rocksdb/docs/_includes/header.html new file mode 100644 index 0000000000..8108d222be --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/header.html @@ -0,0 +1,19 @@ +
+
+
+ +

{{ site.title }}

+

{{ site.tagline }}

+ +
+

{% if page.excerpt %}{{ page.excerpt | strip_html }}{% else %}{{ site.description }}{% endif %}

+
+
+ {% for promo in site.data.promo %} + {% include plugins/{{promo.type}}.html button_href=promo.href button_text=promo.text %} +
+ {% endfor %} +
+
+
+
diff --git a/thirdparty/rocksdb/docs/_includes/hero.html b/thirdparty/rocksdb/docs/_includes/hero.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/thirdparty/rocksdb/docs/_includes/home_header.html b/thirdparty/rocksdb/docs/_includes/home_header.html new file mode 100644 index 0000000000..90880d17cf --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/home_header.html @@ -0,0 +1,22 @@ +
+
+
+
+

{{ site.tagline }}

+
+

{% if page.excerpt %}{{ page.excerpt | strip_html }}{% else %}{{ site.description }}{% endif %}

+
+
+ {% for promo in site.data.promo %} +
+ {% include plugins/{{promo.type}}.html href=promo.href text=promo.text children=promo.children %} +
+ {% endfor %} +
+
+ +
+
+
diff --git a/thirdparty/rocksdb/docs/_includes/katex_import.html b/thirdparty/rocksdb/docs/_includes/katex_import.html new file mode 100644 index 0000000000..6d6b7cf44a --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/katex_import.html @@ -0,0 +1,3 @@ + + + diff --git a/thirdparty/rocksdb/docs/_includes/katex_render.html b/thirdparty/rocksdb/docs/_includes/katex_render.html new file mode 100644 index 0000000000..56e2e89743 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/katex_render.html @@ -0,0 +1,210 @@ + diff --git a/thirdparty/rocksdb/docs/_includes/nav.html b/thirdparty/rocksdb/docs/_includes/nav.html new file mode 100644 index 0000000000..9c6fed06b1 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/nav.html @@ -0,0 +1,37 @@ +
+
+
+ + +

{{ site.title }}

+
+ + + +
+
+
diff --git a/thirdparty/rocksdb/docs/_includes/nav/collection_nav.html b/thirdparty/rocksdb/docs/_includes/nav/collection_nav.html new file mode 100644 index 0000000000..a3c7a2dd35 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/nav/collection_nav.html @@ -0,0 +1,64 @@ +
+ +
+ diff --git a/thirdparty/rocksdb/docs/_includes/nav/collection_nav_group.html b/thirdparty/rocksdb/docs/_includes/nav/collection_nav_group.html new file mode 100644 index 0000000000..b236ac5e3f --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/nav/collection_nav_group.html @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/nav/collection_nav_group_item.html b/thirdparty/rocksdb/docs/_includes/nav/collection_nav_group_item.html new file mode 100644 index 0000000000..fbb063deb7 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/nav/collection_nav_group_item.html @@ -0,0 +1 @@ + diff --git a/thirdparty/rocksdb/docs/_includes/nav/header_nav.html b/thirdparty/rocksdb/docs/_includes/nav/header_nav.html new file mode 100644 index 0000000000..0fe945cdcd --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/nav/header_nav.html @@ -0,0 +1,30 @@ +
+ + +
+ \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/nav_search.html b/thirdparty/rocksdb/docs/_includes/nav_search.html new file mode 100644 index 0000000000..84956b9f78 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/nav_search.html @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/all_share.html b/thirdparty/rocksdb/docs/_includes/plugins/all_share.html new file mode 100644 index 0000000000..59b00d615f --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/all_share.html @@ -0,0 +1,3 @@ +
+ {% include plugins/like_button.html %}{% include plugins/twitter_share.html %}{% include plugins/google_share.html %} +
\ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/ascii_cinema.html b/thirdparty/rocksdb/docs/_includes/plugins/ascii_cinema.html new file mode 100644 index 0000000000..7d3f971480 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/ascii_cinema.html @@ -0,0 +1,2 @@ +
+ \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/button.html b/thirdparty/rocksdb/docs/_includes/plugins/button.html new file mode 100644 index 0000000000..9e499fe3f3 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/button.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/github_star.html b/thirdparty/rocksdb/docs/_includes/plugins/github_star.html new file mode 100644 index 0000000000..6aea70fc73 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/github_star.html @@ -0,0 +1,4 @@ +
+ Star +
+ \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/github_watch.html b/thirdparty/rocksdb/docs/_includes/plugins/github_watch.html new file mode 100644 index 0000000000..64233b57b6 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/github_watch.html @@ -0,0 +1,4 @@ +
+ Watch +
+ \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/google_share.html b/thirdparty/rocksdb/docs/_includes/plugins/google_share.html new file mode 100644 index 0000000000..1b557db86c --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/google_share.html @@ -0,0 +1,5 @@ +
+
+
+ + diff --git a/thirdparty/rocksdb/docs/_includes/plugins/iframe.html b/thirdparty/rocksdb/docs/_includes/plugins/iframe.html new file mode 100644 index 0000000000..525b59f227 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/iframe.html @@ -0,0 +1,6 @@ +
+ +
+
+ {% include plugins/button.html href=include.href text=include.text %} +
\ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/like_button.html b/thirdparty/rocksdb/docs/_includes/plugins/like_button.html new file mode 100644 index 0000000000..bcb8a7beef --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/like_button.html @@ -0,0 +1,18 @@ +
+ \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/plugin_row.html b/thirdparty/rocksdb/docs/_includes/plugins/plugin_row.html new file mode 100644 index 0000000000..800f50b821 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/plugin_row.html @@ -0,0 +1,5 @@ +
+{% for child in include.children %} + {% include plugins/{{child.type}}.html href=child.href text=child.text %} +{% endfor %} +
\ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/post_social_plugins.html b/thirdparty/rocksdb/docs/_includes/plugins/post_social_plugins.html new file mode 100644 index 0000000000..a2ecb90eeb --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/post_social_plugins.html @@ -0,0 +1,41 @@ +
+ +
+
+ + + diff --git a/thirdparty/rocksdb/docs/_includes/plugins/slideshow.html b/thirdparty/rocksdb/docs/_includes/plugins/slideshow.html new file mode 100644 index 0000000000..69fa2b300e --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/slideshow.html @@ -0,0 +1,88 @@ +
+ + + \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_includes/plugins/twitter_follow.html b/thirdparty/rocksdb/docs/_includes/plugins/twitter_follow.html new file mode 100644 index 0000000000..b0f25dc605 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/twitter_follow.html @@ -0,0 +1,12 @@ + + + diff --git a/thirdparty/rocksdb/docs/_includes/plugins/twitter_share.html b/thirdparty/rocksdb/docs/_includes/plugins/twitter_share.html new file mode 100644 index 0000000000..a60f2a8dff --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/plugins/twitter_share.html @@ -0,0 +1,11 @@ +
+ +
+ diff --git a/thirdparty/rocksdb/docs/_includes/post.html b/thirdparty/rocksdb/docs/_includes/post.html new file mode 100644 index 0000000000..3ae0a2a808 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/post.html @@ -0,0 +1,40 @@ +
+
+
+ {% for author_idx in page.author %} +
+ {% assign author = site.data.authors[author_idx] %} + {% if author.fbid %} +
+ {{ author.fullname }} +
+ {% endif %} + {% if author.full_name %} + + {% endif %} +
+ {% endfor %} +
+

{% if include.truncate %}{{ page.title }}{% else %}{{ page.title }}{% endif %}

+ +
+
+ {% if include.truncate %} + {% if page.content contains '' %} + {{ page.content | split:'' | first | markdownify }} + + {% else %} + {{ page.content | markdownify }} + {% endif %} + {% else %} + {{ content }} + {% endif %} + {% unless include.truncate %} + {% include plugins/like_button.html %} + {% endunless %} +
+
diff --git a/thirdparty/rocksdb/docs/_includes/powered_by.html b/thirdparty/rocksdb/docs/_includes/powered_by.html new file mode 100644 index 0000000000..c629429cd0 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/powered_by.html @@ -0,0 +1,28 @@ +{% if site.data.powered_by.first.items or site.data.powered_by_highlight.first.items %} +
+
+ {% if site.data.powered_by_highlight.first.title %} +

{{ site.data.powered_by_highlight.first.title }}

+ {% else %} +

{{ site.data.powered_by.first.title }}

+ {% endif %} + {% if site.data.powered_by_highlight.first.items %} +
+ {% for item in site.data.powered_by_highlight.first.items %} +
+ {{ item.name }} +
+ {% endfor %} +
+ {% endif %} +
+ {% for item in site.data.powered_by.first.items %} + + {% endfor %} +
+
Does your app use {{ site.title }}? Add it to this list with a pull request!
+
+
+{% endif %} diff --git a/thirdparty/rocksdb/docs/_includes/social_plugins.html b/thirdparty/rocksdb/docs/_includes/social_plugins.html new file mode 100644 index 0000000000..9b36580dc0 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/social_plugins.html @@ -0,0 +1,31 @@ + +
+ +
+ + + diff --git a/thirdparty/rocksdb/docs/_includes/ui/button.html b/thirdparty/rocksdb/docs/_includes/ui/button.html new file mode 100644 index 0000000000..729ccc33b9 --- /dev/null +++ b/thirdparty/rocksdb/docs/_includes/ui/button.html @@ -0,0 +1 @@ +{{ include.button_text }} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_layouts/basic.html b/thirdparty/rocksdb/docs/_layouts/basic.html new file mode 100644 index 0000000000..65bd21060c --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/basic.html @@ -0,0 +1,12 @@ +--- +layout: doc_default +--- + +
+
+
+ {{ content }} +
+
+
+ diff --git a/thirdparty/rocksdb/docs/_layouts/blog.html b/thirdparty/rocksdb/docs/_layouts/blog.html new file mode 100644 index 0000000000..1b0da41359 --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/blog.html @@ -0,0 +1,11 @@ +--- +category: blog +layout: blog_default +--- + +
+
+ {{ content }} +
+
+ diff --git a/thirdparty/rocksdb/docs/_layouts/blog_default.html b/thirdparty/rocksdb/docs/_layouts/blog_default.html new file mode 100644 index 0000000000..a29d58d3dd --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/blog_default.html @@ -0,0 +1,14 @@ + + + {% include head.html %} + + {% include nav.html alwayson=true %} + + + diff --git a/thirdparty/rocksdb/docs/_layouts/default.html b/thirdparty/rocksdb/docs/_layouts/default.html new file mode 100644 index 0000000000..0167d9fd91 --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/default.html @@ -0,0 +1,12 @@ + + + {% include head.html %} + + {% include nav.html alwayson=true %} + + + + diff --git a/thirdparty/rocksdb/docs/_layouts/doc_default.html b/thirdparty/rocksdb/docs/_layouts/doc_default.html new file mode 100644 index 0000000000..4a4139247c --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/doc_default.html @@ -0,0 +1,14 @@ + + + {% include head.html %} + + {% include nav.html alwayson=true %} + + + diff --git a/thirdparty/rocksdb/docs/_layouts/doc_page.html b/thirdparty/rocksdb/docs/_layouts/doc_page.html new file mode 100644 index 0000000000..dba761e7d7 --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/doc_page.html @@ -0,0 +1,10 @@ +--- +layout: doc_default +--- + +
+
+ {{ content }} +
+
+ diff --git a/thirdparty/rocksdb/docs/_layouts/docs.html b/thirdparty/rocksdb/docs/_layouts/docs.html new file mode 100644 index 0000000000..749dafabbe --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/docs.html @@ -0,0 +1,5 @@ +--- +layout: doc_page +--- + +{% include doc.html %} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_layouts/home.html b/thirdparty/rocksdb/docs/_layouts/home.html new file mode 100644 index 0000000000..e3c320f55c --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/home.html @@ -0,0 +1,17 @@ + + + {% include head.html %} + + {% include nav.html alwayson=true %} + + + diff --git a/thirdparty/rocksdb/docs/_layouts/page.html b/thirdparty/rocksdb/docs/_layouts/page.html new file mode 100644 index 0000000000..bec36805b2 --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/page.html @@ -0,0 +1,3 @@ +--- +layout: blog +--- diff --git a/thirdparty/rocksdb/docs/_layouts/plain.html b/thirdparty/rocksdb/docs/_layouts/plain.html new file mode 100644 index 0000000000..fccc02ce17 --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/plain.html @@ -0,0 +1,10 @@ +--- +layout: default +--- + +
+
+ {{ content }} +
+
+ diff --git a/thirdparty/rocksdb/docs/_layouts/post.html b/thirdparty/rocksdb/docs/_layouts/post.html new file mode 100644 index 0000000000..4c92cf214c --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/post.html @@ -0,0 +1,8 @@ +--- +collection: blog +layout: blog +--- + +
+{% include post.html %} +
\ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_layouts/redirect.html b/thirdparty/rocksdb/docs/_layouts/redirect.html new file mode 100644 index 0000000000..c24f817484 --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/redirect.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/thirdparty/rocksdb/docs/_layouts/top-level.html b/thirdparty/rocksdb/docs/_layouts/top-level.html new file mode 100644 index 0000000000..fccc02ce17 --- /dev/null +++ b/thirdparty/rocksdb/docs/_layouts/top-level.html @@ -0,0 +1,10 @@ +--- +layout: default +--- + +
+
+ {{ content }} +
+
+ diff --git a/thirdparty/rocksdb/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown b/thirdparty/rocksdb/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown new file mode 100644 index 0000000000..f9e4a54447 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-03-27-how-to-backup-rocksdb.markdown @@ -0,0 +1,135 @@ +--- +title: How to backup RocksDB? +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/191/how-to-backup-rocksdb/ +--- + +In RocksDB, we have implemented an easy way to backup your DB. Here is a simple example: + + + + #include "rocksdb/db.h" + #include "utilities/backupable_db.h" + using namespace rocksdb; + + DB* db; + DB::Open(Options(), "/tmp/rocksdb", &db); + BackupableDB* backupable_db = new BackupableDB(db, BackupableDBOptions("/tmp/rocksdb_backup")); + backupable_db->Put(...); // do your thing + backupable_db->CreateNewBackup(); + delete backupable_db; // no need to also delete db + + + + +This simple example will create a backup of your DB in "/tmp/rocksdb_backup". Creating new BackupableDB consumes DB* and you should be calling all the DB methods on object `backupable_db` going forward. + +Restoring is also easy: + + + + RestoreBackupableDB* restore = new RestoreBackupableDB(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup")); + restore->RestoreDBFromLatestBackup("/tmp/rocksdb", "/tmp/rocksdb"); + delete restore; + + + + +This code will restore the backup back to "/tmp/rocksdb". The second parameter is the location of log files (In some DBs they are different from DB directory, but usually they are the same. See Options::wal_dir for more info). + +An alternative API for backups is to use BackupEngine directly: + + + + #include "rocksdb/db.h" + #include "utilities/backupable_db.h" + using namespace rocksdb; + + DB* db; + DB::Open(Options(), "/tmp/rocksdb", &db); + db->Put(...); // do your thing + BackupEngine* backup_engine = BackupEngine::NewBackupEngine(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup")); + backup_engine->CreateNewBackup(db); + delete db; + delete backup_engine; + + + + +Restoring with BackupEngine is similar to RestoreBackupableDB: + + + + BackupEngine* backup_engine = BackupEngine::NewBackupEngine(Env::Default(), BackupableDBOptions("/tmp/rocksdb_backup")); + backup_engine->RestoreDBFromLatestBackup("/tmp/rocksdb", "/tmp/rocksdb"); + delete backup_engine; + + + + +Backups are incremental. You can create a new backup with `CreateNewBackup()` and only the new data will be copied to backup directory (for more details on what gets copied, see "Under the hood"). Checksum is always calculated for any backuped file (including sst, log, and etc). It is used to make sure files are kept sound in the file system. Checksum is also verified for files from the previous backups even though they do not need to be copied. A checksum mismatch aborts the current backup (see "Under the hood" for more details). Once you have more backups saved, you can issue `GetBackupInfo()` call to get a list of all backups together with information on timestamp of the backup and the size (please note that sum of all backups' sizes is bigger than the actual size of the backup directory because some data is shared by multiple backups). Backups are identified by their always-increasing IDs. `GetBackupInfo()` is available both in `BackupableDB` and `RestoreBackupableDB`. + +You probably want to keep around only small number of backups. To delete old backups, just call `PurgeOldBackups(N)`, where N is how many backups you'd like to keep. All backups except the N newest ones will be deleted. You can also choose to delete arbitrary backup with call `DeleteBackup(id)`. + +`RestoreDBFromLatestBackup()` will restore the DB from the latest consistent backup. An alternative is `RestoreDBFromBackup()` which takes a backup ID and restores that particular backup. Checksum is calculated for any restored file and compared against the one stored during the backup time. If a checksum mismatch is detected, the restore process is aborted and `Status::Corruption` is returned. Very important thing to note here: Let's say you have backups 1, 2, 3, 4. If you restore from backup 2 and start writing more data to your database, newly created backup will delete old backups 3 and 4 and create new backup 3 on top of 2. + + + +## Advanced usage + + +Let's say you want to backup your DB to HDFS. There is an option in `BackupableDBOptions` to set `backup_env`, which will be used for all file I/O related to backup dir (writes when backuping, reads when restoring). If you set it to HDFS Env, all the backups will be stored in HDFS. + +`BackupableDBOptions::info_log` is a Logger object that is used to print out LOG messages if not-nullptr. + +If `BackupableDBOptions::sync` is true, we will sync data to disk after every file write, guaranteeing that backups will be consistent after a reboot or if machine crashes. Setting it to false will speed things up a bit, but some (newer) backups might be inconsistent. In most cases, everything should be fine, though. + +If you set `BackupableDBOptions::destroy_old_data` to true, creating new `BackupableDB` will delete all the old backups in the backup directory. + +`BackupableDB::CreateNewBackup()` method takes a parameter `flush_before_backup`, which is false by default. When `flush_before_backup` is true, `BackupableDB` will first issue a memtable flush and only then copy the DB files to the backup directory. Doing so will prevent log files from being copied to the backup directory (since flush will delete them). If `flush_before_backup` is false, backup will not issue flush before starting the backup. In that case, the backup will also include log files corresponding to live memtables. Backup will be consistent with current state of the database regardless of `flush_before_backup` parameter. + + + +## Under the hood + + +`BackupableDB` implements `DB` interface and adds four methods to it: `CreateNewBackup()`, `GetBackupInfo()`, `PurgeOldBackups()`, `DeleteBackup()`. Any `DB` interface calls will get forwarded to underlying `DB` object. + +When you call `BackupableDB::CreateNewBackup()`, it does the following: + + + + + + 1. Disable file deletions + + + + 2. Get live files (this includes table files, current and manifest file). + + + + 3. Copy live files to the backup directory. Since table files are immutable and filenames unique, we don't copy a table file that is already present in the backup directory. For example, if there is a file `00050.sst` already backed up and `GetLiveFiles()` returns `00050.sst`, we will not copy that file to the backup directory. However, checksum is calculated for all files regardless if a file needs to be copied or not. If a file is already present, the calculated checksum is compared against previously calculated checksum to make sure nothing crazy happened between backups. If a mismatch is detected, backup is aborted and the system is restored back to the state before `BackupableDB::CreateNewBackup()` is called. One thing to note is that a backup abortion could mean a corruption from a file in backup directory or the corresponding live file in current DB. Both manifest and current files are copied, since they are not immutable. + + + + 4. If `flush_before_backup` was set to false, we also need to copy log files to the backup directory. We call `GetSortedWalFiles()` and copy all live files to the backup directory. + + + + 5. Enable file deletions + + + + +Backup IDs are always increasing and we have a file `LATEST_BACKUP` that contains the ID of the latest backup. If we crash in middle of backing up, on a restart we will detect that there are newer backup files than `LATEST_BACKUP` claims there are. In that case, we will delete any backup newer than `LATEST_BACKUP` and clean up all the files since some of the table files might be corrupted. Having corrupted table files in the backup directory is dangerous because of our deduplication strategy. + + + +## Further reading + + +For the API details, see `include/utilities/backupable_db.h`. For the implementation, see `utilities/backupable/backupable_db.cc`. diff --git a/thirdparty/rocksdb/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown b/thirdparty/rocksdb/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown new file mode 100644 index 0000000000..89ffb2d97e --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-03-27-how-to-persist-in-memory-rocksdb-database.markdown @@ -0,0 +1,54 @@ +--- +title: How to persist in-memory RocksDB database? +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/245/how-to-persist-in-memory-rocksdb-database/ +--- + +In recent months, we have focused on optimizing RocksDB for in-memory workloads. With growing RAM sizes and strict low-latency requirements, lots of applications decide to keep their entire data in memory. Running in-memory database with RocksDB is easy -- just mount your RocksDB directory on tmpfs or ramfs [1]. Even if the process crashes, RocksDB can recover all of your data from in-memory filesystem. However, what happens if the machine reboots? + + + +In this article we will explain how you can recover your in-memory RocksDB database even after a machine reboot. + +Every update to RocksDB is written to two places - one is an in-memory data structure called memtable and second is write-ahead log. Write-ahead log can be used to completely recover the data in memtable. By default, when we flush the memtable to table file, we also delete the current log, since we don't need it anymore for recovery (the data from the log is "persisted" in the table file -- we say that the log file is obsolete). However, if your table file is stored in in-memory file system, you may need the obsolete write-ahead log to recover the data after the machine reboots. Here's how you can do that. + +Options::wal_dir is the directory where RocksDB stores write-ahead log files. If you configure this directory to be on flash or disk, you will not lose current log file on machine reboot. +Options::WAL_ttl_seconds is the timeout when we delete the archived log files. If the timeout is non-zero, obsolete log files will be moved to `archive/` directory under Options::wal_dir. Those archived log files will only be deleted after the specified timeout. + +Let's assume Options::wal_dir is a directory on persistent storage and Options::WAL_ttl_seconds is set to one day. To fully recover the DB, we also need to backup the current snapshot of the database (containing table and metadata files) with a frequency of less than one day. RocksDB provides an utility that enables you to easily backup the snapshot of your database. You can learn more about it here: [How to backup RocksDB?](https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F) + +You should configure the backup process to avoid backing up log files, since they are already stored in persistent storage. To do that, set BackupableDBOptions::backup_log_files to false. + +Restore process by default cleans up entire DB and WAL directory. Since we didn't include log files in the backup, we need to make sure that restoring the database doesn't delete log files in WAL directory. When restoring, configure RestoreOptions::keep_log_file to true. That option will also move any archived log files back to WAL directory, enabling RocksDB to replay all archived log files and rebuild the in-memory database state. + +To reiterate, here's what you have to do: + + + + + * Set DB directory to tmpfs or ramfs mounted drive + + + + * Set Options::wal_log to a directory on persistent storage + + + + * Set Options::WAL_ttl_seconds to T seconds + + + + * Backup RocksDB every T/2 seconds, with BackupableDBOptions::backup_log_files = false + + + + * When you lose data, restore from backup with RestoreOptions::keep_log_file = true + + + + + +[1] You might also want to consider using [PlainTable format](https://github.com/facebook/rocksdb/wiki/PlainTable-Format) for table files diff --git a/thirdparty/rocksdb/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown b/thirdparty/rocksdb/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown new file mode 100644 index 0000000000..7ccbdbaada --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-04-02-the-1st-rocksdb-local-meetup-held-on-march-27-2014.markdown @@ -0,0 +1,53 @@ +--- +title: The 1st RocksDB Local Meetup Held on March 27, 2014 +layout: post +author: xjin +category: blog +redirect_from: + - /blog/323/the-1st-rocksdb-local-meetup-held-on-march-27-2014/ +--- + +On Mar 27, 2014, RocksDB team @ Facebook held the 1st RocksDB local meetup in FB HQ (Menlo Park, California). We invited around 80 guests from 20+ local companies, including LinkedIn, Twitter, Dropbox, Square, Pinterest, MapR, Microsoft and IBM. Finally around 50 guests showed up, totaling around 60% show-up rate. + + + +[![Resize of 20140327_200754](/static/images/Resize-of-20140327_200754-300x225.jpg)](/static/images/Resize-of-20140327_200754-300x225.jpg) + +RocksDB team @ Facebook gave four talks about the latest progress and experience on RocksDB: + + + + + * [Supporting a 1PB In-Memory Workload](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Haobo-RocksDB-In-Memory.pdf) + + + + + * [Column Families in RocksDB](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Igor-Column-Families.pdf) + + + + + * ["Lockless" Get() in RocksDB?](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf) + + + + + * [Prefix Hashing in RocksDB](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Siying-Prefix-Hash.pdf) + + +A very interesting question asked by a massive number of guests is: does RocksDB plan to provide replication functionality? Obviously, many applications need a resilient and distributed storage solution, not just single-node storage. We are considering how to approach this issue. + +When will be the next meetup? We haven't decided yet. We will see whether the community is interested in it and how it can help RocksDB grow. + +If you have any questions or feedback for the meetup or RocksDB, please let us know in [our Facebook group](https://www.facebook.com/groups/rocksdb.dev/). + +### Comments + +**[Rajiv](geetasen@gmail.com)** + +Have any of these talks been recorded and if so will they be published? + +**[Igor Canadi](icanadi@fb.com)** + +Yes, I think we plan to publish them soon. diff --git a/thirdparty/rocksdb/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown b/thirdparty/rocksdb/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown new file mode 100644 index 0000000000..7be7842a5f --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-04-07-rocksdb-2-8-release.markdown @@ -0,0 +1,40 @@ +--- +title: RocksDB 2.8 release +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/371/rocksdb-2-8-release/ +--- + +Check out the new RocksDB 2.8 release on [Github](https://github.com/facebook/rocksdb/releases/tag/2.8.fb). + +RocksDB 2.8. is mostly focused on improving performance for in-memory workloads. We are seeing read QPS as high as 5M (we will write a separate blog post on this). + + + +Here is the summary of new features: + + * Added a new table format called PlainTable, which is optimized for RAM storage (ramfs or tmpfs). You can read more details about it on [our wiki](https://github.com/facebook/rocksdb/wiki/PlainTable-Format). + + + * New prefixed memtable format HashLinkedList, which is optimized for cases where there are only a few keys for each prefix. + + + * Merge operator supports a new function PartialMergeMulti() that allows users to do partial merges against multiple operands. This function enables big speedups for workloads that use merge operators. + + + * Added a V2 compaction filter interface. It buffers the kv-pairs sharing the same key prefix, process them in batches, and return the batched results back to DB. + + + * Geo-spatial support for locations and radial-search. + + + * Improved read performance using thread local cache for frequently accessed data. + + + * Stability improvements -- we're now ignoring partially written tailing record to MANIFEST or WAL files. + + + +We have also introduced small incompatible API changes (mostly for advanced users). You can see full release notes in our [HISTORY.my](https://github.com/facebook/rocksdb/blob/2.8.fb/HISTORY.md) file. diff --git a/thirdparty/rocksdb/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown b/thirdparty/rocksdb/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown new file mode 100644 index 0000000000..368055d2c2 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-04-21-indexing-sst-files-for-better-lookup-performance.markdown @@ -0,0 +1,28 @@ +--- +title: Indexing SST Files for Better Lookup Performance +layout: post +author: leijin +category: blog +redirect_from: + - /blog/431/indexing-sst-files-for-better-lookup-performance/ +--- + +For a `Get()` request, RocksDB goes through mutable memtable, list of immutable memtables, and SST files to look up the target key. SST files are organized in levels. + +On level 0, files are sorted based on the time they are flushed. Their key range (as defined by FileMetaData.smallest and FileMetaData.largest) are mostly overlapped with each other. So it needs to look up every L0 file. + + + +Compaction is scheduled periodically to pick up files from an upper level and merges them with files from lower level. As a result, key/values are moved from L0 down the LSM tree gradually. Compaction sorts key/values and split them into files. From level 1 and below, SST files are sorted based on key. Their key range are mutually exclusive. Instead of scanning through each SST file and checking if a key falls into its range, RocksDB performs a binary search based on FileMetaData.largest to locate a candidate file that can potentially contain the target key. This reduces complexity from O(N) to O(log(N)). However, log(N) can still be large for bottom levels. For a fan-out ratio of 10, level 3 can have 1000 files. That requires 10 comparisons to locate a candidate file. This is a significant cost for an in-memory database when you can do [several million gets per second](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks). + +One observation to this problem is that: after the LSM tree is built, an SST file's position in its level is fixed. Furthermore, its order relative to files from the next level is also fixed. Based on this idea, we can perform [fractional cascading](http://en.wikipedia.org/wiki/Fractional_cascading) kind of optimization to narrow down the binary search range. Here is an example: + +[![tree_example](/static/images/tree_example1.png)](/static/images/tree_example1.png) + +Level 1 has 2 files and level 2 has 8 files. Now, we want to look up key 80. A binary search based FileMetaData.largest tells you file 1 is the candidate. Then key 80 is compared with its FileMetaData.smallest and FileMetaData.largest to decide if it falls into the range. The comparison shows 80 is less than FileMetaData.smallest (100), so file 1 does not possibly contain key 80. We to proceed to check level 2. Usually, we need to do binary search among all 8 files on level 2. But since we already know target key 80 is less than 100 and only file 1 to file 3 can contain key less than 100, we can safely exclude other files from the search. As a result we cut down the search space from 8 files to 3 files. + +Let's look at another example. We want to get key 230. A binary search on level 1 locates to file 2 (this also implies key 230 is larger than file 1's FileMetaData.largest 200). A comparison with file 2's range shows the target key is smaller than file 2's FileMetaData.smallest 300. Even though, we couldn't find key on level 1, we have derived hints that target key is in range between 200 and 300. Any files on level 2 that cannot overlap with [200, 300] can be safely excluded. As a result, we only need to look at file 5 and file 6 on level 2. + +Inspired by this concept, we pre-build pointers at compaction time on level 1 files that point to a range of files on level 2. For example, file 1 on level 1 points to file 3 (on level 2) on the left and file 4 on the right. File 2 will point to level 2 files 6 and 7. At query time, these pointers are used to determine the actual binary search range based on comparison result. + +Our benchmark shows that this optimization improves lookup QPS by ~5% for similar setup mentioned [here](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks). diff --git a/thirdparty/rocksdb/docs/_posts/2014-05-14-lock.markdown b/thirdparty/rocksdb/docs/_posts/2014-05-14-lock.markdown new file mode 100644 index 0000000000..12009cc88c --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-05-14-lock.markdown @@ -0,0 +1,88 @@ +--- +title: Reducing Lock Contention in RocksDB +layout: post +author: sdong +category: blog +redirect_from: + - /blog/521/lock/ +--- + +In this post, we briefly introduce the recent improvements we did to RocksDB to improve the issue of lock contention costs. + +RocksDB has a simple thread synchronization mechanism (See [RocksDB Architecture Guide](https://github.com/facebook/rocksdb/wiki/Rocksdb-Architecture-Guide)  to understand terms used below, like SST tables or mem tables). SST tables are immutable after being written and mem tables are lock-free data structures supporting single writer and multiple readers. There is only one single major lock, the DB mutex (DBImpl.mutex_) protecting all the meta operations, including: + + + + * Increase or decrease reference counters of mem tables and SST tables + + + * Change and check meta data structures, before and after finishing compactions, flushes and new mem table creations + + + * Coordinating writers + + +This DB mutex used to be scalability bottleneck preventing us from scaling to more than 16 threads. To address the issue, we improved RocksDB in several ways. + +1. Consolidate reference counters and introduce "super version". For every read operation, mutex was acquired, and reference counters for each mem table and each SST table were increased. One such operation is not expensive but if you are building a high throughput server with lots of reads, the lock contention will become the bottleneck. This is especially true if you store all your data in RAM. + +To solve this problem, we created a meta-meta data structure called “[super version](https://reviews.facebook.net/rROCKSDB1fdb3f7dc60e96394e3e5b69a46ede5d67fb976c)”, which holds reference counters to all those mem table and SST tables, so that readers only need to increase the reference counters for this single data structure. In RocksDB, list of live mem tables and SST tables only changes infrequently, which would happen when new mem tables are created or flush/compaction happens. Now, at those times, a new super version is created with their reference counters increased. A super version lists live mem tables and SST tables so a reader only needs acquire the lock in order to find the latest super version and increase its reference counter. From the super version, the reader can find all the mem and SST tables which are safety accessible as long as the reader holds the reference count for the super version. + +2. We replace some reference counters to stc::atomic objects, so that decreasing reference count of an object usually doesn’t need to be inside the mutex any more. + +3. Make fetching super version and reference counting lock-free in read queries. After consolidating reference counting to one single super version and removing the locking for decreasing reference counts, in read case, we only acquire mutex for one thing: fetch the latest super version and increase the reference count for that (dereference the counter is done in an atomic decrease). We designed and implemented a (mostly) lock-free approach to do it. See [details](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf). We will write a separate blog post for that. + +4. Avoid disk I/O inside the mutex. As we know, each disk I/O to hard drives takes several milliseconds. It can be even longer if file system journal is involved or I/Os are queued. Even occasional disk I/O within mutex can cause huge performance outliers. +We identified in two situations, we might do disk I/O inside mutex and we removed them: +(1) Opening and closing transactional log files. We moved those operations out of the mutex. +(2) Information logging. In multiple places we write to logs within mutex. There is a chance that file write will wait for disk I/O to finish before finishing, even if fsync() is not issued, especially in EXT systems. We occasionally see 100+ milliseconds write() latency on EXT. Instead of removing those logging, we came up with a solution of delay logging. When inside mutex, instead of directly writing to the log file, we write to a log buffer, with the timing information. As soon as mutex is released, we flush the log buffer to log files. + +5. Reduce object creation inside the mutex. +Object creation can be slow because it involves malloc (in our case). Malloc sometimes is slow because it needs to lock some shared data structures. Allocating can also be slow because we sometimes do expensive operations in some of our classes' constructors. For these reasons, we try to reduce object creations inside the mutex. Here are two examples: + +(1) std::vector uses malloc inside. We introduced “[autovector](https://reviews.facebook.net/rROCKSDBc01676e46d3be08c3c140361ef1f5884f47d3b3c)” data structure, in which memory for first a few elements are pre-allocated as members of the autovector class. When an autovector is used as a stack variable, no malloc will be needed unless the pre-allocated buffer is used up. This autovector is quite useful for manipulating those meta data structures. Those meta operations are often locked inside DB mutex. + +(2) When building an iterator, we used to creating iterator of every live men table and SST table within the mutex and a merging iterator on top of them. Besides malloc, some of those iterators can be quite expensive to create, like sorting. Now, instead of doing that, we simply increase the reference counters of them, and release the mutex before creating any iterator. + +6. Deal with mutexes in LRU caches. +When I said there was only one single major lock, I was lying. In RocksDB, all LRU caches had exclusive mutexes within to protect writes to the LRU lists, which are done in both of read and write operations. LRU caches are used in block cache and table cache. Both of them are accessed more frequently than DB data structures. Lock contention of these two locks are as intense as the DB mutex. Even if LRU cache is sharded into ShardedLRUCache, we can still see lock contentions, especially table caches. We further address this issue in two way: +(1) Bypassing table caches. A table cache maintains list of SST table’s read handlers. Those handlers contain SST files’ descriptors, table metadata, and possibly data indexes, as well as bloom filters. When the table handler needs to be evicted based on LRU, those information is cleared. When the SST table needs to be read and its table handler is not in LRU cache, the table is opened and those metadata is loaded. In some cases, users want to tune the system in a way that table handler evictions should never happen. It is common for high-throughput, low-latency servers. We introduce a mode where table cache is bypassed in read queries. In this mode, all table handlers are cached and accessed directly, so there is no need to query and adjust table caches for reading the database. It is the users’ responsibility to reserve enough resource for it. This mode can be turned on by setting options.max_open_files=-1. + +(2) [New PlainTable format](//github.com/facebook/rocksdb/wiki/PlainTable-Format) (optimized for SST in ramfs/tmpfs) does not organize data by blocks. Data are located by memory addresses so no block cache is needed. + +With all of those improvements, lock contention is not a bottleneck anymore, which is shown in our [memory-only benchmark](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks) . Furthermore, lock contentions are not causing some huge (50 milliseconds+) latency outliers they used to cause. + +### Comments + +**[Lee Hounshell](lee@apsalar.com)** + +Please post an example of reading the same rocksdb concurrently. + +We are using the latest 3.0 rocksdb; however, when two separate processes +try and open the same rocksdb for reading, only one of the open requests +succeed. The other open always fails with “db/LOCK: Resource temporarily unavailable” So far we have not found an option that allows sharing the rocksdb for reads. An example would be most appreciated. + +**[Siying Dong](siying.d@fb.com)** + +Sorry for the delay. We don’t have feature support for this scenario yet. Here is an example you can work around this problem. You can build a snapshot of the DB by doing this: + +1. create a separate directory on the same host for a snapshot of the DB. +1. call `DB::DisableFileDeletions()` +1. call `DB::GetLiveFiles()` to get a full list of the files. +1. for all the files except manifest, add a hardlink file in your new directory pointing to the original file +1. copy the manifest file and truncate the size (you can read the comments of `DB::GetLiveFiles()` for more information) +1. call `DB::EnableFileDeletions()` +1. now you can open the snapshot directory in another process to access those files. Please remember to delete the directory after reading the data to allow those files to be recycled. + +By the way, the best way to ask those questions is in our [facebook group](https://www.facebook.com/groups/rocksdb.dev/). Let us know if you need any further help. + +**[Darshan](darshan.ghumare@gmail.com)** + +Will this consistency problem of RocksDB all occurs in case of single put/write? +What all ACID properties is supported by RocksDB, only durability irrespective of single or batch write? + +**[Siying Dong](siying.d@fb.com)** + +We recently [introduced optimistic transaction](https://reviews.facebook.net/D33435) which can help you ensure all of ACID. + +This blog post is mainly about optimizations in implementation. The RocksDB consistency semantic is not changed. diff --git a/thirdparty/rocksdb/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown b/thirdparty/rocksdb/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown new file mode 100644 index 0000000000..61c90dc936 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-05-19-rocksdb-3-0-release.markdown @@ -0,0 +1,24 @@ +--- +title: RocksDB 3.0 release +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/557/rocksdb-3-0-release/ +--- + +Check out new RocksDB release on [Github](https://github.com/facebook/rocksdb/releases/tag/3.0.fb)! + +New features in RocksDB 3.0: + + * [Column Family support](https://github.com/facebook/rocksdb/wiki/Column-Families) + + + * [Ability to chose different checksum function](https://github.com/facebook/rocksdb/commit/0afc8bc29a5800e3212388c327c750d32e31f3d6) + + + * Deprecated ReadOptions::prefix_seek and ReadOptions::prefix + + + +Check out the full [change log](https://github.com/facebook/rocksdb/blob/3.0.fb/HISTORY.md). diff --git a/thirdparty/rocksdb/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown b/thirdparty/rocksdb/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown new file mode 100644 index 0000000000..30156742b2 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-05-22-rocksdb-3-1-release.markdown @@ -0,0 +1,20 @@ +--- +title: RocksDB 3.1 release +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/575/rocksdb-3-1-release/ +--- + +Check out the new release on [Github](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.1)! + +New features in RocksDB 3.1: + + * [Materialized hash index](https://github.com/facebook/rocksdb/commit/0b3d03d026a7248e438341264b4c6df339edc1d7) + + + * [FIFO compaction style](https://github.com/facebook/rocksdb/wiki/FIFO-compaction-style) + + +We released 3.1 so fast after 3.0 because one of our internal customers needed materialized hash index. diff --git a/thirdparty/rocksdb/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown b/thirdparty/rocksdb/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown new file mode 100644 index 0000000000..6a641f2335 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-06-23-plaintable-a-new-file-format.markdown @@ -0,0 +1,47 @@ +--- +title: PlainTable — A New File Format +layout: post +author: sdong +category: blog +redirect_from: + - /blog/599/plaintable-a-new-file-format/ +--- + +In this post, we are introducing "PlainTable" -- a file format we designed for RocksDB, initially to satisfy a production use case at Facebook. + +Design goals: + +1. All data stored in memory, in files stored in tmpfs/ramfs. Support DBs larger than 100GB (may be sharded across multiple RocksDB instance). +1. Optimize for [prefix hashing](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Siying-Prefix-Hash.pdf) +1. Less than or around 1 micro-second average latency for single Get() or Seek(). +1. Minimize memory consumption. +1. Queries efficiently return empty results + + + +Notice that our priority was not to maximize query performance, but to strike a balance between query performance and memory consumption. PlainTable query performance is not as good as you would see with a nicely-designed hash table, but they are of the same order of magnitude, while keeping memory overhead to a minimum. + +Since we are targeting micro-second latency, it is on the level of the number of CPU cache misses (if they cannot be parallellized, which are usually the case for index look-ups). On our target hardware with Intel CPUs of multiple sockets with NUMA, we can only allow 4-5 CPU cache misses (including costs of data TLB). + +To meet our requirements, given that only hash prefix iterating is needed, we made two decisions: + +1. to use a hash index, which is +1. directly addressed to rows, with no block structure. + +Having addressed our latency goal, the next task was to design a very compact hash index to minimize memory consumption. Some tricks we used to meet this goal: + +1. We only use 32-bit integers for data and index offsets.The first bit serves as a flag, so we can avoid using 8-byte pointers. +1. We never copy keys or parts of keys to index search structures. We store only offsets from which keys can be retrieved, to make comparisons with search keys. +1. Since our file is immutable, we can accurately estimate the number of hash buckets needed. + +To make sure the format works efficiently with empty queries, we added a bloom filter check before the query. This adds only one cache miss for non-empty cases [1], but avoids multiple cache misses for most empty results queries. This is a good trade-off for use cases with a large percentage of empty results. + +These are the design goals and basic ideas of PlainTable file format. For detailed information, see [this wiki page](https://github.com/facebook/rocksdb/wiki/PlainTable-Format). + +[1] Bloom filter checks typically require multiple memory access. However, because they are independent, they usually do not make the CPU pipeline stale. In any case, we improved the bloom filter to improve data locality - we may cover this further in a future blog post. + +### Comments + +**[Siying Dong](siying.d@fb.com)** + +Does [http://rocksdb.org/feed/](http://rocksdb.org/feed/) work? diff --git a/thirdparty/rocksdb/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown b/thirdparty/rocksdb/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown new file mode 100644 index 0000000000..4411c7ae31 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-06-27-avoid-expensive-locks-in-get.markdown @@ -0,0 +1,89 @@ +--- +title: Avoid Expensive Locks in Get() +layout: post +author: leijin +category: blog +redirect_from: + - /blog/677/avoid-expensive-locks-in-get/ +--- + +As promised in the previous [blog post](blog/2014/05/14/lock.html)! + +RocksDB employs a multiversion concurrency control strategy. Before reading data, it needs to grab the current version, which is encapsulated in a data structure called [SuperVersion](https://reviews.facebook.net/rROCKSDB1fdb3f7dc60e96394e3e5b69a46ede5d67fb976c). + + + +At the beginning of `GetImpl()`, it used to do this: + + + mutex_.Lock(); + auto* s = super_version_->Ref(); + mutex_.Unlock(); + + +The lock is necessary because pointer super_version_ may be updated, the corresponding SuperVersion may be deleted while Ref() is in progress. + + +`Ref()` simply increases the reference counter and returns “this” pointer. However, this simple operation posed big challenges for in-memory workload and stopped RocksDB from scaling read throughput beyond 8 cores. Running 32 read threads on a 32-core CPU leads to [70% system CPU usage](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf). This is outrageous! + + + + +Luckily, we found a way to circumvent this problem by using [thread local storage](http://en.wikipedia.org/wiki/Thread-local_storage). Version change is a rare event comparable to millions of read requests. On the very first Get() request, each thread pays the mutex cost to acquire a reference to the new super version. Instead of releasing the reference after use, the reference is cached in thread’s local storage. An atomic variable is used to track global super version number. Subsequent reads simply compare the local super version number against the global super version number. If they are the same, the cached super version reference may be used directly, at no cost. If a version change is detected, mutex must be acquired to update the reference. The cost of mutex lock is amortized among millions of reads and becomes negligible. + + + + +The code looks something like this: + + + + + + SuperVersion* s = thread_local_->Get(); + if (s->version_number != super_version_number_.load()) { + // slow path, cleanup of current super version is omitted + mutex_.Lock(); + s = super_version_->Ref(); + mutex_.Unlock(); + } + + + + +The result is quite amazing. RocksDB can nicely [scale to 32 cores](https://github.com/facebook/rocksdb/raw/gh-pages/talks/2014-03-27-RocksDB-Meetup-Lei-Lockless-Get.pdf) and most CPU time is spent in user land. + + + + +Daryl Grove gives a pretty good [comparison between mutex and atomic](https://blogs.oracle.com/d/entry/the_cost_of_mutexes). However, the real cost difference lies beyond what is shown in the assembly code. Mutex can keep threads spinning on CPU or even trigger thread context switches in which all readers compete to access the critical area. Our approach prevents mutual competition by directing threads to check against a global version which does not change at high frequency, and is therefore much more cache-friendly. + + + + +The new approach entails one issue: a thread can visit GetImpl() once but can never come back again. SuperVersion is referenced and cached in its thread local storage. All resources (e.g., memtables, files) which belong to that version are frozen. A “supervisor” is required to visit each thread’s local storage and free its resources without incurring a lock. We designed a lockless sweep using CAS (compare and switch instruction). Here is how it works: + + + + +(1) A reader thread uses CAS to acquire SuperVersion from its local storage and to put in a special flag (SuperVersion::kSVInUse). + + + + +(2) Upon completion of GetImpl(), the reader thread tries to return SuperVersion to local storage by CAS, expecting the special flag (SuperVersion::kSVInUse) in its local storage. If it does not see SuperVersion::kSVInUse, that means a “sweep” was done and the reader thread is responsible for cleanup (this is expensive, but does not happen often on the hot path). + + + + +(3) After any flush/compaction, the background thread performs a sweep (CAS) across all threads’ local storage and frees encountered SuperVersion. A reader thread must re-acquire a new SuperVersion reference on its next visit. + +### Comments + +**[David Barbour](dmbarbour@gmail.com)** + +Please post an example of reading the same rocksdb concurrently. + +We are using the latest 3.0 rocksdb; however, when two separate processes +try and open the same rocksdb for reading, only one of the open requests +succeed. The other open always fails with “db/LOCK: Resource temporarily unavailable” So far we have not found an option that allows sharing the rocksdb for reads. An example would be most appreciated. diff --git a/thirdparty/rocksdb/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown b/thirdparty/rocksdb/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown new file mode 100644 index 0000000000..e4eba6af4b --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-06-27-rocksdb-3-2-release.markdown @@ -0,0 +1,30 @@ +--- +title: RocksDB 3.2 release +layout: post +author: leijin +category: blog +redirect_from: + - /blog/647/rocksdb-3-2-release/ +--- + +Check out new RocksDB release on [GitHub](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.2)! + +New Features in RocksDB 3.2: + + * PlainTable now supports a new key encoding: for keys of the same prefix, the prefix is only written once. It can be enabled through encoding_type paramter of NewPlainTableFactory() + + + * Add AdaptiveTableFactory, which is used to convert from a DB of PlainTable to BlockBasedTabe, or vise versa. It can be created using NewAdaptiveTableFactory() + + + +Public API changes: + + + * We removed seek compaction as a concept from RocksDB + + + * Add two paramters to NewHashLinkListRepFactory() for logging on too many entries in a hash bucket when flushing + + + * Added new option BlockBasedTableOptions::hash_index_allow_collision. When enabled, prefix hash index for block-based table will not store prefix and allow hash collision, reducing memory consumption diff --git a/thirdparty/rocksdb/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown b/thirdparty/rocksdb/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown new file mode 100644 index 0000000000..d858e4fafe --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-07-29-rocksdb-3-3-release.markdown @@ -0,0 +1,34 @@ +--- +title: RocksDB 3.3 Release +layout: post +author: yhciang +category: blog +redirect_from: + - /blog/1301/rocksdb-3-3-release/ +--- + +Check out new RocksDB release on [GitHub](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.3)! + +New Features in RocksDB 3.3: + + * **JSON API prototype**. + + + * **Performance improvement on HashLinkList**: We addressed performance outlier of HashLinkList caused by skewed bucket by switching data in the bucket from linked list to skip list. Add parameter threshold_use_skiplist in NewHashLinkListRepFactory(). + + + + * **More effective on storage space reclaim**: RocksDB is now able to reclaim storage space more effectively during the compaction process. This is done by compensating the size of each deletion entry by the 2X average value size, which makes compaction to be triggerred by deletion entries more easily. + + + * **TimeOut API to write**: Now WriteOptions have a variable called timeout_hint_us. With timeout_hint_us set to non-zero, any write associated with this timeout_hint_us may be aborted when it runs longer than the specified timeout_hint_us, and it is guaranteed that any write completes earlier than the specified time-out will not be aborted due to the time-out condition. + + + * **rate_limiter option**: We added an option that controls total throughput of flush and compaction. The throughput is specified in bytes/sec. Flush always has precedence over compaction when available bandwidth is constrained. + + + +Public API changes: + + + * Removed NewTotalOrderPlainTableFactory because it is not used and implemented semantically incorrect. diff --git a/thirdparty/rocksdb/docs/_posts/2014-09-12-cuckoo.markdown b/thirdparty/rocksdb/docs/_posts/2014-09-12-cuckoo.markdown new file mode 100644 index 0000000000..22178f7cac --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-09-12-cuckoo.markdown @@ -0,0 +1,74 @@ +--- +title: Cuckoo Hashing Table Format +layout: post +author: radheshyam +category: blog +redirect_from: + - /blog/1427/new-bloom-filter-format/ +--- + +## Introduction + +We recently introduced a new [Cuckoo Hashing](http://en.wikipedia.org/wiki/Cuckoo_hashing) based SST file format which is optimized for fast point lookups. The new format was built for applications which require very high point lookup rates (~4Mqps) in read only mode but do not use operations like range scan, merge operator, etc. But, the existing RocksDB file formats were built to support range scan and other operations and the current best point lookup in RocksDB is 1.2 Mqps given by [PlainTable](https://github.com/facebook/rocksdb/wiki/PlainTable-Format)[ format](https://github.com/facebook/rocksdb/wiki/PlainTable-Format). This prompted a hashing based file format, which we present here. The new table format uses a cache friendly version of Cuckoo Hashing algorithm with only 1 or 2 memory accesses per lookup. + + + +Goals: + + * Reduce memory accesses per lookup to 1 or 2 + + + * Get an end to end point lookup rate of at least 4 Mqps + + + * Minimize database size + + +Assumptions: + + * Key length and value length are fixed + + + * The database is operated in read only mode + + +Non-goals: + + + * While optimizing the performance of Get() operation was our primary goal, compaction and build times were secondary. We may work on improving them in future. + + +Details for setting up the table format can be found in [GitHub](https://github.com/facebook/rocksdb/wiki/CuckooTable-Format). + + +## Cuckoo Hashing Algorithm + +In order to achieve high lookup speeds, we did multiple optimizations, including a cache friendly cuckoo hash algorithm. Cuckoo Hashing uses multiple hash functions, _h1, ..., __hn._ + +### Original Cuckoo Hashing + +To insert any new key _k_, we compute hashes of the key _h1(k), ..., __hn__(k)_. We insert the key in the first hash location that is free. If all the locations are blocked, we try to move one of the colliding keys to a different location by trying to re-insert it. + +Finding smallest set of keys to displace in order to accommodate the new key is naturally a shortest path problem in a directed graph where nodes are buckets of hash table and there is an edge from bucket _A_ to bucket _B_ if the element stored in bucket _A_ can be accommodated in bucket _B_ using one of the hash functions. The source nodes are the possible hash locations for the given key _k_ and destination is any one of the empty buckets. We use this algorithm to handle collision. + +To retrieve a key _k_, we compute hashes, _h1(k), ..., __hn__(k)_ and the key must be present in one of these locations. + +Our goal is to minimize average (and maximum) number of hash functions required and hence the number of memory accesses. In our experiments, with a hash utilization of 90%, we found that the average number of lookups is 1.8 and maximum is 3. Around 44% of keys are accommodated in first hash location and 33% in second location. + + +### Cache Friendly Cuckoo Hashing + +We noticed the following two sub-optimal properties in original Cuckoo implementation: + + + * If the key is not present in first hash location, we jump to second hash location which may not be in cache. This results in many cache misses. + + + * Because only 44% of keys are located in first cuckoo block, we couldn't have an optimal prefetching strategy - prefetching all hash locations for a key is wasteful. But prefetching only the first hash location helps only 44% of cases. + + + +The solution is to insert more keys near first location. In case of collision in the first hash location - _h1(k)_, we try to insert it in next few buckets, _h1(k)+1, _h1(k)+2, _..., h1(k)+t-1_. If all of these _t_ locations are occupied, we skip over to next hash function _h2_ and repeat the process. We call the set of _t_ buckets as a _Cuckoo Block_. We chose _t_ such that size of a block is not bigger than a cache line and we prefetch the first cuckoo block. + + +With the new algorithm, for 90% hash utilization, we found that 85% of keys are accommodated in first Cuckoo Block. Prefetching the first cuckoo block yields best results. For a database of 100 million keys with key length 8 and value length 4, the hash algorithm alone can achieve 9.6 Mqps and we are working on improving it further. End to end RocksDB performance results can be found [here](https://github.com/facebook/rocksdb/wiki/CuckooTable-Format). diff --git a/thirdparty/rocksdb/docs/_posts/2014-09-12-new-bloom-filter-format.markdown b/thirdparty/rocksdb/docs/_posts/2014-09-12-new-bloom-filter-format.markdown new file mode 100644 index 0000000000..96fa50a401 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-09-12-new-bloom-filter-format.markdown @@ -0,0 +1,52 @@ +--- +title: New Bloom Filter Format +layout: post +author: zagfox +category: blog +redirect_from: + - /blog/1367/cuckoo/ +--- + +## Introduction + +In this post, we are introducing "full filter block" --- a new bloom filter format for [block based table](https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format). This could bring about 40% of improvement for key query under in-memory (all data stored in memory, files stored in tmpfs/ramfs, an [example](https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks) workload. The main idea behind is to generate a big filter that covers all the keys in SST file to avoid lots of unnecessary memory look ups. + + + + +## What is Bloom Filter + +In brief, [bloom filter](https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter) is a bits array generated for a set of keys that could tell if an arbitrary key may exist in that set. + +In RocksDB, we generate such a bloom filter for each SST file. When we conduct a query for a key, we first goes to the bloom filter block of SST file. If key may exist in filter, we goes into data block in SST file to search for the key. If not, we would return directly. So it could help speed up point look up operation a lot. + +## Original Bloom Filter Format + +Original bloom filter creates filters for each individual data block in SST file. It has complex structure (ref [here](https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format#filter-meta-block)) which results in a lot of non-adjacent memory look ups. + +Here's the work flow for checking original bloom filter in block based table: + +1. Given the target key, we goes to the index block to get the "data block ID" where this key may reside. +1. Using the "data block ID", we goes to the filter block and get the correct "offset of filter". +1. Using the "offset of filter", we goes to the actual filter and do the checking. + +## New Bloom Filter Format + +New bloom filter creates filter for all keys in SST file and we name it "full filter". The data structure of full filter is very simple, there is just one big filter: + +    [ full filter ] + +In this way, the work flow of bloom filter checking is much simplified. + +(1) Given the target key, we goes directly to the filter block and conduct the filter checking. + +To be specific, there would be no checking for index block and no address jumping inside of filter block. + +Though it is a big filter, the total filter size would be the same as the original filter. + +One little draw back is that the new bloom filter introduces more memory consumption when building SST file because we need to buffer keys (or their hashes) before generating filter. Original filter just creates a bunch of small filters so it just buffer a small amount of keys. For full filter, we buffer hashes of all keys, which would take more memory when SST file size increases. + + +## Usage & Customization + +You can refer to the document here for [usage](https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter#usage-of-new-bloom-filter) and [customization](https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter#customize-your-own-filterpolicy). diff --git a/thirdparty/rocksdb/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown b/thirdparty/rocksdb/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown new file mode 100644 index 0000000000..1878a5a567 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2014-09-15-rocksdb-3-5-release.markdown @@ -0,0 +1,38 @@ +--- +title: RocksDB 3.5 Release! +layout: post +author: leijin +category: blog +redirect_from: + - /blog/1547/rocksdb-3-5-release/ +--- + +New RocksDB release - 3.5! + + +**New Features** + + + 1. Add include/utilities/write_batch_with_index.h, providing a utility class to query data out of WriteBatch when building it. + + + 2. new ReadOptions.total_order_seek to force total order seek when block-based table is built with hash index. + + + +**Public API changes** + + + 1. The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key. + + + 2. Move BlockBasedTable related options to BlockBasedTableOptions from Options. Change corresponding JNI interface. Options affected include: no_block_cache, block_cache, block_cache_compressed, block_size, block_size_deviation, block_restart_interval, filter_policy, whole_key_filtering. filter_policy is changed to shared_ptr from a raw pointer. + + + 3. Remove deprecated options: disable_seek_compaction and db_stats_log_interval + + + 4. OptimizeForPointLookup() takes one parameter for block cache size. It now builds hash index, bloom filter, and block cache. + + +[https://github.com/facebook/rocksdb/releases/tag/v3.5](https://github.com/facebook/rocksdb/releases/tag/rocksdb-3.5) diff --git a/thirdparty/rocksdb/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown b/thirdparty/rocksdb/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown new file mode 100644 index 0000000000..f18de0bbc3 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-01-16-migrating-from-leveldb-to-rocksdb-2.markdown @@ -0,0 +1,112 @@ +--- +title: Migrating from LevelDB to RocksDB +layout: post +author: lgalanis +category: blog +redirect_from: + - /blog/1811/migrating-from-leveldb-to-rocksdb-2/ +--- + +If you have an existing application that uses LevelDB and would like to migrate to using RocksDB, one problem you need to overcome is to map the options for LevelDB to proper options for RocksDB. As of release 3.9 this can be automatically done by using our option conversion utility found in rocksdb/utilities/leveldb_options.h. What is needed, is to first replace `leveldb::Options` with `rocksdb::LevelDBOptions`. Then, use `rocksdb::ConvertOptions( )` to convert the `LevelDBOptions` struct into appropriate RocksDB options. Here is an example: + + + +LevelDB code: + +```c++ +#include +#include "leveldb/db.h" + +using namespace leveldb; + +int main(int argc, char** argv) { + DB *db; + + Options opt; + opt.create_if_missing = true; + opt.max_open_files = 1000; + opt.block_size = 4096; + + Status s = DB::Open(opt, "/tmp/mydb", &db); + + delete db; +} +``` + +RocksDB code: + +```c++ +#include +#include "rocksdb/db.h" +#include "rocksdb/utilities/leveldb_options.h" + +using namespace rocksdb; + +int main(int argc, char** argv) { + DB *db; + + LevelDBOptions opt; + opt.create_if_missing = true; + opt.max_open_files = 1000; + opt.block_size = 4096; + + Options rocksdb_options = ConvertOptions(opt); + // add rocksdb specific options here + + Status s = DB::Open(rocksdb_options, "/tmp/mydb_rocks", &db); + + delete db; +} +``` + +The difference is: + +```diff +-#include "leveldb/db.h" ++#include "rocksdb/db.h" ++#include "rocksdb/utilities/leveldb_options.h" + +-using namespace leveldb; ++using namespace rocksdb; + +- Options opt; ++ LevelDBOptions opt; + +- Status s = DB::Open(opt, "/tmp/mydb", &db); ++ Options rocksdb_options = ConvertOptions(opt); ++ // add rockdb specific options here ++ ++ Status s = DB::Open(rocksdb_options, "/tmp/mydb_rocks", &db); +``` + +Once you get up and running with RocksDB you can then focus on tuning RocksDB further by modifying the converted options struct. + +The reason why ConvertOptions is handy is because a lot of individual options in RocksDB have moved to other structures in different components. For example, block_size is not available in struct rocksdb::Options. It resides in struct rocksdb::BlockBasedTableOptions, which is used to create a TableFactory object that RocksDB uses internally to create the proper TableBuilder objects. If you were to write your application from scratch it would look like this: + +RocksDB code from scratch: + +```c++ +#include +#include "rocksdb/db.h" +#include "rocksdb/table.h" + +using namespace rocksdb; + +int main(int argc, char** argv) { + DB *db; + + Options opt; + opt.create_if_missing = true; + opt.max_open_files = 1000; + + BlockBasedTableOptions topt; + topt.block_size = 4096; + opt.table_factory.reset(NewBlockBasedTableFactory(topt)); + + Status s = DB::Open(opt, "/tmp/mydb_rocks", &db); + + delete db; +} +``` + +The LevelDBOptions utility can ease migration to RocksDB from LevelDB and allows us to break down the various options across classes as it is needed. diff --git a/thirdparty/rocksdb/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown b/thirdparty/rocksdb/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown new file mode 100644 index 0000000000..cddc0dd01f --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-02-24-reading-rocksdb-options-from-a-file.markdown @@ -0,0 +1,41 @@ +--- +title: Reading RocksDB options from a file +layout: post +author: lgalanis +category: blog +redirect_from: + - /blog/1883/reading-rocksdb-options-from-a-file/ +--- + +RocksDB options can be provided using a file or any string to RocksDB. The format is straightforward: `write_buffer_size=1024;max_write_buffer_number=2`. Any whitespace around `=` and `;` is OK. Moreover, options can be nested as necessary. For example `BlockBasedTableOptions` can be nested as follows: `write_buffer_size=1024; max_write_buffer_number=2; block_based_table_factory={block_size=4k};`. Similarly any white space around `{` or `}` is ok. Here is what it looks like in code: + + + +```c++ +#include +#include "rocksdb/db.h" +#include "rocksdb/table.h" +#include "rocksdb/utilities/convenience.h" + +using namespace rocksdb; + +int main(int argc, char** argv) { + DB *db; + + Options opt; + + std::string options_string = + "create_if_missing=true;max_open_files=1000;" + "block_based_table_factory={block_size=4096}"; + + Status s = GetDBOptionsFromString(opt, options_string, &opt); + + s = DB::Open(opt, "/tmp/mydb_rocks", &db); + + // use db + + delete db; +} +``` + +Using `GetDBOptionsFromString` is a convenient way of changing options for your RocksDB application without needing to resort to recompilation or tedious command line parsing. diff --git a/thirdparty/rocksdb/docs/_posts/2015-02-27-write-batch-with-index.markdown b/thirdparty/rocksdb/docs/_posts/2015-02-27-write-batch-with-index.markdown new file mode 100644 index 0000000000..7f9f776536 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-02-27-write-batch-with-index.markdown @@ -0,0 +1,20 @@ +--- +title: 'WriteBatchWithIndex: Utility for Implementing Read-Your-Own-Writes' +layout: post +author: sdong +category: blog +redirect_from: + - /blog/1901/write-batch-with-index/ +--- + +RocksDB can be used as a storage engine of a higher level database. In fact, we are currently plugging RocksDB into MySQL and MongoDB as one of their storage engines. RocksDB can help with guaranteeing some of the ACID properties: durability is guaranteed by RocksDB by design; while consistency and isolation need to be enforced by concurrency controls on top of RocksDB; Atomicity can be implemented by committing a transaction's writes with one write batch to RocksDB in the end. + + + +However, if we enforce atomicity by only committing all writes in the end of the transaction in one batch, you cannot get the updated value from RocksDB previously written by the same transaction (read-your-own-write). To read the updated value, the databases on top of RocksDB need to maintain an internal buffer for all the written keys, and when a read happens they need to merge the result from RocksDB and from this buffer. This is a problem we faced when building the RocksDB storage engine in MongoDB. We solved it by creating a utility class, WriteBatchWithIndex (a write batch with a searchable index) and made it part of public API so that the community can also benefit from it. + +Before talking about the index part, let me introduce write batch first. The write batch class, `WriteBatch`, is a RocksDB data structure for atomic writes of multiple keys. Users can buffer their updates to a `WriteBatch` by calling `write_batch.Put("key1", "value1")` or `write_batch.Delete("key2")`, similar as calling RocksDB's functions of the same names. In the end, they call `db->Write(write_batch)` to atomically update all those batched operations to the DB. It is how a database can guarantee atomicity, as shown above. Adding a searchable index to `WriteBatch`, we now have `WriteBatchWithIndex`. Users can put updates to WriteBatchIndex in the same way as to `WriteBatch`. In the end, users can get a `WriteBatch` object from it and issue `db->Write()`. Additionally, users can create an iterator of a WriteBatchWithIndex, seek to any key location and iterate from there. + +To implement read-your-own-write using `WriteBatchWithIndex`, every time the user creates a transaction, we create a `WriteBatchWithIndex` attached to it. All the writes of the transaction go to the `WriteBatchWithIndex` first. When we commit the transaction, we atomically write the batch to RocksDB. When the user wants to call `Get()`, we first check if the value exists in the `WriteBatchWithIndex` and return the value if existing, by seeking and reading from an iterator of the write batch, before checking data in RocksDB. For example, here is the we implement it in MongoDB's RocksDB storage engine: [link](https://github.com/mongodb/mongo/blob/a31cc114a89a3645e97645805ba77db32c433dce/src/mongo/db/storage/rocks/rocks_recovery_unit.cpp#L245-L260). If a range query comes, we pass a DB's iterator to `WriteBatchWithIndex`, which creates a super iterator which combines the results from the DB iterator with the batch's iterator. Using this super iterator, we can iterate the DB with the transaction's own writes. Here is the iterator creation codes in MongoDB's RocksDB storage engine: [link](https://github.com/mongodb/mongo/blob/a31cc114a89a3645e97645805ba77db32c433dce/src/mongo/db/storage/rocks/rocks_recovery_unit.cpp#L266-L269). In this way, the database can solve the read-your-own-write problem by using RocksDB to handle a transaction's uncommitted writes. + +Using `WriteBatchWithIndex`, we successfully implemented read-your-own-writes in the RocksDB storage engine of MongoDB. If you also have a read-your-own-write problem, `WriteBatchWithIndex` can help you implement it quickly and correctly. diff --git a/thirdparty/rocksdb/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown b/thirdparty/rocksdb/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown new file mode 100644 index 0000000000..1ffe2c532e --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-04-22-integrating-rocksdb-with-mongodb-2.markdown @@ -0,0 +1,16 @@ +--- +title: Integrating RocksDB with MongoDB +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/1967/integrating-rocksdb-with-mongodb-2/ +--- + +Over the last couple of years, we have been busy integrating RocksDB with various services here at Facebook that needed to store key-value pairs locally. We have also seen other companies using RocksDB as local storage components of their distributed systems. + + + +The next big challenge for us is to bring RocksDB storage engine to general purpose databases. Today we have an exciting milestone to share with our community! We're running MongoDB with RocksDB in production and seeing great results! You can read more about it here: [http://blog.parse.com/announcements/mongodb-rocksdb-parse/](http://blog.parse.com/announcements/mongodb-rocksdb-parse/) + +Keep tuned for benchmarks and more stability and performance improvements. diff --git a/thirdparty/rocksdb/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown b/thirdparty/rocksdb/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown new file mode 100644 index 0000000000..f3a55faae1 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-06-12-rocksdb-in-osquery.markdown @@ -0,0 +1,10 @@ +--- +title: RocksDB in osquery +layout: post +author: icanadi +category: lgalanis +redirect_from: + - /blog/1997/rocksdb-in-osquery/ +--- + +Check out [this](https://code.facebook.com/posts/1411870269134471/how-rocksdb-is-used-in-osquery/) blog post by [Mike Arpaia](https://www.facebook.com/mike.arpaia) and [Ted Reed](https://www.facebook.com/treeded) about how osquery leverages RocksDB to build an embedded pub-sub system. This article is a great read and contains insights on how to properly use RocksDB. diff --git a/thirdparty/rocksdb/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown b/thirdparty/rocksdb/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown new file mode 100644 index 0000000000..b3e2703fc6 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-07-15-rocksdb-2015-h2-roadmap.markdown @@ -0,0 +1,92 @@ +--- +title: RocksDB 2015 H2 roadmap +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/2015/rocksdb-2015-h2-roadmap/ +--- + +Every 6 months, RocksDB team gets together to prioritize the work ahead of us. We just went through this exercise and we wanted to share the results with the community. Here's what RocksDB team will be focusing on for the next 6 months: + + + +**MyRocks** + +As you might know, we're working hard to integrate RocksDB as a storage engine for MySQL. This project is pretty important for us because we're heavy users of MySQL. We're already getting pretty good performance results, but there is more work to be done. We need to focus on both performance and stability. The most high priority items on are list are: + + + + + 1. Reduce CPU costs of RocksDB as a MySQL storage engine + + + 2. Implement pessimistic concurrency control to support repeatable read isolation level in MyRocks + + + 3. Reduce P99 read latency, which is high mostly because of lingering tombstones + + + 4. Port ZSTD compression + + +**MongoRocks** + +Another database that we're working on is MongoDB. The project of integrating MongoDB with RocksDB storage engine is called MongoRocks. It's already running in production at Parse [1] and we're seeing surprisingly few issues. Our plans for the next half: + + + + + 1. Keep improving performance and stability, possibly reuse work done on MyRocks (workloads are pretty similar). + + + 2. Increase internal and external adoption. + + + 3. Support new MongoDB 3.2. + + +**RocksDB on cheaper storage media** + +Up to now, our mission was to build the best key-value store “for fast storage” (flash and in-memory). However, there are some use-cases at Facebook that don't need expensive high-end storage. In the next six months, we plan to deploy RocksDB on cheaper storage media. We will optimize performance to RocksDB on either or both: + + + + + 1. Hard drive storage array. + + + 2. Tiered Storage. + + +**Quality of Service** + +When talking to our customers, there are couple of issues that keep reoccurring. We need to fix them to make our customers happy. We will improve RocksDB to provide better assurance of performance and resource usage. Non-exhaustive list includes: + + + + + 1. Iterate P99 can be high due to the presence of tombstones. + + + 2. Write stalls can happen during high write loads. + + + 3. Better control of memory and disk usage. + + + 4. Service quality and performance of backup engine. + + +**Operation's user experience** + +As we increase deployment of RocksDB, engineers are spending more time on debugging RocksDB issues. We plan to improve user experience when running RocksDB. The goal is to reduce TTD (time-to-debug). The work includes monitoring, visualizations and documentations. + +[1]( http://blog.parse.com/announcements/mongodb-rocksdb-parse/](http://blog.parse.com/announcements/mongodb-rocksdb-parse/) + + +### Comments + +**[Mike](allspace2012@outlook.com)** + +What’s the status of this roadmap? “RocksDB on cheaper storage media”, has this been implemented? diff --git a/thirdparty/rocksdb/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown b/thirdparty/rocksdb/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown new file mode 100644 index 0000000000..fe7b7b2681 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-07-17-spatial-indexing-in-rocksdb.markdown @@ -0,0 +1,78 @@ +--- +title: Spatial indexing in RocksDB +layout: post +author: icanadi +category: blog +redirect_from: + - /blog/2039/spatial-indexing-in-rocksdb/ +--- + +About a year ago, there was a need to develop a spatial database at Facebook. We needed to store and index Earth's map data. Before building our own, we looked at the existing spatial databases. They were all very good technology, but also general purpose. We could sacrifice a general-purpose API, so we thought we could build a more performant database, since it would be specifically designed for our use-case. Furthermore, we decided to build the spatial database on top of RocksDB, because we have a lot of operational experience with running and tuning RocksDB at a large scale. + + + +When we started looking at this project, the first thing that surprised us was that our planet is not that big. Earth's entire map data can fit in memory on a reasonably high-end machine. Thus, we also decided to build a spatial database optimized for memory-resident dataset. + +The first use-case of our spatial database was an experimental map renderer. As part of our project, we successfully loaded [Open Street Maps](https://www.openstreetmap.org/) dataset and hooked it up with [Mapnik](http://mapnik.org/), a map rendering engine. + +The usual Mapnik workflow is to load the map data into a SQL-based database and then define map layers with SQL statements. To render a tile, Mapnik needs to execute a couple of SQL queries. The benefit of this approach is that you don't need to reload your database when you change your map style. You can just change your SQL query and Mapnik picks it up. In our model, we decided to precompute the features we need for each tile. We need to know the map style before we create the database. However, when rendering the map tile, we only fetch the features that we need to render. + +We haven't open sourced the RocksDB Mapnik plugin or the database loading pipeline. However, the spatial indexing is available in RocksDB under a name [SpatialDB](https://github.com/facebook/rocksdb/blob/master/include/rocksdb/utilities/spatial_db.h). The API is focused on map rendering use-case, but we hope that it can also be used for other spatial-based applications. + +Let's take a tour of the API. When you create a spatial database, you specify the spatial indexes that need to be built. Each spatial index is defined by a bounding box and granularity. For map rendering, we create a spatial index for each zoom levels. Higher zoom levels have more granularity. + + + + SpatialDB::Create( + SpatialDBOptions(), + "/data/map", { + SpatialIndexOptions("zoom10", BoundingBox(0, 0, 100, 100), 10), + SpatialIndexOptions("zoom16", BoundingBox(0, 0, 100, 100), 16) + } + ); + + + + +When you insert a feature (building, street, country border) into SpatialDB, you need to specify the list of spatial indexes that will index the feature. In the loading phase we process the map style to determine the list of zoom levels on which we'll render the feature. For example, we will not render the building on zoom level that shows an entire country. Building will only be indexed on higher zoom level's index. Country borders will be indexes on all zoom levels. + + + + FeatureSet feature; + feature.Set("type", "building"); + feature.Set("height", 6); + db->Insert(WriteOptions(), BoundingBox(5, 5, 10, 10), + well_known_binary_blob, feature, {"zoom16"}); + + + + +The indexing part is pretty simple. For each feature, we first find a list of index tiles that it intersects. Then, we add a link from the tile's [quad key](https://msdn.microsoft.com/en-us/library/bb259689.aspx) to the feature's primary key. Using quad keys improves data locality, i.e. features closer together geographically will have similar quad keys. Even though we're optimizing for a memory-resident dataset, data locality is still very important due to different caching effects. + +After you're done inserting all the features, you can call an API Compact() that will compact the dataset and speed up read queries. + + + + db->Compact(); + + + + +SpatialDB's query specifies: 1) bounding box we're interested in, and 2) a zoom level. We find all tiles that intersect with the query's bounding box and return all features in those tiles. + + + + + Cursor* c = db_->Query(ReadOptions(), BoundingBox(1, 1, 7, 7), "zoom16"); + for (c->Valid(); c->Next()) { + Render(c->blob(), c->feature_set()); + } + + + + +Note: `Render()` function is not part of RocksDB. You will need to use one of many open source map renderers, for example check out [Mapnik](http://mapnik.org/). + +TL;DR If you need an embedded spatial database, check out RocksDB's SpatialDB. [Let us know](https://www.facebook.com/groups/rocksdb.dev/) how we can make it better. + +If you're interested in learning more, check out this [talk](https://www.youtube.com/watch?v=T1jWsDMONM8). diff --git a/thirdparty/rocksdb/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown b/thirdparty/rocksdb/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown new file mode 100644 index 0000000000..b6bb47d533 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-07-22-rocksdb-is-now-available-in-windows-platform.markdown @@ -0,0 +1,30 @@ +--- +title: RocksDB is now available in Windows Platform +layout: post +author: dmitrism +category: blog +redirect_from: + - /blog/2033/rocksdb-is-now-available-in-windows-platform/ +--- + +Over the past 6 months we have seen a number of use cases where RocksDB is successfully used by the community and various companies to achieve high throughput and volume in a modern server environment. + +We at Microsoft Bing could not be left behind. As a result we are happy to [announce](http://bit.ly/1OmWBT9) the availability of the Windows Port created here at Microsoft which we intend to use as a storage option for one of our key/value data stores. + + + +We are happy to make this available for the community. Keep tuned for more announcements to come. + +### Comments + +**[Siying Dong](siying.d@fb.com)** + +Appreciate your contributions to RocksDB project! I believe it will benefits many users! + +**[empresas sevilla](oxofkx@gmail.com)** + +Magnifico artículo|, un placer leer el blog + +**[jak usunac](tomogedac@o2.pl)** + +I believe it will benefits too diff --git a/thirdparty/rocksdb/docs/_posts/2015-07-23-dynamic-level.markdown b/thirdparty/rocksdb/docs/_posts/2015-07-23-dynamic-level.markdown new file mode 100644 index 0000000000..0ff3a0542f --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-07-23-dynamic-level.markdown @@ -0,0 +1,29 @@ +--- +title: Dynamic Level Size for Level-Based Compaction +layout: post +author: sdong +category: blog +redirect_from: + - /blog/2207/dynamic-level/ +--- + +In this article, we follow up on the first part of an answer to one of the questions in our [AMA](https://www.reddit.com/r/IAmA/comments/3de3cv/we_are_rocksdb_engineering_team_ask_us_anything/ct4a8tb), the dynamic level size in level-based compaction. + + + +Level-based compaction is the original LevelDB compaction style and one of the two major compaction styles in RocksDB (See [our wiki](https://github.com/facebook/rocksdb/wiki/RocksDB-Basics#multi-threaded-compactions)). In RocksDB we introduced parallelism and more configurable options to it but the main algorithm stayed the same, until we recently introduced the dynamic level size mode. + + +In level-based compaction, we organize data to different sorted runs, called levels. Each level has a target size.  Usually target size of levels increases by the same size multiplier. For example, you can set target size of level 1 to be 1GB, and size multiplier to be 10, and the target size of level 1, 2, 3, 4 will be 1GB, 10GB, 100GB and 1000GB. Before level 1, there will be some staging file flushed from mem tables, called Level 0 files, which will later be merged to level 1. Compactions will be triggered as soon as actual size of a level exceeds its target size. We will merge a subset of data of that level to next level, to reduce size of the level. More compactions will be triggered until sizes of all the levels are lower than their target sizes. In a steady state, the size of each level will be around the same size of the size of level targets. + + +Level-based compaction’s advantage is its good space efficiency. We usually use the metric space amplification to measure the space efficiency. In this article ignore the effects of data compression so space amplification= size_on_file_system / size_of_user_data. + + +How do we estimate space amplification of level-based compaction? We focus specifically on the databases in steady state, which means database size is stable or grows slowly over time. This means updates will add roughly the same or little more data than what is removed by deletes. Given that, if we compact all the data all to the last level, the size of level will be equal as the size of last level before the compaction. On the other hand, the size of user data will be approximately the size of DB if we compact all the levels down to the last level. So the size of the last level will be a good estimation of user data size. So total size of the DB divided by the size of the last level will be a good estimation of space amplification. + + +Applying the equation, if we have four non-zero levels, their sizes are 1GB, 10GB, 100GB, 1000GB, the size amplification will be approximately (1000GB + 100GB + 10GB + 1GB) / 1000GB = 1.111, which is a very good number. However, there is a catch here: how to make sure the last level’s size is 1000GB, the same as the level’s size target? A user has to fine tune level sizes to achieve this number and will need to re-tune if DB size changes. The theoretic number 1.11 is hard to achieve in practice. In a worse case, if you have the target size of last level to be 1000GB but the user data is only 200GB, then the actual space amplification will be (200GB + 100GB + 10GB + 1GB) / 200GB = 1.555, a much worse number. + + +To solve this problem, my colleague Igor Kabiljo came up with a solution of dynamic level size target mode. You can enable it by setting options.level_compaction_dynamic_level_bytes=true. In this mode, size target of levels are changed dynamically based on size of the last level. Suppose the level size multiplier to be 10, and the DB size is 200GB. The target size of the last level is automatically set to be the actual size of the level, which is 200GB, the second to last level’s size target will be automatically set to be size_last_level / 10 = 20GB, the third last level’s will be size_last_level/100 = 2GB, and next level to be size_last_level/1000 = 200MB. We stop here because 200MB is within the range of the first level. In this way, we can achieve the 1.111 space amplification, without fine tuning of the level size targets. More details can be found in [code comments of the option](https://github.com/facebook/rocksdb/blob/v3.11/include/rocksdb/options.h#L366-L423) in the header file. diff --git a/thirdparty/rocksdb/docs/_posts/2015-10-27-getthreadlist.markdown b/thirdparty/rocksdb/docs/_posts/2015-10-27-getthreadlist.markdown new file mode 100644 index 0000000000..332a29f020 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-10-27-getthreadlist.markdown @@ -0,0 +1,193 @@ +--- +title: GetThreadList +layout: post +author: yhciang +category: blog +redirect_from: + - /blog/2261/getthreadlist/ +--- + +We recently added a new API, called `GetThreadList()`, that exposes the RocksDB background thread activity. With this feature, developers will be able to obtain the real-time information about the currently running compactions and flushes such as the input / output size, elapsed time, the number of bytes it has written. Below is an example output of `GetThreadList`. To better illustrate the example, we have put a sample output of `GetThreadList` into a table where each column represents a thread status: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ThreadID +140716395198208 +140716416169728 +
DB +db1 +db2 +
CF +default +picachu +
ThreadType +High Pri +Low Pri +
Operation +Flush +Compaction +
ElapsedTime +143.459 ms +607.538 ms +
Stage +FlushJob::WriteLevel0Table +CompactionJob::Install +
OperationProperties + +BytesMemtables 4092938 +BytesWritten 1050701 + +BaseInputLevel 1 +BytesRead 4876417 +BytesWritten 4140109 +IsDeletion 0 +IsManual 0 +IsTrivialMove 0 +JobID 146 +OutputLevel 2 +TotalInputBytes 4883044 +
+ +In the above output, we can see `GetThreadList()` reports the activity of two threads: one thread running flush job (middle column) and the other thread running a compaction job (right-most column). In each thread status, it shows basic information about the thread such as thread id, it's target db / column family, and the job it is currently doing and the current status of the job. For instance, we can see thread 140716416169728 is doing compaction on the `picachu` column family in database `db2`. In addition, we can see the compaction has been running for 600 ms, and it has read 4876417 bytes out of 4883044 bytes. This indicates the compaction is about to complete. The stage property indicates which code block the thread is currently executing. For instance, thread 140716416169728 is currently running `CompactionJob::Install`, which further indicates the compaction job is almost done. + +Below we briefly describe its API. + + +## How to Enable it? + + +To enable thread-tracking of a rocksdb instance, simply set `enable_thread_tracking` to true in its DBOptions: + +```c++ +// If true, then the status of the threads involved in this DB will +// be tracked and available via GetThreadList() API. +// +// Default: false +bool enable_thread_tracking; +``` + + + +## The API + + +The GetThreadList API is defined in [include/rocksdb/env.h](https://github.com/facebook/rocksdb/blob/master/include/rocksdb/env.h#L317-L318), which is an Env +function: + +```c++ +virtual Status GetThreadList(std::vector* thread_list) +``` + +Since an Env can be shared across multiple rocksdb instances, the output of +`GetThreadList()` include the background activity of all the rocksdb instances +that using the same Env. + +The `GetThreadList()` API simply returns a vector of `ThreadStatus`, each describes +the current status of a thread. The `ThreadStatus` structure, defined in +[include/rocksdb/thread_status.h](https://github.com/facebook/rocksdb/blob/master/include/rocksdb/thread_status.h), contains the following information: + +```c++ +// An unique ID for the thread. +const uint64_t thread_id; + +// The type of the thread, it could be HIGH_PRIORITY, +// LOW_PRIORITY, and USER +const ThreadType thread_type; + +// The name of the DB instance where the thread is currently +// involved with. It would be set to empty string if the thread +// does not involve in any DB operation. +const std::string db_name; + +// The name of the column family where the thread is currently +// It would be set to empty string if the thread does not involve +// in any column family. +const std::string cf_name; + +// The operation (high-level action) that the current thread is involved. +const OperationType operation_type; + +// The elapsed time in micros of the current thread operation. +const uint64_t op_elapsed_micros; + +// An integer showing the current stage where the thread is involved +// in the current operation. +const OperationStage operation_stage; + +// A list of properties that describe some details about the current +// operation. Same field in op_properties[] might have different +// meanings for different operations. +uint64_t op_properties[kNumOperationProperties]; + +// The state (lower-level action) that the current thread is involved. +const StateType state_type; +``` + +If you are interested in the background thread activity of your RocksDB application, please feel free to give `GetThreadList()` a try :) diff --git a/thirdparty/rocksdb/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown b/thirdparty/rocksdb/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown new file mode 100644 index 0000000000..6852b8ffa3 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-11-10-use-checkpoints-for-efficient-snapshots.markdown @@ -0,0 +1,45 @@ +--- +title: Use Checkpoints for Efficient Snapshots +layout: post +author: rven2 +category: blog +redirect_from: + - /blog/2609/use-checkpoints-for-efficient-snapshots/ +--- + +**Checkpoint** is a feature in RocksDB which provides the ability to take a snapshot of a running RocksDB database in a separate directory. Checkpoints can be used as a point in time snapshot, which can be opened Read-only to query rows as of the point in time or as a Writeable snapshot by opening it Read-Write. Checkpoints can be used for both full and incremental backups. + + + + +The Checkpoint feature enables RocksDB to create a consistent snapshot of a given RocksDB database in the specified directory. If the snapshot is on the same filesystem as the original database, the SST files will be hard-linked, otherwise SST files will be copied. The manifest and CURRENT files will be copied. In addition, if there are multiple column families, log files will be copied for the period covering the start and end of the checkpoint, in order to provide a consistent snapshot across column families. + + + + +A Checkpoint object needs to be created for a database before checkpoints are created. The API is as follows: + + + + +`Status Create(DB* db, Checkpoint** checkpoint_ptr);` + + + + +Given a checkpoint object and a directory, the CreateCheckpoint function creates a consistent snapshot of the database in the given directory. + + + + +`Status CreateCheckpoint(const std::string& checkpoint_dir);` + + + + +The directory should not already exist and will be created by this API. The directory will be an absolute path. The checkpoint can be used as a ​read-only copy of the DB or can be opened as a standalone DB. When opened read/write, the SST files continue to be hard links and these links are removed when the files are obsoleted. When the user is done with the snapshot, the user can delete the directory to remove the snapshot. + + + + +Checkpoints are used for online backup in ​MyRocks. which is MySQL using RocksDB as the storage engine . ([MySQL on RocksDB](https://github.com/facebook/mysql-5.6)) ​ diff --git a/thirdparty/rocksdb/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown b/thirdparty/rocksdb/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown new file mode 100644 index 0000000000..b21b04fe38 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2015-11-16-analysis-file-read-latency-by-level.markdown @@ -0,0 +1,244 @@ +--- +title: Analysis File Read Latency by Level +layout: post +author: sdong +category: blog +redirect_from: + - /blog/2537/analysis-file-read-latency-by-level/ +--- + +In many use cases of RocksDB, people rely on OS page cache for caching compressed data. With this approach, verifying effective of the OS page caching is challenging, because file system is a black box to users. + +As an example, a user can tune the DB as following: use level-based compaction, with L1 - L4 sizes to be 1GB, 10GB, 100GB and 1TB. And they reserve about 20GB memory as OS page cache, expecting level 0, 1 and 2 are mostly cached in memory, leaving only reads from level 3 and 4 requiring disk I/Os. However, in practice, it's not easy to verify whether OS page cache does exactly what we expect. For example, if we end up with doing 4 instead of 2 I/Os per query, it's not easy for users to figure out whether the it's because of efficiency of OS page cache or reading multiple blocks for a level. Analysis like it is especially important if users run RocksDB on hard drive disks, for the gap of latency between hard drives and memory is much higher than flash-based SSDs. + + + +In order to make tuning easier, we added new instrumentation to help users analysis latency distribution of file reads in different levels. If users turn DB statistics on, we always keep track of distribution of file read latency for each level. Users can retrieve the information by querying DB property “rocksdb.stats” ( [https://github.com/facebook/rocksdb/blob/v3.13.1/include/rocksdb/db.h#L315-L316](https://github.com/facebook/rocksdb/blob/v3.13.1/include/rocksdb/db.h#L315-L316) ). It will also printed out as a part of compaction summary in info logs periodically. + +The output looks like this: + + +``` +** Level 0 read latency histogram (micros): +Count: 696 Average: 489.8118 StdDev: 222.40 +Min: 3.0000 Median: 452.3077 Max: 1896.0000 +Percentiles: P50: 452.31 P75: 641.30 P99: 1068.00 P99.9: 1860.80 P99.99: 1896.00 +------------------------------------------------------ +[ 2, 3 ) 1 0.144% 0.144% +[ 18, 20 ) 1 0.144% 0.287% +[ 45, 50 ) 5 0.718% 1.006% +[ 50, 60 ) 26 3.736% 4.741% # +[ 60, 70 ) 6 0.862% 5.603% +[ 90, 100 ) 1 0.144% 5.747% +[ 120, 140 ) 2 0.287% 6.034% +[ 140, 160 ) 1 0.144% 6.178% +[ 160, 180 ) 1 0.144% 6.322% +[ 200, 250 ) 9 1.293% 7.615% +[ 250, 300 ) 45 6.466% 14.080% # +[ 300, 350 ) 88 12.644% 26.724% ### +[ 350, 400 ) 88 12.644% 39.368% ### +[ 400, 450 ) 71 10.201% 49.569% ## +[ 450, 500 ) 65 9.339% 58.908% ## +[ 500, 600 ) 74 10.632% 69.540% ## +[ 600, 700 ) 92 13.218% 82.759% ### +[ 700, 800 ) 64 9.195% 91.954% ## +[ 800, 900 ) 35 5.029% 96.983% # +[ 900, 1000 ) 12 1.724% 98.707% +[ 1000, 1200 ) 6 0.862% 99.569% +[ 1200, 1400 ) 2 0.287% 99.856% +[ 1800, 2000 ) 1 0.144% 100.000% + +** Level 1 read latency histogram (micros): +(......not pasted.....) + +** Level 2 read latency histogram (micros): +(......not pasted.....) + +** Level 3 read latency histogram (micros): +(......not pasted.....) + +** Level 4 read latency histogram (micros): +(......not pasted.....) + +** Level 5 read latency histogram (micros): +Count: 25583746 Average: 421.1326 StdDev: 385.11 +Min: 1.0000 Median: 376.0011 Max: 202444.0000 +Percentiles: P50: 376.00 P75: 438.00 P99: 1421.68 P99.9: 4164.43 P99.99: 9056.52 +------------------------------------------------------ +[ 0, 1 ) 2351 0.009% 0.009% +[ 1, 2 ) 6077 0.024% 0.033% +[ 2, 3 ) 8471 0.033% 0.066% +[ 3, 4 ) 788 0.003% 0.069% +[ 4, 5 ) 393 0.002% 0.071% +[ 5, 6 ) 786 0.003% 0.074% +[ 6, 7 ) 1709 0.007% 0.080% +[ 7, 8 ) 1769 0.007% 0.087% +[ 8, 9 ) 1573 0.006% 0.093% +[ 9, 10 ) 1495 0.006% 0.099% +[ 10, 12 ) 3043 0.012% 0.111% +[ 12, 14 ) 2259 0.009% 0.120% +[ 14, 16 ) 1233 0.005% 0.125% +[ 16, 18 ) 762 0.003% 0.128% +[ 18, 20 ) 451 0.002% 0.130% +[ 20, 25 ) 794 0.003% 0.133% +[ 25, 30 ) 1279 0.005% 0.138% +[ 30, 35 ) 1172 0.005% 0.142% +[ 35, 40 ) 1363 0.005% 0.148% +[ 40, 45 ) 409 0.002% 0.149% +[ 45, 50 ) 105 0.000% 0.150% +[ 50, 60 ) 80 0.000% 0.150% +[ 60, 70 ) 280 0.001% 0.151% +[ 70, 80 ) 1583 0.006% 0.157% +[ 80, 90 ) 4245 0.017% 0.174% +[ 90, 100 ) 6572 0.026% 0.200% +[ 100, 120 ) 9724 0.038% 0.238% +[ 120, 140 ) 3713 0.015% 0.252% +[ 140, 160 ) 2383 0.009% 0.261% +[ 160, 180 ) 18344 0.072% 0.333% +[ 180, 200 ) 51873 0.203% 0.536% +[ 200, 250 ) 631722 2.469% 3.005% +[ 250, 300 ) 2721970 10.639% 13.644% ## +[ 300, 350 ) 5909249 23.098% 36.742% ##### +[ 350, 400 ) 6522507 25.495% 62.237% ##### +[ 400, 450 ) 4296332 16.793% 79.030% ### +[ 450, 500 ) 2130323 8.327% 87.357% ## +[ 500, 600 ) 1553208 6.071% 93.428% # +[ 600, 700 ) 642129 2.510% 95.938% # +[ 700, 800 ) 372428 1.456% 97.394% +[ 800, 900 ) 187561 0.733% 98.127% +[ 900, 1000 ) 85858 0.336% 98.462% +[ 1000, 1200 ) 82730 0.323% 98.786% +[ 1200, 1400 ) 50691 0.198% 98.984% +[ 1400, 1600 ) 38026 0.149% 99.133% +[ 1600, 1800 ) 32991 0.129% 99.261% +[ 1800, 2000 ) 30200 0.118% 99.380% +[ 2000, 2500 ) 62195 0.243% 99.623% +[ 2500, 3000 ) 36684 0.143% 99.766% +[ 3000, 3500 ) 21317 0.083% 99.849% +[ 3500, 4000 ) 10216 0.040% 99.889% +[ 4000, 4500 ) 8351 0.033% 99.922% +[ 4500, 5000 ) 4152 0.016% 99.938% +[ 5000, 6000 ) 6328 0.025% 99.963% +[ 6000, 7000 ) 3253 0.013% 99.976% +[ 7000, 8000 ) 2082 0.008% 99.984% +[ 8000, 9000 ) 1546 0.006% 99.990% +[ 9000, 10000 ) 1055 0.004% 99.994% +[ 10000, 12000 ) 1566 0.006% 100.000% +[ 12000, 14000 ) 761 0.003% 100.003% +[ 14000, 16000 ) 462 0.002% 100.005% +[ 16000, 18000 ) 226 0.001% 100.006% +[ 18000, 20000 ) 126 0.000% 100.006% +[ 20000, 25000 ) 107 0.000% 100.007% +[ 25000, 30000 ) 43 0.000% 100.007% +[ 30000, 35000 ) 15 0.000% 100.007% +[ 35000, 40000 ) 14 0.000% 100.007% +[ 40000, 45000 ) 16 0.000% 100.007% +[ 45000, 50000 ) 1 0.000% 100.007% +[ 50000, 60000 ) 22 0.000% 100.007% +[ 60000, 70000 ) 10 0.000% 100.007% +[ 70000, 80000 ) 5 0.000% 100.007% +[ 80000, 90000 ) 14 0.000% 100.007% +[ 90000, 100000 ) 11 0.000% 100.007% +[ 100000, 120000 ) 33 0.000% 100.007% +[ 120000, 140000 ) 6 0.000% 100.007% +[ 140000, 160000 ) 3 0.000% 100.007% +[ 160000, 180000 ) 7 0.000% 100.007% +[ 200000, 250000 ) 2 0.000% 100.007% +``` + + +In this example, you can see we only issued 696 reads from level 0 while issued 25 million reads from level 5. The latency distribution is also clearly shown among those reads. This will be helpful for users to analysis OS page cache efficiency. + +Currently the read latency per level includes reads from data blocks, index blocks, as well as bloom filter blocks. We are also working on a feature to break down those three type of blocks. + +### Comments + +**[Tao Feng](fengtao04@gmail.com)** + +Is this feature also included in RocksJava? + +**[Siying Dong](siying.d@fb.com)** + +Should be. As long as you enable statistics, you should be able to get the value from `RocksDB.getProperty()` with property `rocksdb.dbstats`. Let me know if you can’t find it. + +**[chiddu](cnbscience@gmail.com)** + +> In this example, you can see we only issued 696 reads from level 0 while issued 256K reads from level 5. + +Isn’t it 2.5 M of reads instead of 256K ? . + +Also could anyone please provide more description on the histogram ? especially + +> Count: 25583746 Average: 421.1326 StdDev: 385.11 +> Min: 1.0000 Median: 376.0011 Max: 202444.0000 +> Percentiles: P50: 376.00 P75: 438.00 P99: 1421.68 P99.9: 4164.43 P99.99: 9056.52 + +and + +> [ 0, 1 ) 2351 0.009% 0.009% +> [ 1, 2 ) 6077 0.024% 0.033% +> [ 2, 3 ) 8471 0.033% 0.066% +> [ 3, 4 ) 788 0.003% 0.069%” + +thanks in advance + +**[Siying Dong](siying.d@fb.com)** + +Thank you for pointing out the mistake. I fixed it now. + +In this output, there are 2.5 million samples, average latency is 421 micro seconds, with standard deviation 385. Median is 376, max value is 202 milliseconds. 0.009% has value of 1, 0.024% has value of 1, 0.033% has value of 2. Accumulated value from 0 to 2 is 0.066%. + +Hope it helps. + +**[chiddu](cnbscience@gmail.com)** + +Thank you Siying for the quick reply, I was running couple of benchmark testing to check the performance of rocksdb on SSD. One of the test is similar to what is mentioned in the wiki, TEST 4 : Random read , except the key_size is 10 and value_size is 20. I am inserting 1 billion hashes and reading 1 billion hashes with 32 threads. The histogram shows something like this + +``` +Level 5 read latency histogram (micros): +Count: 7133903059 Average: 480.4357 StdDev: 309.18 +Min: 0.0000 Median: 551.1491 Max: 224142.0000 +Percentiles: P50: 551.15 P75: 651.44 P99: 996.52 P99.9: 2073.07 P99.99: 3196.32 +—————————————————— +[ 0, 1 ) 28587385 0.401% 0.401% +[ 1, 2 ) 686572516 9.624% 10.025% ## +[ 2, 3 ) 567317522 7.952% 17.977% ## +[ 3, 4 ) 44979472 0.631% 18.608% +[ 4, 5 ) 50379685 0.706% 19.314% +[ 5, 6 ) 64930061 0.910% 20.224% +[ 6, 7 ) 22613561 0.317% 20.541% +…………more…………. +``` + +If I understand your previous comment correctly, + +1. How is it that the count is around 7 billion when I have only inserted 1 billion hashes ? is the stat broken ? +1. What does the percentiles and the numbers signify ? +1. 0, 1 ) 28587385 0.401% 0.401% what does this “28587385” stand for in the histogram row ? + +**[Siying Dong](siying.d@fb.com)** + +If I remember correctly, with db_bench, if you specify –num=1000000000 –threads=32, it is every thread reading one billion keys, total of 32 billions. Is it the case you ran into? + +28,587,385 means that number of data points take the value [0,1) +28,587,385 / 7,133,903,058 = 0.401% provides percentage. + +**[chiddu](cnbscience@gmail.com)** + +I do have `num=1000000000` and `t=32`. The script says reading 1 billion hashes and not 32 billion hashes. + +this is the script on which I have used + +``` +echo “Load 1B keys sequentially into database…..” +bpl=10485760;overlap=10;mcz=2;del=300000000;levels=6;ctrig=4; delay=8; stop=12; wbn=3; mbc=20; mb=67108864;wbs=134217728; dds=1; sync=0; r=1000000000; t=1; vs=20; bs=4096; cs=1048576; of=500000; si=1000000; ./db_bench –benchmarks=fillseq –disable_seek_compaction=1 –mmap_read=0 –statistics=1 –histogram=1 –num=$r –threads=$t –value_size=$vs –block_size=$bs –cache_size=$cs –bloom_bits=10 –cache_numshardbits=6 –open_files=$of –verify_checksum=1 –db=/data/mysql/leveldb/test –sync=$sync –disable_wal=1 –compression_type=none –stats_interval=$si –compression_ratio=0.5 –disable_data_sync=$dds –write_buffer_size=$wbs –target_file_size_base=$mb –max_write_buffer_number=$wbn –max_background_compactions=$mbc –level0_file_num_compaction_trigger=$ctrig –level0_slowdown_writes_trigger=$delay –level0_stop_writes_trigger=$stop –num_levels=$levels –delete_obsolete_files_period_micros=$del –min_level_to_compress=$mcz –max_grandparent_overlap_factor=$overlap –stats_per_interval=1 –max_bytes_for_level_base=$bpl –use_existing_db=0 –key_size=10 + +echo “Reading 1B keys in database in random order….” +bpl=10485760;overlap=10;mcz=2;del=300000000;levels=6;ctrig=4; delay=8; stop=12; wbn=3; mbc=20; mb=67108864;wbs=134217728; dds=0; sync=0; r=1000000000; t=32; vs=20; bs=4096; cs=1048576; of=500000; si=1000000; ./db_bench –benchmarks=readrandom –disable_seek_compaction=1 –mmap_read=0 –statistics=1 –histogram=1 –num=$r –threads=$t –value_size=$vs –block_size=$bs –cache_size=$cs –bloom_bits=10 –cache_numshardbits=6 –open_files=$of –verify_checksum=1 –db=/some_data_base –sync=$sync –disable_wal=1 –compression_type=none –stats_interval=$si –compression_ratio=0.5 –disable_data_sync=$dds –write_buffer_size=$wbs –target_file_size_base=$mb –max_write_buffer_number=$wbn –max_background_compactions=$mbc –level0_file_num_compaction_trigger=$ctrig –level0_slowdown_writes_trigger=$delay –level0_stop_writes_trigger=$stop –num_levels=$levels –delete_obsolete_files_period_micros=$del –min_level_to_compress=$mcz –max_grandparent_overlap_factor=$overlap –stats_per_interval=1 –max_bytes_for_level_base=$bpl –use_existing_db=1 –key_size=10 +``` + +After running this script, there were no issues wrt to loading billion hashes , but when it came to reading part, its been almost 4 days and still I have only read 7 billion hashes and have read 200 million hashes in 2 and half days. Is there something which is missing in db_bench or something which I am missing ? + +**[Siying Dong](siying.d@fb.com)** + +It’s a printing error then. If you have `num=1000000000` and `t=32`, it will be 32 threads, and each reads 1 billion keys. diff --git a/thirdparty/rocksdb/docs/_posts/2016-01-29-compaction_pri.markdown b/thirdparty/rocksdb/docs/_posts/2016-01-29-compaction_pri.markdown new file mode 100644 index 0000000000..ba9ee627c9 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2016-01-29-compaction_pri.markdown @@ -0,0 +1,51 @@ +--- +title: Option of Compaction Priority +layout: post +author: sdong +category: blog +redirect_from: + - /blog/2921/compaction_pri/ +--- + +The most popular compaction style of RocksDB is level-based compaction, which is an improved version of LevelDB's compaction algorithm. Page 9- 16 of this [slides](https://github.com/facebook/rocksdb/blob/gh-pages/talks/2015-09-29-HPTS-Siying-RocksDB.pdf) gives an illustrated introduction of this compaction style. The basic idea that: data is organized by multiple levels with exponential increasing target size. Except a special level 0, every level is key-range partitioned into many files. When size of a level exceeds its target size, we pick one or more of its files, and merge the file into the next level. + + + +Which file to pick to compact is an interesting question. LevelDB only uses one thread for compaction and it always picks files in round robin manner. We implemented multi-thread compaction in RocksDB by picking multiple files from the same level and compact them in parallel. We had to move away from LevelDB's file picking approach. Recently, we created an option [options.compaction_pri](https://github.com/facebook/rocksdb/blob/d6c838f1e130d8860407bc771fa6d4ac238859ba/include/rocksdb/options.h#L83-L93), which indicated three different algorithms to pick files to compact. + +Why do we need to multiple algorithms to choose from? Because there are different factors to consider when picking the files, and we now don't yet know how to balance them automatically, so we expose it to users to choose. Here are factors to consider: + +**Write amplification** + +When we estimate write amplification, we usually simplify the problem by assuming keys are uniformly distributed inside each level. In reality, it is not the case, even if user updates are uniformly distributed across the whole key range. For instance, when we compact one file of a level to the next level, it creates a hole. Over time, incoming compaction will fill data to the hole, but the density will still be lower for a while. Picking a file with keys least densely populated is more expensive to get the file to the next level, because there will be more overlapping files in the next level so we need to rewrite more data. For example, assume a file is 100MB, if an L2 file overlaps with 8 L3 files, we need to rewrite about 800MB of data to get the file to L3. If the file overlaps with 12 L3 files, we'll need to rewrite about 1200MB to get a file of the same size out of L2. It uses 50% more writes. (This analysis ignores the key density of the next level, because the range covers N times of files in that level so one hole only impacts write amplification by 1/N) + +If all the updates are uniformly distributed, LevelDB's approach optimizes write amplification, because a file being picked covers a range whose last compaction time to the next level is the oldest, so the range will accumulated keys from incoming compactions for the longest and the density is the highest. + +We created a compaction priority **kOldestSmallestSeqFirst** for the same effect. With this mode, we always pick the file covers the oldest updates in the level, which usually is contains the densest key range. If you have a use case where writes are uniformly distributed across the key space and you want to reduce write amplification, you should set options.compaction_pri=kOldestSmallestSeqFirst. + +**Optimize for small working set** + +We are assuming updates are uniformly distributed across the whole key space in previous analysis. However, in many use cases, there are subset of keys that are frequently updated while other key ranges are very cold. In this case, keeping hot key ranges from compacting to deeper levels will benefit write amplification, as well as space amplification. For example, if in a DB only key 150-160 are updated and other keys are seldom updated. If level 1 contains 20 keys, we want to keep 150-160 all stay in level 1. Because when next level 0 -> 1 compaction comes, it will simply overwrite existing keys so size level 1 doesn't increase, so no need to schedule further compaction for level 1->2. On the other hand, if we compact key 150-155 to level2, when a new Level 1->2 compaction comes, it increases the size of level 1, making size of level 1 exceed target size and more compactions will be needed, which generates more writes. + +The compaction priority **kOldestLargestSeqFirst** optimizes this use case. In this mode, we will pick a file whose latest update is the oldest. It means there is no incoming data for the range for the longest. Usually it is the coldest range. By compacting coldest range first, we leave the hot ranges in the level. If your use case is to overwrite existing keys in a small range, try options.compaction_pri=kOldestLargestSeqFirst**.** + +**Drop delete marker sooner** + +If one file contains a lot of delete markers, it may slow down iterating over this area, because we still need to iterate those deleted keys just to ignore them. Furthermore, the sooner we compact delete keys into the last level, the sooner the disk space is reclaimed, so it is good for space efficiency. + +Our default compaction priority **kByCompensatedSize** considers the case. If number of deletes in a file exceeds number of inserts, it is more likely to be picked for compaction. The more number of deletes exceed inserts, the more likely it is being compacted. The optimization is added to avoid the worst performance of space efficiency and query performance when a large percentage of the DB is deleted. + +**Efficiency of compaction filter** + +Usually people use [compaction filters](https://github.com/facebook/rocksdb/blob/v4.1/include/rocksdb/options.h#L201-L226) to clean up old data to free up space. Picking files to compact may impact space efficiency. We don't yet have a a compaction priority to optimize this case. In some of our use cases, we solved the problem in a different way: we have an external service checking modify time of all SST files. If any of the files is too old, we force the single file to compaction by calling DB::CompactFiles() using the single file. In this way, we can provide a time bound of data passing through compaction filters. + + +In all, there three choices of compaction priority modes optimizing different scenarios. if you have a new use case, we suggest you start with `options.compaction_pri=kOldestSmallestSeqFirst` (note it is not the default one for backward compatible reason). If you want to further optimize your use case, you can try other two use cases if your use cases apply. + +If you have good ideas about better compaction picker approach, you are welcome to implement and benchmark it. We'll be glad to review and merge your a pull requests. + +### Comments + +**[Mark Callaghan](mdcallag@gmail.com)** + +Performance results for compaction_pri values and linkbench are explained at [http://smalldatum.blogspot.com/2016/02/compaction-priority-in-rocksdb.html](http://smalldatum.blogspot.com/2016/02/compaction-priority-in-rocksdb.html) diff --git a/thirdparty/rocksdb/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown b/thirdparty/rocksdb/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown new file mode 100644 index 0000000000..409015cc8c --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2016-02-24-rocksdb-4-2-release.markdown @@ -0,0 +1,41 @@ +--- +title: RocksDB 4.2 Release! +layout: post +author: sdong +category: blog +redirect_from: + - /blog/3017/rocksdb-4-2-release/ +--- + +New RocksDB release - 4.2! + + +**New Features** + + 1. Introduce CreateLoggerFromOptions(), this function create a Logger for provided DBOptions. + + + 2. Add GetAggregatedIntProperty(), which returns the sum of the GetIntProperty of all the column families. + + + 3. Add MemoryUtil in rocksdb/utilities/memory.h. It currently offers a way to get the memory usage by type from a list rocksdb instances. + + + + + +**Public API changes** + + 1. CompactionFilter::Context includes information of Column Family ID + + + 2. The need-compaction hint given by TablePropertiesCollector::NeedCompact() will be persistent and recoverable after DB recovery. This introduces a breaking format change. If you use this experimental feature, including NewCompactOnDeletionCollectorFactory() in the new version, you may not be able to directly downgrade the DB back to version 4.0 or lower. + + + 3. TablePropertiesCollectorFactory::CreateTablePropertiesCollector() now takes an option Context, containing the information of column family ID for the file being written. + + + 4. Remove DefaultCompactionFilterFactory. + + +[https://github.com/facebook/rocksdb/releases/tag/v4.2](https://github.com/facebook/rocksdb/releases/tag/v4.2) diff --git a/thirdparty/rocksdb/docs/_posts/2016-02-25-rocksdb-ama.markdown b/thirdparty/rocksdb/docs/_posts/2016-02-25-rocksdb-ama.markdown new file mode 100644 index 0000000000..2ba04f39a1 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2016-02-25-rocksdb-ama.markdown @@ -0,0 +1,20 @@ +--- +title: RocksDB AMA +layout: post +author: yhchiang +category: blog +redirect_from: + - /blog/3065/rocksdb-ama/ +--- + +RocksDB developers are doing a Reddit Ask-Me-Anything now at 10AM – 11AM PDT! We welcome you to stop by and ask any RocksDB related questions, including existing / upcoming features, tuning tips, or database design. + +Here are some enhancements that we'd like to focus on over the next six months: + +* 2-Phase Commit +* Lua support in some custom functions +* Backup and repair tools +* Direct I/O to bypass OS cache +* RocksDB Java API + +[https://www.reddit.com/r/IAmA/comments/47k1si/we_are_rocksdb_developers_ask_us_anything/](https://www.reddit.com/r/IAmA/comments/47k1si/we_are_rocksdb_developers_ask_us_anything/) diff --git a/thirdparty/rocksdb/docs/_posts/2016-03-07-rocksdb-options-file.markdown b/thirdparty/rocksdb/docs/_posts/2016-03-07-rocksdb-options-file.markdown new file mode 100644 index 0000000000..703449b01a --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2016-03-07-rocksdb-options-file.markdown @@ -0,0 +1,24 @@ +--- +title: RocksDB Options File +layout: post +author: yhciang +category: blog +redirect_from: + - /blog/3089/rocksdb-options-file/ +--- + +In RocksDB 4.3, we added a new set of features that makes managing RocksDB options easier. Specifically: + + * **Persisting Options Automatically**: Each RocksDB database will now automatically persist its current set of options into an INI file on every successful call of DB::Open(), SetOptions(), and CreateColumnFamily() / DropColumnFamily(). + + + + * **Load Options from File**: We added [LoadLatestOptions() / LoadOptionsFromFile()](https://github.com/facebook/rocksdb/blob/4.3.fb/include/rocksdb/utilities/options_util.h#L48-L58) that enables developers to construct RocksDB options object from an options file. + + + + * **Sanity Check Options**: We added [CheckOptionsCompatibility](https://github.com/facebook/rocksdb/blob/4.3.fb/include/rocksdb/utilities/options_util.h#L64-L77) that performs compatibility check on two sets of RocksDB options. + + + +Want to know more about how to use this new features? Check out the [RocksDB Options File wiki page](https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File) and start using this new feature today! diff --git a/thirdparty/rocksdb/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown b/thirdparty/rocksdb/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown new file mode 100644 index 0000000000..247768d307 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2016-04-26-rocksdb-4-5-1-released.markdown @@ -0,0 +1,60 @@ +--- +title: RocksDB 4.5.1 Released! +layout: post +author: sdong +category: blog +redirect_from: + - /blog/3179/rocksdb-4-5-1-released/ +--- + +## 4.5.1 (3/25/2016) + +### Bug Fixes + + *  Fix failures caused by the destorying order of singleton objects. + +
+ +## 4.5.0 (2/5/2016) + +### Public API Changes + + * Add a new perf context level between kEnableCount and kEnableTime. Level 2 now does not include timers for mutexes. + * Statistics of mutex operation durations will not be measured by default. If you want to have them enabled, you need to set Statistics::stats_level_ to kAll. + * DBOptions::delete_scheduler and NewDeleteScheduler() are removed, please use DBOptions::sst_file_manager and NewSstFileManager() instead + +### New Features + * ldb tool now supports operations to non-default column families. + * Add kPersistedTier to ReadTier. This option allows Get and MultiGet to read only the persited data and skip mem-tables if writes were done with disableWAL = true. + * Add DBOptions::sst_file_manager. Use NewSstFileManager() in include/rocksdb/sst_file_manager.h to create a SstFileManager that can be used to track the total size of SST files and control the SST files deletion rate. + +
+ + + +## 4.4.0 (1/14/2016) + +### Public API Changes + + * Change names in CompactionPri and add a new one. + * Deprecate options.soft_rate_limit and add options.soft_pending_compaction_bytes_limit. + * If options.max_write_buffer_number > 3, writes will be slowed down when writing to the last write buffer to delay a full stop. + * Introduce CompactionJobInfo::compaction_reason, this field include the reason to trigger the compaction. + * After slow down is triggered, if estimated pending compaction bytes keep increasing, slowdown more. + * Increase default options.delayed_write_rate to 2MB/s. + * Added a new parameter --path to ldb tool. --path accepts the name of either MANIFEST, SST or a WAL file. Either --db or --path can be used when calling ldb. + +
+ +## 4.3.0 (12/8/2015) + +### New Features + + * CompactionFilter has new member function called IgnoreSnapshots which allows CompactionFilter to be called even if there are snapshots later than the key. + * RocksDB will now persist options under the same directory as the RocksDB database on successful DB::Open, CreateColumnFamily, DropColumnFamily, and SetOptions. + * Introduce LoadLatestOptions() in rocksdb/utilities/options_util.h. This function can construct the latest DBOptions / ColumnFamilyOptions used by the specified RocksDB intance. + * Introduce CheckOptionsCompatibility() in rocksdb/utilities/options_util.h. This function checks whether the input set of options is able to open the specified DB successfully. + +### Public API Changes + + * When options.db_write_buffer_size triggers, only the column family with the largest column family size will be flushed, not all the column families. diff --git a/thirdparty/rocksdb/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown b/thirdparty/rocksdb/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown new file mode 100644 index 0000000000..b42a66e301 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2016-07-26-rocksdb-4-8-released.markdown @@ -0,0 +1,48 @@ +--- +title: RocksDB 4.8 Released! +layout: post +author: yiwu +category: blog +redirect_from: + - /blog/3239/rocksdb-4-8-released/ +--- + +## 4.8.0 (5/2/2016) + +### [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#public-api-change-1)Public API Change + + * Allow preset compression dictionary for improved compression of block-based tables. This is supported for zlib, zstd, and lz4. The compression dictionary's size is configurable via CompressionOptions::max_dict_bytes. + * Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see [https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F](https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F) + * Expose estimate of per-level compression ratio via DB property: "rocksdb.compression-ratio-at-levelN". + * Added EventListener::OnTableFileCreationStarted. EventListener::OnTableFileCreated will be called on failure case. User can check creation status via TableFileCreationInfo::status. + +### [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#new-features-2)New Features + + * Add ReadOptions::readahead_size. If non-zero, NewIterator will create a new table reader which performs reads of the given size. + +
+ + + +## [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#470-482016)4.7.0 (4/8/2016) + +### [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#public-api-change-2)Public API Change + + * rename options compaction_measure_io_stats to report_bg_io_stats and include flush too. + * Change some default options. Now default options will optimize for server-workloads. Also enable slowdown and full stop triggers for pending compaction bytes. These changes may cause sub-optimal performance or significant increase of resource usage. To avoid these risks, users can open existing RocksDB with options extracted from RocksDB option files. See [https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File](https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File) for how to use RocksDB option files. Or you can call Options.OldDefaults() to recover old defaults. DEFAULT_OPTIONS_HISTORY.md will track change history of default options. + +
+ +## [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#460-3102016)4.6.0 (3/10/2016) + +### [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#public-api-changes-1)Public API Changes + + * Change default of BlockBasedTableOptions.format_version to 2. It means default DB created by 4.6 or up cannot be opened by RocksDB version 3.9 or earlier + * Added strict_capacity_limit option to NewLRUCache. If the flag is set to true, insert to cache will fail if no enough capacity can be free. Signature of Cache::Insert() is updated accordingly. + * Tickers [NUMBER_DB_NEXT, NUMBER_DB_PREV, NUMBER_DB_NEXT_FOUND, NUMBER_DB_PREV_FOUND, ITER_BYTES_READ] are not updated immediately. The are updated when the Iterator is deleted. + * Add monotonically increasing counter (DB property "rocksdb.current-super-version-number") that increments upon any change to the LSM tree. + +### [](https://github.com/facebook/rocksdb/blob/master/HISTORY.md#new-features-3)New Features + + * Add CompactionPri::kMinOverlappingRatio, a compaction picking mode friendly to write amplification. + * Deprecate Iterator::IsKeyPinned() and replace it with Iterator::GetProperty() with prop_name="rocksdb.iterator.is.key.pinned" diff --git a/thirdparty/rocksdb/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown b/thirdparty/rocksdb/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown new file mode 100644 index 0000000000..87c20eb47d --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2016-09-28-rocksdb-4-11-2-released.markdown @@ -0,0 +1,49 @@ +--- +title: RocksDB 4.11.2 Released! +layout: post +author: sdong +category: blog +--- +We abandoned release candidates 4.10.x and directly go to 4.11.2 from 4.9, to make sure the latest release is stable. In 4.11.2, we fixed several data corruption related bugs introduced in 4.9.0. + +## 4.11.2 (9/15/2016) + +### Bug fixes + + * Segfault when failing to open an SST file for read-ahead iterators. + * WAL without data for all CFs is not deleted after recovery. + + + +## 4.11.1 (8/30/2016) + +### Bug Fixes + + * Mitigate the regression bug of deadlock condition during recovery when options.max_successive_merges hits. + * Fix data race condition related to hash index in block based table when putting indexes in the block cache. + +## 4.11.0 (8/1/2016) + +### Public API Change + + * options.memtable_prefix_bloom_huge_page_tlb_size => memtable_huge_page_size. When it is set, RocksDB will try to allocate memory from huge page for memtable too, rather than just memtable bloom filter. + +### New Features + + * A tool to migrate DB after options change. See include/rocksdb/utilities/option_change_migration.h. + * Add ReadOptions.background_purge_on_iterator_cleanup. If true, we avoid file deletion when destorying iterators. + +## 4.10.0 (7/5/2016) + +### Public API Change + + * options.memtable_prefix_bloom_bits changes to options.memtable_prefix_bloom_bits_ratio and deprecate options.memtable_prefix_bloom_probes + * enum type CompressionType and PerfLevel changes from char to unsigned char. Value of all PerfLevel shift by one. + * Deprecate options.filter_deletes. + +### New Features + + * Add avoid_flush_during_recovery option. + * Add a read option background_purge_on_iterator_cleanup to avoid deleting files in foreground when destroying iterators. Instead, a job is scheduled in high priority queue and would be executed in a separate background thread. + * RepairDB support for column families. RepairDB now associates data with non-default column families using information embedded in the SST/WAL files (4.7 or later). For data written by 4.6 or earlier, RepairDB associates it with the default column family. + * Add options.write_buffer_manager which allows users to control total memtable sizes across multiple DB instances. diff --git a/thirdparty/rocksdb/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown b/thirdparty/rocksdb/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown new file mode 100644 index 0000000000..fb0413055d --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-01-06-rocksdb-5-0-1-released.markdown @@ -0,0 +1,26 @@ +--- +title: RocksDB 5.0.1 Released! +layout: post +author: yiwu +category: blog +--- + +### Public API Change + + * Options::max_bytes_for_level_multiplier is now a double along with all getters and setters. + * Support dynamically change `delayed_write_rate` and `max_total_wal_size` options via SetDBOptions(). + * Introduce DB::DeleteRange for optimized deletion of large ranges of contiguous keys. + * Support dynamically change `delayed_write_rate` option via SetDBOptions(). + * Options::allow_concurrent_memtable_write and Options::enable_write_thread_adaptive_yield are now true by default. + * Remove Tickers::SEQUENCE_NUMBER to avoid confusion if statistics object is shared among RocksDB instance. Alternatively DB::GetLatestSequenceNumber() can be used to get the same value. + * Options.level0_stop_writes_trigger default value changes from 24 to 32. + * New compaction filter API: CompactionFilter::FilterV2(). Allows to drop ranges of keys. + * Removed flashcache support. + * DB::AddFile() is deprecated and is replaced with DB::IngestExternalFile(). DB::IngestExternalFile() remove all the restrictions that existed for DB::AddFile. + +### New Features + + * Add avoid_flush_during_shutdown option, which speeds up DB shutdown by not flushing unpersisted data (i.e. with disableWAL = true). Unpersisted data will be lost. The options is dynamically changeable via SetDBOptions(). + * Add memtable_insert_with_hint_prefix_extractor option. The option is mean to reduce CPU usage for inserting keys into memtable, if keys can be group by prefix and insert for each prefix are sequential or almost sequential. See include/rocksdb/options.h for more details. + * Add LuaCompactionFilter in utilities. This allows developers to write compaction filters in Lua. To use this feature, LUA_PATH needs to be set to the root directory of Lua. + * No longer populate "LATEST_BACKUP" file in backup directory, which formerly contained the number of the latest backup. The latest backup can be determined by finding the highest numbered file in the "meta/" subdirectory. diff --git a/thirdparty/rocksdb/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown b/thirdparty/rocksdb/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown new file mode 100644 index 0000000000..35bafb219c --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-02-07-rocksdb-5-1-2-released.markdown @@ -0,0 +1,15 @@ +--- +title: RocksDB 5.1.2 Released! +layout: post +author: maysamyabandeh +category: blog +--- + +### Public API Change +* Support dynamically change `delete_obsolete_files_period_micros` option via SetDBOptions(). +* Added EventListener::OnExternalFileIngested which will be called when IngestExternalFile() add a file successfully. +* BackupEngine::Open and BackupEngineReadOnly::Open now always return error statuses matching those of the backup Env. + +### Bug Fixes +* Fix the bug that if 2PC is enabled, checkpoints may loss some recent transactions. +* When file copying is needed when creating checkpoints or bulk loading files, fsync the file after the file copying. diff --git a/thirdparty/rocksdb/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown b/thirdparty/rocksdb/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown new file mode 100644 index 0000000000..9a43a846a4 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-02-17-bulkoad-ingest-sst-file.markdown @@ -0,0 +1,50 @@ +--- +title: Bulkloading by ingesting external SST files +layout: post +author: IslamAbdelRahman +category: blog +--- + +## Introduction + +One of the basic operations of RocksDB is writing to RocksDB, Writes happen when user call (DB::Put, DB::Write, DB::Delete ... ), but what happens when you write to RocksDB ? .. this is a brief description of what happens. +- User insert a new key/value by calling DB::Put() (or DB::Write()) +- We create a new entry for the new key/value in our in-memory structure (memtable / SkipList by default) and we assign it a new sequence number. +- When the memtable exceeds a specific size (64 MB for example), we convert this memtable to a SST file, and put this file in level 0 of our LSM-Tree +- Later, compaction will kick in and move data from level 0 to level 1, and then from level 1 to level 2 .. and so on + +But what if we can skip these steps and add data to the lowest possible level directly ? This is what bulk-loading does + +## Bulkloading + +- Write all of our keys and values into SST file outside of the DB +- Add the SST file into the LSM directly + +This is bulk-loading, and in specific use-cases it allow users to achieve faster data loading and better write-amplification. + +and doing it is as simple as +```cpp +Options options; +SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator); +Status s = sst_file_writer.Open(file_path); +assert(s.ok()); + +// Insert rows into the SST file, note that inserted keys must be +// strictly increasing (based on options.comparator) +for (...) { + s = sst_file_writer.Add(key, value); + assert(s.ok()); +} + +// Ingest the external SST file into the DB +s = db_->IngestExternalFile({"/home/usr/file1.sst"}, IngestExternalFileOptions()); +assert(s.ok()); +``` + +You can find more details about how to generate SST files and ingesting them into RocksDB in this [wiki page](https://github.com/facebook/rocksdb/wiki/Creating-and-Ingesting-SST-files) + +## Use cases +There are multiple use cases where bulkloading could be useful, for example +- Generating SST files in offline jobs in Hadoop, then downloading and ingesting the SST files into RocksDB +- Migrating shards between machines by dumping key-range in SST File and loading the file in a different machine +- Migrating from a different storage (InnoDB to RocksDB migration in MyRocks) diff --git a/thirdparty/rocksdb/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown b/thirdparty/rocksdb/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown new file mode 100644 index 0000000000..c6ce27d64d --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-03-02-rocksdb-5-2-1-released.markdown @@ -0,0 +1,22 @@ +--- +title: RocksDB 5.2.1 Released! +layout: post +author: sdong +category: blog +--- + +### Public API Change +* NewLRUCache() will determine number of shard bits automatically based on capacity, if the user doesn't pass one. This also impacts the default block cache when the user doesn't explict provide one. +* Change the default of delayed slowdown value to 16MB/s and further increase the L0 stop condition to 36 files. + +### New Features +* Added new overloaded function GetApproximateSizes that allows to specify if memtable stats should be computed only without computing SST files' stats approximations. +* Added new function GetApproximateMemTableStats that approximates both number of records and size of memtables. +* (Experimental) Two-level indexing that partition the index and creates a 2nd level index on the partitions. The feature can be enabled by setting kTwoLevelIndexSearch as IndexType and configuring index_per_partition. + +### Bug Fixes +* RangeSync() should work if ROCKSDB_FALLOCATE_PRESENT is not set +* Fix wrong results in a data race case in Get() +* Some fixes related to 2PC. +* Fix several bugs in Direct I/O supports. +* Fix a regression bug which can cause Seek() to miss some keys if the return key has been updated many times after the snapshot which is used by the iterator. diff --git a/thirdparty/rocksdb/docs/_posts/2017-05-12-partitioned-index-filter.markdown b/thirdparty/rocksdb/docs/_posts/2017-05-12-partitioned-index-filter.markdown new file mode 100644 index 0000000000..a537feb0c7 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-05-12-partitioned-index-filter.markdown @@ -0,0 +1,34 @@ +--- +title: Partitioned Index/Filters +layout: post +author: maysamyabandeh +category: blog +--- + +As DB/mem ratio gets larger, the memory footprint of filter/index blocks becomes non-trivial. Although `cache_index_and_filter_blocks` allows storing only a subset of them in block cache, their relatively large size negatively affects the performance by i) occupying the block cache space that could otherwise be used for caching data, ii) increasing the load on the disk storage by loading them into the cache after a miss. Here we illustrate these problems in more detail and explain how partitioning index/filters alleviates the overhead. + +### How large are the index/filter blocks? + +RocksDB has by default one index/filter block per SST file. The size of the index/filter varies based on the configuration but for a SST of size 256MB the index/filter block of size 0.5/5MB is typical, which is much larger than the typical data block size of 4-32KB. That is fine when all index/filters fit perfectly into memory and hence are read once per SST lifetime, not so much when they compete with data blocks for the block cache space and are also likely to be re-read many times from the disk. + +### What is the big deal with large index/filter blocks? + +When index/filter blocks are stored in block cache they are effectively competing with data blocks (as well as with each other) on this scarce resource. A filter of size 5MB is occupying the space that could otherwise be used to cache 1000s of data blocks (of size 4KB). This would result in more cache misses for data blocks. The large index/filters also kick each other out of the block cache more often and exacerbate their own cache miss rate too. This is while only a small part of the index/filter block might have been actually used during its lifetime in the cache. + +After the cache miss of an index/filter, it has to be reloaded from the disk, and its large size is not helping in reducing the IO cost. While a simple point lookup might need at most a couple of data block reads (of size 4KB) one from each layer of LSM, it might end up also loading multiple megabytes of index/filter blocks. If that happens often then the disk is spending more time serving index/filters rather than the actual data blocks. + +## What is partitioned index/filters? + +With partitioning, the index/filter of a SST file is partitioned into smaller blocks with an additional top-level index on them. When reading an index/filter, only top-level index is loaded into memory. The partitioned index/filter then uses the top-level index to load on demand into the block cache the partitions that are required to perform the index/filter query. The top-level index, which has much smaller memory footprint, can be stored in heap or block cache depending on the `cache_index_and_filter_blocks` setting. + +### Success stories + +#### HDD, 100TB DB + +In this example we have a DB of size 86G on HDD and emulate the small memory that is present to a node with 100TB of data by using direct IO (skipping OS file cache) and a very small block cache of size 60MB. Partitioning improves throughput by 11x from 5 op/s to 55 op/s. + +#### SSD, Linkbench + +In this example we have a DB of size 300G on SSD and emulate the small memory that would be available in presence of other DBs on the same node by by using direct IO (skipping OS file cache) and block cache of size 6G and 2G. Without partitioning the linkbench throughput drops from 38k tps to 23k when reducing block cache size from 6G to 2G. With partitioning the throughput drops from 38k to only 30k. + +Learn more [here](https://github.com/facebook/rocksdb/wiki/Partitioned-Index-Filters). diff --git a/thirdparty/rocksdb/docs/_posts/2017-05-14-core-local-stats.markdown b/thirdparty/rocksdb/docs/_posts/2017-05-14-core-local-stats.markdown new file mode 100644 index 0000000000..a806541fca --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-05-14-core-local-stats.markdown @@ -0,0 +1,106 @@ +--- +title: Core-local Statistics +layout: post +author: ajkr +category: blog +--- + +## Origins: Global Atomics + +Until RocksDB 4.12, ticker/histogram statistics were implemented with std::atomic values shared across the entire program. A ticker consists of a single atomic, while a histogram consists of several atomics to represent things like min/max/per-bucket counters. These statistics could be updated by all user/background threads. + +For concurrent/high-throughput workloads, cache line bouncing of atomics caused high CPU utilization. For example, we have tickers that count block cache hits and misses. Almost every user read increments these tickers a few times. Many concurrent user reads would cause the cache lines containing these atomics to bounce between cores. + +### Performance + +Here are perf results for 32 reader threads where most reads (99%+) are served by uncompressed block cache. Such a scenario stresses the statistics code heavily. + +Benchmark command: `TEST_TMPDIR=/dev/shm/ perf record -g ./db_bench -statistics -use_existing_db=true -benchmarks=readrandom -threads=32 -cache_size=1048576000 -num=1000000 -reads=1000000 && perf report -g --children` + +Perf snippet for "cycles" event: + +``` + Children Self Command Shared Object Symbol ++ 30.33% 30.17% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick ++ 3.65% 0.98% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime +``` + +Perf snippet for "cache-misses" event: + +``` + Children Self Command Shared Object Symbol ++ 19.54% 19.50% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick ++ 3.44% 0.57% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime +``` + +The high CPU overhead for updating tickers and histograms corresponds well to the high cache misses. + +## Thread-locals: Faster Updates + +Since RocksDB 4.12, ticker/histogram statistics use thread-local storage. Each thread has a local set of atomic values that no other thread can update. This prevents the cache line bouncing problem described above. Even though updates to a given value are always made by the same thread, atomics are still useful to synchronize with aggregations for querying statistics. + +Implementing this approach involved a couple challenges. First, each query for a statistic's global value must aggregate all threads' local values. This adds some overhead, which may pass unnoticed if statistics are queried infrequently. Second, exited threads' local values are still needed to provide accurate statistics. We handle this by merging a thread's local values into process-wide variables upon thread exit. + +### Performance + +Update benchmark setup is same as before. CPU overhead improved 7.8x compared to global atomics, corresponding to a 17.8x reduction in cache-misses overhead. + +Perf snippet for "cycles" event: + +``` + Children Self Command Shared Object Symbol ++ 2.96% 0.87% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick ++ 1.37% 0.10% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime +``` + +Perf snippet for "cache-misses" event: + +``` + Children Self Command Shared Object Symbol ++ 1.21% 0.65% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick + 0.08% 0.00% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime +``` + +To measure statistics query latency, we ran sysbench with 4K OLTP clients concurrently with one client that queries statistics repeatedly. Times shown are in milliseconds. + +``` + min: 18.45 + avg: 27.91 + max: 231.65 + 95th percentile: 55.82 +``` + +## Core-locals: Faster Querying + +The thread-local approach is working well for applications calling RocksDB from only a few threads, or polling statistics infrequently. Eventually, though, we found use cases where those assumptions do not hold. For example, one application has per-connection threads and typically runs into performance issues when connection count grows very high. For debugging such issues, they want high-frequency statistics polling to correlate issues in their application with changes in RocksDB's state. + +Once [PR #2258](https://github.com/facebook/rocksdb/pull/2258) lands, ticker/histogram statistics will be local to each CPU core. Similarly to thread-local, each core updates only its local values, thus avoiding cache line bouncing. Local values are still atomics to make aggregation possible. With this change, query work depends only on number of cores, not the number of threads. So, applications with many more threads than cores can no longer impact statistics query latency. + +### Performance + +Update benchmark setup is same as before. CPU overhead worsened ~23% compared to thread-local, while cache performance was unchanged. + +Perf snippet for "cycles" event: + +``` + Children Self Command Shared Object Symbol ++ 2.96% 0.87% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick ++ 1.37% 0.10% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime +``` + +Perf snippet for "cache-misses" event: + +``` + Children Self Command Shared Object Symbol ++ 1.21% 0.65% db_bench db_bench [.] rocksdb::StatisticsImpl::recordTick + 0.08% 0.00% db_bench db_bench [.] rocksdb::StatisticsImpl::measureTime +``` + +Query latency is measured same as before with times in milliseconds. Average latency improved by 6.3x compared to thread-local. + +``` + min: 2.47 + avg: 4.45 + max: 91.13 + 95th percentile: 7.56 +``` diff --git a/thirdparty/rocksdb/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown b/thirdparty/rocksdb/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown new file mode 100644 index 0000000000..561dab4c20 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-05-26-rocksdb-5-4-5-released.markdown @@ -0,0 +1,39 @@ +--- +title: RocksDB 5.4.5 Released! +layout: post +author: sagar0 +category: blog +--- + +### Public API Change +* Support dynamically changing `stats_dump_period_sec` option via SetDBOptions(). +* Added ReadOptions::max_skippable_internal_keys to set a threshold to fail a request as incomplete when too many keys are being skipped while using iterators. +* DB::Get in place of std::string accepts PinnableSlice, which avoids the extra memcpy of value to std::string in most of cases. + * PinnableSlice releases the pinned resources that contain the value when it is destructed or when ::Reset() is called on it. + * The old API that accepts std::string, although discouraged, is still supported. +* Replace Options::use_direct_writes with Options::use_direct_io_for_flush_and_compaction. See Direct IO wiki for details. + +### New Features +* Memtable flush can be avoided during checkpoint creation if total log file size is smaller than a threshold specified by the user. +* Introduce level-based L0->L0 compactions to reduce file count, so write delays are incurred less often. +* (Experimental) Partitioning filters which creates an index on the partitions. The feature can be enabled by setting partition_filters when using kFullFilter. Currently the feature also requires two-level indexing to be enabled. Number of partitions is the same as the number of partitions for indexes, which is controlled by metadata_block_size. +* DB::ResetStats() to reset internal stats. +* Added CompactionEventListener and EventListener::OnFlushBegin interfaces. +* Added DB::CreateColumnFamilie() and DB::DropColumnFamilies() to bulk create/drop column families. +* Facility for cross-building RocksJava using Docker. + +### Bug Fixes +* Fix WriteBatchWithIndex address use after scope error. +* Fix WritableFile buffer size in direct IO. +* Add prefetch to PosixRandomAccessFile in buffered io. +* Fix PinnableSlice access invalid address when row cache is enabled. +* Fix huge fallocate calls fail and make XFS unhappy. +* Fix memory alignment with logical sector size. +* Fix alignment in ReadaheadRandomAccessFile. +* Fix bias with read amplification stats (READ_AMP_ESTIMATE_USEFUL_BYTES and READ_AMP_TOTAL_READ_BYTES). +* Fix a manual / auto compaction data race. +* Fix CentOS 5 cross-building of RocksJava. +* Build and link with ZStd when creating the static RocksJava build. +* Fix snprintf's usage to be cross-platform. +* Fix build errors with blob DB. +* Fix readamp test type inconsistency. diff --git a/thirdparty/rocksdb/docs/_posts/2017-06-26-17-level-based-changes.markdown b/thirdparty/rocksdb/docs/_posts/2017-06-26-17-level-based-changes.markdown new file mode 100644 index 0000000000..9e838eb7f2 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-06-26-17-level-based-changes.markdown @@ -0,0 +1,60 @@ +--- +title: Level-based Compaction Changes +layout: post +author: ajkr +category: blog +--- + +### Introduction + +RocksDB provides an option to limit the number of L0 files, which bounds read-amplification. Since L0 files (unlike files at lower levels) can span the entire key-range, a key might be in any file, thus reads need to check them one-by-one. Users often wish to configure a low limit to improve their read latency. + +Although, the mechanism with which we enforce L0's file count limit may be unappealing. When the limit is reached, RocksDB intentionally delays user writes. This slows down accumulation of files in L0, and frees up resources for compacting files down to lower levels. But adding delays will significantly increase user-visible write latency jitter. + +Also, due to how L0 files can span the entire key-range, compaction parallelization is limited. Files at L0 or L1 may be locked due to involvement in pending L0->L1 or L1->L2 compactions. We can only schedule a parallel L0->L1 compaction if it does not require any of the locked files, which is typically not the case. + +To handle these constraints better, we added a new type of compaction, L0->L0. It quickly reduces file count in L0 and can be scheduled even when L1 files are locked, unlike L0->L1. We also changed the L0->L1 picking algorithm to increase opportunities for parallelism. + +### Old L0->L1 Picking Logic + +Previously, our logic for picking which L0 file to compact was the same as every other level: pick the largest file in the level. One special property of L0->L1 compaction is that files can overlap in the input level, so those overlapping files must be pulled in as well. For example, a compaction may look like this: + +![full-range.png](/static/images/compaction/full-range.png) + +This compaction pulls in every L0 and L1 file. This happens regardless of which L0 file is initially chosen as each file overlaps with every other file. + +Users may insert their data less uniformly in the key-range. For example, a database may look like this during L0->L1 compaction: + +![part-range-old.png](/static/images/compaction/part-range-old.png) + +Let's say the third file from the top is the largest, and let's say the top two files are created after the compaction started. When the compaction is picked, the fourth L0 file and six rightmost L1 files are pulled in due to overlap. Notice this leaves the database in a state where we might not be able to schedule parallel compactions. For example, if the sixth file from the top is the next largest, we can't compact it because it overlaps with the top two files, which overlap with the locked L0 files. + +We can now see the high-level problems with this approach more clearly. First, locked files in L0 or L1 prevent us from parallelizing compactions. When locked files block L0->L1 compaction, there is nothing we can do to eliminate L0 files. Second, L0->L1 compactions are relatively slow. As we saw, when keys are uniformly distributed, L0->L1 compacts two entire levels. While this is happening, new files are being flushed to L0, advancing towards the file count limit. + +### New L0->L0 Algorithm + +We introduced compaction within L0 to improve both parallelization and speed of reducing L0 file count. An L0->L0 compaction may look like this: + +![l1-l2-contend.png](/static/images/compaction/l1-l2-contend.png) + +Say the L1->L2 compaction started first. Now L0->L1 is prevented by the locked L1 file. In this case, we compact files within L0. This allows us to start the work for eliminating L0 files earlier. It also lets us do less work since we don't pull in any L1 files, whereas L0->L1 compaction would've pulled in all of them. This lets us quickly reduce L0 file count to keep read-amp low while sustaining large bursts of writes (i.e., fast accumulation of L0 files). + +The tradeoff is this increases total compaction work, as we're now compacting files without contributing towards our eventual goal of moving them towards lower levels. Our benchmarks, though, consistently show less compaction stalls and improved write throughput. One justification is that L0 file data is highly likely in page cache and/or block cache due to it being recently written and frequently accessed. So, this type of compaction is relatively cheap compared to compactions at lower levels. + +This feature is available since RocksDB 5.4. + +### New L0->L1 Picking Logic + +Recall how the old L0->L1 picking algorithm chose the largest L0 file for compaction. This didn't fit well with L0->L0 compaction, which operates on a span of files. That span begins at the newest L0 file, and expands towards older files as long as they're not being compacted. Since the largest file may be anywhere, the old L0->L1 picking logic could arbitrarily prevent us from getting a long span of files. See the second illustration in this post for a scenario where this would happen. + +So, we changed the L0->L1 picking algorithm to start from the oldest file and expand towards newer files as long as they're not being compacted. For example: + +![l0-l1-contend.png](/static/images/compaction/l0-l1-contend.png) + +Now, there can never be L0 files unreachable for L0->L0 due to L0->L1 selecting files in the middle. When longer spans of files are available for L0->L0, we perform less compaction work per deleted L0 file, thus improving efficiency. + +This feature will be available in RocksDB 5.7. + +### Performance Changes + +Mark Callaghan did the most extensive benchmarking of this feature's impact on MyRocks. See his results [here](http://smalldatum.blogspot.com/2017/05/innodb-myrocks-and-tokudb-on-insert.html). Note the primary change between his March 17 and April 14 builds is the latter performs L0->L0 compaction. diff --git a/thirdparty/rocksdb/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown b/thirdparty/rocksdb/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown new file mode 100644 index 0000000000..d7856088bf --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-06-29-rocksdb-5-5-1-released.markdown @@ -0,0 +1,22 @@ +--- +title: RocksDB 5.5.1 Released! +layout: post +author: lightmark +category: blog +--- + +### New Features +* FIFO compaction to support Intra L0 compaction too with CompactionOptionsFIFO.allow_compaction=true. +* Statistics::Reset() to reset user stats. +* ldb add option --try_load_options, which will open DB with its own option file. +* Introduce WriteBatch::PopSavePoint to pop the most recent save point explicitly. +* Support dynamically change `max_open_files` option via SetDBOptions() +* Added DB::CreateColumnFamilie() and DB::DropColumnFamilies() to bulk create/drop column families. +* Add debugging function `GetAllKeyVersions` to see internal versions of a range of keys. +* Support file ingestion with universal compaction style +* Support file ingestion behind with option `allow_ingest_behind` +* New option enable_pipelined_write which may improve write throughput in case writing from multiple threads and WAL enabled. + +### Bug Fixes +* Fix the bug that Direct I/O uses direct reads for non-SST file +* Fix the bug that flush doesn't respond to fsync result diff --git a/thirdparty/rocksdb/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown b/thirdparty/rocksdb/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown new file mode 100644 index 0000000000..3b54ffd5ad --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-07-25-rocksdb-5-6-1-released.markdown @@ -0,0 +1,22 @@ +--- +title: RocksDB 5.6.1 Released! +layout: post +author: yiwu +category: blog +--- + +### Public API Change +* Scheduling flushes and compactions in the same thread pool is no longer supported by setting `max_background_flushes=0`. Instead, users can achieve this by configuring their high-pri thread pool to have zero threads. See https://github.com/facebook/rocksdb/wiki/Thread-Pool for more details. +* Replace `Options::max_background_flushes`, `Options::max_background_compactions`, and `Options::base_background_compactions` all with `Options::max_background_jobs`, which automatically decides how many threads to allocate towards flush/compaction. +* options.delayed_write_rate by default take the value of options.rate_limiter rate. +* Replace global variable `IOStatsContext iostats_context` with `IOStatsContext* get_iostats_context()`; replace global variable `PerfContext perf_context` with `PerfContext* get_perf_context()`. + +### New Features +* Change ticker/histogram statistics implementations to use core-local storage. This improves aggregation speed compared to our previous thread-local approach, particularly for applications with many threads. See http://rocksdb.org/blog/2017/05/14/core-local-stats.html for more details. +* Users can pass a cache object to write buffer manager, so that they can cap memory usage for memtable and block cache using one single limit. +* Flush will be triggered when 7/8 of the limit introduced by write_buffer_manager or db_write_buffer_size is triggered, so that the hard threshold is hard to hit. See https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager for more details. +* Introduce WriteOptions.low_pri. If it is true, low priority writes will be throttled if the compaction is behind. See https://github.com/facebook/rocksdb/wiki/Low-Priority-Write for more details. +* `DB::IngestExternalFile()` now supports ingesting files into a database containing range deletions. + +### Bug Fixes +* Shouldn't ignore return value of fsync() in flush. diff --git a/thirdparty/rocksdb/docs/_posts/2017-08-24-pinnableslice.markdown b/thirdparty/rocksdb/docs/_posts/2017-08-24-pinnableslice.markdown new file mode 100644 index 0000000000..7ac2fec34b --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-08-24-pinnableslice.markdown @@ -0,0 +1,37 @@ +--- +title: PinnableSlice; less memcpy with point lookups +layout: post +author: maysamyabandeh +category: blog +--- + +The classic API for [DB::Get](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L310) receives a std::string as argument to which it will copy the value. The memcpy overhead could be non-trivial when the value is large. The [new API](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L322) receives a PinnableSlice instead, which avoids memcpy in most of the cases. + +### What is PinnableSlice? + +Similarly to Slice, PinnableSlice refers to some in-memory data so it does not incur the memcpy cost. To ensure that the data will not be erased while it is being processed by the user, PinnableSlice, as its name suggests, has the data pinned in memory. The pinned data are released when PinnableSlice object is destructed or when ::Reset is invoked explicitly on it. + +### How good is it? + +Here are the improvements in throughput for an [in-memory benchmark](https://github.com/facebook/rocksdb/pull/1756#issuecomment-286201693): +* value 1k byte: 14% +* value 10k byte: 34% + +### Any limitations? + +PinnableSlice tries to avoid memcpy as much as possible. The primary gain is when reading large values from the block cache. There are however cases that it would still have to copy the data into its internal buffer. The reason is mainly the complexity of implementation and if there is enough motivation on the application side. the scope of PinnableSlice could be extended to such cases too. These include: +* Merged values +* Reads from memtables + +### How to use it? + +```cpp +PinnableSlice pinnable_val; +while (!stopped) { + auto s = db->Get(opt, cf, key, &pinnable_val); + // ... use it + pinnable_val.Reset(); // then release it immediately +} +``` + +You can also [initialize the internal buffer](https://github.com/facebook/rocksdb/blob/9e583711144f580390ce21a49a8ceacca338fcd5/include/rocksdb/db.h#L314) of PinnableSlice by passing your own string in the constructor. [simple_example.cc](https://github.com/facebook/rocksdb/blob/master/examples/simple_example.cc) demonstrates that with more examples. diff --git a/thirdparty/rocksdb/docs/_posts/2017-08-25-flushwal.markdown b/thirdparty/rocksdb/docs/_posts/2017-08-25-flushwal.markdown new file mode 100644 index 0000000000..2dc5626ad4 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-08-25-flushwal.markdown @@ -0,0 +1,26 @@ +--- +title: FlushWAL; less fwrite, faster writes +layout: post +author: maysamyabandeh +category: blog +--- + +When `DB::Put` is called, the data is written to both memtable (to be flushed to SST files later) and the WAL (write-ahead log) if it is enabled. In the case of a crash, RocksDB can recover as much as the memtable state that is reflected into the WAL. By default RocksDB automatically flushes the WAL from the application memory to the OS buffer after each `::Put`. It however can be configured to perform the flush manually after an explicit call to `::FlushWAL`. Not doing fwrite syscall after each `::Put` offers a tradeoff between reliability and write latency for the general case. As we explain below, some applications such as MyRocks benefit from this API to gain higher write throughput with however no compromise in reliability. + +### How much is the gain? + +Using `::FlushWAL` API along with setting `DBOptions.concurrent_prepare`, MyRocks achieves 40% higher throughput in Sysbench's [update-nonindex](https://github.com/akopytov/sysbench/blob/master/src/lua/oltp_update_non_index.lua) benchmark. + +### Write, Flush, and Sync + +The write to the WAL is first written to the application memory buffer. The buffer in the next step is "flushed" to OS buffer by calling fwrite syscall. The OS buffer is later "synced" to the persistent storage. The data in the OS buffer, although not persisted yet, will survive the application crash. By default, the flush occurs automatically upon each call to `DB::Put` or `DB::Write`. The user can additionally request sync after each write by setting `WriteOptions::sync`. + +### FlushWAL API + +The user can turn off the automatic flush of the WAL by setting `DBOptions::manual_wal_flush`. In that case, the WAL buffer is flushed when it is either full or `DB::FlushWAL` is called by the user. The API also accepts a boolean argument should we want to sync right after the flush: `::FlushWAL(true)`. + +### Success story: MyRocks + +Some applications that use RocksDB, already have other machinsims in place to provide reliability. MySQL for example uses 2PC (two-phase commit) to write to both binlog as well as the storage engine such as InnoDB and MyRocks. The group commit logic in MySQL allows the 1st phase (Prepare) to be run in parallel but after a commit group is formed performs the 2nd phase (Commit) in a serial manner. This makes low commit latency in the storage engine essential for acheiving high throughput. The commit in MyRocks includes writing to the RocksDB WAL, which as explaiend above, by default incures the latency of flushing the WAL new appends to the OS buffer. + +Since binlog helps in recovering from some failure scenarios, MySQL can provide reliability without however needing a storage WAL flush after each individual commit. MyRocks benefits from this property, disables automatic WAL flush in RocksDB, and manually calls `::FlushWAL` when requested by MySQL. diff --git a/thirdparty/rocksdb/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown b/thirdparty/rocksdb/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown new file mode 100644 index 0000000000..a22dcaa1cc --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-09-28-rocksdb-5-8-released.markdown @@ -0,0 +1,25 @@ +--- +title: RocksDB 5.8 Released! +layout: post +author: maysamyabandeh +category: blog +--- + +### Public API Change +* Users of `Statistics::getHistogramString()` will see fewer histogram buckets and different bucket endpoints. +* `Slice::compare` and BytewiseComparator `Compare` no longer accept `Slice`s containing nullptr. +* `Transaction::Get` and `Transaction::GetForUpdate` variants with `PinnableSlice` added. + +### New Features +* Add Iterator::Refresh(), which allows users to update the iterator state so that they can avoid some initialization costs of recreating iterators. +* Replace dynamic_cast<> (except unit test) so people can choose to build with RTTI off. With make, release mode is by default built with -fno-rtti and debug mode is built without it. Users can override it by setting USE_RTTI=0 or 1. +* Universal compactions including the bottom level can be executed in a dedicated thread pool. This alleviates head-of-line blocking in the compaction queue, which cause write stalling, particularly in multi-instance use cases. Users can enable this feature via `Env::SetBackgroundThreads(N, Env::Priority::BOTTOM)`, where `N > 0`. +* Allow merge operator to be called even with a single merge operand during compactions, by appropriately overriding `MergeOperator::AllowSingleOperand`. +* Add `DB::VerifyChecksum()`, which verifies the checksums in all SST files in a running DB. +* Block-based table support for disabling checksums by setting `BlockBasedTableOptions::checksum = kNoChecksum`. + +### Bug Fixes +* Fix wrong latencies in `rocksdb.db.get.micros`, `rocksdb.db.write.micros`, and `rocksdb.sst.read.micros`. +* Fix incorrect dropping of deletions during intra-L0 compaction. +* Fix transient reappearance of keys covered by range deletions when memtable prefix bloom filter is enabled. +* Fix potentially wrong file smallest key when range deletions separated by snapshot are written together. diff --git a/thirdparty/rocksdb/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown b/thirdparty/rocksdb/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown new file mode 100644 index 0000000000..d2e6204e1e --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-12-18-17-auto-tuned-rate-limiter.markdown @@ -0,0 +1,28 @@ +--- +title: Auto-tuned Rate Limiter +layout: post +author: ajkr +category: blog +--- + +### Introduction + +Our rate limiter has been hard to configure since users need to pick a value that is low enough to prevent background I/O spikes, which can impact user-visible read/write latencies. Meanwhile, picking too low a value can cause memtables and L0 files to pile up, eventually leading to writes stalling. Tuning the rate limiter has been especially difficult for users whose DB instances have different workloads, or have workloads that vary over time, or commonly both. + +To address this, in RocksDB 5.9 we released a dynamic rate limiter that adjusts itself over time according to demand for background I/O. It can be enabled simply by passing `auto_tuned=true` in the `NewGenericRateLimiter()` call. In this case `rate_bytes_per_sec` will indicate the upper-bound of the window within which a rate limit will be picked dynamically. The chosen rate limit will be much lower unless absolutely necessary, so setting this to the device's maximum throughput is a reasonable choice on dedicated hosts. + +### Algorithm + +We use a simple multiplicative-increase, multiplicative-decrease algorithm. We measure demand for background I/O as the ratio of intervals where the rate limiter is drained. There are low and high watermarks for this ratio, which will trigger a change in rate limit when breached. The rate limit can move within a window bounded by the user-specified upper-bound, and a lower-bound that we derive internally. Users can expect this lower bound to be 1-2 orders of magnitude less than the provided upper-bound (so don't provide INT64_MAX as your upper-bound), although it's subject to change. + +### Benchmark Results + +Data is ingested at 10MB/s and the rate limiter was created with 1000MB/s as its upper bound. The dynamically chosen rate limit hovers around 125MB/s. The other clustering of points at 50MB/s is due to number of compaction threads being reduced to one when there's no compaction pressure. + +![](/static/images/rate-limiter/write-KBps-series.png) + +![](/static/images/rate-limiter/auto-tuned-write-KBps-series.png) + +The following graph summarizes the above two time series graphs in CDF form. In particular, notice the p90 - p100 for background write rate are significantly lower with auto-tuned rate limiter enabled. + +![](/static/images/rate-limiter/write-KBps-cdf.png) diff --git a/thirdparty/rocksdb/docs/_posts/2017-12-19-write-prepared-txn.markdown b/thirdparty/rocksdb/docs/_posts/2017-12-19-write-prepared-txn.markdown new file mode 100644 index 0000000000..439b3f83cc --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2017-12-19-write-prepared-txn.markdown @@ -0,0 +1,41 @@ +--- +title: WritePrepared Transactions +layout: post +author: maysamyabandeh +category: blog +--- + +RocksDB supports both optimistic and pessimistic concurrency controls. The pessimistic transactions make use of locks to provide isolation between the transactions. The default write policy in pessimistic transactions is _WriteCommitted_, which means that the data is written to the DB, i.e., the memtable, only after the transaction is committed. This policy simplified the implementation but came with some limitations in throughput, transaction size, and variety in supported isolation levels. In the below, we explain these in detail and present the other write policies, _WritePrepared_ and _WriteUnprepared_. We then dive into the design of _WritePrepared_ transactions. + +### WriteCommitted, Pros and Cons + +With _WriteCommitted_ write policy, the data is written to the memtable only after the transaction commits. This greatly simplifies the read path as any data that is read by other transactions can be assumed to be committed. This write policy, however, implies that the writes are buffered in memory in the meanwhile. This makes memory a bottleneck for large transactions. The delay of the commit phase in 2PC (two-phase commit) also becomes noticeable since most of the work, i.e., writing to memtable, is done at the commit phase. When the commit of multiple transactions are done in a serial fashion, such as in 2PC implementation of MySQL, the lengthy commit latency becomes a major contributor to lower throughput. Moreover this write policy cannot provide weaker isolation levels, such as READ UNCOMMITTED, that could potentially provide higher throughput for some applications. + +### Alternatives: _WritePrepared_ and _WriteUnprepared_ + +To tackle the lengthy commit issue, we should do memtable writes at earlier phases of 2PC so that the commit phase become lightweight and fast. 2PC is composed of Write stage, where the transaction `::Put` is invoked, the prepare phase, where `::Prepare` is invoked (upon which the DB promises to commit the transaction if later is requested), and commit phase, where `::Commit` is invoked and the transaction writes become visible to all readers. To make the commit phase lightweight, the memtable write could be done at either `::Prepare` or `::Put` stages, resulting into _WritePrepared_ and _WriteUnprepared_ write policies respectively. The downside is that when another transaction is reading data, it would need a way to tell apart which data is committed, and if they are, whether they are committed before the transaction's start, i.e., in the read snapshot of the transaction. _WritePrepared_ would still have the issue of buffering the data, which makes the memory the bottleneck for large transactions. It however provides a good milestone for transitioning from _WriteCommitted_ to _WriteUnprepared_ write policy. Here we explain the design of _WritePrepared_ policy. We will cover the changes that make the design to also supported _WriteUnprepared_ in an upcoming post. + +### _WritePrepared_ in a nutshell + +These are the primary design questions that needs to be addressed: +1) How do we identify the key/values in the DB with transactions that wrote them? +2) How do we figure if a key/value written by transaction Txn_w is in the read snapshot of the reading transaction Txn_r? +3) How do we rollback the data written by aborted transactions? + +With _WritePrepared_, a transaction still buffers the writes in a write batch object in memory. When 2PC `::Prepare` is called, it writes the in-memory write batch to the WAL (write-ahead log) as well as to the memtable(s) (one memtable per column family); We reuse the existing notion of sequence numbers in RocksDB to tag all the key/values in the same write batch with the same sequence number, `prepare_seq`, which is also used as the identifier for the transaction. At commit time, it writes a commit marker to the WAL, whose sequence number, `commit_seq`, will be used as the commit timestamp of the transaction. Before releasing the commit sequence number to the readers, it stores a mapping from `prepare_seq` to `commit_seq` in an in-memory data structure that we call _CommitCache_. When a transaction reading values from the DB (tagged with `prepare_seq`) it makes use of the _CommitCache_ to figure if `commit_seq` of the value is in its read snapshot. To rollback an aborted transaction, we apply the status before the transaction by making another write that cancels out the writes of the aborted transaction. + +The _CommitCache_ is a lock-free data structure that caches the recent commit entries. Looking up the entries in the cache must be enough for almost all th transactions that commit in a timely manner. When evicting the older entries from the cache, it still maintains some other data structures to cover the corner cases for transactions that takes abnormally too long to finish. We will cover them in the design details below. + +### Benchmark Results +Here we presents the improvements observed in MyRocks with sysbench and linkbench: +* benchmark...........tps.........p95 latency....cpu/query +* insert...................68% +* update-noindex...30%......38% +* update-index.......61%.......28% +* read-write............6%........3.5% +* read-only...........-1.2%.....-1.8% +* linkbench.............1.9%......+overall........0.6% + +Here are also the detailed results for [In-Memory Sysbench](https://gist.github.com/maysamyabandeh/bdb868091b2929a6d938615fdcf58424) and [SSD Sysbench](https://gist.github.com/maysamyabandeh/ff94f378ab48925025c34c47eff99306) curtesy of [@mdcallag](https://github.com/mdcallag). + +Learn more [here](https://github.com/facebook/rocksdb/wiki/WritePrepared-Transactions). diff --git a/thirdparty/rocksdb/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown b/thirdparty/rocksdb/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown new file mode 100644 index 0000000000..9f32d3f94c --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2018-02-05-rocksdb-5-10-2-released.markdown @@ -0,0 +1,22 @@ +--- +title: RocksDB 5.10.2 Released! +layout: post +author: siying +category: blog +--- + +### Public API Change +* When running `make` with environment variable `USE_SSE` set and `PORTABLE` unset, will use all machine features available locally. Previously this combination only compiled SSE-related features. + +### New Features +* CRC32C is now using the 3-way pipelined SSE algorithm `crc32c_3way` on supported platforms to improve performance. The system will choose to use this algorithm on supported platforms automatically whenever possible. If PCLMULQDQ is not supported it will fall back to the old Fast_CRC32 algorithm. +* Provide lifetime hints when writing files on Linux. This reduces hardware write-amp on storage devices supporting multiple streams. +* Add a DB stat, `NUMBER_ITER_SKIP`, which returns how many internal keys were skipped during iterations (e.g., due to being tombstones or duplicate versions of a key). +* Add PerfContext counters, `key_lock_wait_count` and `key_lock_wait_time`, which measure the number of times transactions wait on key locks and total amount of time waiting. + +### Bug Fixes +* Fix IOError on WAL write doesn't propagate to write group follower +* Make iterator invalid on merge error. +* Fix performance issue in `IngestExternalFile()` affecting databases with large number of SST files. +* Fix possible corruption to LSM structure when `DeleteFilesInRange()` deletes a subset of files spanned by a `DeleteRange()` marker. +* Fix DB::Flush() keep waiting after flush finish under certain condition. diff --git a/thirdparty/rocksdb/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown b/thirdparty/rocksdb/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown new file mode 100644 index 0000000000..c0e8c44258 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2018-08-01-rocksdb-tuning-advisor.markdown @@ -0,0 +1,58 @@ +--- +title: Rocksdb Tuning Advisor +layout: post +author: poojam23 +category: blog +--- + +The performance of Rocksdb is contingent on its tuning. However, because +of the complexity of its underlying technology and a large number of +configurable parameters, a good configuration is sometimes hard to obtain. The aim of +the python command-line tool, Rocksdb Advisor, is to automate the process of +suggesting improvements in the configuration based on advice from Rocksdb +experts. + +### Overview + +Experts share their wisdom as rules comprising of conditions and suggestions in the INI format (refer +[rules.ini](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rules.ini)). +Users provide the Rocksdb configuration that they want to improve upon (as the +familiar Rocksdb OPTIONS file — +[example](https://github.com/facebook/rocksdb/blob/master/examples/rocksdb_option_file_example.ini)) +and the path of the file which contains Rocksdb logs and statistics. +The [Advisor](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rule_parser_example.py) +creates appropriate DataSource objects (for Rocksdb +[logs](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_log_parser.py), +[options](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_options_parser.py), +[statistics](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_stats_fetcher.py) etc.) +and provides them to the [Rules Engine](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rule_parser.py). +The Rules uses rules from experts to parse data-sources and trigger appropriate rules. +The Advisor's output gives information about which rules were triggered, +why they were triggered and what each of them suggests. Each suggestion +provided by a triggered rule advises some action on a Rocksdb +configuration option, for example, increase CFOptions.write_buffer_size, +set bloom_bits to 2 etc. + +### Usage + +An example command to run the tool: + +```shell +cd rocksdb/tools/advisor +python3 -m advisor.rule_parser_example --rules_spec=advisor/rules.ini --rocksdb_options=test/input_files/OPTIONS-000005 --log_files_path_prefix=test/input_files/LOG-0 --stats_dump_period_sec=20 +``` + +Sample output where a Rocksdb log-based rule has been triggered : + +```shell +Rule: stall-too-many-memtables +LogCondition: stall-too-many-memtables regex: Stopping writes because we have \d+ immutable memtables \(waiting for flush\), max_write_buffer_number is set to \d+ +Suggestion: inc-bg-flush option : DBOptions.max_background_flushes action : increase suggested_values : ['2'] +Suggestion: inc-write-buffer option : CFOptions.max_write_buffer_number action : increase +scope: col_fam: +{'default'} +``` + +### Read more + +For more information, refer to [advisor](https://github.com/facebook/rocksdb/tree/master/tools/advisor/README.md). diff --git a/thirdparty/rocksdb/docs/_posts/2018-08-23-data-block-hash-index.markdown b/thirdparty/rocksdb/docs/_posts/2018-08-23-data-block-hash-index.markdown new file mode 100644 index 0000000000..c4b24ec2ac --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2018-08-23-data-block-hash-index.markdown @@ -0,0 +1,118 @@ +--- +title: Improving Point-Lookup Using Data Block Hash Index +layout: post +author: fgwu +category: blog +--- +We've designed and implemented a _data block hash index_ in RocksDB that has the benefit of both reducing the CPU util and increasing the throughput for point lookup queries with a reasonable and tunable space overhead. + +Specifially, we append a compact hash table to the end of the data block for efficient indexing. It is backward compatible with the data base created without this feature. After turned on the hash index feature, existing data will be gradually converted to the hash index format. + +Benchmarks with `db_bench` show the CPU utilization of one of the main functions in the point lookup code path, `DataBlockIter::Seek()`, is reduced by 21.8%, and the overall RocksDB throughput is increased by 10% under purely cached workloads, at an overhead of 4.6% more space. Shadow testing with Facebook production traffic shows good CPU improvements too. + + +### How to use it +Two new options are added as part of this feature: `BlockBasedTableOptions::data_block_index_type` and `BlockBasedTableOptions::data_block_hash_table_util_ratio`. + +The hash index is disabled by default unless `BlockBasedTableOptions::data_block_index_type` is set to `data_block_index_type = kDataBlockBinaryAndHash`. The hash table utilization ratio is adjustable using `BlockBasedTableOptions::data_block_hash_table_util_ratio`, which is valid only if `data_block_index_type = kDataBlockBinaryAndHash`. + + +``` +// the definitions can be found in include/rocksdb/table.h + +// The index type that will be used for the data block. +enum DataBlockIndexType : char { + kDataBlockBinarySearch = 0, // traditional block type + kDataBlockBinaryAndHash = 1, // additional hash index +}; + +// Set to kDataBlockBinaryAndHash to enable hash index +DataBlockIndexType data_block_index_type = kDataBlockBinarySearch; + +// #entries/#buckets. It is valid only when data_block_hash_index_type is +// kDataBlockBinaryAndHash. +double data_block_hash_table_util_ratio = 0.75; + +``` + + +### Data Block Hash Index Design + +Current data block format groups adjacent keys together as a restart interval. One block consists of multiple restart intervals. The byte offset of the beginning of each restart interval, i.e. a restart point, is stored in an array called restart interval index or binary seek index. RocksDB does a binary search when performing point lookup for keys in data blocks to find the right restart interval the key may reside. We will use binary seek and binary search interchangeably in this post. + +In order to find the right location where the key may reside using binary search, multiple key parsing and comparison are needed. Each binary search branching triggers CPU cache miss, causing much CPU utilization. We have seen that this binary search takes up considerable CPU in production use-cases. + +![](/static/images/data-block-hash-index/block-format-binary-seek.png) + +We implemented a hash map at the end of the block to index the key to reduce the CPU overhead of the binary search. The hash index is just an array of pointers pointing into the binary seek index. + +![](/static/images/data-block-hash-index/block-format-hash-index.png) + + +Each array element is considered as a hash bucket when storing the location of a key (or more precisely, the restart index of the restart interval where the key resides). When multiple keys happen to hash into the same bucket (hash collision), we just mark the bucket as “collision”. So that when later querying on that key, the hash table lookup knows that there was a hash collision happened so it can fall back to the traditional binary search to find the location of the key. + +We define hash table utilization ratio as the #keys/#buckets. If a utilization ratio is 0.5 and there are 100 buckets, 50 keys are stored in the bucket. The less the util ratio, the less hash collision, and the less chance for a point lookup falls back to binary seek (fall back ratio) due to the collision. So a small util ratio has more benefit to reduce the CPU time but introduces more space overhead. + +Space overhead depends on the util ratio. Each bucket is a `uint8_t` (i.e. one byte). For a util ratio of 1, the space overhead is 1Byte per key, the fall back ratio observed is ~52%. + +![](/static/images/data-block-hash-index/hash-index-data-structure.png) + +### Things that Need Attention + +**Customized Comparator** + +Hash index will hash different keys (keys with different content, or byte sequence) into different hash values. This assumes the comparator will not treat different keys as equal if they have different content. + +The default bytewise comparator orders the keys in alphabetical order and works well with hash index, as different keys will never be regarded as equal. However, some specially crafted comparators will do. For example, say, a `StringToIntComparator` can convert a string into an integer, and use the integer to perform the comparison. Key string “16” and “0x10” is equal to each other as seen by this `StringToIntComparator`, but they probably hash to different value. Later queries to one form of the key will not be able to find the existing key been stored in the other format. + +We add a new function member to the comparator interface: + +``` +virtual bool CanKeysWithDifferentByteContentsBeEqual() const { return true; } +``` + + +Every comparator implementation should override this function and specify the behavior of the comparator. If a comparator can regard different keys equal, the function returns true, and as a result the hash index feature will not be enabled, and vice versa. + +NOTE: to use the hash index feature, one should 1) have a comparator that can never treat different keys as equal; and 2) override the `CanKeysWithDifferentByteContentsBeEqual()` function to return `false`, so the hash index can be enabled. + + +**Util Ratio's Impact on Data Block Cache** + +Adding the hash index to the end of the data block essentially takes up the data block cache space, making the effective data block cache size smaller and increasing the data block cache miss ratio. Therefore, a very small util ratio will result in a large data block cache miss ratio, and the extra I/O may drag down the throughput gain achieved by the hash index lookup. Besides, when compression is enabled, cache miss also incurs data block decompression, which is CPU-consuming. Therefore the CPU may even increase if using a too small util ratio. The best util ratio depends on workloads, cache to data ratio, disk bandwidth/latency etc. In our experiment, we found util ratio = 0.5 ~ 1 is a good range to explore that brings both CPU and throughput gains. + + +### Limitations + +As we use `uint8_t` to store binary seek index, i.e. restart interval index, the total number of restart intervals cannot be more than 253 (we reserved 255 and 254 as special flags). For blocks having a larger number of restart intervals, the hash index will not be created and the point lookup will be done by traditional binary seek. + +Data block hash index only supports point lookup. We do not support range lookup. Range lookup request will fall back to BinarySeek. + +RocksDB supports many types of records, such as `Put`, `Delete`, `Merge`, etc (visit [here](https://github.com/facebook/rocksdb/wiki/rocksdb-basics) for more information). Currently we only support `Put` and `Delete`, but not `Merge`. Internally we have a limited set of supported record types: + + +``` +kPutRecord, <=== supported +kDeleteRecord, <=== supported +kSingleDeleteRecord, <=== supported +kTypeBlobIndex, <=== supported +``` + +For records not supported, the searching process will fall back to the traditional binary seek. + + + +### Evaluation +To evaluate the CPU util reduction and isolate other factors such as disk I/O and block decompression, we first evaluate the hash idnex in a purely cached workload. We observe that the CPU utilization of one of the main functions in the point lookup code path, DataBlockIter::Seek(), is reduced by 21.8% and the overall throughput is increased by 10% at an overhead of 4.6% more space. + +However, general worload is not always purely cached. So we also evaluate the performance under different cache space pressure. In the following test, we use `db_bench` with RocksDB deployed on SSDs. The total DB size is 5~6GB, and it is about 14GB if decompressed. Different block cache sizes are used, ranging from 14GB down to 2GB, with an increasing cache miss ratio. + +Orange bars are representing our hash index performance. We use a hash util ratio of 1.0 in this test. Block size are set to 16KiB with the restart interval as 16. + +![](/static/images/data-block-hash-index/perf-throughput.png) +![](/static/images/data-block-hash-index/perf-cache-miss.png) + +We can see that if cache size is greater than 8GB, hash index can bring throughput gain. Cache size greater than 8GB can be translated to a cache miss ratio smaller than 40%. So if the workload has a cache miss ratio smaller than 40%, hash index is able to increase the throughput. + +Besides, shadow testing with Facebook production traffic shows good CPU improvements too. + diff --git a/thirdparty/rocksdb/docs/_posts/2018-11-21-delete-range.markdown b/thirdparty/rocksdb/docs/_posts/2018-11-21-delete-range.markdown new file mode 100644 index 0000000000..96fc3562d1 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2018-11-21-delete-range.markdown @@ -0,0 +1,292 @@ +--- +title: "DeleteRange: A New Native RocksDB Operation" +layout: post +author: +- abhimadan +- ajkr +category: blog +--- +## Motivation + +### Deletion patterns in LSM + +Deleting a range of keys is a common pattern in RocksDB. Most systems built on top of +RocksDB have multi-component key schemas, where keys sharing a common prefix are +logically related. Here are some examples. + +MyRocks is a MySQL fork using RocksDB as its storage engine. Each key's first +four bytes identify the table or index to which that key belongs. Thus dropping +a table or index involves deleting all the keys with that prefix. + +Rockssandra is a Cassandra variant that uses RocksDB as its storage engine. One +of its admin tool commands, `nodetool cleanup`, removes key-ranges that have been migrated +to other nodes in the cluster. + +Marketplace uses RocksDB to store product data. Its key begins with product ID, +and it stores various data associated with the product in separate keys. When a +product is removed, all these keys must be deleted. + +When we decide what to improve, we try to find a use case that's common across +users, since we want to build a generally useful system, not one that has many +one-off features for individual users. The range deletion pattern is common as +illustrated above, so from this perspective it's a good target for optimization. + +### Existing mechanisms: challenges and opportunities + +The most common pattern we see is scan-and-delete, i.e., advance an iterator +through the to-be-deleted range, and issue a `Delete` for each key. This is +slow (involves read I/O) so cannot be done in any critical path. Additionally, +it creates many tombstones, which slows down iterators and doesn't offer a deadline +for space reclamation. + +Another common pattern is using a custom compaction filter that drops keys in +the deleted range(s). This deletes the range asynchronously, so cannot be used +in cases where readers must not see keys in deleted ranges. Further, it has the +disadvantage of outputting tombstones to all but the bottom level. That's +because compaction cannot detect whether dropping a key would cause an older +version at a lower level to reappear. + +If space reclamation time is important, or it is important that the deleted +range not affect iterators, the user can trigger `CompactRange` on the deleted +range. This can involve arbitrarily long waits in the compaction queue, and +increases write-amp. By the time it's finished, however, the range is completely +gone from the LSM. + +`DeleteFilesInRange` can be used prior to compacting the deleted range as long +as snapshot readers do not need to access them. It drops files that are +completely contained in the deleted range. That saves write-amp because, in +`CompactRange`, the file data would have to be rewritten several times before it +reaches the bottom of the LSM, where tombstones can finally be dropped. + +In addition to the above approaches having various drawbacks, they are quite +complicated to reason about and implement. In an ideal world, deleting a range +of keys would be (1) simple, i.e., a single API call; (2) synchronous, i.e., +when the call finishes, the keys are guaranteed to be wiped from the DB; (3) low +latency so it can be used in critical paths; and (4) a first-class operation +with all the guarantees of any other write, like atomicity, crash-recovery, etc. + +## v1: Getting it to work + +### Where to persist them? + +The first place we thought about storing them is inline with the data blocks. +We could not think of a good way to do it, however, since the start of a range +tombstone covering a key could be anywhere, making binary search impossible. +So, we decided to investigate segregated storage. + +A second solution we considered is appending to the manifest. This file is +append-only, periodically compacted, and stores metadata like the level to which +each SST belongs. This is tempting because it leverages an existing file, which +is maintained in the background and fully read when the DB is opened. However, +it conceptually violates the manifest's purpose, which is to store metadata. It +also has no way to detect when a range tombstone no longer covers anything and +is droppable. Further, it'd be possible for keys above a range tombstone to disappear +when they have their seqnums zeroed upon compaction to the bottommost level. + +A third candidate is using a separate column family. This has similar problems +to the manifest approach. That is, we cannot easily detect when a range +tombstone is obsolete, and seqnum zeroing can cause a key +to go from above a range tombstone to below, i.e., disappearing. The upside is +we can reuse logic for memory buffering, consistent reads/writes, etc. + +The problems with the second and third solutions indicate a need for range +tombstones to be aware of flush/compaction. An easy way to achieve this is put +them in the SST files themselves - but not in the data blocks, as explained for +the first solution. So, we introduced a separate meta-block for range tombstones. +This resolved the problem of when to obsolete range tombstones, as it's simple: +when they're compacted to the bottom level. We also reused the LSM invariants +that newer versions of a key are always in a higher level to prevent the seqnum +zeroing problem. This approach has the side benefit of constraining the range +tombstones seen during reads to ones in a similar key-range. + +![](/static/images/delrange/delrange_sst_blocks.png) +{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} + +*When there are range tombstones in an SST, they are segregated in a separate meta-block* +{: style="text-align: center"} + +![](/static/images/delrange/delrange_key_schema.png) +{: style="display: block; margin-left: auto; margin-right: auto; width: 80%"} + +*Logical range tombstones (left) and their corresponding physical key-value representation (right)* +{: style="text-align: center"} + +### Write path + +`WriteBatch` stores range tombstones in its buffer which are logged to the WAL and +then applied to a dedicated range tombstone memtable during `Write`. Later in +the background the range tombstone memtable and its corresponding data memtable +are flushed together into a single SST with a range tombstone meta-block. SSTs +periodically undergo compaction which rewrites SSTs with point data and range +tombstones dropped or merged wherever possible. + +We chose to use a dedicated memtable for range tombstones. The memtable +representation is always skiplist in order to minimize overhead in the usual +case, which is the memtable contains zero or a small number of range tombstones. +The range tombstones are segregated to a separate memtable for the same reason +we segregated range tombstones in SSTs. That is, we did not know how to +interleave the range tombstone with point data in a way that we would be able to +find it for arbitrary keys that it covers. + +![](/static/images/delrange/delrange_write_path.png) +{: style="display: block; margin-left: auto; margin-right: auto; width: 70%"} + +*Lifetime of point keys and range tombstones in RocksDB* +{: style="text-align: center"} + +During flush and compaction, we chose to write out all non-obsolete range +tombstones unsorted. Sorting by a single dimension is easy to implement, but +doesn't bring asymptotic improvement to queries over range data. Ideally, we +want to store skylines (see “Read Path” subsection below) computed over our ranges so we can binary search. +However, a couple of concerns cause doing this in flush and compaction to feel +unsatisfactory: (1) we need to store multiple skylines, one for each snapshot, +which further complicates the range tombstone meta-block encoding; and (2) even +if we implement this, the range tombstone memtable still needs to be linearly +scanned. Given these concerns we decided to defer collapsing work to the read +side, hoping a good caching strategy could optimize this at some future point. + + +### Read path + +In point lookups, we aggregate range tombstones in an unordered vector as we +search through live memtable, immutable memtables, and then SSTs. When a key is +found that matches the lookup key, we do a scan through the vector, checking +whether the key is deleted. + +In iterators, we aggregate range tombstones into a skyline as we visit live +memtable, immutable memtables, and SSTs. The skyline is expensive to construct but fast to determine whether a key is covered. The skyline keeps track of the most recent range tombstone found to optimize `Next` and `Prev`. + +|![](/static/images/delrange/delrange_uncollapsed.png) |![](/static/images/delrange/delrange_collapsed.png) | + +*([Image source: Leetcode](https://leetcode.com/problems/the-skyline-problem/description/)) The skyline problem involves taking building location/height data in the +unsearchable form of A and converting it to the form of B, which is +binary-searchable. With overlapping range tombstones, to achieve efficient +searching we need to solve an analogous problem, where the x-axis is the +key-space and the y-axis is the sequence number.* +{: style="text-align: center"} + +### Performance characteristics + +For the v1 implementation, writes are much faster compared to the scan and +delete (optionally within a transaction) pattern. `DeleteRange` only logs to WAL +and applies to memtable. Logging to WAL always `fflush`es, and optionally +`fsync`s or `fdatasync`s. Applying to memtable is always an in-memory operation. +Since range tombstones have a dedicated skiplist memtable, the complexity of inserting is O(log(T)), where T is the number of existing buffered range tombstones. + +Reading in the presence of v1 range tombstones, however, is much slower than reads +in a database where scan-and-delete has happened, due to the linear scan over +range tombstone memtables/meta-blocks. + +Iterating in a database with v1 range tombstones is usually slower than in a +scan-and-delete database, although the gap lessens as iterations grow longer. +When an iterator is first created and seeked, we construct a skyline over its +tombstones. This operation is O(T\*log(T)) where T is the number of tombstones +found across live memtable, immutable memtable, L0 files, and one file from each +of the L1+ levels. However, moving the iterator forwards or backwards is simply +a constant-time operation (excluding edge cases, e.g., many range tombstones +between consecutive point keys). + +## v2: Making it fast + +`DeleteRange`’s negative impact on read perf is a barrier to its adoption. The +root cause is range tombstones are not stored or cached in a format that can be +efficiently searched. We needed to design DeleteRange so that we could maintain +write performance while making read performance competitive with workarounds +used in production (e.g., scan-and-delete). + +### Representations + +The key idea of the redesign is that, instead of globally collapsing range tombstones, + we can locally “fragment” them for each SST file and memtable to guarantee that: + +* no range tombstones overlap; and +* range tombstones are ordered by start key. + +Combined, these properties make range tombstones binary searchable. This + fragmentation will happen on the read path, but unlike the previous design, we can + easily cache many of these range tombstone fragments on the read path. + +### Write path + +The write path remains unchanged. + +### Read path + +When an SST file is opened, its range tombstones are fragmented and cached. For point + lookups, we binary search each file's fragmented range tombstones for one that covers + the lookup key. Unlike the old design, once we find a tombstone, we no longer need to + search for the key in lower levels, since we know that any keys on those levels will be + covered (though we do still check the current level since there may be keys written after + the range tombstone). + +For range scans, we create iterators over all the fragmented range + tombstones and store them in a list, seeking each one to cover the start key of the range + scan (if possible), and query each encountered key in this structure as in the old design, + advancing range tombstone iterators as necessary. In effect, we implicitly create a skyline. + This requires significantly less work on iterator creation, but since each memtable/SST has +its own range tombstone iterator, querying range tombstones requires key comparisons (and +possibly iterator increments) for several iterators (as opposed to v1, where we had a global +collapsed representation of all range tombstones). As a result, very long range scans may become + slower than before, but short range scans are an order of magnitude faster, which are the + more common class of range scan. + +## Benchmarks + +To understand the performance of this new design, we used `db_bench` to compare point lookup, short range scan, + and long range scan performance across: + +* the v1 DeleteRange design, +* the scan-and-delete workaround, and +* the v2 DeleteRange design. + +In these benchmarks, we used a database with 5 million data keys, and 10000 range tombstones (ignoring +those dropped during compaction) that were written in regular intervals after 4.5 million data keys were written. +Writing the range tombstones ensures that most of them are not compacted away, and we have more tombstones +in higher levels that cover keys in lower levels, which allows the benchmarks to exercise more interesting behavior +when reading deleted keys. + +Point lookup benchmarks read 100000 keys from a database using `readwhilewriting`. Range scan benchmarks used +`seekrandomwhilewriting` and seeked 100000 times, and advanced up to 10 keys away from the seek position for short range scans, and advanced up to 1000 keys away from the seek position for long range scans. + +The results are summarized in the tables below, averaged over 10 runs (note the +different SHAs for v1 benchmarks are due to a new `db_bench` flag that was added in order to compare performance with databases with no tombstones; for brevity, those results are not reported here). Also note that the block cache was large enough to hold the entire db, so the large throughput is due to limited I/Os and little time spent on decompression. The range tombstone blocks are always pinned uncompressed in memory. We believe these setup details should not affect relative performance between versions. + +### Point Lookups + +|Name |SHA |avg micros/op |avg ops/sec | +|v1 |35cd754a6 |1.3179 |759,830.90 | +|scan-del |7528130e3 |0.6036 |1,667,237.70 | +|v2 |7528130e3 |0.6128 |1,634,633.40 | + +### Short Range Scans + +|Name |SHA |avg micros/op |avg ops/sec | +|v1 |0ed738fdd |6.23 |176,562.00 | +|scan-del |PR 4677 |2.6844 |377,313.00 | +|v2 |PR 4677 |2.8226 |361,249.70 | + +### Long Range scans + +|Name |SHA |avg micros/op |avg ops/sec | +|v1 |0ed738fdd |52.7066 |19,074.00 | +|scan-del |PR 4677 |38.0325 |26,648.60 | +|v2 |PR 4677 |41.2882 |24,714.70 | + +## Future Work + +Note that memtable range tombstones are fragmented every read; for now this is acceptable, + since we expect there to be relatively few range tombstones in memtables (and users can + enforce this by keeping track of the number of memtable range deletions and manually flushing + after it passes a threshold). In the future, a specialized data structure can be used for storing + range tombstones in memory to avoid this work. + +Another future optimization is to create a new format version that requires range tombstones to + be stored in a fragmented form. This would save time when opening SST files, and when `max_open_files` +is not -1 (i.e., files may be opened several times). + +## Acknowledgements + +Special thanks to Peter Mattis and Nikhil Benesch from Cockroach Labs, who were early users of +DeleteRange v1 in production, contributed the cleanest/most efficient v1 aggregation implementation, found and fixed bugs, and provided initial DeleteRange v2 design and continued help. + +Thanks to Huachao Huang and Jinpeng Zhang from PingCAP for early DeleteRange v1 adoption, bug reports, and fixes. diff --git a/thirdparty/rocksdb/docs/_posts/2019-03-08-format-version-4.markdown b/thirdparty/rocksdb/docs/_posts/2019-03-08-format-version-4.markdown new file mode 100644 index 0000000000..ce657696c7 --- /dev/null +++ b/thirdparty/rocksdb/docs/_posts/2019-03-08-format-version-4.markdown @@ -0,0 +1,36 @@ +--- +title: format_version 4 +layout: post +author: maysamyabandeh +category: blog +--- + +The data blocks in RocksDB consist of a sequence of key/values pairs sorted by key, where the pairs are grouped into _restart intervals_ specified by `block_restart_interval`. Up to RocksDB version 5.14, where the latest and default value of `BlockBasedTableOptions::format_version` is 2, the format of index and data blocks are the same: index blocks use the same key format of <`user_key`,`seq`> and encode pointers to data blocks, <`offset`,`size`>, to a byte string and use them as values. The only difference is that the index blocks use `index_block_restart_interval` for the size of _restart intervals_. `format_version=`3,4 offer more optimized, backward-compatible, yet forward-incompatible format for index blocks. + +### Pros + +Using `format_version`=4 significantly reduces the index block size, in some cases around 4-5x. This frees more space in block cache, which would result in higher hit rate for data and filter blocks, or offer the same performance with a smaller block cache size. + +### Cons + +Being _forward-incompatible_ means that if you enable `format_version=`4 you cannot downgrade to a RocksDB version lower than 5.16. + +### How to use it? + +- `BlockBasedTableOptions::format_version` = 4 +- `BlockBasedTableOptions::index_block_restart_interval` = 16 + +### What is format_version 3? +(Since RocksDB 5.15) In most cases, the sequence number `seq` is not necessary for keys in the index blocks. In such cases, `format_version`=3 skips encoding the sequence number and sets `index_key_is_user_key` in TableProperties, which is used by the reader to know how to decode the index block. + +### What is format_version 4? +(Since RocksDB 5.16) Changes the format of index blocks by delta encoding the index values, which are the block handles. This saves the encoding of `BlockHandle::offset` of the non-head index entries in each restart interval. If used, `TableProperties::index_value_is_delta_encoded` is set, which is used by the reader to know how to decode the index block. The format of each key is (shared_size, non_shared_size, shared, non_shared). The format of each value, i.e., block handle, is (offset, size) whenever the shared_size is 0, which included the first entry in each restart point. Otherwise the format is delta-size = block handle size - size of last block handle. + +The index format in `format_version=4` would be as follows: + + restart_point 0: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) + restart_point 1: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) + ... + restart_point n-1: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) + where, k is key, v is value, and its encoding is in parenthesis. + diff --git a/thirdparty/rocksdb/docs/_sass/_base.scss b/thirdparty/rocksdb/docs/_sass/_base.scss new file mode 100644 index 0000000000..6d26d9feba --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_base.scss @@ -0,0 +1,492 @@ +body { + background: $secondary-bg; + color: $text; + font: normal #{$base-font-size}/#{$base-line-height} $base-font-family; + height: 100vh; + text-align: left; + text-rendering: optimizeLegibility; +} + +img { + max-width: 100%; +} + +article { + p { + img { + max-width: 100%; + display:block; + margin-left: auto; + margin-right: auto; + } + } +} + +a { + border-bottom: 1px dotted $primary-bg; + color: $text; + text-decoration: none; + -webkit-transition: background 0.3s, color 0.3s; + transition: background 0.3s, color 0.3s; +} + +blockquote { + padding: 15px 30px 15px 15px; + margin: 20px 0 0 10px; + background-color: rgba(204, 122, 111, 0.1); + border-left: 10px solid rgba(191, 87, 73, 0.2); +} + +#fb_oss a { + border: 0; +} + +h1, h2, h3, h4 { + font-family: $header-font-family; + font-weight: 900; +} + +.navPusher { + border-top: $header-height + $header-ptop + $header-pbot solid $primary-bg; + height: 100%; + left: 0; + position: relative; + z-index: 99; +} + +.homeContainer { + background: $primary-bg; + color: $primary-overlay; + + a { + color: $primary-overlay; + } + + .homeSplashFade { + color: white; + } + + .homeWrapper { + padding: 2em 10px; + text-align: left; + + .wrapper { + margin: 0px auto; + max-width: $content-width; + padding: 0 20px; + } + + .projectLogo { + img { + height: 100px; + margin-bottom: 0px; + } + } + + h1#project_title { + font-family: $header-font-family; + font-size: 300%; + letter-spacing: -0.08em; + line-height: 1em; + margin-bottom: 80px; + } + + h2#project_tagline { + font-family: $header-font-family; + font-size: 200%; + letter-spacing: -0.04em; + line-height: 1em; + } + } +} + +.wrapper { + margin: 0px auto; + max-width: $content-width; + padding: 0 10px; +} + +.projectLogo { + display: none; + + img { + height: 100px; + margin-bottom: 0px; + } +} + +section#intro { + margin: 40px 0; +} + +.fbossFontLight { + font-family: $base-font-family; + font-weight: 300; + font-style: normal; +} + +.fb-like { + display: block; + margin-bottom: 20px; + width: 100%; +} + +.center { + display: block; + text-align: center; +} + +.mainContainer { + background: $secondary-bg; + overflow: auto; + + .mainWrapper { + padding: 4vh 10px; + text-align: left; + + .allShareBlock { + padding: 10px 0; + + .pluginBlock { + margin: 12px 0; + padding: 0; + } + } + + a { + &:hover, + &:focus { + background: $primary-bg; + color: $primary-overlay; + } + } + + em, i { + font-style: italic; + } + + strong, b { + font-weight: bold; + } + + h1 { + font-size: 300%; + line-height: 1em; + padding: 1.4em 0 1em; + text-align: left; + } + + h2 { + font-size: 250%; + line-height: 1em; + margin-bottom: 20px; + padding: 1.4em 0 20px; + text-align: left; + + & { + border-bottom: 1px solid darken($primary-bg, 10%); + color: darken($primary-bg, 10%); + font-size: 22px; + padding: 10px 0; + } + + &.blockHeader { + border-bottom: 1px solid white; + color: white; + font-size: 22px; + margin-bottom: 20px; + padding: 10px 0; + } + } + + h3 { + font-size: 150%; + line-height: 1.2em; + padding: 1em 0 0.8em; + } + + h4 { + font-size: 130%; + line-height: 1.2em; + padding: 1em 0 0.8em; + } + + p { + padding: 0.8em 0; + } + + ul { + list-style: disc; + } + + ol, ul { + padding-left: 24px; + li { + padding-bottom: 4px; + padding-left: 6px; + } + } + + strong { + font-weight: bold; + } + + .post { + position: relative; + + .katex { + font-weight: 700; + } + + &.basicPost { + margin-top: 30px; + } + + a { + color: $primary-bg; + + &:hover, + &:focus { + color: #fff; + } + } + + h2 { + border-bottom: 4px solid $primary-bg; + font-size: 130%; + } + + h3 { + border-bottom: 1px solid $primary-bg; + font-size: 110%; + } + + ol { + list-style: decimal outside none; + } + + .post-header { + padding: 1em 0; + + h1 { + font-size: 150%; + line-height: 1em; + padding: 0.4em 0 0; + + a { + border: none; + } + } + + .post-meta { + color: $primary-bg; + font-family: $header-font-family; + text-align: center; + } + } + + .postSocialPlugins { + padding-top: 1em; + } + + .docPagination { + background: $primary-bg; + bottom: 0px; + left: 0px; + position: absolute; + right: 0px; + + .pager { + display: inline-block; + width: 50%; + } + + .pagingNext { + float: right; + text-align: right; + } + + a { + border: none; + color: $primary-overlay; + display: block; + padding: 4px 12px; + + &:hover { + background-color: $secondary-bg; + color: $text; + } + + .pagerLabel { + display: inline; + } + + .pagerTitle { + display: none; + } + } + } + } + + .posts { + .post { + margin-bottom: 6vh; + } + } + } +} + +#integrations_title { + font-size: 250%; + margin: 80px 0; +} + +.ytVideo { + height: 0; + overflow: hidden; + padding-bottom: 53.4%; /* 16:9 */ + padding-top: 25px; + position: relative; +} + +.ytVideo iframe, +.ytVideo object, +.ytVideo embed { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} + +@media only screen and (min-width: 480px) { + h1#project_title { + font-size: 500%; + } + + h2#project_tagline { + font-size: 250%; + } + + .projectLogo { + img { + margin-bottom: 10px; + height: 200px; + } + } + + .homeContainer .homeWrapper { + padding-left: 10px; + padding-right: 10px; + } + + .mainContainer { + .mainWrapper { + .post { + h2 { + font-size: 180%; + } + + h3 { + font-size: 120%; + } + + .docPagination { + a { + .pagerLabel { + display: none; + } + .pagerTitle { + display: inline; + } + } + } + } + } + } +} + +@media only screen and (min-width: 900px) { + .homeContainer { + .homeWrapper { + position: relative; + + #inner { + box-sizing: border-box; + max-width: 600px; + padding-right: 40px; + } + + .projectLogo { + align-items: center; + bottom: 0; + display: flex; + justify-content: flex-end; + left: 0; + padding: 2em 20px 4em; + position: absolute; + right: 20px; + top: 0; + + img { + height: 100%; + max-height: 250px; + } + } + } + } +} + +@media only screen and (min-width: 1024px) { + .mainContainer { + .mainWrapper { + .post { + box-sizing: border-box; + display: block; + + .post-header { + h1 { + font-size: 250%; + } + } + } + + .posts { + .post { + margin-bottom: 4vh; + width: 100%; + } + } + } + } +} + +@media only screen and (min-width: 1200px) { + .homeContainer { + .homeWrapper { + #inner { + max-width: 750px; + } + } + } + + .wrapper { + max-width: 1100px; + } +} + +@media only screen and (min-width: 1500px) { + .homeContainer { + .homeWrapper { + #inner { + max-width: 1100px; + padding-bottom: 40px; + padding-top: 40px; + } + } + } + + .wrapper { + max-width: 1400px; + } +} diff --git a/thirdparty/rocksdb/docs/_sass/_blog.scss b/thirdparty/rocksdb/docs/_sass/_blog.scss new file mode 100644 index 0000000000..12a73c1fcd --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_blog.scss @@ -0,0 +1,47 @@ +.blogContainer { + .posts { + margin-top: 60px; + + .post { + border: 1px solid $primary-bg; + border-radius: 3px; + padding: 10px 20px 20px; + } + } + + .lonePost { + margin-top: 60px; + + .post { + padding: 10px 0px 0px; + } + } + + .post-header { + h1 { + text-align: center; + } + + .post-authorName { + color: rgba($text, 0.7); + font-size: 14px; + font-weight: 900; + margin-top: 0; + padding: 0; + text-align: center; + } + + .authorPhoto { + border-radius: 50%; + height: 50px; + left: 50%; + margin-left: auto; + margin-right: auto; + display: inline-block; + overflow: hidden; + position: static; + top: -25px; + width: 50px; + } + } +} diff --git a/thirdparty/rocksdb/docs/_sass/_buttons.scss b/thirdparty/rocksdb/docs/_sass/_buttons.scss new file mode 100644 index 0000000000..a0371618fc --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_buttons.scss @@ -0,0 +1,47 @@ +.button { + border: 1px solid $primary-bg; + border-radius: 3px; + color: $primary-bg; + display: inline-block; + font-size: 14px; + font-weight: 900; + line-height: 1.2em; + padding: 10px; + text-transform: uppercase; + transition: background 0.3s, color 0.3s; + + &:hover { + background: $primary-bg; + color: $primary-overlay; + } +} + +.homeContainer { + .button { + border-color: $primary-overlay; + border-width: 1px; + color: $primary-overlay; + + &:hover { + background: $primary-overlay; + color: $primary-bg; + } + } +} + +.blockButton { + display: block; +} + +.edit-page-link { + float: right; + font-size: 14px; + font-weight: normal; + line-height: 20px; + opacity: 0.6; + transition: opacity 0.5s; +} + +.edit-page-link:hover { + opacity: 1; +} diff --git a/thirdparty/rocksdb/docs/_sass/_footer.scss b/thirdparty/rocksdb/docs/_sass/_footer.scss new file mode 100644 index 0000000000..5b74395179 --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_footer.scss @@ -0,0 +1,82 @@ +.footerContainer { + background: $secondary-bg; + color: $primary-bg; + overflow: hidden; + padding: 0 10px; + text-align: left; + + .footerWrapper { + border-top: 1px solid $primary-bg; + padding: 0; + + .footerBlocks { + align-items: center; + align-content: center; + display: flex; + flex-flow: row wrap; + margin: 0 -20px; + padding: 10px 0; + } + + .footerSection { + box-sizing: border-box; + flex: 1 1 25%; + font-size: 14px; + min-width: 275px; + padding: 0px 20px; + + a { + border: 0; + color: inherit; + display: inline-block; + line-height: 1.2em; + } + + .footerLink { + padding-right: 20px; + } + } + + .fbOpenSourceFooter { + align-items: center; + display: flex; + flex-flow: row nowrap; + max-width: 25%; + + .facebookOSSLogoSvg { + flex: 0 0 31px; + height: 30px; + margin-right: 10px; + width: 31px; + + path { + fill: $primary-bg; + } + + .middleRing { + opacity: 0.7; + } + + .innerRing { + opacity: 0.45; + } + } + + h2 { + display: block; + font-weight: 900; + line-height: 1em; + } + } + } +} + +@media only screen and (min-width: 900px) { + .footerSection { + &.rightAlign { + margin-left: auto; + max-width: 25%; + text-align: right; + } + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_gridBlock.scss b/thirdparty/rocksdb/docs/_sass/_gridBlock.scss new file mode 100644 index 0000000000..679b31c14c --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_gridBlock.scss @@ -0,0 +1,115 @@ +.gridBlock { + margin: -5px 0; + padding: 0; + padding-bottom: 20px; + + .blockElement { + padding: 5px 0; + + img { + max-width: 100%; + } + + h3 { + border-bottom: 1px solid rgba($primary-bg, 0.5); + color: $primary-bg; + font-size: 18px; + margin: 0; + padding: 10px 0; + } + } + + .gridClear { + clear: both; + } + +} + +.gridBlock .alignCenter { + text-align: center; +} +.gridBlock .alignRight { + text-align: right; +} +.gridBlock .imageAlignSide { + align-items: center; + display: flex; + flex-flow: row wrap; +} +.blockImage { + max-width: 150px; + width: 50%; +} +.imageAlignTop .blockImage { + margin-bottom: 20px; +} +.imageAlignTop.alignCenter .blockImage { + margin-left: auto; + margin-right: auto; +} +.imageAlignSide .blockImage { + flex: 0 1 100px; + margin-right: 20px; +} +.imageAlignSide .blockContent { + flex: 1 1; +} + +@media only screen and (max-width: 1023px) { + .responsiveList .blockContent { + position: relative; + } + .responsiveList .blockContent > div { + padding-left: 20px; + } + .responsiveList .blockContent::before { + content: "\2022"; + position: absolute; + } +} + +@media only screen and (min-width: 1024px) { + .gridBlock { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: -10px -10px 10px -10px; + + .twoByGridBlock { + box-sizing: border-box; + flex: 1 0 50%; + padding: 10px; + } + + .fourByGridBlock { + box-sizing: border-box; + flex: 1 0 25%; + padding: 10px; + } + } + + h2 + .gridBlock { + padding-top: 20px; + } +} + +@media only screen and (min-width: 1400px) { + .gridBlock { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: -10px -20px 10px -20px; + + .twoByGridBlock { + box-sizing: border-box; + flex: 1 0 50%; + padding: 10px 20px; + } + + .fourByGridBlock { + box-sizing: border-box; + flex: 1 0 25%; + padding: 10px 20px; + } + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_header.scss b/thirdparty/rocksdb/docs/_sass/_header.scss new file mode 100644 index 0000000000..b4cd071139 --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_header.scss @@ -0,0 +1,138 @@ +.fixedHeaderContainer { + background: $primary-bg; + color: $primary-overlay; + height: $header-height; + padding: $header-ptop 0 $header-pbot; + position: fixed; + width: 100%; + z-index: 9999; + + a { + align-items: center; + border: 0; + color: $primary-overlay; + display: flex; + flex-flow: row nowrap; + height: $header-height; + } + + header { + display: flex; + flex-flow: row nowrap; + position: relative; + text-align: left; + + img { + height: 24px; + margin-right: 10px; + } + + h2 { + display: block; + font-family: $header-font-family; + font-weight: 900; + line-height: 18px; + position: relative; + } + } +} + +.navigationFull { + height: 34px; + margin-left: auto; + + nav { + position: relative; + + ul { + display: flex; + flex-flow: row nowrap; + margin: 0 -10px; + + li { + padding: 0 10px; + display: block; + + a { + border: 0; + color: $primary-overlay-special; + font-size: 16px; + font-weight: 400; + line-height: 1.2em; + + &:hover { + border-bottom: 2px solid $primary-overlay; + color: $primary-overlay; + } + } + + &.navItemActive { + a { + color: $primary-overlay; + } + } + } + } + } +} + +/* 900px + + + .fixedHeaderContainer { + .navigationWrapper { + nav { + padding: 0 1em; + position: relative; + top: -9px; + + ul { + margin: 0 -0.4em; + li { + display: inline-block; + + a { + padding: 14px 0.4em; + border: 0; + color: $primary-overlay-special; + display: inline-block; + + &:hover { + color: $primary-overlay; + } + } + + &.navItemActive { + a { + color: $primary-overlay; + } + } + } + } + } + + &.navigationFull { + display: inline-block; + } + + &.navigationSlider { + display: none; + } + } + } + + 1200px + + .fixedHeaderContainer { + header { + max-width: 1100px; + } + } + + 1500px + .fixedHeaderContainer { + header { + max-width: 1400px; + } + } + */ \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_poweredby.scss b/thirdparty/rocksdb/docs/_sass/_poweredby.scss new file mode 100644 index 0000000000..4155b60536 --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_poweredby.scss @@ -0,0 +1,69 @@ +.poweredByContainer { + background: $primary-bg; + color: $primary-overlay; + margin-bottom: 20px; + + a { + color: $primary-overlay; + } + + .poweredByWrapper { + h2 { + border-color: $primary-overlay-special; + color: $primary-overlay-special; + } + } + + .poweredByMessage { + color: $primary-overlay-special; + font-size: 14px; + padding-top: 20px; + } +} + +.poweredByItems { + display: flex; + flex-flow: row wrap; + margin: 0 -10px; +} + +.poweredByItem { + box-sizing: border-box; + flex: 1 0 50%; + line-height: 1.1em; + padding: 5px 10px; + + &.itemLarge { + flex-basis: 100%; + padding: 10px; + text-align: center; + + &:nth-child(4) { + padding-bottom: 20px; + } + + img { + max-height: 30px; + } + } +} + +@media only screen and (min-width: 480px) { + .itemLarge { + flex-basis: 50%; + max-width: 50%; + } +} + +@media only screen and (min-width: 1024px) { + .poweredByItem { + flex-basis: 25%; + max-width: 25%; + + &.itemLarge { + padding-bottom: 20px; + text-align: left; + } + } +} + diff --git a/thirdparty/rocksdb/docs/_sass/_promo.scss b/thirdparty/rocksdb/docs/_sass/_promo.scss new file mode 100644 index 0000000000..8c9a809dcb --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_promo.scss @@ -0,0 +1,55 @@ +.promoSection { + display: flex; + flex-flow: column wrap; + font-size: 125%; + line-height: 1.6em; + margin: -10px 0; + position: relative; + z-index: 99; + + .promoRow { + padding: 10px 0; + + .pluginWrapper { + display: block; + + &.ghWatchWrapper, &.ghStarWrapper { + height: 28px; + } + } + + .pluginRowBlock { + display: flex; + flex-flow: row wrap; + margin: 0 -2px; + + .pluginWrapper { + padding: 0 2px; + } + } + } +} + +iframe.pluginIframe { + height: 500px; + margin-top: 20px; + width: 100%; +} + +.iframeContent { + display: none; +} + +.iframePreview { + display: inline-block; + margin-top: 20px; +} + +@media only screen and (min-width: 1024px) { + .iframeContent { + display: block; + } + .iframePreview { + display: none; + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_react_docs_nav.scss b/thirdparty/rocksdb/docs/_sass/_react_docs_nav.scss new file mode 100644 index 0000000000..f0a651e7fa --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_react_docs_nav.scss @@ -0,0 +1,332 @@ +.docsNavContainer { + background: $sidenav; + height: 35px; + left: 0; + position: fixed; + width: 100%; + z-index: 100; +} + +.docMainWrapper { + .wrapper { + &.mainWrapper { + padding-left: 0; + padding-right: 0; + padding-top: 10px; + } + } +} + +.docsSliderActive { + .docsNavContainer { + box-sizing: border-box; + height: 100%; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding-bottom: 50px; + } + + .mainContainer { + display: none; + } +} + +.navBreadcrumb { + box-sizing: border-box; + display: flex; + flex-flow: row nowrap; + font-size: 12px; + height: 35px; + overflow: hidden; + padding: 5px 10px; + + a, span { + border: 0; + color: $sidenav-text; + } + + i { + padding: 0 3px; + } +} + +nav.toc { + position: relative; + + section { + padding: 0px; + position: relative; + + .navGroups { + display: none; + padding: 40px 10px 10px; + } + } + + .toggleNav { + background: $sidenav; + color: $sidenav-text; + position: relative; + transition: background-color 0.3s, color 0.3s; + + .navToggle { + cursor: pointer; + height: 24px; + margin-right: 10px; + position: relative; + text-align: left; + width: 18px; + + &::before, &::after { + content: ""; + position: absolute; + top: 50%; + left: 0; + left: 8px; + width: 3px; + height: 6px; + border: 5px solid $sidenav-text; + border-width: 5px 0; + margin-top: -8px; + transform: rotate(45deg); + z-index: 1; + } + + &::after { + transform: rotate(-45deg); + } + + i { + &::before, &::after { + content: ""; + position: absolute; + top: 50%; + left: 2px; + background: transparent; + border-width: 0 5px 5px; + border-style: solid; + border-color: transparent $sidenav-text; + height: 0; + margin-top: -7px; + opacity: 1; + width: 5px; + z-index: 10; + } + + &::after { + border-width: 5px 5px 0; + margin-top: 2px; + } + } + } + + .navGroup { + background: $sidenav-overlay; + margin: 1px 0; + + ul { + display: none; + } + + h3 { + background: $sidenav-overlay; + color: $sidenav-text; + cursor: pointer; + font-size: 14px; + font-weight: 400; + line-height: 1.2em; + padding: 10px; + transition: color 0.2s; + + i:not(:empty) { + width: 16px; + height: 16px; + display: inline-block; + box-sizing: border-box; + text-align: center; + color: rgba($sidenav-text, 0.5); + margin-right: 10px; + transition: color 0.2s; + } + + &:hover { + color: $primary-bg; + + i:not(:empty) { + color: $primary-bg; + } + } + } + + &.navGroupActive { + background: $sidenav-active; + color: $sidenav-text; + + ul { + display: block; + padding-bottom: 10px; + padding-top: 10px; + } + + h3 { + background: $primary-bg; + color: $primary-overlay; + + i { + display: none; + } + } + } + } + + ul { + padding-left: 0; + padding-right: 24px; + + li { + list-style-type: none; + padding-bottom: 0; + padding-left: 0; + + a { + border: none; + color: $sidenav-text; + display: inline-block; + font-size: 14px; + line-height: 1.1em; + margin: 2px 10px 5px; + padding: 5px 0 2px; + transition: color 0.3s; + + &:hover, + &:focus { + color: $primary-bg; + } + + &.navItemActive { + color: $primary-bg; + font-weight: 900; + } + } + } + } + } + + .toggleNavActive { + .navBreadcrumb { + background: $sidenav; + margin-bottom: 20px; + position: fixed; + width: 100%; + } + + section { + .navGroups { + display: block; + } + } + + + .navToggle { + &::before, &::after { + border-width: 6px 0; + height: 0px; + margin-top: -6px; + } + + i { + opacity: 0; + } + } + } +} + +.docsNavVisible { + .navPusher { + .mainContainer { + padding-top: 35px; + } + } +} + +@media only screen and (min-width: 900px) { + .navBreadcrumb { + padding: 5px 0; + } + + nav.toc { + section { + .navGroups { + padding: 40px 0 0; + } + } + } +} + +@media only screen and (min-width: 1024px) { + .navToggle { + display: none; + } + + .docsSliderActive { + .mainContainer { + display: block; + } + } + + .docsNavVisible { + .navPusher { + .mainContainer { + padding-top: 0; + } + } + } + + .docsNavContainer { + background: none; + box-sizing: border-box; + height: auto; + margin: 40px 40px 0 0; + overflow-y: auto; + position: relative; + width: 300px; + } + + nav.toc { + section { + .navGroups { + display: block; + padding-top: 0px; + } + } + + .toggleNavActive { + .navBreadcrumb { + margin-bottom: 0; + position: relative; + } + } + } + + .docMainWrapper { + display: flex; + flex-flow: row nowrap; + margin-bottom: 40px; + + .wrapper { + padding-left: 0; + padding-right: 0; + + &.mainWrapper { + padding-top: 0; + } + } + } + + .navBreadcrumb { + display: none; + h2 { + padding: 0 10px; + } + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_react_header_nav.scss b/thirdparty/rocksdb/docs/_sass/_react_header_nav.scss new file mode 100644 index 0000000000..13c0e562b7 --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_react_header_nav.scss @@ -0,0 +1,141 @@ +.navigationFull { + display: none; +} + +.navigationSlider { + position: absolute; + right: 0px; + + .navSlideout { + cursor: pointer; + padding-top: 4px; + position: absolute; + right: 10px; + top: 0; + transition: top 0.3s; + z-index: 101; + } + + .slidingNav { + background: $secondary-bg; + box-sizing: border-box; + height: 0px; + overflow-x: hidden; + padding: 0; + position: absolute; + right: 0px; + top: 0; + transition: height 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55), width 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); + width: 0; + + ul { + flex-flow: column nowrap; + list-style: none; + padding: 10px; + + li { + margin: 0; + padding: 2px 0; + + a { + color: $primary-bg; + display: inline; + margin: 3px 5px; + padding: 2px 0px; + transition: background-color 0.3s; + + &:focus, + &:hover { + border-bottom: 2px solid $primary-bg; + } + } + } + } + } + + .navSlideoutActive { + .slidingNav { + height: auto; + padding-top: $header-height + $header-pbot; + width: 300px; + } + + .navSlideout { + top: -2px; + .menuExpand { + span:nth-child(1) { + background-color: $text; + top: 16px; + transform: rotate(45deg); + } + span:nth-child(2) { + opacity: 0; + } + span:nth-child(3) { + background-color: $text; + transform: rotate(-45deg); + } + } + } + } +} + +.menuExpand { + display: flex; + flex-flow: column nowrap; + height: 20px; + justify-content: space-between; + + span { + background: $primary-overlay; + border-radius: 3px; + display: block; + flex: 0 0 4px; + height: 4px; + position: relative; + top: 0; + transition: background-color 0.3s, top 0.3s, opacity 0.3s, transform 0.3s; + width: 20px; + } +} + +.navPusher { + border-top: $header-height + $header-ptop + $header-pbot solid $primary-bg; + position: relative; + left: 0; + z-index: 99; + height: 100%; + + &::after { + position: absolute; + top: 0; + right: 0; + width: 0; + height: 0; + background: rgba(0,0,0,0.4); + content: ''; + opacity: 0; + -webkit-transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s; + transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s; + } + + .sliderActive &::after { + width: 100%; + height: 100%; + opacity: 1; + -webkit-transition: opacity 0.5s; + transition: opacity 0.5s; + z-index: 100; + } +} + + +@media only screen and (min-width: 1024px) { + .navigationFull { + display: block; + } + + .navigationSlider { + display: none; + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_reset.scss b/thirdparty/rocksdb/docs/_sass/_reset.scss new file mode 100644 index 0000000000..0e5f2e0c1d --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_reset.scss @@ -0,0 +1,43 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/thirdparty/rocksdb/docs/_sass/_search.scss b/thirdparty/rocksdb/docs/_sass/_search.scss new file mode 100644 index 0000000000..eadfa11d1e --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_search.scss @@ -0,0 +1,142 @@ +input[type="search"] { + -moz-appearance: none; + -webkit-appearance: none; +} + +.navSearchWrapper { + align-self: center; + position: relative; + + &::before { + border: 3px solid $primary-overlay-special; + border-radius: 50%; + content: " "; + display: block; + height: 6px; + left: 15px; + width: 6px; + position: absolute; + top: 4px; + z-index: 1; + } + + &::after { + background: $primary-overlay-special; + content: " "; + height: 7px; + left: 24px; + position: absolute; + transform: rotate(-45deg); + top: 12px; + width: 3px; + z-index: 1; + } + + .aa-dropdown-menu { + background: $secondary-bg; + border: 3px solid rgba($text, 0.25); + color: $text; + font-size: 14px; + left: auto !important; + line-height: 1.2em; + right: 0 !important; + + .algolia-docsearch-suggestion--category-header { + background: $primary-overlay-special; + color: $primary-bg; + + .algolia-docsearch-suggestion--highlight { + background-color: $primary-bg; + color: $primary-overlay; + } + } + + .algolia-docsearch-suggestion--title .algolia-docsearch-suggestion--highlight, + .algolia-docsearch-suggestion--subcategory-column .algolia-docsearch-suggestion--highlight { + color: $primary-bg; + } + + .algolia-docsearch-suggestion__secondary, + .algolia-docsearch-suggestion--subcategory-column { + border-color: rgba($text, 0.3); + } + } +} + +input#search_input { + padding-left: 25px; + font-size: 14px; + line-height: 20px; + border-radius: 20px; + background-color: rgba($primary-overlay-special, 0.25); + border: none; + color: rgba($primary-overlay-special, 0); + outline: none; + position: relative; + transition: background-color .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), width .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), color .2s ease; + width: 60px; + + &:focus, &:active { + background-color: $secondary-bg; + color: $text; + width: 240px; + } +} + +.navigationSlider { + .navSearchWrapper { + &::before { + left: 6px; + top: 6px; + } + + &::after { + left: 15px; + top: 14px; + } + } + + input#search_input_react { + box-sizing: border-box; + padding-left: 25px; + font-size: 14px; + line-height: 20px; + border-radius: 20px; + background-color: rgba($primary-overlay-special, 0.25); + border: none; + color: $text; + outline: none; + position: relative; + transition: background-color .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), width .2s cubic-bezier(0.68, -0.55, 0.265, 1.55), color .2s ease; + width: 100%; + + &:focus, &:active { + background-color: $primary-bg; + color: $primary-overlay; + } + } + + .algolia-docsearch-suggestion--subcategory-inline { + display: none; + } + + & > span { + width: 100%; + } + + .aa-dropdown-menu { + background: $secondary-bg; + border: 0px solid $secondary-bg; + color: $text; + font-size: 12px; + line-height: 2em; + max-height: 140px; + min-width: auto; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; + padding: 0; + border-radius: 0; + position: relative !important; + width: 100%; + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_slideshow.scss b/thirdparty/rocksdb/docs/_sass/_slideshow.scss new file mode 100644 index 0000000000..cd98a6cdba --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_slideshow.scss @@ -0,0 +1,48 @@ +.slideshow { + position: relative; + + .slide { + display: none; + + img { + display: block; + margin: 0 auto; + } + + &.slideActive { + display: block; + } + + a { + border: none; + display: block; + } + } + + .pagination { + display: block; + margin: -10px; + padding: 1em 0; + text-align: center; + width: 100%; + + .pager { + background: transparent; + border: 2px solid rgba(255, 255, 255, 0.5); + border-radius: 50%; + cursor: pointer; + display: inline-block; + height: 12px; + margin: 10px; + transition: background-color 0.3s, border-color 0.3s; + width: 12px; + + &.pagerActive { + background: rgba(255, 255, 255, 0.5); + border-width: 4px; + height: 8px; + width: 8px; + } + } + } +} diff --git a/thirdparty/rocksdb/docs/_sass/_syntax-highlighting.scss b/thirdparty/rocksdb/docs/_sass/_syntax-highlighting.scss new file mode 100644 index 0000000000..e55c88a2ea --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_syntax-highlighting.scss @@ -0,0 +1,129 @@ + + +.rougeHighlight { background-color: $code-bg; color: #93a1a1 } +.rougeHighlight .c { color: #586e75 } /* Comment */ +.rougeHighlight .err { color: #93a1a1 } /* Error */ +.rougeHighlight .g { color: #93a1a1 } /* Generic */ +.rougeHighlight .k { color: #859900 } /* Keyword */ +.rougeHighlight .l { color: #93a1a1 } /* Literal */ +.rougeHighlight .n { color: #93a1a1 } /* Name */ +.rougeHighlight .o { color: #859900 } /* Operator */ +.rougeHighlight .x { color: #cb4b16 } /* Other */ +.rougeHighlight .p { color: #93a1a1 } /* Punctuation */ +.rougeHighlight .cm { color: #586e75 } /* Comment.Multiline */ +.rougeHighlight .cp { color: #859900 } /* Comment.Preproc */ +.rougeHighlight .c1 { color: #72c02c; } /* Comment.Single */ +.rougeHighlight .cs { color: #859900 } /* Comment.Special */ +.rougeHighlight .gd { color: #2aa198 } /* Generic.Deleted */ +.rougeHighlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ +.rougeHighlight .gr { color: #dc322f } /* Generic.Error */ +.rougeHighlight .gh { color: #cb4b16 } /* Generic.Heading */ +.rougeHighlight .gi { color: #859900 } /* Generic.Inserted */ +.rougeHighlight .go { color: #93a1a1 } /* Generic.Output */ +.rougeHighlight .gp { color: #93a1a1 } /* Generic.Prompt */ +.rougeHighlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ +.rougeHighlight .gu { color: #cb4b16 } /* Generic.Subheading */ +.rougeHighlight .gt { color: #93a1a1 } /* Generic.Traceback */ +.rougeHighlight .kc { color: #cb4b16 } /* Keyword.Constant */ +.rougeHighlight .kd { color: #268bd2 } /* Keyword.Declaration */ +.rougeHighlight .kn { color: #859900 } /* Keyword.Namespace */ +.rougeHighlight .kp { color: #859900 } /* Keyword.Pseudo */ +.rougeHighlight .kr { color: #268bd2 } /* Keyword.Reserved */ +.rougeHighlight .kt { color: #dc322f } /* Keyword.Type */ +.rougeHighlight .ld { color: #93a1a1 } /* Literal.Date */ +.rougeHighlight .m { color: #2aa198 } /* Literal.Number */ +.rougeHighlight .s { color: #2aa198 } /* Literal.String */ +.rougeHighlight .na { color: #93a1a1 } /* Name.Attribute */ +.rougeHighlight .nb { color: #B58900 } /* Name.Builtin */ +.rougeHighlight .nc { color: #268bd2 } /* Name.Class */ +.rougeHighlight .no { color: #cb4b16 } /* Name.Constant */ +.rougeHighlight .nd { color: #268bd2 } /* Name.Decorator */ +.rougeHighlight .ni { color: #cb4b16 } /* Name.Entity */ +.rougeHighlight .ne { color: #cb4b16 } /* Name.Exception */ +.rougeHighlight .nf { color: #268bd2 } /* Name.Function */ +.rougeHighlight .nl { color: #93a1a1 } /* Name.Label */ +.rougeHighlight .nn { color: #93a1a1 } /* Name.Namespace */ +.rougeHighlight .nx { color: #93a1a1 } /* Name.Other */ +.rougeHighlight .py { color: #93a1a1 } /* Name.Property */ +.rougeHighlight .nt { color: #268bd2 } /* Name.Tag */ +.rougeHighlight .nv { color: #268bd2 } /* Name.Variable */ +.rougeHighlight .ow { color: #859900 } /* Operator.Word */ +.rougeHighlight .w { color: #93a1a1 } /* Text.Whitespace */ +.rougeHighlight .mf { color: #2aa198 } /* Literal.Number.Float */ +.rougeHighlight .mh { color: #2aa198 } /* Literal.Number.Hex */ +.rougeHighlight .mi { color: #2aa198 } /* Literal.Number.Integer */ +.rougeHighlight .mo { color: #2aa198 } /* Literal.Number.Oct */ +.rougeHighlight .sb { color: #586e75 } /* Literal.String.Backtick */ +.rougeHighlight .sc { color: #2aa198 } /* Literal.String.Char */ +.rougeHighlight .sd { color: #93a1a1 } /* Literal.String.Doc */ +.rougeHighlight .s2 { color: #2aa198 } /* Literal.String.Double */ +.rougeHighlight .se { color: #cb4b16 } /* Literal.String.Escape */ +.rougeHighlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */ +.rougeHighlight .si { color: #2aa198 } /* Literal.String.Interpol */ +.rougeHighlight .sx { color: #2aa198 } /* Literal.String.Other */ +.rougeHighlight .sr { color: #dc322f } /* Literal.String.Regex */ +.rougeHighlight .s1 { color: #2aa198 } /* Literal.String.Single */ +.rougeHighlight .ss { color: #2aa198 } /* Literal.String.Symbol */ +.rougeHighlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ +.rougeHighlight .vc { color: #268bd2 } /* Name.Variable.Class */ +.rougeHighlight .vg { color: #268bd2 } /* Name.Variable.Global */ +.rougeHighlight .vi { color: #268bd2 } /* Name.Variable.Instance */ +.rougeHighlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + +.highlighter-rouge { + color: darken(#72c02c, 8%); + font: 800 12px/1.5em Hack, monospace; + max-width: 100%; + + .rougeHighlight { + border-radius: 3px; + margin: 20px 0; + padding: 0px; + overflow-x: scroll; + -webkit-overflow-scrolling: touch; + + table { + background: none; + border: none; + + tbody { + tr { + background: none; + display: flex; + flex-flow: row nowrap; + + td { + display: block; + flex: 1 1; + + &.gutter { + border-right: 1px solid lighten($code-bg, 10%); + color: lighten($code-bg, 15%); + margin-right: 10px; + max-width: 40px; + padding-right: 10px; + + pre { + max-width: 20px; + } + } + } + } + } + } + } +} + +p > .highlighter-rouge, +li > .highlighter-rouge, +a > .highlighter-rouge { + font-size: 16px; + font-weight: 400; + line-height: inherit; +} + +a:hover { + .highlighter-rouge { + color: white; + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_sass/_tables.scss b/thirdparty/rocksdb/docs/_sass/_tables.scss new file mode 100644 index 0000000000..f847c70137 --- /dev/null +++ b/thirdparty/rocksdb/docs/_sass/_tables.scss @@ -0,0 +1,47 @@ +table { + background: $lightergrey; + border: 1px solid $lightgrey; + border-collapse: collapse; + display:table; + margin: 20px 0; + + thead { + border-bottom: 1px solid $lightgrey; + display: table-header-group; + } + tbody { + display: table-row-group; + } + tr { + display: table-row; + &:nth-of-type(odd) { + background: $greyish; + } + + th, td { + border-right: 1px dotted $lightgrey; + display: table-cell; + font-size: 14px; + line-height: 1.3em; + padding: 10px; + text-align: left; + + &:last-of-type { + border-right: 0; + } + + code { + color: $green; + display: inline-block; + font-size: 12px; + } + } + + th { + color: #000000; + font-weight: bold; + font-family: $header-font-family; + text-transform: uppercase; + } + } +} \ No newline at end of file diff --git a/thirdparty/rocksdb/docs/_top-level/support.md b/thirdparty/rocksdb/docs/_top-level/support.md new file mode 100644 index 0000000000..64165751fe --- /dev/null +++ b/thirdparty/rocksdb/docs/_top-level/support.md @@ -0,0 +1,22 @@ +--- +layout: top-level +title: Support +id: support +category: support +--- + +## Need help? + +Do not hesitate to ask questions if you are having trouble with RocksDB. + +### GitHub issues + +Use [GitHub issues](https://github.com/facebook/rocksdb/issues) to report bugs, issues and feature requests for the RocksDB codebase. + +### Facebook Group + +Use the [RocksDB Facebook group](https://www.facebook.com/groups/rocksdb.dev/) for general questions and discussion about RocksDB. + +### FAQ + +Check out a list of [commonly asked questions](/docs/support/faq) about RocksDB. diff --git a/thirdparty/rocksdb/docs/blog/all.html b/thirdparty/rocksdb/docs/blog/all.html new file mode 100644 index 0000000000..3be2d3bff2 --- /dev/null +++ b/thirdparty/rocksdb/docs/blog/all.html @@ -0,0 +1,20 @@ +--- +id: all +layout: blog +category: blog +--- + +
+
+

All Posts

+ {% for post in site.posts %} + {% assign author = site.data.authors[post.author] %} +

+ + {{ post.title }} + + on {{ post.date | date: "%B %e, %Y" }} by {{ author.display_name }} +

+ {% endfor %} +
+
diff --git a/thirdparty/rocksdb/docs/blog/index.html b/thirdparty/rocksdb/docs/blog/index.html new file mode 100644 index 0000000000..9f6b25d03c --- /dev/null +++ b/thirdparty/rocksdb/docs/blog/index.html @@ -0,0 +1,12 @@ +--- +id: blog +title: Blog +layout: blog +category: blog +--- + +
+ {% for page in site.posts %} + {% include post.html truncate=true %} + {% endfor %} +
diff --git a/thirdparty/rocksdb/docs/css/main.scss b/thirdparty/rocksdb/docs/css/main.scss new file mode 100644 index 0000000000..48a3e14ef9 --- /dev/null +++ b/thirdparty/rocksdb/docs/css/main.scss @@ -0,0 +1,149 @@ +--- +# Only the main Sass file needs front matter (the dashes are enough) +--- +@charset "utf-8"; + +@font-face { + font-family: 'Lato'; + src: url("{{ '/static/fonts/LatoLatin-Italic.woff2' }}") format('woff2'), + url("{{ '/static/fonts/LatoLatin-Italic.woff' }}") format('woff'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Lato'; + src: url("{{ '/static/fonts/LatoLatin-Black.woff2' }}") format('woff2'), + url("{{ '/static/fonts/LatoLatin-Black.woff' }}") format('woff'); + font-weight: 900; + font-style: normal; +} + +@font-face { + font-family: 'Lato'; + src: url("{{ '/static/fonts/LatoLatin-BlackItalic.woff2' }}") format('woff2'), + url("{{ '/static/fonts/LatoLatin-BlackItalic.woff' }}") format('woff'); + font-weight: 900; + font-style: italic; +} + +@font-face { + font-family: 'Lato'; + src: url("{{ '/static/fonts/LatoLatin-Light.woff2' }}") format('woff2'), + url("{{ '/static/fonts/LatoLatin-Light.woff' }}") format('woff'); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: 'Lato'; + src: url("{{ '/static/fonts/LatoLatin-Regular.woff2' }}") format('woff2'), + url("{{ '/static/fonts/LatoLatin-Regular.woff' }}") format('woff'); + font-weight: normal; + font-style: normal; +} + +// Our variables +$base-font-family: 'Lato', Calibri, Arial, sans-serif; +$header-font-family: 'Lato', 'Helvetica Neue', Arial, sans-serif; +$base-font-size: 18px; +$small-font-size: $base-font-size * 0.875; +$base-line-height: 1.4em; + +$spacing-unit: 12px; + +// Two configured colors (see _config.yml) +$primary-bg: {{ site.color.primary }}; +$secondary-bg: {{ site.color.secondary }}; + +// $primary-bg overlays +{% if site.color.primary-overlay == 'light' %} +$primary-overlay: darken($primary-bg, 70%); +$primary-overlay-special: darken($primary-bg, 40%); +{% else %} +$primary-overlay: #fff; +$primary-overlay-special: lighten($primary-bg, 30%); +{% endif %} + +// $secondary-bg overlays +{% if site.color.secondary-overlay == 'light' %} +$text: #393939; +$sidenav: darken($secondary-bg, 20%); +$sidenav-text: $text; +$sidenav-overlay: darken($sidenav, 10%); +$sidenav-active: lighten($sidenav, 10%); +{% else %} +$text: #fff; +$sidenav: lighten($secondary-bg, 20%); +$sidenav-text: $text; +$sidenav-overlay: lighten($sidenav, 10%); +$sidenav-active: darken($sidenav, 10%); +{% endif %} + +$code-bg: #002b36; + +$header-height: 34px; +$header-ptop: 10px; +$header-pbot: 8px; + +// Width of the content area +$content-width: 900px; + +// Table setting variables +$lightergrey: #F8F8F8; +$greyish: #E8E8E8; +$lightgrey: #B0B0B0; +$green: #2db04b; + +// Using media queries with like this: +// @include media-query($on-palm) { +// .wrapper { +// padding-right: $spacing-unit / 2; +// padding-left: $spacing-unit / 2; +// } +// } +@mixin media-query($device) { + @media screen and (max-width: $device) { + @content; + } +} + + + +// Import partials from `sass_dir` (defaults to `_sass`) +@import + "reset", + "base", + "header", + "search", + "syntax-highlighting", + "promo", + "buttons", + "gridBlock", + "poweredby", + "footer", + "react_header_nav", + "react_docs_nav", + "tables", + "blog" +; + +// Anchor links +// http://ben.balter.com/2014/03/13/pages-anchor-links/ +.header-link { + position: absolute; + margin-left: 0.2em; + opacity: 0; + + -webkit-transition: opacity 0.2s ease-in-out 0.1s; + -moz-transition: opacity 0.2s ease-in-out 0.1s; + -ms-transition: opacity 0.2s ease-in-out 0.1s; +} + +h2:hover .header-link, +h3:hover .header-link, +h4:hover .header-link, +h5:hover .header-link, +h6:hover .header-link { + opacity: 1; +} diff --git a/thirdparty/rocksdb/docs/doc-type-examples/2016-04-07-blog-post-example.md b/thirdparty/rocksdb/docs/doc-type-examples/2016-04-07-blog-post-example.md new file mode 100644 index 0000000000..ef954d63a7 --- /dev/null +++ b/thirdparty/rocksdb/docs/doc-type-examples/2016-04-07-blog-post-example.md @@ -0,0 +1,21 @@ +--- +title: Blog Post Example +layout: post +author: exampleauthor +category: blog +--- + +Any local blog posts would go in the `_posts` directory. + +This is an example blog post introduction, try to keep it short and about a paragraph long, to encourage people to click through to read the entire post. + + + +Everything below the `` tag will only show on the actual blog post page, not on the `/blog/` index. + +Author is defined in `_data/authors.yml` + + +## No posts? + +If you have no blog for your site, you can remove the entire `_posts` folder. Otherwise add markdown files in here. See CONTRIBUTING.md for details. diff --git a/thirdparty/rocksdb/docs/doc-type-examples/docs-hello-world.md b/thirdparty/rocksdb/docs/doc-type-examples/docs-hello-world.md new file mode 100644 index 0000000000..c7094ba5af --- /dev/null +++ b/thirdparty/rocksdb/docs/doc-type-examples/docs-hello-world.md @@ -0,0 +1,12 @@ +--- +docid: hello-world +title: Hello, World! +layout: docs +permalink: /docs/hello-world.html +--- + +Any local docs would go in the `_docs` directory. + +## No documentation? + +If you have no documentation for your site, you can remove the entire `_docs` folder. Otherwise add markdown files in here. See CONTRIBUTING.md for details. diff --git a/thirdparty/rocksdb/docs/doc-type-examples/top-level-example.md b/thirdparty/rocksdb/docs/doc-type-examples/top-level-example.md new file mode 100644 index 0000000000..67b1fa7110 --- /dev/null +++ b/thirdparty/rocksdb/docs/doc-type-examples/top-level-example.md @@ -0,0 +1,8 @@ +--- +layout: top-level +title: Support Example +id: top-level-example +category: top-level +--- + +This is a static page disconnected from the blog or docs collections that can be added at a top-level (i.e., the same level as `index.md`). diff --git a/thirdparty/rocksdb/docs/docs/index.html b/thirdparty/rocksdb/docs/docs/index.html new file mode 100644 index 0000000000..fa6ec8b5a6 --- /dev/null +++ b/thirdparty/rocksdb/docs/docs/index.html @@ -0,0 +1,6 @@ +--- +id: docs +title: Docs +layout: redirect +destination: getting-started.html +--- diff --git a/thirdparty/rocksdb/docs/feed.xml b/thirdparty/rocksdb/docs/feed.xml new file mode 100644 index 0000000000..725f00566c --- /dev/null +++ b/thirdparty/rocksdb/docs/feed.xml @@ -0,0 +1,30 @@ +--- +layout: null +--- + + + + {{ site.title | xml_escape }} + {{ site.description | xml_escape }} + https://rocksdb.org/feed.xml + + {{ site.time | date_to_rfc822 }} + {{ site.time | date_to_rfc822 }} + Jekyll v{{ jekyll.version }} + {% for post in site.posts limit:10 %} + + {{ post.title | xml_escape }} + {{ post.content | xml_escape }} + {{ post.date | date_to_rfc822 }} + {{ post.url | absolute_url }} + {{ post.url | absolute_url }} + {% for tag in post.tags %} + {{ tag | xml_escape }} + {% endfor %} + {% for cat in post.categories %} + {{ cat | xml_escape }} + {% endfor %} + + {% endfor %} + + diff --git a/thirdparty/rocksdb/docs/index.md b/thirdparty/rocksdb/docs/index.md new file mode 100644 index 0000000000..2b9570d230 --- /dev/null +++ b/thirdparty/rocksdb/docs/index.md @@ -0,0 +1,9 @@ +--- +layout: home +title: RocksDB | A persistent key-value store +id: home +--- + +## Features + +{% include content/gridblocks.html data_source=site.data.features align="center" %} diff --git a/thirdparty/rocksdb/docs/static/favicon.png b/thirdparty/rocksdb/docs/static/favicon.png new file mode 100644 index 0000000000..7f668f38f7 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/favicon.png differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Black.woff b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Black.woff new file mode 100644 index 0000000000..d1e2579bf8 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Black.woff differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Black.woff2 b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Black.woff2 new file mode 100644 index 0000000000..4127b4d0b9 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Black.woff2 differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff new file mode 100644 index 0000000000..142c1c9c48 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff2 b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff2 new file mode 100644 index 0000000000..e9862e6909 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-BlackItalic.woff2 differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Italic.woff b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Italic.woff new file mode 100644 index 0000000000..d8cf84c8b9 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Italic.woff differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Italic.woff2 b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Italic.woff2 new file mode 100644 index 0000000000..aaa5a35c3d Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Italic.woff2 differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Light.woff b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Light.woff new file mode 100644 index 0000000000..e7d4278cce Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Light.woff differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Light.woff2 b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Light.woff2 new file mode 100644 index 0000000000..b6d028836e Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Light.woff2 differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Regular.woff b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Regular.woff new file mode 100644 index 0000000000..bf73a6d9f9 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Regular.woff differ diff --git a/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Regular.woff2 b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Regular.woff2 new file mode 100644 index 0000000000..a4d084bfb7 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/fonts/LatoLatin-Regular.woff2 differ diff --git a/thirdparty/rocksdb/docs/static/images/Resize-of-20140327_200754-300x225.jpg b/thirdparty/rocksdb/docs/static/images/Resize-of-20140327_200754-300x225.jpg new file mode 100644 index 0000000000..9f93151019 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/Resize-of-20140327_200754-300x225.jpg differ diff --git a/thirdparty/rocksdb/docs/static/images/binaryseek.png b/thirdparty/rocksdb/docs/static/images/binaryseek.png new file mode 100644 index 0000000000..0e213f0482 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/binaryseek.png differ diff --git a/thirdparty/rocksdb/docs/static/images/compaction/full-range.png b/thirdparty/rocksdb/docs/static/images/compaction/full-range.png new file mode 100644 index 0000000000..5b2c9fc61e Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/compaction/full-range.png differ diff --git a/thirdparty/rocksdb/docs/static/images/compaction/l0-l1-contend.png b/thirdparty/rocksdb/docs/static/images/compaction/l0-l1-contend.png new file mode 100644 index 0000000000..bcf8ec73a7 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/compaction/l0-l1-contend.png differ diff --git a/thirdparty/rocksdb/docs/static/images/compaction/l1-l2-contend.png b/thirdparty/rocksdb/docs/static/images/compaction/l1-l2-contend.png new file mode 100644 index 0000000000..6dafbbbf29 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/compaction/l1-l2-contend.png differ diff --git a/thirdparty/rocksdb/docs/static/images/compaction/part-range-old.png b/thirdparty/rocksdb/docs/static/images/compaction/part-range-old.png new file mode 100644 index 0000000000..1cc723d13b Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/compaction/part-range-old.png differ diff --git a/thirdparty/rocksdb/docs/static/images/data-block-hash-index/block-format-binary-seek.png b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/block-format-binary-seek.png new file mode 100644 index 0000000000..0e213f0482 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/block-format-binary-seek.png differ diff --git a/thirdparty/rocksdb/docs/static/images/data-block-hash-index/block-format-hash-index.png b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/block-format-hash-index.png new file mode 100644 index 0000000000..accb8639e8 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/block-format-hash-index.png differ diff --git a/thirdparty/rocksdb/docs/static/images/data-block-hash-index/hash-index-data-structure.png b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/hash-index-data-structure.png new file mode 100644 index 0000000000..9acc71d8e5 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/hash-index-data-structure.png differ diff --git a/thirdparty/rocksdb/docs/static/images/data-block-hash-index/perf-cache-miss.png b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/perf-cache-miss.png new file mode 100644 index 0000000000..71788735d0 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/perf-cache-miss.png differ diff --git a/thirdparty/rocksdb/docs/static/images/data-block-hash-index/perf-throughput.png b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/perf-throughput.png new file mode 100644 index 0000000000..54948af2f8 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/data-block-hash-index/perf-throughput.png differ diff --git a/thirdparty/rocksdb/docs/static/images/delrange/delrange_collapsed.png b/thirdparty/rocksdb/docs/static/images/delrange/delrange_collapsed.png new file mode 100644 index 0000000000..52246c2c1d Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/delrange/delrange_collapsed.png differ diff --git a/thirdparty/rocksdb/docs/static/images/delrange/delrange_key_schema.png b/thirdparty/rocksdb/docs/static/images/delrange/delrange_key_schema.png new file mode 100644 index 0000000000..0a14d4a3a5 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/delrange/delrange_key_schema.png differ diff --git a/thirdparty/rocksdb/docs/static/images/delrange/delrange_sst_blocks.png b/thirdparty/rocksdb/docs/static/images/delrange/delrange_sst_blocks.png new file mode 100644 index 0000000000..6003e42ae8 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/delrange/delrange_sst_blocks.png differ diff --git a/thirdparty/rocksdb/docs/static/images/delrange/delrange_uncollapsed.png b/thirdparty/rocksdb/docs/static/images/delrange/delrange_uncollapsed.png new file mode 100644 index 0000000000..39c7097af9 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/delrange/delrange_uncollapsed.png differ diff --git a/thirdparty/rocksdb/docs/static/images/delrange/delrange_write_path.png b/thirdparty/rocksdb/docs/static/images/delrange/delrange_write_path.png new file mode 100644 index 0000000000..229dfb349a Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/delrange/delrange_write_path.png differ diff --git a/thirdparty/rocksdb/docs/static/images/pcache-blockindex.jpg b/thirdparty/rocksdb/docs/static/images/pcache-blockindex.jpg new file mode 100644 index 0000000000..9c18bde93a Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/pcache-blockindex.jpg differ diff --git a/thirdparty/rocksdb/docs/static/images/pcache-fileindex.jpg b/thirdparty/rocksdb/docs/static/images/pcache-fileindex.jpg new file mode 100644 index 0000000000..51f4e095ce Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/pcache-fileindex.jpg differ diff --git a/thirdparty/rocksdb/docs/static/images/pcache-filelayout.jpg b/thirdparty/rocksdb/docs/static/images/pcache-filelayout.jpg new file mode 100644 index 0000000000..771ee60c15 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/pcache-filelayout.jpg differ diff --git a/thirdparty/rocksdb/docs/static/images/pcache-readiopath.jpg b/thirdparty/rocksdb/docs/static/images/pcache-readiopath.jpg new file mode 100644 index 0000000000..4993f0072a Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/pcache-readiopath.jpg differ diff --git a/thirdparty/rocksdb/docs/static/images/pcache-tieredstorage.jpg b/thirdparty/rocksdb/docs/static/images/pcache-tieredstorage.jpg new file mode 100644 index 0000000000..c362a2d693 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/pcache-tieredstorage.jpg differ diff --git a/thirdparty/rocksdb/docs/static/images/pcache-writeiopath.jpg b/thirdparty/rocksdb/docs/static/images/pcache-writeiopath.jpg new file mode 100644 index 0000000000..561b551811 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/pcache-writeiopath.jpg differ diff --git a/thirdparty/rocksdb/docs/static/images/promo-adapt.svg b/thirdparty/rocksdb/docs/static/images/promo-adapt.svg new file mode 100644 index 0000000000..7cd44434db --- /dev/null +++ b/thirdparty/rocksdb/docs/static/images/promo-adapt.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/thirdparty/rocksdb/docs/static/images/promo-flash.svg b/thirdparty/rocksdb/docs/static/images/promo-flash.svg new file mode 100644 index 0000000000..79810c30a9 --- /dev/null +++ b/thirdparty/rocksdb/docs/static/images/promo-flash.svg @@ -0,0 +1,28 @@ + + + + + + + + + + +]> + + + + + + + + + + + diff --git a/thirdparty/rocksdb/docs/static/images/promo-operations.svg b/thirdparty/rocksdb/docs/static/images/promo-operations.svg new file mode 100644 index 0000000000..3036294ab9 --- /dev/null +++ b/thirdparty/rocksdb/docs/static/images/promo-operations.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/thirdparty/rocksdb/docs/static/images/promo-performance.svg b/thirdparty/rocksdb/docs/static/images/promo-performance.svg new file mode 100644 index 0000000000..be8a101203 --- /dev/null +++ b/thirdparty/rocksdb/docs/static/images/promo-performance.svg @@ -0,0 +1,134 @@ + + + + + + + + + + +netalloy chequered flag + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/rocksdb/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png b/thirdparty/rocksdb/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png new file mode 100644 index 0000000000..b4b24849cd Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/rate-limiter/auto-tuned-write-KBps-series.png differ diff --git a/thirdparty/rocksdb/docs/static/images/rate-limiter/write-KBps-cdf.png b/thirdparty/rocksdb/docs/static/images/rate-limiter/write-KBps-cdf.png new file mode 100644 index 0000000000..742f985bf0 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/rate-limiter/write-KBps-cdf.png differ diff --git a/thirdparty/rocksdb/docs/static/images/rate-limiter/write-KBps-series.png b/thirdparty/rocksdb/docs/static/images/rate-limiter/write-KBps-series.png new file mode 100644 index 0000000000..c7bdcb95aa Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/rate-limiter/write-KBps-series.png differ diff --git a/thirdparty/rocksdb/docs/static/images/tree_example1.png b/thirdparty/rocksdb/docs/static/images/tree_example1.png new file mode 100644 index 0000000000..9f725860c6 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/images/tree_example1.png differ diff --git a/thirdparty/rocksdb/docs/static/logo.svg b/thirdparty/rocksdb/docs/static/logo.svg new file mode 100644 index 0000000000..e6e1e8afa0 --- /dev/null +++ b/thirdparty/rocksdb/docs/static/logo.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + diff --git a/thirdparty/rocksdb/docs/static/og_image.png b/thirdparty/rocksdb/docs/static/og_image.png new file mode 100644 index 0000000000..4e2759e617 Binary files /dev/null and b/thirdparty/rocksdb/docs/static/og_image.png differ diff --git a/thirdparty/rocksdb/env/env.cc b/thirdparty/rocksdb/env/env.cc index ae0b111be8..fde03577d2 100644 --- a/thirdparty/rocksdb/env/env.cc +++ b/thirdparty/rocksdb/env/env.cc @@ -22,6 +22,22 @@ namespace rocksdb { Env::~Env() { } +std::string Env::PriorityToString(Env::Priority priority) { + switch (priority) { + case Env::Priority::BOTTOM: + return "Bottom"; + case Env::Priority::LOW: + return "Low"; + case Env::Priority::HIGH: + return "High"; + case Env::Priority::USER: + return "User"; + case Env::Priority::TOTAL: + assert(false); + } + return "Invalid"; +} + uint64_t Env::GetThreadID() const { std::hash hasher; return hasher(std::this_thread::get_id()); @@ -29,7 +45,7 @@ uint64_t Env::GetThreadID() const { Status Env::ReuseWritableFile(const std::string& fname, const std::string& old_fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) { Status s = RenameFile(old_fname, fname); if (!s.ok()) { @@ -73,9 +89,21 @@ RandomAccessFile::~RandomAccessFile() { WritableFile::~WritableFile() { } -Logger::~Logger() { +MemoryMappedFileBuffer::~MemoryMappedFileBuffer() {} + +Logger::~Logger() {} + +Status Logger::Close() { + if (!closed_) { + closed_ = true; + return CloseImpl(); + } else { + return Status::OK(); + } } +Status Logger::CloseImpl() { return Status::NotSupported(); } + FileLock::~FileLock() { } @@ -85,15 +113,19 @@ void LogFlush(Logger *info_log) { } } -void Log(Logger* info_log, const char* format, ...) { +static void Logv(Logger *info_log, const char* format, va_list ap) { if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::INFO_LEVEL) { - va_list ap; - va_start(ap, format); info_log->Logv(InfoLogLevel::INFO_LEVEL, format, ap); - va_end(ap); } } +void Log(Logger* info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Logv(info_log, format, ap); + va_end(ap); +} + void Logger::Logv(const InfoLogLevel log_level, const char* format, va_list ap) { static const char* kInfoLogLevelNames[5] = { "DEBUG", "INFO", "WARN", "ERROR", "FATAL" }; @@ -108,6 +140,8 @@ void Logger::Logv(const InfoLogLevel log_level, const char* format, va_list ap) // are INFO level. We don't want to add extra costs to those existing // logging. Logv(format, ap); + } else if (log_level == InfoLogLevel::HEADER_LEVEL) { + LogHeader(format, ap); } else { char new_format[500]; snprintf(new_format, sizeof(new_format) - 1, "[%s] %s", @@ -116,157 +150,166 @@ void Logger::Logv(const InfoLogLevel log_level, const char* format, va_list ap) } } - -void Log(const InfoLogLevel log_level, Logger* info_log, const char* format, - ...) { +static void Logv(const InfoLogLevel log_level, Logger *info_log, const char *format, va_list ap) { if (info_log && info_log->GetInfoLogLevel() <= log_level) { - va_list ap; - va_start(ap, format); - if (log_level == InfoLogLevel::HEADER_LEVEL) { info_log->LogHeader(format, ap); } else { info_log->Logv(log_level, format, ap); } - - va_end(ap); } } -void Header(Logger* info_log, const char* format, ...) { +void Log(const InfoLogLevel log_level, Logger* info_log, const char* format, + ...) { + va_list ap; + va_start(ap, format); + Logv(log_level, info_log, format, ap); + va_end(ap); +} + +static void Headerv(Logger *info_log, const char *format, va_list ap) { if (info_log) { - va_list ap; - va_start(ap, format); info_log->LogHeader(format, ap); - va_end(ap); } } -void Debug(Logger* info_log, const char* format, ...) { +void Header(Logger* info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Headerv(info_log, format, ap); + va_end(ap); +} + +static void Debugv(Logger* info_log, const char* format, va_list ap) { if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::DEBUG_LEVEL) { - va_list ap; - va_start(ap, format); info_log->Logv(InfoLogLevel::DEBUG_LEVEL, format, ap); - va_end(ap); } } -void Info(Logger* info_log, const char* format, ...) { +void Debug(Logger* info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Debugv(info_log, format, ap); + va_end(ap); +} + +static void Infov(Logger* info_log, const char* format, va_list ap) { if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::INFO_LEVEL) { - va_list ap; - va_start(ap, format); info_log->Logv(InfoLogLevel::INFO_LEVEL, format, ap); - va_end(ap); } } -void Warn(Logger* info_log, const char* format, ...) { +void Info(Logger* info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Infov(info_log, format, ap); + va_end(ap); +} + +static void Warnv(Logger* info_log, const char* format, va_list ap) { if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::WARN_LEVEL) { - va_list ap; - va_start(ap, format); info_log->Logv(InfoLogLevel::WARN_LEVEL, format, ap); - va_end(ap); } } -void Error(Logger* info_log, const char* format, ...) { + +void Warn(Logger* info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Warnv(info_log, format, ap); + va_end(ap); +} + +static void Errorv(Logger* info_log, const char* format, va_list ap) { if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::ERROR_LEVEL) { - va_list ap; - va_start(ap, format); info_log->Logv(InfoLogLevel::ERROR_LEVEL, format, ap); - va_end(ap); } } -void Fatal(Logger* info_log, const char* format, ...) { + +void Error(Logger* info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Errorv(info_log, format, ap); + va_end(ap); +} + +static void Fatalv(Logger* info_log, const char* format, va_list ap) { if (info_log && info_log->GetInfoLogLevel() <= InfoLogLevel::FATAL_LEVEL) { - va_list ap; - va_start(ap, format); info_log->Logv(InfoLogLevel::FATAL_LEVEL, format, ap); - va_end(ap); } } -void LogFlush(const shared_ptr& info_log) { - if (info_log) { - info_log->Flush(); - } +void Fatal(Logger* info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Fatalv(info_log, format, ap); + va_end(ap); +} + +void LogFlush(const std::shared_ptr& info_log) { + LogFlush(info_log.get()); } -void Log(const InfoLogLevel log_level, const shared_ptr& info_log, +void Log(const InfoLogLevel log_level, const std::shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->Logv(log_level, format, ap); - va_end(ap); - } + va_list ap; + va_start(ap, format); + Logv(log_level, info_log.get(), format, ap); + va_end(ap); } -void Header(const shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->LogHeader(format, ap); - va_end(ap); - } +void Header(const std::shared_ptr& info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Headerv(info_log.get(), format, ap); + va_end(ap); } -void Debug(const shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->Logv(InfoLogLevel::DEBUG_LEVEL, format, ap); - va_end(ap); - } +void Debug(const std::shared_ptr& info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Debugv(info_log.get(), format, ap); + va_end(ap); } -void Info(const shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->Logv(InfoLogLevel::INFO_LEVEL, format, ap); - va_end(ap); - } +void Info(const std::shared_ptr& info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Infov(info_log.get(), format, ap); + va_end(ap); } -void Warn(const shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->Logv(InfoLogLevel::WARN_LEVEL, format, ap); - va_end(ap); - } +void Warn(const std::shared_ptr& info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Warnv(info_log.get(), format, ap); + va_end(ap); } -void Error(const shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->Logv(InfoLogLevel::ERROR_LEVEL, format, ap); - va_end(ap); - } +void Error(const std::shared_ptr& info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Errorv(info_log.get(), format, ap); + va_end(ap); } -void Fatal(const shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->Logv(InfoLogLevel::FATAL_LEVEL, format, ap); - va_end(ap); - } +void Fatal(const std::shared_ptr& info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Fatalv(info_log.get(), format, ap); + va_end(ap); } -void Log(const shared_ptr& info_log, const char* format, ...) { - if (info_log) { - va_list ap; - va_start(ap, format); - info_log->Logv(InfoLogLevel::INFO_LEVEL, format, ap); - va_end(ap); - } +void Log(const std::shared_ptr& info_log, const char* format, ...) { + va_list ap; + va_start(ap, format); + Logv(info_log.get(), format, ap); + va_end(ap); } Status WriteStringToFile(Env* env, const Slice& data, const std::string& fname, bool should_sync) { - unique_ptr file; + std::unique_ptr file; EnvOptions soptions; Status s = env->NewWritableFile(fname, &file, soptions); if (!s.ok()) { @@ -285,7 +328,7 @@ Status WriteStringToFile(Env* env, const Slice& data, const std::string& fname, Status ReadFileToString(Env* env, const std::string& fname, std::string* data) { EnvOptions soptions; data->clear(); - unique_ptr file; + std::unique_ptr file; Status s = env->NewSequentialFile(fname, &file, soptions); if (!s.ok()) { return s; @@ -333,6 +376,8 @@ EnvOptions Env::OptimizeForLogWrite(const EnvOptions& env_options, const DBOptions& db_options) const { EnvOptions optimized_env_options(env_options); optimized_env_options.bytes_per_sync = db_options.wal_bytes_per_sync; + optimized_env_options.writable_file_max_buffer_size = + db_options.writable_file_max_buffer_size; return optimized_env_options; } @@ -363,8 +408,7 @@ EnvOptions Env::OptimizeForCompactionTableWrite( EnvOptions Env::OptimizeForCompactionTableRead( const EnvOptions& env_options, const ImmutableDBOptions& db_options) const { EnvOptions optimized_env_options(env_options); - optimized_env_options.use_direct_reads = - db_options.use_direct_io_for_flush_and_compaction; + optimized_env_options.use_direct_reads = db_options.use_direct_reads; return optimized_env_options; } diff --git a/thirdparty/rocksdb/env/env_basic_test.cc b/thirdparty/rocksdb/env/env_basic_test.cc index 254c71fadc..3efae758a2 100644 --- a/thirdparty/rocksdb/env/env_basic_test.cc +++ b/thirdparty/rocksdb/env/env_basic_test.cc @@ -21,8 +21,8 @@ class NormalizingEnvWrapper : public EnvWrapper { explicit NormalizingEnvWrapper(Env* base) : EnvWrapper(base) {} // Removes . and .. from directory listing - virtual Status GetChildren(const std::string& dir, - std::vector* result) override { + Status GetChildren(const std::string& dir, + std::vector* result) override { Status status = EnvWrapper::GetChildren(dir, result); if (status.ok()) { result->erase(std::remove_if(result->begin(), result->end(), @@ -35,7 +35,7 @@ class NormalizingEnvWrapper : public EnvWrapper { } // Removes . and .. from directory listing - virtual Status GetChildrenFileAttributes( + Status GetChildrenFileAttributes( const std::string& dir, std::vector* result) override { Status status = EnvWrapper::GetChildrenFileAttributes(dir, result); if (status.ok()) { @@ -57,14 +57,12 @@ class EnvBasicTestWithParam : public testing::Test, std::string test_dir_; EnvBasicTestWithParam() : env_(GetParam()) { - test_dir_ = test::TmpDir(env_) + "/env_basic_test"; + test_dir_ = test::PerThreadDBPath(env_, "env_basic_test"); } - void SetUp() { - env_->CreateDirIfMissing(test_dir_); - } + void SetUp() override { env_->CreateDirIfMissing(test_dir_); } - void TearDown() { + void TearDown() override { std::vector files; env_->GetChildren(test_dir_, &files); for (const auto& file : files) { @@ -133,7 +131,7 @@ INSTANTIATE_TEST_CASE_P(CustomEnv, EnvMoreTestWithParam, TEST_P(EnvBasicTestWithParam, Basics) { uint64_t file_size; - unique_ptr writable_file; + std::unique_ptr writable_file; std::vector children; // Check that the directory is empty. @@ -186,8 +184,8 @@ TEST_P(EnvBasicTestWithParam, Basics) { ASSERT_EQ(0U, file_size); // Check that opening non-existent file fails. - unique_ptr seq_file; - unique_ptr rand_file; + std::unique_ptr seq_file; + std::unique_ptr rand_file; ASSERT_TRUE(!env_->NewSequentialFile(test_dir_ + "/non_existent", &seq_file, soptions_) .ok()); @@ -208,9 +206,9 @@ TEST_P(EnvBasicTestWithParam, Basics) { } TEST_P(EnvBasicTestWithParam, ReadWrite) { - unique_ptr writable_file; - unique_ptr seq_file; - unique_ptr rand_file; + std::unique_ptr writable_file; + std::unique_ptr seq_file; + std::unique_ptr rand_file; Slice result; char scratch[100]; @@ -247,7 +245,7 @@ TEST_P(EnvBasicTestWithParam, ReadWrite) { } TEST_P(EnvBasicTestWithParam, Misc) { - unique_ptr writable_file; + std::unique_ptr writable_file; ASSERT_OK(env_->NewWritableFile(test_dir_ + "/b", &writable_file, soptions_)); // These are no-ops, but we test they return success. @@ -266,14 +264,14 @@ TEST_P(EnvBasicTestWithParam, LargeWrite) { write_data.append(1, static_cast(i)); } - unique_ptr writable_file; + std::unique_ptr writable_file; ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_)); ASSERT_OK(writable_file->Append("foo")); ASSERT_OK(writable_file->Append(write_data)); ASSERT_OK(writable_file->Close()); writable_file.reset(); - unique_ptr seq_file; + std::unique_ptr seq_file; Slice result; ASSERT_OK(env_->NewSequentialFile(test_dir_ + "/f", &seq_file, soptions_)); ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo". @@ -340,7 +338,7 @@ TEST_P(EnvMoreTestWithParam, GetChildren) { // if dir is a file, returns IOError ASSERT_OK(env_->CreateDir(test_dir_)); - unique_ptr writable_file; + std::unique_ptr writable_file; ASSERT_OK( env_->NewWritableFile(test_dir_ + "/file", &writable_file, soptions_)); ASSERT_OK(writable_file->Close()); diff --git a/thirdparty/rocksdb/env/env_chroot.cc b/thirdparty/rocksdb/env/env_chroot.cc index 6a1fda8a83..8a7fb44999 100644 --- a/thirdparty/rocksdb/env/env_chroot.cc +++ b/thirdparty/rocksdb/env/env_chroot.cc @@ -38,9 +38,9 @@ class ChrootEnv : public EnvWrapper { #endif } - virtual Status NewSequentialFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) override { + Status NewSequentialFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -49,9 +49,9 @@ class ChrootEnv : public EnvWrapper { options); } - virtual Status NewRandomAccessFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewRandomAccessFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -60,9 +60,9 @@ class ChrootEnv : public EnvWrapper { options); } - virtual Status NewWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -71,10 +71,10 @@ class ChrootEnv : public EnvWrapper { options); } - virtual Status ReuseWritableFile(const std::string& fname, - const std::string& old_fname, - unique_ptr* result, - const EnvOptions& options) override { + Status ReuseWritableFile(const std::string& fname, + const std::string& old_fname, + std::unique_ptr* result, + const EnvOptions& options) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -88,9 +88,9 @@ class ChrootEnv : public EnvWrapper { options); } - virtual Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewRandomRWFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -99,8 +99,8 @@ class ChrootEnv : public EnvWrapper { options); } - virtual Status NewDirectory(const std::string& dir, - unique_ptr* result) override { + Status NewDirectory(const std::string& dir, + std::unique_ptr* result) override { auto status_and_enc_path = EncodePathWithNewBasename(dir); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -108,7 +108,7 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::NewDirectory(status_and_enc_path.second, result); } - virtual Status FileExists(const std::string& fname) override { + Status FileExists(const std::string& fname) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -116,8 +116,8 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::FileExists(status_and_enc_path.second); } - virtual Status GetChildren(const std::string& dir, - std::vector* result) override { + Status GetChildren(const std::string& dir, + std::vector* result) override { auto status_and_enc_path = EncodePath(dir); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -125,7 +125,7 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::GetChildren(status_and_enc_path.second, result); } - virtual Status GetChildrenFileAttributes( + Status GetChildrenFileAttributes( const std::string& dir, std::vector* result) override { auto status_and_enc_path = EncodePath(dir); if (!status_and_enc_path.first.ok()) { @@ -135,7 +135,7 @@ class ChrootEnv : public EnvWrapper { result); } - virtual Status DeleteFile(const std::string& fname) override { + Status DeleteFile(const std::string& fname) override { auto status_and_enc_path = EncodePath(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -143,7 +143,7 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::DeleteFile(status_and_enc_path.second); } - virtual Status CreateDir(const std::string& dirname) override { + Status CreateDir(const std::string& dirname) override { auto status_and_enc_path = EncodePathWithNewBasename(dirname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -151,7 +151,7 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::CreateDir(status_and_enc_path.second); } - virtual Status CreateDirIfMissing(const std::string& dirname) override { + Status CreateDirIfMissing(const std::string& dirname) override { auto status_and_enc_path = EncodePathWithNewBasename(dirname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -159,7 +159,7 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::CreateDirIfMissing(status_and_enc_path.second); } - virtual Status DeleteDir(const std::string& dirname) override { + Status DeleteDir(const std::string& dirname) override { auto status_and_enc_path = EncodePath(dirname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -167,8 +167,7 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::DeleteDir(status_and_enc_path.second); } - virtual Status GetFileSize(const std::string& fname, - uint64_t* file_size) override { + Status GetFileSize(const std::string& fname, uint64_t* file_size) override { auto status_and_enc_path = EncodePath(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -176,8 +175,8 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::GetFileSize(status_and_enc_path.second, file_size); } - virtual Status GetFileModificationTime(const std::string& fname, - uint64_t* file_mtime) override { + Status GetFileModificationTime(const std::string& fname, + uint64_t* file_mtime) override { auto status_and_enc_path = EncodePath(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -186,8 +185,7 @@ class ChrootEnv : public EnvWrapper { file_mtime); } - virtual Status RenameFile(const std::string& src, - const std::string& dest) override { + Status RenameFile(const std::string& src, const std::string& dest) override { auto status_and_src_enc_path = EncodePath(src); if (!status_and_src_enc_path.first.ok()) { return status_and_src_enc_path.first; @@ -200,8 +198,7 @@ class ChrootEnv : public EnvWrapper { status_and_dest_enc_path.second); } - virtual Status LinkFile(const std::string& src, - const std::string& dest) override { + Status LinkFile(const std::string& src, const std::string& dest) override { auto status_and_src_enc_path = EncodePath(src); if (!status_and_src_enc_path.first.ok()) { return status_and_src_enc_path.first; @@ -214,7 +211,7 @@ class ChrootEnv : public EnvWrapper { status_and_dest_enc_path.second); } - virtual Status LockFile(const std::string& fname, FileLock** lock) override { + Status LockFile(const std::string& fname, FileLock** lock) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -225,7 +222,7 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::LockFile(status_and_enc_path.second, lock); } - virtual Status GetTestDirectory(std::string* path) override { + Status GetTestDirectory(std::string* path) override { // Adapted from PosixEnv's implementation since it doesn't provide a way to // create directory in the chroot. char buf[256]; @@ -237,8 +234,8 @@ class ChrootEnv : public EnvWrapper { return Status::OK(); } - virtual Status NewLogger(const std::string& fname, - shared_ptr* result) override { + Status NewLogger(const std::string& fname, + std::shared_ptr* result) override { auto status_and_enc_path = EncodePathWithNewBasename(fname); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; @@ -246,8 +243,8 @@ class ChrootEnv : public EnvWrapper { return EnvWrapper::NewLogger(status_and_enc_path.second, result); } - virtual Status GetAbsolutePath(const std::string& db_path, - std::string* output_path) override { + Status GetAbsolutePath(const std::string& db_path, + std::string* output_path) override { auto status_and_enc_path = EncodePath(db_path); if (!status_and_enc_path.first.ok()) { return status_and_enc_path.first; diff --git a/thirdparty/rocksdb/env/env_encryption.cc b/thirdparty/rocksdb/env/env_encryption.cc index 6b688a6602..aa59e66357 100644 --- a/thirdparty/rocksdb/env/env_encryption.cc +++ b/thirdparty/rocksdb/env/env_encryption.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include "rocksdb/env_encryption.h" #include "util/aligned_buffer.h" @@ -42,7 +43,7 @@ class EncryptedSequentialFile : public SequentialFile { // If an error was encountered, returns a non-OK status. // // REQUIRES: External synchronization - virtual Status Read(size_t n, Slice* result, char* scratch) override { + Status Read(size_t n, Slice* result, char* scratch) override { assert(scratch); Status status = file_->Read(n, result, scratch); if (!status.ok()) { @@ -60,7 +61,7 @@ class EncryptedSequentialFile : public SequentialFile { // file, and Skip will return OK. // // REQUIRES: External synchronization - virtual Status Skip(uint64_t n) override { + Status Skip(uint64_t n) override { auto status = file_->Skip(n); if (!status.ok()) { return status; @@ -71,26 +72,25 @@ class EncryptedSequentialFile : public SequentialFile { // Indicates the upper layers if the current SequentialFile implementation // uses direct IO. - virtual bool use_direct_io() const override { - return file_->use_direct_io(); - } + bool use_direct_io() const override { return file_->use_direct_io(); } // Use the returned alignment value to allocate // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { - return file_->GetRequiredBufferAlignment(); + size_t GetRequiredBufferAlignment() const override { + return file_->GetRequiredBufferAlignment(); } // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. - virtual Status InvalidateCache(size_t offset, size_t length) override { + Status InvalidateCache(size_t offset, size_t length) override { return file_->InvalidateCache(offset + prefixLength_, length); } // Positioned Read for direct I/O // If Direct I/O enabled, offset, n, and scratch should be properly aligned - virtual Status PositionedRead(uint64_t offset, size_t n, Slice* result, char* scratch) override { + Status PositionedRead(uint64_t offset, size_t n, Slice* result, + char* scratch) override { assert(scratch); offset += prefixLength_; // Skip prefix auto status = file_->PositionedRead(offset, n, result, scratch); @@ -101,7 +101,6 @@ class EncryptedSequentialFile : public SequentialFile { status = stream_->Decrypt(offset, (char*)result->data(), result->size()); return status; } - }; // A file abstraction for randomly reading the contents of a file. @@ -125,7 +124,8 @@ class EncryptedRandomAccessFile : public RandomAccessFile { // // Safe for concurrent use by multiple threads. // If Direct I/O enabled, offset, n, and scratch should be aligned properly. - virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override { + Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override { assert(scratch); offset += prefixLength_; auto status = file_->Read(offset, n, result, scratch); @@ -137,7 +137,7 @@ class EncryptedRandomAccessFile : public RandomAccessFile { } // Readahead the file starting from offset by n bytes for caching. - virtual Status Prefetch(uint64_t offset, size_t n) override { + Status Prefetch(uint64_t offset, size_t n) override { //return Status::OK(); return file_->Prefetch(offset + prefixLength_, n); } @@ -150,37 +150,33 @@ class EncryptedRandomAccessFile : public RandomAccessFile { // may not have been modified. // // This function guarantees, for IDs from a given environment, two unique ids - // cannot be made equal to eachother by adding arbitrary bytes to one of + // cannot be made equal to each other by adding arbitrary bytes to one of // them. That is, no unique ID is the prefix of another. // // This function guarantees that the returned ID will not be interpretable as // a single varint. // // Note: these IDs are only valid for the duration of the process. - virtual size_t GetUniqueId(char* id, size_t max_size) const override { + size_t GetUniqueId(char* id, size_t max_size) const override { return file_->GetUniqueId(id, max_size); }; - virtual void Hint(AccessPattern pattern) override { - file_->Hint(pattern); - } + void Hint(AccessPattern pattern) override { file_->Hint(pattern); } // Indicates the upper layers if the current RandomAccessFile implementation // uses direct IO. - virtual bool use_direct_io() const override { - return file_->use_direct_io(); - } + bool use_direct_io() const override { return file_->use_direct_io(); } // Use the returned alignment value to allocate // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { - return file_->GetRequiredBufferAlignment(); + size_t GetRequiredBufferAlignment() const override { + return file_->GetRequiredBufferAlignment(); } // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. - virtual Status InvalidateCache(size_t offset, size_t length) override { + Status InvalidateCache(size_t offset, size_t length) override { return file_->InvalidateCache(offset + prefixLength_, length); } }; @@ -247,16 +243,18 @@ class EncryptedWritableFile : public WritableFileWrapper { // Indicates the upper layers if the current WritableFile implementation // uses direct IO. - virtual bool use_direct_io() const override { return file_->use_direct_io(); } + bool use_direct_io() const override { return file_->use_direct_io(); } // Use the returned alignment value to allocate // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { return file_->GetRequiredBufferAlignment(); } + size_t GetRequiredBufferAlignment() const override { + return file_->GetRequiredBufferAlignment(); + } /* * Get the size of valid data in the file. */ - virtual uint64_t GetFileSize() override { + uint64_t GetFileSize() override { return file_->GetFileSize() - prefixLength_; } @@ -264,7 +262,7 @@ class EncryptedWritableFile : public WritableFileWrapper { // before closing. It is not always possible to keep track of the file // size due to whole pages writes. The behavior is undefined if called // with other writes to follow. - virtual Status Truncate(uint64_t size) override { + Status Truncate(uint64_t size) override { return file_->Truncate(size + prefixLength_); } @@ -272,7 +270,7 @@ class EncryptedWritableFile : public WritableFileWrapper { // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. // This call has no effect on dirty pages in the cache. - virtual Status InvalidateCache(size_t offset, size_t length) override { + Status InvalidateCache(size_t offset, size_t length) override { return file_->InvalidateCache(offset + prefixLength_, length); } @@ -282,7 +280,7 @@ class EncryptedWritableFile : public WritableFileWrapper { // This asks the OS to initiate flushing the cached data to disk, // without waiting for completion. // Default implementation does nothing. - virtual Status RangeSync(uint64_t offset, uint64_t nbytes) override { + Status RangeSync(uint64_t offset, uint64_t nbytes) override { return file_->RangeSync(offset + prefixLength_, nbytes); } @@ -291,12 +289,12 @@ class EncryptedWritableFile : public WritableFileWrapper { // of space on devices where it can result in less file // fragmentation and/or less waste from over-zealous filesystem // pre-allocation. - virtual void PrepareWrite(size_t offset, size_t len) override { + void PrepareWrite(size_t offset, size_t len) override { file_->PrepareWrite(offset + prefixLength_, len); } // Pre-allocates space for a file. - virtual Status Allocate(uint64_t offset, uint64_t len) override { + Status Allocate(uint64_t offset, uint64_t len) override { return file_->Allocate(offset + prefixLength_, len); } }; @@ -314,17 +312,17 @@ class EncryptedRandomRWFile : public RandomRWFile { // Indicates if the class makes use of direct I/O // If false you must pass aligned buffer to Write() - virtual bool use_direct_io() const override { return file_->use_direct_io(); } + bool use_direct_io() const override { return file_->use_direct_io(); } // Use the returned alignment value to allocate // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { - return file_->GetRequiredBufferAlignment(); + size_t GetRequiredBufferAlignment() const override { + return file_->GetRequiredBufferAlignment(); } // Write bytes in `data` at offset `offset`, Returns Status::OK() on success. // Pass aligned buffer when use_direct_io() returns true. - virtual Status Write(uint64_t offset, const Slice& data) override { + Status Write(uint64_t offset, const Slice& data) override { AlignedBuffer buf; Status status; Slice dataToWrite(data); @@ -347,7 +345,8 @@ class EncryptedRandomRWFile : public RandomRWFile { // Read up to `n` bytes starting from offset `offset` and store them in // result, provided `scratch` size should be at least `n`. // Returns Status::OK() on success. - virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override { + Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override { assert(scratch); offset += prefixLength_; auto status = file_->Read(offset, n, result, scratch); @@ -358,21 +357,13 @@ class EncryptedRandomRWFile : public RandomRWFile { return status; } - virtual Status Flush() override { - return file_->Flush(); - } + Status Flush() override { return file_->Flush(); } - virtual Status Sync() override { - return file_->Sync(); - } + Status Sync() override { return file_->Sync(); } - virtual Status Fsync() override { - return file_->Fsync(); - } + Status Fsync() override { return file_->Fsync(); } - virtual Status Close() override { - return file_->Close(); - } + Status Close() override { return file_->Close(); } }; // EncryptedEnv implements an Env wrapper that adds encryption to files stored on disk. @@ -384,9 +375,9 @@ class EncryptedEnv : public EnvWrapper { } // NewSequentialFile opens a file for sequential reading. - virtual Status NewSequentialFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) override { + Status NewSequentialFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); if (options.use_mmap_reads) { return Status::InvalidArgument(); @@ -421,9 +412,9 @@ class EncryptedEnv : public EnvWrapper { } // NewRandomAccessFile opens a file for random read access. - virtual Status NewRandomAccessFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewRandomAccessFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); if (options.use_mmap_reads) { return Status::InvalidArgument(); @@ -456,11 +447,11 @@ class EncryptedEnv : public EnvWrapper { (*result) = std::unique_ptr(new EncryptedRandomAccessFile(underlying.release(), stream.release(), prefixLength)); return Status::OK(); } - + // NewWritableFile opens a file for sequential writing. - virtual Status NewWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); if (options.use_mmap_writes) { return Status::InvalidArgument(); @@ -504,9 +495,9 @@ class EncryptedEnv : public EnvWrapper { // returns non-OK. // // The returned file will only be accessed by one thread at a time. - virtual Status ReopenWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status ReopenWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); if (options.use_mmap_writes) { return Status::InvalidArgument(); @@ -544,10 +535,10 @@ class EncryptedEnv : public EnvWrapper { } // Reuse an existing file by renaming it and opening it as writable. - virtual Status ReuseWritableFile(const std::string& fname, - const std::string& old_fname, - unique_ptr* result, - const EnvOptions& options) override { + Status ReuseWritableFile(const std::string& fname, + const std::string& old_fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); if (options.use_mmap_writes) { return Status::InvalidArgument(); @@ -584,14 +575,14 @@ class EncryptedEnv : public EnvWrapper { return Status::OK(); } - // Open `fname` for random read and write, if file dont exist the file + // Open `fname` for random read and write, if file doesn't exist the file // will be created. On success, stores a pointer to the new file in // *result and returns OK. On failure returns non-OK. // // The returned file will only be accessed by one thread at a time. - virtual Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewRandomRWFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); if (options.use_mmap_reads || options.use_mmap_writes) { return Status::InvalidArgument(); @@ -649,7 +640,8 @@ class EncryptedEnv : public EnvWrapper { // NotFound if "dir" does not exist, the calling process does not have // permission to access "dir", or if "dir" is invalid. // IOError if an IO Error was encountered - virtual Status GetChildrenFileAttributes(const std::string& dir, std::vector* result) override { + Status GetChildrenFileAttributes( + const std::string& dir, std::vector* result) override { auto status = EnvWrapper::GetChildrenFileAttributes(dir, result); if (!status.ok()) { return status; @@ -660,10 +652,10 @@ class EncryptedEnv : public EnvWrapper { it->size_bytes -= prefixLength; } return Status::OK(); - } + } // Store the size of fname in *file_size. - virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) override { + Status GetFileSize(const std::string& fname, uint64_t* file_size) override { auto status = EnvWrapper::GetFileSize(fname, file_size); if (!status.ok()) { return status; @@ -671,7 +663,7 @@ class EncryptedEnv : public EnvWrapper { size_t prefixLength = provider_->GetPrefixLength(); assert(*file_size >= prefixLength); *file_size -= prefixLength; - return Status::OK(); + return Status::OK(); } private: @@ -692,7 +684,7 @@ Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char *data, size_t auto blockSize = BlockSize(); uint64_t blockIndex = fileOffset / blockSize; size_t blockOffset = fileOffset % blockSize; - unique_ptr blockBuffer; + std::unique_ptr blockBuffer; std::string scratch; AllocateScratch(scratch); @@ -705,8 +697,8 @@ Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char *data, size_t // We're not encrypting a full block. // Copy data to blockBuffer if (!blockBuffer.get()) { - // Allocate buffer - blockBuffer = unique_ptr(new char[blockSize]); + // Allocate buffer + blockBuffer = std::unique_ptr(new char[blockSize]); } block = blockBuffer.get(); // Copy plain data to block buffer @@ -737,11 +729,13 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t auto blockSize = BlockSize(); uint64_t blockIndex = fileOffset / blockSize; size_t blockOffset = fileOffset % blockSize; - unique_ptr blockBuffer; + std::unique_ptr blockBuffer; std::string scratch; AllocateScratch(scratch); + assert(fileOffset < dataSize); + // Decrypt individual blocks. while (1) { char *block = data; @@ -750,8 +744,8 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t // We're not decrypting a full block. // Copy data to blockBuffer if (!blockBuffer.get()) { - // Allocate buffer - blockBuffer = unique_ptr(new char[blockSize]); + // Allocate buffer + blockBuffer = std::unique_ptr(new char[blockSize]); } block = blockBuffer.get(); // Copy encrypted data to block buffer @@ -765,6 +759,14 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t // Copy decrypted data back to `data`. memmove(data, block + blockOffset, n); } + + // Simply decrementing dataSize by n could cause it to underflow, + // which will very likely make it read over the original bounds later + assert(dataSize >= n); + if (dataSize < n) { + return Status::Corruption("Cannot decrypt data at given offset"); + } + dataSize -= n; if (dataSize == 0) { return Status::OK(); @@ -828,7 +830,7 @@ Status CTRCipherStream::DecryptBlock(uint64_t blockIndex, char *data, char* scra // GetPrefixLength returns the length of the prefix that is added to every file // and used for storing encryption options. // For optimal performance, the prefix length should be a multiple of -// the a page size. +// the page size. size_t CTREncryptionProvider::GetPrefixLength() { return defaultPrefixLength; } @@ -844,7 +846,9 @@ static void decodeCTRParameters(const char *prefix, size_t blockSize, uint64_t & // CreateNewPrefix initialized an allocated block of prefix memory // for a new file. -Status CTREncryptionProvider::CreateNewPrefix(const std::string& fname, char *prefix, size_t prefixLength) { +Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/, + char* prefix, + size_t prefixLength) { // Create & seed rnd. Random rnd((uint32_t)Env::Default()->NowMicros()); // Fill entire prefix block with random values. @@ -873,18 +877,29 @@ Status CTREncryptionProvider::CreateNewPrefix(const std::string& fname, char *pr // in plain text. // Returns the amount of space (starting from the start of the prefix) // that has been initialized. -size_t CTREncryptionProvider::PopulateSecretPrefixPart(char *prefix, size_t prefixLength, size_t blockSize) { +size_t CTREncryptionProvider::PopulateSecretPrefixPart(char* /*prefix*/, + size_t /*prefixLength*/, + size_t /*blockSize*/) { // Nothing to do here, put in custom data in override when needed. return 0; } -Status CTREncryptionProvider::CreateCipherStream(const std::string& fname, const EnvOptions& options, Slice &prefix, unique_ptr* result) { +Status CTREncryptionProvider::CreateCipherStream( + const std::string& fname, const EnvOptions& options, Slice& prefix, + std::unique_ptr* result) { // Read plain text part of prefix. auto blockSize = cipher_.BlockSize(); uint64_t initialCounter; Slice iv; decodeCTRParameters(prefix.data(), blockSize, initialCounter, iv); + // If the prefix is smaller than twice the block size, we would below read a + // very large chunk of the file (and very likely read over the bounds) + assert(prefix.size() >= 2 * blockSize); + if (prefix.size() < 2 * blockSize) { + return Status::Corruption("Unable to read from file " + fname + ": read attempt would read beyond file bounds"); + } + // Decrypt the encrypted part of the prefix, starting from block 2 (block 0, 1 with initial counter & IV are unencrypted) CTRCipherStream cipherStream(cipher_, iv.data(), initialCounter); auto status = cipherStream.Decrypt(0, (char*)prefix.data() + (2 * blockSize), prefix.size() - (2 * blockSize)); @@ -898,9 +913,12 @@ Status CTREncryptionProvider::CreateCipherStream(const std::string& fname, const // CreateCipherStreamFromPrefix creates a block access cipher stream for a file given // given name and options. The given prefix is already decrypted. -Status CTREncryptionProvider::CreateCipherStreamFromPrefix(const std::string& fname, const EnvOptions& options, - uint64_t initialCounter, const Slice& iv, const Slice& prefix, unique_ptr* result) { - (*result) = unique_ptr(new CTRCipherStream(cipher_, iv.data(), initialCounter)); +Status CTREncryptionProvider::CreateCipherStreamFromPrefix( + const std::string& /*fname*/, const EnvOptions& /*options*/, + uint64_t initialCounter, const Slice& iv, const Slice& /*prefix*/, + std::unique_ptr* result) { + (*result) = std::unique_ptr( + new CTRCipherStream(cipher_, iv.data(), initialCounter)); return Status::OK(); } diff --git a/thirdparty/rocksdb/env/env_hdfs.cc b/thirdparty/rocksdb/env/env_hdfs.cc index d98020c76b..5acf9301c6 100644 --- a/thirdparty/rocksdb/env/env_hdfs.cc +++ b/thirdparty/rocksdb/env/env_hdfs.cc @@ -11,13 +11,14 @@ #ifndef ROCKSDB_HDFS_FILE_C #define ROCKSDB_HDFS_FILE_C -#include #include #include #include +#include #include #include #include "rocksdb/status.h" +#include "util/logging.h" #include "util/string_util.h" #define HDFS_EXISTS 0 @@ -36,9 +37,11 @@ namespace { // Log error message static Status IOError(const std::string& context, int err_number) { - return (err_number == ENOSPC) ? - Status::NoSpace(context, strerror(err_number)) : - Status::IOError(context, strerror(err_number)); + return (err_number == ENOSPC) + ? Status::NoSpace(context, strerror(err_number)) + : (err_number == ENOENT) + ? Status::PathNotFound(context, strerror(err_number)) + : Status::IOError(context, strerror(err_number)); } // assume that there is one global logger for now. It is not thread-safe, @@ -222,7 +225,7 @@ class HdfsWritableFile: public WritableFile { filename_.c_str()); const char* src = data.data(); size_t left = data.size(); - size_t ret = hdfsWrite(fileSys_, hfile_, src, left); + size_t ret = hdfsWrite(fileSys_, hfile_, src, static_cast(left)); ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsWritableFile Appended %s\n", filename_.c_str()); if (ret != left) { @@ -252,7 +255,8 @@ class HdfsWritableFile: public WritableFile { // This is used by HdfsLogger to write data to the debug log file virtual Status Append(const char* src, size_t size) { - if (hdfsWrite(fileSys_, hfile_, src, size) != (tSize)size) { + if (hdfsWrite(fileSys_, hfile_, src, static_cast(size)) != + static_cast(size)) { return IOError(filename_, errno); } return Status::OK(); @@ -277,6 +281,18 @@ class HdfsLogger : public Logger { HdfsWritableFile* file_; uint64_t (*gettid_)(); // Return the thread id for the current thread + Status HdfsCloseHelper() { + ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsLogger closed %s\n", + file_->getName().c_str()); + if (mylog != nullptr && mylog == this) { + mylog = nullptr; + } + return Status::OK(); + } + + protected: + virtual Status CloseImpl() override { return HdfsCloseHelper(); } + public: HdfsLogger(HdfsWritableFile* f, uint64_t (*gettid)()) : file_(f), gettid_(gettid) { @@ -284,16 +300,15 @@ class HdfsLogger : public Logger { file_->getName().c_str()); } - virtual ~HdfsLogger() { - ROCKS_LOG_DEBUG(mylog, "[hdfs] HdfsLogger closed %s\n", - file_->getName().c_str()); - delete file_; - if (mylog != nullptr && mylog == this) { - mylog = nullptr; + ~HdfsLogger() override { + if (!closed_) { + closed_ = true; + HdfsCloseHelper(); } } - virtual void Logv(const char* format, va_list ap) { + using Logger::Logv; + void Logv(const char* format, va_list ap) override { const uint64_t thread_id = (*gettid_)(); // We try twice: the first time with a fixed-size stack allocated buffer, @@ -370,8 +385,8 @@ const std::string HdfsEnv::pathsep = "/"; // open a file for sequential reading Status HdfsEnv::NewSequentialFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& /*options*/) { result->reset(); HdfsReadableFile* f = new HdfsReadableFile(fileSys_, fname); if (f == nullptr || !f->isValid()) { @@ -385,8 +400,8 @@ Status HdfsEnv::NewSequentialFile(const std::string& fname, // open a file for random reading Status HdfsEnv::NewRandomAccessFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& /*options*/) { result->reset(); HdfsReadableFile* f = new HdfsReadableFile(fileSys_, fname); if (f == nullptr || !f->isValid()) { @@ -400,8 +415,8 @@ Status HdfsEnv::NewRandomAccessFile(const std::string& fname, // create a new file for writing Status HdfsEnv::NewWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& /*options*/) { result->reset(); Status s; HdfsWritableFile* f = new HdfsWritableFile(fileSys_, fname); @@ -419,14 +434,16 @@ class HdfsDirectory : public Directory { explicit HdfsDirectory(int fd) : fd_(fd) {} ~HdfsDirectory() {} - virtual Status Fsync() { return Status::OK(); } + Status Fsync() override { return Status::OK(); } + + int GetFd() const { return fd_; } private: int fd_; }; Status HdfsEnv::NewDirectory(const std::string& name, - unique_ptr* result) { + std::unique_ptr* result) { int value = hdfsExists(fileSys_, name.c_str()); switch (value) { case HDFS_EXISTS: @@ -464,10 +481,10 @@ Status HdfsEnv::GetChildren(const std::string& path, pHdfsFileInfo = hdfsListDirectory(fileSys_, path.c_str(), &numEntries); if (numEntries >= 0) { for(int i = 0; i < numEntries; i++) { - char* pathname = pHdfsFileInfo[i].mName; - char* filename = std::rindex(pathname, '/'); - if (filename != nullptr) { - result->push_back(filename+1); + std::string pathname(pHdfsFileInfo[i].mName); + size_t pos = pathname.rfind("/"); + if (std::string::npos != pos) { + result->push_back(pathname.substr(pos + 1)); } } if (pHdfsFileInfo != nullptr) { @@ -558,19 +575,17 @@ Status HdfsEnv::RenameFile(const std::string& src, const std::string& target) { return IOError(src, errno); } -Status HdfsEnv::LockFile(const std::string& fname, FileLock** lock) { +Status HdfsEnv::LockFile(const std::string& /*fname*/, FileLock** lock) { // there isn's a very good way to atomically check and create // a file via libhdfs *lock = nullptr; return Status::OK(); } -Status HdfsEnv::UnlockFile(FileLock* lock) { - return Status::OK(); -} +Status HdfsEnv::UnlockFile(FileLock* /*lock*/) { return Status::OK(); } Status HdfsEnv::NewLogger(const std::string& fname, - shared_ptr* result) { + std::shared_ptr* result) { HdfsWritableFile* f = new HdfsWritableFile(fileSys_, fname); if (f == nullptr || !f->isValid()) { delete f; @@ -598,13 +613,13 @@ Status NewHdfsEnv(Env** hdfs_env, const std::string& fsname) { // dummy placeholders used when HDFS is not available namespace rocksdb { - Status HdfsEnv::NewSequentialFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) { - return Status::NotSupported("Not compiled with hdfs support"); - } +Status HdfsEnv::NewSequentialFile(const std::string& /*fname*/, + std::unique_ptr* /*result*/, + const EnvOptions& /*options*/) { + return Status::NotSupported("Not compiled with hdfs support"); +} - Status NewHdfsEnv(Env** hdfs_env, const std::string& fsname) { + Status NewHdfsEnv(Env** /*hdfs_env*/, const std::string& /*fsname*/) { return Status::NotSupported("Not compiled with hdfs support"); } } diff --git a/thirdparty/rocksdb/env/env_posix.cc b/thirdparty/rocksdb/env/env_posix.cc index 5a671d72fe..387c027939 100644 --- a/thirdparty/rocksdb/env/env_posix.cc +++ b/thirdparty/rocksdb/env/env_posix.cc @@ -20,10 +20,12 @@ #include #include #include -#ifdef OS_LINUX +#if defined(OS_LINUX) || defined(OS_SOLARIS) || defined(OS_ANDROID) #include #include +#include #endif +#include #include #include #include @@ -48,6 +50,7 @@ #include "rocksdb/options.h" #include "rocksdb/slice.h" #include "util/coding.h" +#include "util/compression_context_cache.h" #include "util/logging.h" #include "util/random.h" #include "util/string_util.h" @@ -73,32 +76,15 @@ ThreadStatusUpdater* CreateThreadStatusUpdater() { return new ThreadStatusUpdater(); } +inline mode_t GetDBFileMode(bool allow_non_owner_access) { + return allow_non_owner_access ? 0644 : 0600; +} + // list of pathnames that are locked static std::set lockedFiles; static port::Mutex mutex_lockedFiles; -static int LockOrUnlock(const std::string& fname, int fd, bool lock) { - mutex_lockedFiles.Lock(); - if (lock) { - // If it already exists in the lockedFiles set, then it is already locked, - // and fail this lock attempt. Otherwise, insert it into lockedFiles. - // This check is needed because fcntl() does not detect lock conflict - // if the fcntl is issued by the same thread that earlier acquired - // this lock. - if (lockedFiles.insert(fname).second == false) { - mutex_lockedFiles.Unlock(); - errno = ENOLCK; - return -1; - } - } else { - // If we are unlocking, then verify that we had locked it earlier, - // it should already exist in lockedFiles. Remove it from lockedFiles. - if (lockedFiles.erase(fname) != 1) { - mutex_lockedFiles.Unlock(); - errno = ENOLCK; - return -1; - } - } +static int LockOrUnlock(int fd, bool lock) { errno = 0; struct flock f; memset(&f, 0, sizeof(f)); @@ -107,11 +93,7 @@ static int LockOrUnlock(const std::string& fname, int fd, bool lock) { f.l_start = 0; f.l_len = 0; // Lock/unlock entire file int value = fcntl(fd, F_SETLK, &f); - if (value == -1 && lock) { - // if there is an error in locking, then remove the pathname from lockedfiles - lockedFiles.erase(fname); - } - mutex_lockedFiles.Unlock(); + return value; } @@ -121,11 +103,23 @@ class PosixFileLock : public FileLock { std::string filename; }; +int cloexec_flags(int flags, const EnvOptions* options) { + // If the system supports opening the file with cloexec enabled, + // do so, as this avoids a race condition if a db is opened around + // the same time that a child process is forked +#ifdef O_CLOEXEC + if (options == nullptr || options->set_fd_cloexec) { + flags |= O_CLOEXEC; + } +#endif + return flags; +} + class PosixEnv : public Env { public: PosixEnv(); - virtual ~PosixEnv() { + ~PosixEnv() override { for (const auto tid : threads_to_join_) { pthread_join(tid, nullptr); } @@ -147,12 +141,12 @@ class PosixEnv : public Env { } } - virtual Status NewSequentialFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewSequentialFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); int fd = -1; - int flags = O_RDONLY; + int flags = cloexec_flags(O_RDONLY, &options); FILE* file = nullptr; if (options.use_direct_reads && !options.use_mmap_reads) { @@ -166,7 +160,7 @@ class PosixEnv : public Env { do { IOSTATS_TIMER_GUARD(open_nanos); - fd = open(fname.c_str(), flags, 0644); + fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_)); } while (fd < 0 && errno == EINTR); if (fd < 0) { return IOError("While opening a file for sequentially reading", fname, @@ -197,13 +191,14 @@ class PosixEnv : public Env { return Status::OK(); } - virtual Status NewRandomAccessFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewRandomAccessFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); Status s; int fd; - int flags = O_RDONLY; + int flags = cloexec_flags(O_RDONLY, &options); + if (options.use_direct_reads && !options.use_mmap_reads) { #ifdef ROCKSDB_LITE return Status::IOError(fname, "Direct I/O not supported in RocksDB lite"); @@ -216,7 +211,7 @@ class PosixEnv : public Env { do { IOSTATS_TIMER_GUARD(open_nanos); - fd = open(fname.c_str(), flags, 0644); + fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_)); } while (fd < 0 && errno == EINTR); if (fd < 0) { return IOError("While open a file for random read", fname, errno); @@ -236,9 +231,9 @@ class PosixEnv : public Env { size, options)); } else { s = IOError("while mmap file for read", fname, errno); + close(fd); } } - close(fd); } else { if (options.use_direct_reads && !options.use_mmap_reads) { #ifdef OS_MACOSX @@ -254,7 +249,7 @@ class PosixEnv : public Env { } virtual Status OpenWritableFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options, bool reopen = false) { result->reset(); @@ -285,9 +280,11 @@ class PosixEnv : public Env { flags |= O_WRONLY; } + flags = cloexec_flags(flags, &options); + do { IOSTATS_TIMER_GUARD(open_nanos); - fd = open(fname.c_str(), flags, 0644); + fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_)); } while (fd < 0 && errno == EINTR); if (fd < 0) { @@ -335,22 +332,22 @@ class PosixEnv : public Env { return s; } - virtual Status NewWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { return OpenWritableFile(fname, result, options, false); } - virtual Status ReopenWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status ReopenWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { return OpenWritableFile(fname, result, options, true); } - virtual Status ReuseWritableFile(const std::string& fname, - const std::string& old_fname, - unique_ptr* result, - const EnvOptions& options) override { + Status ReuseWritableFile(const std::string& fname, + const std::string& old_fname, + std::unique_ptr* result, + const EnvOptions& options) override { result->reset(); Status s; int fd = -1; @@ -373,9 +370,12 @@ class PosixEnv : public Env { flags |= O_WRONLY; } + flags = cloexec_flags(flags, &options); + do { IOSTATS_TIMER_GUARD(open_nanos); - fd = open(old_fname.c_str(), flags, 0644); + fd = open(old_fname.c_str(), flags, + GetDBFileMode(allow_non_owner_access_)); } while (fd < 0 && errno == EINTR); if (fd < 0) { s = IOError("while reopen file for write", fname, errno); @@ -427,17 +427,18 @@ class PosixEnv : public Env { result->reset(new PosixWritableFile(fname, fd, no_mmap_writes_options)); } return s; - - return s; } - virtual Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + Status NewRandomRWFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override { int fd = -1; + int flags = cloexec_flags(O_RDWR, &options); + while (fd < 0) { IOSTATS_TIMER_GUARD(open_nanos); - fd = open(fname.c_str(), O_CREAT | O_RDWR, 0644); + + fd = open(fname.c_str(), flags, GetDBFileMode(allow_non_owner_access_)); if (fd < 0) { // Error while opening the file if (errno == EINTR) { @@ -452,13 +453,57 @@ class PosixEnv : public Env { return Status::OK(); } - virtual Status NewDirectory(const std::string& name, - unique_ptr* result) override { + Status NewMemoryMappedFileBuffer( + const std::string& fname, + std::unique_ptr* result) override { + int fd = -1; + Status status; + int flags = cloexec_flags(O_RDWR, nullptr); + + while (fd < 0) { + IOSTATS_TIMER_GUARD(open_nanos); + fd = open(fname.c_str(), flags, 0644); + if (fd < 0) { + // Error while opening the file + if (errno == EINTR) { + continue; + } + status = + IOError("While open file for raw mmap buffer access", fname, errno); + break; + } + } + uint64_t size; + if (status.ok()) { + status = GetFileSize(fname, &size); + } + void* base = nullptr; + if (status.ok()) { + base = mmap(nullptr, static_cast(size), PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (base == MAP_FAILED) { + status = IOError("while mmap file for read", fname, errno); + } + } + if (status.ok()) { + result->reset( + new PosixMemoryMappedFileBuffer(base, static_cast(size))); + } + if (fd >= 0) { + // don't need to keep it open after mmap has been called + close(fd); + } + return status; + } + + Status NewDirectory(const std::string& name, + std::unique_ptr* result) override { result->reset(); int fd; + int flags = cloexec_flags(0, nullptr); { IOSTATS_TIMER_GUARD(open_nanos); - fd = open(name.c_str(), 0); + fd = open(name.c_str(), flags); } if (fd < 0) { return IOError("While open directory", name, errno); @@ -468,14 +513,15 @@ class PosixEnv : public Env { return Status::OK(); } - virtual Status FileExists(const std::string& fname) override { + Status FileExists(const std::string& fname) override { int result = access(fname.c_str(), F_OK); if (result == 0) { return Status::OK(); } - switch (errno) { + int err = errno; + switch (err) { case EACCES: case ELOOP: case ENAMETOOLONG: @@ -483,14 +529,14 @@ class PosixEnv : public Env { case ENOTDIR: return Status::NotFound(); default: - assert(result == EIO || result == ENOMEM); - return Status::IOError("Unexpected error(" + ToString(result) + + assert(err == EIO || err == ENOMEM); + return Status::IOError("Unexpected error(" + ToString(err) + ") accessing file `" + fname + "' "); } } - virtual Status GetChildren(const std::string& dir, - std::vector* result) override { + Status GetChildren(const std::string& dir, + std::vector* result) override { result->clear(); DIR* d = opendir(dir.c_str()); if (d == nullptr) { @@ -511,7 +557,7 @@ class PosixEnv : public Env { return Status::OK(); } - virtual Status DeleteFile(const std::string& fname) override { + Status DeleteFile(const std::string& fname) override { Status result; if (unlink(fname.c_str()) != 0) { result = IOError("while unlink() file", fname, errno); @@ -519,7 +565,7 @@ class PosixEnv : public Env { return result; }; - virtual Status CreateDir(const std::string& name) override { + Status CreateDir(const std::string& name) override { Status result; if (mkdir(name.c_str(), 0755) != 0) { result = IOError("While mkdir", name, errno); @@ -527,7 +573,7 @@ class PosixEnv : public Env { return result; }; - virtual Status CreateDirIfMissing(const std::string& name) override { + Status CreateDirIfMissing(const std::string& name) override { Status result; if (mkdir(name.c_str(), 0755) != 0) { if (errno != EEXIST) { @@ -541,7 +587,7 @@ class PosixEnv : public Env { return result; }; - virtual Status DeleteDir(const std::string& name) override { + Status DeleteDir(const std::string& name) override { Status result; if (rmdir(name.c_str()) != 0) { result = IOError("file rmdir", name, errno); @@ -549,8 +595,7 @@ class PosixEnv : public Env { return result; }; - virtual Status GetFileSize(const std::string& fname, - uint64_t* size) override { + Status GetFileSize(const std::string& fname, uint64_t* size) override { Status s; struct stat sbuf; if (stat(fname.c_str(), &sbuf) != 0) { @@ -562,8 +607,8 @@ class PosixEnv : public Env { return s; } - virtual Status GetFileModificationTime(const std::string& fname, - uint64_t* file_mtime) override { + Status GetFileModificationTime(const std::string& fname, + uint64_t* file_mtime) override { struct stat s; if (stat(fname.c_str(), &s) !=0) { return IOError("while stat a file for modification time", fname, errno); @@ -571,8 +616,8 @@ class PosixEnv : public Env { *file_mtime = static_cast(s.st_mtime); return Status::OK(); } - virtual Status RenameFile(const std::string& src, - const std::string& target) override { + Status RenameFile(const std::string& src, + const std::string& target) override { Status result; if (rename(src.c_str(), target.c_str()) != 0) { result = IOError("While renaming a file to " + target, src, errno); @@ -580,8 +625,7 @@ class PosixEnv : public Env { return result; } - virtual Status LinkFile(const std::string& src, - const std::string& target) override { + Status LinkFile(const std::string& src, const std::string& target) override { Status result; if (link(src.c_str(), target.c_str()) != 0) { if (errno == EXDEV) { @@ -592,17 +636,67 @@ class PosixEnv : public Env { return result; } - virtual Status LockFile(const std::string& fname, FileLock** lock) override { + Status NumFileLinks(const std::string& fname, uint64_t* count) override { + struct stat s; + if (stat(fname.c_str(), &s) != 0) { + return IOError("while stat a file for num file links", fname, errno); + } + *count = static_cast(s.st_nlink); + return Status::OK(); + } + + Status AreFilesSame(const std::string& first, const std::string& second, + bool* res) override { + struct stat statbuf[2]; + if (stat(first.c_str(), &statbuf[0]) != 0) { + return IOError("stat file", first, errno); + } + if (stat(second.c_str(), &statbuf[1]) != 0) { + return IOError("stat file", second, errno); + } + + if (major(statbuf[0].st_dev) != major(statbuf[1].st_dev) || + minor(statbuf[0].st_dev) != minor(statbuf[1].st_dev) || + statbuf[0].st_ino != statbuf[1].st_ino) { + *res = false; + } else { + *res = true; + } + return Status::OK(); + } + + Status LockFile(const std::string& fname, FileLock** lock) override { *lock = nullptr; Status result; + + mutex_lockedFiles.Lock(); + // If it already exists in the lockedFiles set, then it is already locked, + // and fail this lock attempt. Otherwise, insert it into lockedFiles. + // This check is needed because fcntl() does not detect lock conflict + // if the fcntl is issued by the same thread that earlier acquired + // this lock. + // We must do this check *before* opening the file: + // Otherwise, we will open a new file descriptor. Locks are associated with + // a process, not a file descriptor and when *any* file descriptor is closed, + // all locks the process holds for that *file* are released + if (lockedFiles.insert(fname).second == false) { + mutex_lockedFiles.Unlock(); + errno = ENOLCK; + return IOError("lock ", fname, errno); + } + int fd; + int flags = cloexec_flags(O_RDWR | O_CREAT, nullptr); + { IOSTATS_TIMER_GUARD(open_nanos); - fd = open(fname.c_str(), O_RDWR | O_CREAT, 0644); + fd = open(fname.c_str(), flags, 0644); } if (fd < 0) { result = IOError("while open a file for lock", fname, errno); - } else if (LockOrUnlock(fname, fd, true) == -1) { + } else if (LockOrUnlock(fd, true) == -1) { + // if there is an error in locking, then remove the pathname from lockedfiles + lockedFiles.erase(fname); result = IOError("While lock file", fname, errno); close(fd); } else { @@ -612,33 +706,42 @@ class PosixEnv : public Env { my_lock->filename = fname; *lock = my_lock; } + + mutex_lockedFiles.Unlock(); return result; } - virtual Status UnlockFile(FileLock* lock) override { + Status UnlockFile(FileLock* lock) override { PosixFileLock* my_lock = reinterpret_cast(lock); Status result; - if (LockOrUnlock(my_lock->filename, my_lock->fd_, false) == -1) { + mutex_lockedFiles.Lock(); + // If we are unlocking, then verify that we had locked it earlier, + // it should already exist in lockedFiles. Remove it from lockedFiles. + if (lockedFiles.erase(my_lock->filename) != 1) { + errno = ENOLCK; + result = IOError("unlock", my_lock->filename, errno); + } else if (LockOrUnlock(my_lock->fd_, false) == -1) { result = IOError("unlock", my_lock->filename, errno); } close(my_lock->fd_); delete my_lock; + mutex_lockedFiles.Unlock(); return result; } - virtual void Schedule(void (*function)(void* arg1), void* arg, - Priority pri = LOW, void* tag = nullptr, - void (*unschedFunction)(void* arg) = 0) override; + void Schedule(void (*function)(void* arg1), void* arg, Priority pri = LOW, + void* tag = nullptr, + void (*unschedFunction)(void* arg) = nullptr) override; - virtual int UnSchedule(void* arg, Priority pri) override; + int UnSchedule(void* arg, Priority pri) override; - virtual void StartThread(void (*function)(void* arg), void* arg) override; + void StartThread(void (*function)(void* arg), void* arg) override; - virtual void WaitForJoin() override; + void WaitForJoin() override; - virtual unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const override; + unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const override; - virtual Status GetTestDirectory(std::string* result) override { + Status GetTestDirectory(std::string* result) override { const char* env = getenv("TEST_TMPDIR"); if (env && env[0] != '\0') { *result = env; @@ -652,8 +755,7 @@ class PosixEnv : public Env { return Status::OK(); } - virtual Status GetThreadList( - std::vector* thread_list) override { + Status GetThreadList(std::vector* thread_list) override { assert(thread_status_updater_); return thread_status_updater_->GetThreadList(thread_list); } @@ -669,16 +771,31 @@ class PosixEnv : public Env { return gettid(tid); } - virtual uint64_t GetThreadID() const override { - return gettid(pthread_self()); + uint64_t GetThreadID() const override { return gettid(pthread_self()); } + + Status GetFreeSpace(const std::string& fname, uint64_t* free_space) override { + struct statvfs sbuf; + + if (statvfs(fname.c_str(), &sbuf) < 0) { + return IOError("While doing statvfs", fname, errno); + } + + *free_space = ((uint64_t)sbuf.f_bsize * sbuf.f_bfree); + return Status::OK(); } - virtual Status NewLogger(const std::string& fname, - shared_ptr* result) override { + Status NewLogger(const std::string& fname, + std::shared_ptr* result) override { FILE* f; { IOSTATS_TIMER_GUARD(open_nanos); - f = fopen(fname.c_str(), "w"); + f = fopen(fname.c_str(), "w" +#ifdef __GLIBC_PREREQ +#if __GLIBC_PREREQ(2, 7) + "e" // glibc extension to enable O_CLOEXEC +#endif +#endif + ); } if (f == nullptr) { result->reset(); @@ -694,13 +811,13 @@ class PosixEnv : public Env { } } - virtual uint64_t NowMicros() override { + uint64_t NowMicros() override { struct timeval tv; gettimeofday(&tv, nullptr); return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; } - virtual uint64_t NowNanos() override { + uint64_t NowNanos() override { #if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_AIX) struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -720,9 +837,19 @@ class PosixEnv : public Env { #endif } - virtual void SleepForMicroseconds(int micros) override { usleep(micros); } + uint64_t NowCPUNanos() override { +#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_AIX) || \ + defined(__MACH__) + struct timespec ts; + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); + return static_cast(ts.tv_sec) * 1000000000 + ts.tv_nsec; +#endif + return 0; + } - virtual Status GetHostName(char* name, uint64_t len) override { + void SleepForMicroseconds(int micros) override { usleep(micros); } + + Status GetHostName(char* name, uint64_t len) override { int ret = gethostname(name, static_cast(len)); if (ret < 0) { if (errno == EFAULT || errno == EINVAL) @@ -733,7 +860,7 @@ class PosixEnv : public Env { return Status::OK(); } - virtual Status GetCurrentTime(int64_t* unix_time) override { + Status GetCurrentTime(int64_t* unix_time) override { time_t ret = time(nullptr); if (ret == (time_t) -1) { return IOError("GetCurrentTime", "", errno); @@ -742,9 +869,9 @@ class PosixEnv : public Env { return Status::OK(); } - virtual Status GetAbsolutePath(const std::string& db_path, - std::string* output_path) override { - if (db_path.find('/') == 0) { + Status GetAbsolutePath(const std::string& db_path, + std::string* output_path) override { + if (!db_path.empty() && db_path[0] == '/') { *output_path = db_path; return Status::OK(); } @@ -760,30 +887,46 @@ class PosixEnv : public Env { } // Allow increasing the number of worker threads. - virtual void SetBackgroundThreads(int num, Priority pri) override { + void SetBackgroundThreads(int num, Priority pri) override { assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH); thread_pools_[pri].SetBackgroundThreads(num); } - virtual int GetBackgroundThreads(Priority pri) override { + int GetBackgroundThreads(Priority pri) override { assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH); return thread_pools_[pri].GetBackgroundThreads(); } + Status SetAllowNonOwnerAccess(bool allow_non_owner_access) override { + allow_non_owner_access_ = allow_non_owner_access; + return Status::OK(); + } + // Allow increasing the number of worker threads. - virtual void IncBackgroundThreadsIfNeeded(int num, Priority pri) override { + void IncBackgroundThreadsIfNeeded(int num, Priority pri) override { assert(pri >= Priority::BOTTOM && pri <= Priority::HIGH); thread_pools_[pri].IncBackgroundThreadsIfNeeded(num); } - virtual void LowerThreadPoolIOPriority(Priority pool = LOW) override { + void LowerThreadPoolIOPriority(Priority pool = LOW) override { assert(pool >= Priority::BOTTOM && pool <= Priority::HIGH); #ifdef OS_LINUX thread_pools_[pool].LowerIOPriority(); +#else + (void)pool; +#endif + } + + void LowerThreadPoolCPUPriority(Priority pool = LOW) override { + assert(pool >= Priority::BOTTOM && pool <= Priority::HIGH); +#ifdef OS_LINUX + thread_pools_[pool].LowerCPUPriority(); +#else + (void)pool; #endif } - virtual std::string TimeToString(uint64_t secondsSince1970) override { + std::string TimeToString(uint64_t secondsSince1970) override { const time_t seconds = (time_t)secondsSince1970; struct tm t; int maxsize = 64; @@ -813,6 +956,8 @@ class PosixEnv : public Env { // breaks TransactionLogIteratorStallAtLastRecord unit test. Fix the unit // test and make this false optimized.fallocate_with_keep_size = true; + optimized.writable_file_max_buffer_size = + db_options.writable_file_max_buffer_size; return optimized; } @@ -855,6 +1000,7 @@ class PosixEnv : public Env { return false; } #else + (void)path; return false; #endif } @@ -864,13 +1010,17 @@ class PosixEnv : public Env { std::vector thread_pools_; pthread_mutex_t mu_; std::vector threads_to_join_; + // If true, allow non owner read access for db files. Otherwise, non-owner + // has no access to db files. + bool allow_non_owner_access_; }; PosixEnv::PosixEnv() : checkedDiskForMmap_(false), forceMmapOff_(false), page_size_(getpagesize()), - thread_pools_(Priority::TOTAL) { + thread_pools_(Priority::TOTAL), + allow_non_owner_access_(true) { ThreadPoolImpl::PthreadCall("mutex_init", pthread_mutex_init(&mu_, nullptr)); for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) { thread_pools_[pool_id].SetThreadPriority( @@ -969,6 +1119,8 @@ Env* Env::Default() { // the destructor of static PosixEnv will go first, then the // the singletons of ThreadLocalPtr. ThreadLocalPtr::InitSingletons(); + CompressionContextCache::InitSingleton(); + INIT_SYNC_POINT_SINGLETONS(); static PosixEnv default_env; return &default_env; } diff --git a/thirdparty/rocksdb/env/env_test.cc b/thirdparty/rocksdb/env/env_test.cc index 9ec2f142ed..4780092849 100644 --- a/thirdparty/rocksdb/env/env_test.cc +++ b/thirdparty/rocksdb/env/env_test.cc @@ -73,7 +73,7 @@ struct Deleter { std::unique_ptr NewAligned(const size_t size, const char ch) { char* ptr = nullptr; #ifdef OS_WIN - if (!(ptr = reinterpret_cast(_aligned_malloc(size, kPageSize)))) { + if (nullptr == (ptr = reinterpret_cast(_aligned_malloc(size, kPageSize)))) { return std::unique_ptr(nullptr, Deleter(_aligned_free)); } std::unique_ptr uptr(ptr, Deleter(_aligned_free)); @@ -118,14 +118,14 @@ class EnvPosixTestWithParam } } - ~EnvPosixTestWithParam() { WaitThreadPoolsEmpty(); } + ~EnvPosixTestWithParam() override { WaitThreadPoolsEmpty(); } }; static void SetBool(void* ptr) { reinterpret_cast*>(ptr)->store(true); } -TEST_F(EnvPosixTest, RunImmediately) { +TEST_F(EnvPosixTest, DISABLED_RunImmediately) { for (int pri = Env::BOTTOM; pri < Env::TOTAL; ++pri) { std::atomic called(false); env_->SetBackgroundThreads(1, static_cast(pri)); @@ -135,6 +135,118 @@ TEST_F(EnvPosixTest, RunImmediately) { } } +TEST_F(EnvPosixTest, RunEventually) { + std::atomic called(false); + env_->StartThread(&SetBool, &called); + env_->WaitForJoin(); + ASSERT_TRUE(called.load()); +} + +#ifdef OS_WIN +TEST_F(EnvPosixTest, AreFilesSame) { + { + bool tmp; + if (env_->AreFilesSame("", "", &tmp).IsNotSupported()) { + fprintf(stderr, + "skipping EnvBasicTestWithParam.AreFilesSame due to " + "unsupported Env::AreFilesSame\n"); + return; + } + } + + const EnvOptions soptions; + auto* env = Env::Default(); + std::string same_file_name = test::PerThreadDBPath(env, "same_file"); + std::string same_file_link_name = same_file_name + "_link"; + + std::unique_ptr same_file; + ASSERT_OK(env->NewWritableFile(same_file_name, + &same_file, soptions)); + same_file->Append("random_data"); + ASSERT_OK(same_file->Flush()); + same_file.reset(); + + ASSERT_OK(env->LinkFile(same_file_name, same_file_link_name)); + bool result = false; + ASSERT_OK(env->AreFilesSame(same_file_name, same_file_link_name, &result)); + ASSERT_TRUE(result); +} +#endif + +#ifdef OS_LINUX +TEST_F(EnvPosixTest, DISABLED_FilePermission) { + // Only works for Linux environment + if (env_ == Env::Default()) { + EnvOptions soptions; + std::vector fileNames{ + test::PerThreadDBPath(env_, "testfile"), + test::PerThreadDBPath(env_, "testfile1")}; + std::unique_ptr wfile; + ASSERT_OK(env_->NewWritableFile(fileNames[0], &wfile, soptions)); + ASSERT_OK(env_->NewWritableFile(fileNames[1], &wfile, soptions)); + wfile.reset(); + std::unique_ptr rwfile; + ASSERT_OK(env_->NewRandomRWFile(fileNames[1], &rwfile, soptions)); + + struct stat sb; + for (const auto& filename : fileNames) { + if (::stat(filename.c_str(), &sb) == 0) { + ASSERT_EQ(sb.st_mode & 0777, 0644); + } + env_->DeleteFile(filename); + } + + env_->SetAllowNonOwnerAccess(false); + ASSERT_OK(env_->NewWritableFile(fileNames[0], &wfile, soptions)); + ASSERT_OK(env_->NewWritableFile(fileNames[1], &wfile, soptions)); + wfile.reset(); + ASSERT_OK(env_->NewRandomRWFile(fileNames[1], &rwfile, soptions)); + + for (const auto& filename : fileNames) { + if (::stat(filename.c_str(), &sb) == 0) { + ASSERT_EQ(sb.st_mode & 0777, 0600); + } + env_->DeleteFile(filename); + } + } +} +#endif + +TEST_F(EnvPosixTest, MemoryMappedFileBuffer) { + const int kFileBytes = 1 << 15; // 32 KB + std::string expected_data; + std::string fname = test::PerThreadDBPath(env_, "testfile"); + { + std::unique_ptr wfile; + const EnvOptions soptions; + ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); + + Random rnd(301); + test::RandomString(&rnd, kFileBytes, &expected_data); + ASSERT_OK(wfile->Append(expected_data)); + } + + std::unique_ptr mmap_buffer; + Status status = env_->NewMemoryMappedFileBuffer(fname, &mmap_buffer); + // it should be supported at least on linux +#if !defined(OS_LINUX) + if (status.IsNotSupported()) { + fprintf(stderr, + "skipping EnvPosixTest.MemoryMappedFileBuffer due to " + "unsupported Env::NewMemoryMappedFileBuffer\n"); + return; + } +#endif // !defined(OS_LINUX) + + ASSERT_OK(status); + ASSERT_NE(nullptr, mmap_buffer.get()); + ASSERT_NE(nullptr, mmap_buffer->GetBase()); + ASSERT_EQ(kFileBytes, mmap_buffer->GetLen()); + std::string actual_data(reinterpret_cast(mmap_buffer->GetBase()), + mmap_buffer->GetLen()); + ASSERT_EQ(expected_data, actual_data); +} + TEST_P(EnvPosixTestWithParam, UnSchedule) { std::atomic called(false); env_->SetBackgroundThreads(1, Env::LOW); @@ -171,6 +283,11 @@ TEST_P(EnvPosixTestWithParam, UnSchedule) { WaitThreadPoolsEmpty(); } +// This tests assumes that the last scheduled +// task will run last. In fact, in the allotted +// sleeping time nothing may actually run or they may +// run in any order. The purpose of the test is unclear. +#ifndef OS_WIN TEST_P(EnvPosixTestWithParam, RunMany) { std::atomic last_id(0); @@ -203,6 +320,7 @@ TEST_P(EnvPosixTestWithParam, RunMany) { ASSERT_EQ(4, cur); WaitThreadPoolsEmpty(); } +#endif struct State { port::Mutex mu; @@ -694,14 +812,13 @@ class IoctlFriendlyTmpdir { #ifndef ROCKSDB_LITE TEST_F(EnvPosixTest, PositionedAppend) { - unique_ptr writable_file; + std::unique_ptr writable_file; EnvOptions options; options.use_direct_writes = true; options.use_mmap_writes = false; IoctlFriendlyTmpdir ift; ASSERT_OK(env_->NewWritableFile(ift.name() + "/f", &writable_file, options)); const size_t kBlockSize = 4096; - const size_t kPageSize = 4096; const size_t kDataSize = kPageSize; // Write a page worth of 'a' auto data_ptr = NewAligned(kDataSize, 'a'); @@ -715,7 +832,7 @@ TEST_F(EnvPosixTest, PositionedAppend) { // The file now has 1 sector worth of a followed by a page worth of b // Verify the above - unique_ptr seq_file; + std::unique_ptr seq_file; ASSERT_OK(env_->NewSequentialFile(ift.name() + "/f", &seq_file, options)); char scratch[kPageSize * 2]; Slice result; @@ -734,10 +851,10 @@ TEST_P(EnvPosixTestWithParam, RandomAccessUniqueID) { soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; IoctlFriendlyTmpdir ift; std::string fname = ift.name() + "/testfile"; - unique_ptr wfile; + std::unique_ptr wfile; ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); - unique_ptr file; + std::unique_ptr file; // Get Unique ID ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); @@ -804,7 +921,7 @@ TEST_P(EnvPosixTestWithParam, AllocateTest) { EnvOptions soptions; soptions.use_mmap_writes = false; soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - unique_ptr wfile; + std::unique_ptr wfile; ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); // allocate 100 MB @@ -873,14 +990,14 @@ TEST_P(EnvPosixTestWithParam, RandomAccessUniqueIDConcurrent) { fnames.push_back(ift.name() + "/" + "testfile" + ToString(i)); // Create file. - unique_ptr wfile; + std::unique_ptr wfile; ASSERT_OK(env_->NewWritableFile(fnames[i], &wfile, soptions)); } // Collect and check whether the IDs are unique. std::unordered_set ids; for (const std::string fname : fnames) { - unique_ptr file; + std::unique_ptr file; std::string unique_id; ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE); @@ -916,14 +1033,14 @@ TEST_P(EnvPosixTestWithParam, RandomAccessUniqueIDDeletes) { for (int i = 0; i < 1000; ++i) { // Create file. { - unique_ptr wfile; + std::unique_ptr wfile; ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions)); } // Get Unique ID std::string unique_id; { - unique_ptr file; + std::unique_ptr file; ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions)); size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE); ASSERT_TRUE(id_size > 0); @@ -951,7 +1068,7 @@ TEST_P(EnvPosixTestWithParam, InvalidateCache) { rocksdb::SyncPoint::GetInstance()->EnableProcessing(); EnvOptions soptions; soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; - std::string fname = test::TmpDir(env_) + "/" + "testfile"; + std::string fname = test::PerThreadDBPath(env_, "testfile"); const size_t kSectorSize = 512; auto data = NewAligned(kSectorSize, 0); @@ -959,7 +1076,7 @@ TEST_P(EnvPosixTestWithParam, InvalidateCache) { // Create file. { - unique_ptr wfile; + std::unique_ptr wfile; #if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && !defined(OS_AIX) if (soptions.use_direct_writes) { soptions.use_direct_writes = false; @@ -973,7 +1090,7 @@ TEST_P(EnvPosixTestWithParam, InvalidateCache) { // Random Read { - unique_ptr file; + std::unique_ptr file; auto scratch = NewAligned(kSectorSize, 0); Slice result; #if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && !defined(OS_AIX) @@ -990,7 +1107,7 @@ TEST_P(EnvPosixTestWithParam, InvalidateCache) { // Sequential Read { - unique_ptr file; + std::unique_ptr file; auto scratch = NewAligned(kSectorSize, 0); Slice result; #if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && !defined(OS_AIX) @@ -1018,7 +1135,7 @@ TEST_P(EnvPosixTestWithParam, InvalidateCache) { class TestLogger : public Logger { public: using Logger::Logv; - virtual void Logv(const char* format, va_list ap) override { + void Logv(const char* format, va_list ap) override { log_count++; char new_format[550]; @@ -1100,7 +1217,7 @@ class TestLogger2 : public Logger { public: explicit TestLogger2(size_t max_log_size) : max_log_size_(max_log_size) {} using Logger::Logv; - virtual void Logv(const char* format, va_list ap) override { + void Logv(const char* format, va_list ap) override { char new_format[2000]; std::fill_n(new_format, sizeof(new_format), '2'); { @@ -1134,11 +1251,11 @@ TEST_P(EnvPosixTestWithParam, LogBufferMaxSizeTest) { TEST_P(EnvPosixTestWithParam, Preallocation) { rocksdb::SyncPoint::GetInstance()->EnableProcessing(); - const std::string src = test::TmpDir(env_) + "/" + "testfile"; - unique_ptr srcfile; - EnvOptions soptions; - soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && !defined(OS_AIX) + const std::string src = test::PerThreadDBPath(env_, "testfile"); + std::unique_ptr srcfile; + EnvOptions soptions; + soptions.use_direct_reads = soptions.use_direct_writes = direct_io_; +#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && !defined(OS_AIX) && !defined(OS_OPENBSD) && !defined(OS_FREEBSD) if (soptions.use_direct_writes) { rocksdb::SyncPoint::GetInstance()->SetCallBack( "NewWritableFile:O_DIRECT", [&](void* arg) { @@ -1196,11 +1313,10 @@ TEST_P(EnvPosixTestWithParam, ConsistentChildrenAttributes) { std::string data; for (int i = 0; i < kNumChildren; ++i) { - std::ostringstream oss; - oss << test::TmpDir(env_) << "/testfile_" << i; - const std::string path = oss.str(); - unique_ptr file; -#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && !defined(OS_AIX) + const std::string path = + test::TmpDir(env_) + "/" + "testfile_" + std::to_string(i); + std::unique_ptr file; +#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && !defined(OS_AIX) && !defined(OS_OPENBSD) && !defined(OS_FREEBSD) if (soptions.use_direct_writes) { rocksdb::SyncPoint::GetInstance()->SetCallBack( "NewWritableFile:O_DIRECT", [&](void* arg) { @@ -1219,9 +1335,7 @@ TEST_P(EnvPosixTestWithParam, ConsistentChildrenAttributes) { std::vector file_attrs; ASSERT_OK(env_->GetChildrenFileAttributes(test::TmpDir(env_), &file_attrs)); for (int i = 0; i < kNumChildren; ++i) { - std::ostringstream oss; - oss << "testfile_" << i; - const std::string name = oss.str(); + const std::string name = "testfile_" + std::to_string(i); const std::string path = test::TmpDir(env_) + "/" + name; auto file_attrs_iter = std::find_if( @@ -1250,51 +1364,114 @@ TEST_P(EnvPosixTestWithParam, WritableFileWrapper) { inc(0); } - Status Append(const Slice& data) override { inc(1); return Status::OK(); } - Status Truncate(uint64_t size) override { return Status::OK(); } - Status Close() override { inc(2); return Status::OK(); } - Status Flush() override { inc(3); return Status::OK(); } - Status Sync() override { inc(4); return Status::OK(); } - Status Fsync() override { inc(5); return Status::OK(); } - void SetIOPriority(Env::IOPriority pri) override { inc(6); } - uint64_t GetFileSize() override { inc(7); return 0; } - void GetPreallocationStatus(size_t* block_size, - size_t* last_allocated_block) override { + Status Append(const Slice& /*data*/) override { + inc(1); + return Status::OK(); + } + + Status PositionedAppend(const Slice& /*data*/, + uint64_t /*offset*/) override { + inc(2); + return Status::OK(); + } + + Status Truncate(uint64_t /*size*/) override { + inc(3); + return Status::OK(); + } + + Status Close() override { + inc(4); + return Status::OK(); + } + + Status Flush() override { + inc(5); + return Status::OK(); + } + + Status Sync() override { + inc(6); + return Status::OK(); + } + + Status Fsync() override { + inc(7); + return Status::OK(); + } + + bool IsSyncThreadSafe() const override { inc(8); + return true; } - size_t GetUniqueId(char* id, size_t max_size) const override { + + bool use_direct_io() const override { inc(9); - return 0; + return true; } - Status InvalidateCache(size_t offset, size_t length) override { + + size_t GetRequiredBufferAlignment() const override { inc(10); + return 0; + } + + void SetIOPriority(Env::IOPriority /*pri*/) override { inc(11); } + + Env::IOPriority GetIOPriority() override { + inc(12); + return Env::IOPriority::IO_LOW; + } + + void SetWriteLifeTimeHint(Env::WriteLifeTimeHint /*hint*/) override { + inc(13); + } + + Env::WriteLifeTimeHint GetWriteLifeTimeHint() override { + inc(14); + return Env::WriteLifeTimeHint::WLTH_NOT_SET; + } + + uint64_t GetFileSize() override { + inc(15); + return 0; + } + + void SetPreallocationBlockSize(size_t /*size*/) override { inc(16); } + + void GetPreallocationStatus(size_t* /*block_size*/, + size_t* /*last_allocated_block*/) override { + inc(17); + } + + size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const override { + inc(18); + return 0; + } + + Status InvalidateCache(size_t /*offset*/, size_t /*length*/) override { + inc(19); return Status::OK(); } - protected: - Status Allocate(uint64_t offset, uint64_t len) override { - inc(11); + Status RangeSync(uint64_t /*offset*/, uint64_t /*nbytes*/) override { + inc(20); return Status::OK(); } - Status RangeSync(uint64_t offset, uint64_t nbytes) override { - inc(12); + + void PrepareWrite(size_t /*offset*/, size_t /*len*/) override { inc(21); } + + Status Allocate(uint64_t /*offset*/, uint64_t /*len*/) override { + inc(22); return Status::OK(); } public: - ~Base() { - inc(13); - } + ~Base() override { inc(23); } }; class Wrapper : public WritableFileWrapper { public: explicit Wrapper(WritableFile* target) : WritableFileWrapper(target) {} - - void CallProtectedMethods() { - Allocate(0, 0); - RangeSync(0, 0); - } }; int step = 0; @@ -1303,27 +1480,48 @@ TEST_P(EnvPosixTestWithParam, WritableFileWrapper) { Base b(&step); Wrapper w(&b); w.Append(Slice()); + w.PositionedAppend(Slice(), 0); + w.Truncate(0); w.Close(); w.Flush(); w.Sync(); w.Fsync(); + w.IsSyncThreadSafe(); + w.use_direct_io(); + w.GetRequiredBufferAlignment(); w.SetIOPriority(Env::IOPriority::IO_HIGH); + w.GetIOPriority(); + w.SetWriteLifeTimeHint(Env::WriteLifeTimeHint::WLTH_NOT_SET); + w.GetWriteLifeTimeHint(); w.GetFileSize(); + w.SetPreallocationBlockSize(0); w.GetPreallocationStatus(nullptr, nullptr); w.GetUniqueId(nullptr, 0); w.InvalidateCache(0, 0); - w.CallProtectedMethods(); + w.RangeSync(0, 0); + w.PrepareWrite(0, 0); + w.Allocate(0, 0); } - EXPECT_EQ(14, step); + EXPECT_EQ(24, step); } TEST_P(EnvPosixTestWithParam, PosixRandomRWFile) { - const std::string path = test::TmpDir(env_) + "/random_rw_file"; + const std::string path = test::PerThreadDBPath(env_, "random_rw_file"); env_->DeleteFile(path); std::unique_ptr file; + + // Cannot open non-existing file. + ASSERT_NOK(env_->NewRandomRWFile(path, &file, EnvOptions())); + + // Create the file using WriteableFile + { + std::unique_ptr wf; + ASSERT_OK(env_->NewWritableFile(path, &wf, EnvOptions())); + } + ASSERT_OK(env_->NewRandomRWFile(path, &file, EnvOptions())); char buf[10000]; @@ -1437,10 +1635,22 @@ class RandomRWFileWithMirrorString { }; TEST_P(EnvPosixTestWithParam, PosixRandomRWFileRandomized) { - const std::string path = test::TmpDir(env_) + "/random_rw_file_rand"; + const std::string path = test::PerThreadDBPath(env_, "random_rw_file_rand"); env_->DeleteFile(path); - unique_ptr file; + std::unique_ptr file; + +#ifdef OS_LINUX + // Cannot open non-existing file. + ASSERT_NOK(env_->NewRandomRWFile(path, &file, EnvOptions())); +#endif + + // Create the file using WriteableFile + { + std::unique_ptr wf; + ASSERT_OK(env_->NewWritableFile(path, &wf, EnvOptions())); + } + ASSERT_OK(env_->NewRandomRWFile(path, &file, EnvOptions())); RandomRWFileWithMirrorString file_with_mirror(file.get()); @@ -1470,6 +1680,72 @@ TEST_P(EnvPosixTestWithParam, PosixRandomRWFileRandomized) { env_->DeleteFile(path); } +class TestEnv : public EnvWrapper { + public: + explicit TestEnv() : EnvWrapper(Env::Default()), + close_count(0) { } + + class TestLogger : public Logger { + public: + using Logger::Logv; + TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; } + ~TestLogger() override { + if (!closed_) { + CloseHelper(); + } + } + void Logv(const char* /*format*/, va_list /*ap*/) override{}; + + protected: + Status CloseImpl() override { return CloseHelper(); } + + private: + Status CloseHelper() { + env->CloseCountInc();; + return Status::OK(); + } + TestEnv* env; + }; + + void CloseCountInc() { close_count++; } + + int GetCloseCount() { return close_count; } + + Status NewLogger(const std::string& /*fname*/, + std::shared_ptr* result) override { + result->reset(new TestLogger(this)); + return Status::OK(); + } + + private: + int close_count; +}; + +class EnvTest : public testing::Test {}; + +TEST_F(EnvTest, Close) { + TestEnv* env = new TestEnv(); + std::shared_ptr logger; + Status s; + + s = env->NewLogger("", &logger); + ASSERT_EQ(s, Status::OK()); + logger.get()->Close(); + ASSERT_EQ(env->GetCloseCount(), 1); + // Call Close() again. CloseHelper() should not be called again + logger.get()->Close(); + ASSERT_EQ(env->GetCloseCount(), 1); + logger.reset(); + ASSERT_EQ(env->GetCloseCount(), 1); + + s = env->NewLogger("", &logger); + ASSERT_EQ(s, Status::OK()); + logger.reset(); + ASSERT_EQ(env->GetCloseCount(), 2); + + delete env; +} + INSTANTIATE_TEST_CASE_P(DefaultEnvWithoutDirectIO, EnvPosixTestWithParam, ::testing::Values(std::pair(Env::Default(), false))); @@ -1480,8 +1756,8 @@ INSTANTIATE_TEST_CASE_P(DefaultEnvWithDirectIO, EnvPosixTestWithParam, #endif // !defined(ROCKSDB_LITE) #if !defined(ROCKSDB_LITE) && !defined(OS_WIN) -static unique_ptr chroot_env(NewChrootEnv(Env::Default(), - test::TmpDir(Env::Default()))); +static std::unique_ptr chroot_env( + NewChrootEnv(Env::Default(), test::TmpDir(Env::Default()))); INSTANTIATE_TEST_CASE_P( ChrootEnvWithoutDirectIO, EnvPosixTestWithParam, ::testing::Values(std::pair(chroot_env.get(), false))); diff --git a/thirdparty/rocksdb/env/io_posix.cc b/thirdparty/rocksdb/env/io_posix.cc index c5b14d3eff..628ed84130 100644 --- a/thirdparty/rocksdb/env/io_posix.cc +++ b/thirdparty/rocksdb/env/io_posix.cc @@ -35,6 +35,11 @@ #include "util/string_util.h" #include "util/sync_point.h" +#if defined(OS_LINUX) && !defined(F_SET_RW_HINT) +#define F_LINUX_SPECIFIC_BASE 1024 +#define F_SET_RW_HINT (F_LINUX_SPECIFIC_BASE + 12) +#endif + namespace rocksdb { // A wrapper for fadvise, if the platform doesn't support fadvise, @@ -43,6 +48,10 @@ int Fadvise(int fd, off_t offset, size_t len, int advice) { #ifdef OS_LINUX return posix_fadvise(fd, offset, len, advice); #else + (void)fd; + (void)offset; + (void)len; + (void)advice; return 0; // simply do nothing. #endif } @@ -74,10 +83,14 @@ size_t GetLogicalBufferSize(int __attribute__((__unused__)) fd) { if (!device_dir.empty() && device_dir.back() == '/') { device_dir.pop_back(); } - // NOTE: sda3 does not have a `queue/` subdir, only the parent sda has it. + // NOTE: sda3 and nvme0n1p1 do not have a `queue/` subdir, only the parent sda + // and nvme0n1 have it. // $ ls -al '/sys/dev/block/8:3' // lrwxrwxrwx. 1 root root 0 Jun 26 01:38 /sys/dev/block/8:3 -> // ../../block/sda/sda3 + // $ ls -al '/sys/dev/block/259:4' + // lrwxrwxrwx 1 root root 0 Jan 31 16:04 /sys/dev/block/259:4 -> + // ../../devices/pci0000:17/0000:17:00.0/0000:18:00.0/nvme/nvme0/nvme0n1/nvme0n1p1 size_t parent_end = device_dir.rfind('/', device_dir.length() - 1); if (parent_end == std::string::npos) { return kDefaultPageSize; @@ -86,8 +99,11 @@ size_t GetLogicalBufferSize(int __attribute__((__unused__)) fd) { if (parent_begin == std::string::npos) { return kDefaultPageSize; } - if (device_dir.substr(parent_begin + 1, parent_end - parent_begin - 1) != - "block") { + std::string parent = + device_dir.substr(parent_begin + 1, parent_end - parent_begin - 1); + std::string child = device_dir.substr(parent_end + 1, std::string::npos); + if (parent != "block" && + (child.compare(0, 4, "nvme") || child.find('p') != std::string::npos)) { device_dir = device_dir.substr(0, parent_end); } std::string fname = device_dir + "/queue/logical_block_size"; @@ -175,16 +191,15 @@ Status PosixSequentialFile::Read(size_t n, Slice* result, char* scratch) { Status PosixSequentialFile::PositionedRead(uint64_t offset, size_t n, Slice* result, char* scratch) { - if (use_direct_io()) { - assert(IsSectorAligned(offset, GetRequiredBufferAlignment())); - assert(IsSectorAligned(n, GetRequiredBufferAlignment())); - assert(IsSectorAligned(scratch, GetRequiredBufferAlignment())); - } + assert(use_direct_io()); + assert(IsSectorAligned(offset, GetRequiredBufferAlignment())); + assert(IsSectorAligned(n, GetRequiredBufferAlignment())); + assert(IsSectorAligned(scratch, GetRequiredBufferAlignment())); + Status s; ssize_t r = -1; size_t left = n; char* ptr = scratch; - assert(use_direct_io()); while (left > 0) { r = pread(fd_, ptr, left, static_cast(offset)); if (r <= 0) { @@ -222,6 +237,8 @@ Status PosixSequentialFile::Skip(uint64_t n) { Status PosixSequentialFile::InvalidateCache(size_t offset, size_t length) { #ifndef OS_LINUX + (void)offset; + (void)length; return Status::OK(); #else if (!use_direct_io()) { @@ -248,7 +265,6 @@ size_t PosixHelper::GetUniqueIdFromFile(int fd, char* id, size_t max_size) { struct stat buf; int result = fstat(fd, &buf); - assert(result != -1); if (result == -1) { return 0; } @@ -405,6 +421,8 @@ Status PosixRandomAccessFile::InvalidateCache(size_t offset, size_t length) { return Status::OK(); } #ifndef OS_LINUX + (void)offset; + (void)length; return Status::OK(); #else // free OS pages @@ -429,6 +447,9 @@ PosixMmapReadableFile::PosixMmapReadableFile(const int fd, void* base, size_t length, const EnvOptions& options) : fd_(fd), filename_(fname), mmapped_region_(base), length_(length) { +#ifdef NDEBUG + (void)options; +#endif fd_ = fd_ + 0; // suppress the warning for used variables assert(options.use_mmap_reads); assert(!options.use_direct_reads); @@ -440,10 +461,11 @@ PosixMmapReadableFile::~PosixMmapReadableFile() { fprintf(stdout, "failed to munmap %p length %" ROCKSDB_PRIszt " \n", mmapped_region_, length_); } + close(fd_); } Status PosixMmapReadableFile::Read(uint64_t offset, size_t n, Slice* result, - char* scratch) const { + char* /*scratch*/) const { Status s; if (offset > length_) { *result = Slice(); @@ -459,6 +481,8 @@ Status PosixMmapReadableFile::Read(uint64_t offset, size_t n, Slice* result, Status PosixMmapReadableFile::InvalidateCache(size_t offset, size_t length) { #ifndef OS_LINUX + (void)offset; + (void)length; return Status::OK(); #else // free OS pages @@ -567,6 +591,8 @@ PosixMmapFile::PosixMmapFile(const std::string& fname, int fd, size_t page_size, #ifdef ROCKSDB_FALLOCATE_PRESENT allow_fallocate_ = options.allow_fallocate; fallocate_with_keep_size_ = options.fallocate_with_keep_size; +#else + (void)options; #endif assert((page_size & (page_size - 1)) == 0); assert(options.use_mmap_writes); @@ -667,6 +693,8 @@ uint64_t PosixMmapFile::GetFileSize() { Status PosixMmapFile::InvalidateCache(size_t offset, size_t length) { #ifndef OS_LINUX + (void)offset; + (void)length; return Status::OK(); #else // free OS pages @@ -794,9 +822,10 @@ Status PosixWritableFile::Close() { // trim the extra space preallocated at the end of the file // NOTE(ljin): we probably don't want to surface failure as an IOError, // but it will be nice to log these errors. - int dummy __attribute__((unused)); + int dummy __attribute__((__unused__)); dummy = ftruncate(fd_, filesize_); -#if defined(ROCKSDB_FALLOCATE_PRESENT) && !defined(TRAVIS) +#if defined(ROCKSDB_FALLOCATE_PRESENT) && defined(FALLOC_FL_PUNCH_HOLE) && \ + !defined(TRAVIS) // in some file systems, ftruncate only trims trailing space if the // new file size is smaller than the current size. Calling fallocate // with FALLOC_FL_PUNCH_HOLE flag to explicitly release these unused @@ -858,11 +887,31 @@ bool PosixWritableFile::IsSyncThreadSafe() const { return true; } uint64_t PosixWritableFile::GetFileSize() { return filesize_; } +void PosixWritableFile::SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) { +#ifdef OS_LINUX +// Suppress Valgrind "Unimplemented functionality" error. +#ifndef ROCKSDB_VALGRIND_RUN + if (hint == write_hint_) { + return; + } + if (fcntl(fd_, F_SET_RW_HINT, &hint) == 0) { + write_hint_ = hint; + } +#else + (void)hint; +#endif // ROCKSDB_VALGRIND_RUN +#else + (void)hint; +#endif // OS_LINUX +} + Status PosixWritableFile::InvalidateCache(size_t offset, size_t length) { if (use_direct_io()) { return Status::OK(); } #ifndef OS_LINUX + (void)offset; + (void)length; return Status::OK(); #else // free OS pages @@ -922,7 +971,7 @@ size_t PosixWritableFile::GetUniqueId(char* id, size_t max_size) const { */ PosixRandomRWFile::PosixRandomRWFile(const std::string& fname, int fd, - const EnvOptions& options) + const EnvOptions& /*options*/) : filename_(fname), fd_(fd) {} PosixRandomRWFile::~PosixRandomRWFile() { @@ -1010,6 +1059,11 @@ Status PosixRandomRWFile::Close() { return Status::OK(); } +PosixMemoryMappedFileBuffer::~PosixMemoryMappedFileBuffer() { + // TODO should have error handling though not much we can do... + munmap(this->base_, length_); +} + /* * PosixDirectory */ diff --git a/thirdparty/rocksdb/env/io_posix.h b/thirdparty/rocksdb/env/io_posix.h index 69c98438f2..e6824d3e87 100644 --- a/thirdparty/rocksdb/env/io_posix.h +++ b/thirdparty/rocksdb/env/io_posix.h @@ -41,6 +41,9 @@ static Status IOError(const std::string& context, const std::string& file_name, strerror(err_number)); case ESTALE: return Status::IOError(Status::kStaleFile); + case ENOENT: + return Status::PathNotFound(IOErrorMsg(context, file_name), + strerror(err_number)); default: return Status::IOError(IOErrorMsg(context, file_name), strerror(err_number)); @@ -132,6 +135,7 @@ class PosixWritableFile : public WritableFile { virtual Status Fsync() override; virtual bool IsSyncThreadSafe() const override; virtual bool use_direct_io() const override { return use_direct_io_; } + virtual void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) override; virtual uint64_t GetFileSize() override; virtual Status InvalidateCache(size_t offset, size_t length) override; virtual size_t GetRequiredBufferAlignment() const override { @@ -201,7 +205,7 @@ class PosixMmapFile : public WritableFile { // Means Close() will properly take care of truncate // and it does not need any additional information - virtual Status Truncate(uint64_t size) override { return Status::OK(); } + virtual Status Truncate(uint64_t /*size*/) override { return Status::OK(); } virtual Status Close() override; virtual Status Append(const Slice& data) override; virtual Status Flush() override; @@ -235,6 +239,12 @@ class PosixRandomRWFile : public RandomRWFile { int fd_; }; +struct PosixMemoryMappedFileBuffer : public MemoryMappedFileBuffer { + PosixMemoryMappedFileBuffer(void* _base, size_t _length) + : MemoryMappedFileBuffer(_base, _length) {} + virtual ~PosixMemoryMappedFileBuffer(); +}; + class PosixDirectory : public Directory { public: explicit PosixDirectory(int fd) : fd_(fd) {} diff --git a/thirdparty/rocksdb/env/mock_env.cc b/thirdparty/rocksdb/env/mock_env.cc index 669011c4ee..793a0837ab 100644 --- a/thirdparty/rocksdb/env/mock_env.cc +++ b/thirdparty/rocksdb/env/mock_env.cc @@ -72,9 +72,7 @@ class MemFile { } } - uint64_t Size() const { - return size_; - } + uint64_t Size() const { return size_; } void Truncate(size_t size) { MutexLock lock(&mutex_); @@ -94,35 +92,37 @@ class MemFile { uint64_t end = std::min(start + 512, size_.load()); MutexLock lock(&mutex_); for (uint64_t pos = start; pos < end; ++pos) { - data_[pos] = static_cast(rnd_.Uniform(256)); + data_[static_cast(pos)] = static_cast(rnd_.Uniform(256)); } } Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const { MutexLock lock(&mutex_); const uint64_t available = Size() - std::min(Size(), offset); + size_t offset_ = static_cast(offset); if (n > available) { - n = available; + n = static_cast(available); } if (n == 0) { *result = Slice(); return Status::OK(); } if (scratch) { - memcpy(scratch, &(data_[offset]), n); + memcpy(scratch, &(data_[offset_]), n); *result = Slice(scratch, n); } else { - *result = Slice(&(data_[offset]), n); + *result = Slice(&(data_[offset_]), n); } return Status::OK(); } Status Write(uint64_t offset, const Slice& data) { MutexLock lock(&mutex_); + size_t offset_ = static_cast(offset); if (offset + data.size() > data_.size()) { - data_.resize(offset + data.size()); + data_.resize(offset_ + data.size()); } - data_.replace(offset, data.size(), data.data(), data.size()); + data_.replace(offset_, data.size(), data.data(), data.size()); size_ = data_.size(); modified_time_ = Now(); return Status::OK(); @@ -141,9 +141,7 @@ class MemFile { return Status::OK(); } - uint64_t ModifiedTime() const { - return modified_time_; - } + uint64_t ModifiedTime() const { return modified_time_; } private: uint64_t Now() { @@ -154,9 +152,7 @@ class MemFile { } // Private since only Unref() should be used to delete it. - ~MemFile() { - assert(refs_ == 0); - } + ~MemFile() { assert(refs_ == 0); } // No copying allowed. MemFile(const MemFile&); @@ -187,11 +183,9 @@ class MockSequentialFile : public SequentialFile { file_->Ref(); } - ~MockSequentialFile() { - file_->Unref(); - } + ~MockSequentialFile() override { file_->Unref(); } - virtual Status Read(size_t n, Slice* result, char* scratch) override { + Status Read(size_t n, Slice* result, char* scratch) override { Status s = file_->Read(pos_, n, result, scratch); if (s.ok()) { pos_ += result->size(); @@ -199,15 +193,15 @@ class MockSequentialFile : public SequentialFile { return s; } - virtual Status Skip(uint64_t n) override { + Status Skip(uint64_t n) override { if (pos_ > file_->Size()) { return Status::IOError("pos_ > file_->Size()"); } - const size_t available = file_->Size() - pos_; + const uint64_t available = file_->Size() - pos_; if (n > available) { n = available; } - pos_ += n; + pos_ += static_cast(n); return Status::OK(); } @@ -218,16 +212,12 @@ class MockSequentialFile : public SequentialFile { class MockRandomAccessFile : public RandomAccessFile { public: - explicit MockRandomAccessFile(MemFile* file) : file_(file) { - file_->Ref(); - } + explicit MockRandomAccessFile(MemFile* file) : file_(file) { file_->Ref(); } - ~MockRandomAccessFile() { - file_->Unref(); - } + ~MockRandomAccessFile() override { file_->Unref(); } - virtual Status Read(uint64_t offset, size_t n, Slice* result, - char* scratch) const override { + Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override { return file_->Read(offset, n, result, scratch); } @@ -239,22 +229,22 @@ class MockRandomRWFile : public RandomRWFile { public: explicit MockRandomRWFile(MemFile* file) : file_(file) { file_->Ref(); } - ~MockRandomRWFile() { file_->Unref(); } + ~MockRandomRWFile() override { file_->Unref(); } - virtual Status Write(uint64_t offset, const Slice& data) override { + Status Write(uint64_t offset, const Slice& data) override { return file_->Write(offset, data); } - virtual Status Read(uint64_t offset, size_t n, Slice* result, - char* scratch) const override { + Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override { return file_->Read(offset, n, result, scratch); } - virtual Status Close() override { return file_->Fsync(); } + Status Close() override { return file_->Fsync(); } - virtual Status Flush() override { return Status::OK(); } + Status Flush() override { return Status::OK(); } - virtual Status Sync() override { return file_->Fsync(); } + Status Sync() override { return file_->Fsync(); } private: MemFile* file_; @@ -263,17 +253,14 @@ class MockRandomRWFile : public RandomRWFile { class MockWritableFile : public WritableFile { public: MockWritableFile(MemFile* file, RateLimiter* rate_limiter) - : file_(file), - rate_limiter_(rate_limiter) { + : file_(file), rate_limiter_(rate_limiter) { file_->Ref(); } - ~MockWritableFile() { - file_->Unref(); - } + ~MockWritableFile() override { file_->Unref(); } - virtual Status Append(const Slice& data) override { - uint64_t bytes_written = 0; + Status Append(const Slice& data) override { + size_t bytes_written = 0; while (bytes_written < data.size()) { auto bytes = RequestToken(data.size() - bytes_written); Status s = file_->Append(Slice(data.data() + bytes_written, bytes)); @@ -284,23 +271,23 @@ class MockWritableFile : public WritableFile { } return Status::OK(); } - virtual Status Truncate(uint64_t size) override { - file_->Truncate(size); + Status Truncate(uint64_t size) override { + file_->Truncate(static_cast(size)); return Status::OK(); } - virtual Status Close() override { return file_->Fsync(); } + Status Close() override { return file_->Fsync(); } - virtual Status Flush() override { return Status::OK(); } + Status Flush() override { return Status::OK(); } - virtual Status Sync() override { return file_->Fsync(); } + Status Sync() override { return file_->Fsync(); } - virtual uint64_t GetFileSize() override { return file_->Size(); } + uint64_t GetFileSize() override { return file_->Size(); } private: inline size_t RequestToken(size_t bytes) { if (rate_limiter_ && io_priority_ < Env::IO_TOTAL) { - bytes = std::min(bytes, - static_cast(rate_limiter_->GetSingleBurstBytes())); + bytes = std::min( + bytes, static_cast(rate_limiter_->GetSingleBurstBytes())); rate_limiter_->Request(bytes, io_priority_); } return bytes; @@ -312,17 +299,14 @@ class MockWritableFile : public WritableFile { class MockEnvDirectory : public Directory { public: - virtual Status Fsync() override { return Status::OK(); } + Status Fsync() override { return Status::OK(); } }; class MockEnvFileLock : public FileLock { public: - explicit MockEnvFileLock(const std::string& fname) - : fname_(fname) {} + explicit MockEnvFileLock(const std::string& fname) : fname_(fname) {} - std::string FileName() const { - return fname_; - } + std::string FileName() const { return fname_; } private: const std::string fname_; @@ -335,7 +319,7 @@ class TestMemLogger : public Logger { static const uint64_t flush_every_seconds_ = 5; std::atomic_uint_fast64_t last_flush_micros_; Env* env_; - bool flush_pending_; + std::atomic flush_pending_; public: TestMemLogger(std::unique_ptr f, Env* env, @@ -346,10 +330,9 @@ class TestMemLogger : public Logger { last_flush_micros_(0), env_(env), flush_pending_(false) {} - virtual ~TestMemLogger() { - } + ~TestMemLogger() override {} - virtual void Flush() override { + void Flush() override { if (flush_pending_) { flush_pending_ = false; } @@ -357,7 +340,7 @@ class TestMemLogger : public Logger { } using Logger::Logv; - virtual void Logv(const char* format, va_list ap) override { + void Logv(const char* format, va_list ap) override { // We try twice: the first time with a fixed-size stack allocated buffer, // and the second time with a much larger dynamically allocated buffer. char buffer[500]; @@ -379,17 +362,12 @@ class TestMemLogger : public Logger { const time_t seconds = now_tv.tv_sec; struct tm t; memset(&t, 0, sizeof(t)); - auto ret __attribute__((__unused__)) = localtime_r(&seconds, &t); + struct tm* ret __attribute__((__unused__)); + ret = localtime_r(&seconds, &t); assert(ret); - p += snprintf(p, limit - p, - "%04d/%02d/%02d-%02d:%02d:%02d.%06d ", - t.tm_year + 1900, - t.tm_mon + 1, - t.tm_mday, - t.tm_hour, - t.tm_min, - t.tm_sec, - static_cast(now_tv.tv_usec)); + p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d ", + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, + t.tm_min, t.tm_sec, static_cast(now_tv.tv_usec)); // Print the message if (p < limit) { @@ -402,7 +380,7 @@ class TestMemLogger : public Logger { // Truncate to available space if necessary if (p >= limit) { if (iter == 0) { - continue; // Try again with larger buffer + continue; // Try again with larger buffer } else { p = limit - 1; } @@ -419,8 +397,8 @@ class TestMemLogger : public Logger { file_->Append(Slice(base, write_size)); flush_pending_ = true; log_size_ += write_size; - uint64_t now_micros = static_cast(now_tv.tv_sec) * 1000000 + - now_tv.tv_usec; + uint64_t now_micros = + static_cast(now_tv.tv_sec) * 1000000 + now_tv.tv_usec; if (now_micros - last_flush_micros_ >= flush_every_seconds_ * 1000000) { flush_pending_ = false; last_flush_micros_ = now_micros; @@ -444,14 +422,14 @@ MockEnv::~MockEnv() { } } - // Partial implementation of the Env interface. +// Partial implementation of the Env interface. Status MockEnv::NewSequentialFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& soptions) { + std::unique_ptr* result, + const EnvOptions& /*soptions*/) { auto fn = NormalizePath(fname); MutexLock lock(&mutex_); if (file_map_.find(fn) == file_map_.end()) { - *result = NULL; + *result = nullptr; return Status::IOError(fn, "File not found"); } auto* f = file_map_[fn]; @@ -463,12 +441,12 @@ Status MockEnv::NewSequentialFile(const std::string& fname, } Status MockEnv::NewRandomAccessFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& soptions) { + std::unique_ptr* result, + const EnvOptions& /*soptions*/) { auto fn = NormalizePath(fname); MutexLock lock(&mutex_); if (file_map_.find(fn) == file_map_.end()) { - *result = NULL; + *result = nullptr; return Status::IOError(fn, "File not found"); } auto* f = file_map_[fn]; @@ -480,12 +458,12 @@ Status MockEnv::NewRandomAccessFile(const std::string& fname, } Status MockEnv::NewRandomRWFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& soptions) { + std::unique_ptr* result, + const EnvOptions& /*soptions*/) { auto fn = NormalizePath(fname); MutexLock lock(&mutex_); if (file_map_.find(fn) == file_map_.end()) { - *result = NULL; + *result = nullptr; return Status::IOError(fn, "File not found"); } auto* f = file_map_[fn]; @@ -498,7 +476,7 @@ Status MockEnv::NewRandomRWFile(const std::string& fname, Status MockEnv::ReuseWritableFile(const std::string& fname, const std::string& old_fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) { auto s = RenameFile(old_fname, fname); if (!s.ok()) { @@ -509,7 +487,7 @@ Status MockEnv::ReuseWritableFile(const std::string& fname, } Status MockEnv::NewWritableFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& env_options) { auto fn = NormalizePath(fname); MutexLock lock(&mutex_); @@ -524,8 +502,8 @@ Status MockEnv::NewWritableFile(const std::string& fname, return Status::OK(); } -Status MockEnv::NewDirectory(const std::string& name, - unique_ptr* result) { +Status MockEnv::NewDirectory(const std::string& /*name*/, + std::unique_ptr* result) { result->reset(new MockEnvDirectory()); return Status::OK(); } @@ -540,8 +518,7 @@ Status MockEnv::FileExists(const std::string& fname) { // Now also check if fn exists as a dir for (const auto& iter : file_map_) { const std::string& filename = iter.first; - if (filename.size() >= fn.size() + 1 && - filename[fn.size()] == '/' && + if (filename.size() >= fn.size() + 1 && filename[fn.size()] == '/' && Slice(filename).starts_with(Slice(fn))) { return Status::OK(); } @@ -550,7 +527,7 @@ Status MockEnv::FileExists(const std::string& fname) { } Status MockEnv::GetChildren(const std::string& dir, - std::vector* result) { + std::vector* result) { auto d = NormalizePath(dir); bool found_dir = false; { @@ -566,8 +543,8 @@ Status MockEnv::GetChildren(const std::string& dir, found_dir = true; size_t next_slash = filename.find('/', d.size() + 1); if (next_slash != std::string::npos) { - result->push_back(filename.substr( - d.size() + 1, next_slash - d.size() - 1)); + result->push_back( + filename.substr(d.size() + 1, next_slash - d.size() - 1)); } else { result->push_back(filename.substr(d.size() + 1)); } @@ -598,6 +575,17 @@ Status MockEnv::DeleteFile(const std::string& fname) { return Status::OK(); } +Status MockEnv::Truncate(const std::string& fname, size_t size) { + auto fn = NormalizePath(fname); + MutexLock lock(&mutex_); + auto iter = file_map_.find(fn); + if (iter == file_map_.end()) { + return Status::IOError(fn, "File not found"); + } + iter->second->Truncate(size); + return Status::OK(); +} + Status MockEnv::CreateDir(const std::string& dirname) { auto dn = NormalizePath(dirname); if (file_map_.find(dn) == file_map_.end()) { @@ -632,7 +620,7 @@ Status MockEnv::GetFileSize(const std::string& fname, uint64_t* file_size) { } Status MockEnv::GetFileModificationTime(const std::string& fname, - uint64_t* time) { + uint64_t* time) { auto fn = NormalizePath(fname); MutexLock lock(&mutex_); auto iter = file_map_.find(fn); @@ -672,7 +660,7 @@ Status MockEnv::LinkFile(const std::string& src, const std::string& dest) { } Status MockEnv::NewLogger(const std::string& fname, - shared_ptr* result) { + std::shared_ptr* result) { auto fn = NormalizePath(fname); MutexLock lock(&mutex_); auto iter = file_map_.find(fn); @@ -748,18 +736,6 @@ uint64_t MockEnv::NowNanos() { return EnvWrapper::NowNanos() + fake_sleep_micros_.load() * 1000; } -// Non-virtual functions, specific to MockEnv -Status MockEnv::Truncate(const std::string& fname, size_t size) { - auto fn = NormalizePath(fname); - MutexLock lock(&mutex_); - auto iter = file_map_.find(fn); - if (iter == file_map_.end()) { - return Status::IOError(fn, "File not found"); - } - iter->second->Truncate(size); - return Status::OK(); -} - Status MockEnv::CorruptBuffer(const std::string& fname) { auto fn = NormalizePath(fname); MutexLock lock(&mutex_); @@ -792,7 +768,7 @@ Env* NewMemEnv(Env* base_env) { return new MockEnv(base_env); } #else // ROCKSDB_LITE -Env* NewMemEnv(Env* base_env) { return nullptr; } +Env* NewMemEnv(Env* /*base_env*/) { return nullptr; } #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/env/mock_env.h b/thirdparty/rocksdb/env/mock_env.h index ba1e5fa31e..87b8deaf8c 100644 --- a/thirdparty/rocksdb/env/mock_env.h +++ b/thirdparty/rocksdb/env/mock_env.h @@ -28,28 +28,28 @@ class MockEnv : public EnvWrapper { // Partial implementation of the Env interface. virtual Status NewSequentialFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& soptions) override; virtual Status NewRandomAccessFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& soptions) override; virtual Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) override; virtual Status ReuseWritableFile(const std::string& fname, const std::string& old_fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) override; virtual Status NewWritableFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& env_options) override; virtual Status NewDirectory(const std::string& name, - unique_ptr* result) override; + std::unique_ptr* result) override; virtual Status FileExists(const std::string& fname) override; @@ -60,6 +60,8 @@ class MockEnv : public EnvWrapper { virtual Status DeleteFile(const std::string& fname) override; + virtual Status Truncate(const std::string& fname, size_t size) override; + virtual Status CreateDir(const std::string& dirname) override; virtual Status CreateDirIfMissing(const std::string& dirname) override; @@ -79,7 +81,7 @@ class MockEnv : public EnvWrapper { const std::string& target) override; virtual Status NewLogger(const std::string& fname, - shared_ptr* result) override; + std::shared_ptr* result) override; virtual Status LockFile(const std::string& fname, FileLock** flock) override; @@ -92,9 +94,6 @@ class MockEnv : public EnvWrapper { virtual uint64_t NowMicros() override; virtual uint64_t NowNanos() override; - // Non-virtual functions, specific to MockEnv - Status Truncate(const std::string& fname, size_t size); - Status CorruptBuffer(const std::string& fname); // Doesn't really sleep, just affects output of GetCurrentTime(), NowMicros() diff --git a/thirdparty/rocksdb/env/mock_env_test.cc b/thirdparty/rocksdb/env/mock_env_test.cc index 19e259ccd8..2daf682e76 100644 --- a/thirdparty/rocksdb/env/mock_env_test.cc +++ b/thirdparty/rocksdb/env/mock_env_test.cc @@ -20,16 +20,14 @@ class MockEnvTest : public testing::Test { MockEnvTest() : env_(new MockEnv(Env::Default())) { } - ~MockEnvTest() { - delete env_; - } + ~MockEnvTest() override { delete env_; } }; TEST_F(MockEnvTest, Corrupt) { const std::string kGood = "this is a good string, synced to disk"; const std::string kCorrupted = "this part may be corrupted"; const std::string kFileName = "/dir/f"; - unique_ptr writable_file; + std::unique_ptr writable_file; ASSERT_OK(env_->NewWritableFile(kFileName, &writable_file, soptions_)); ASSERT_OK(writable_file->Append(kGood)); ASSERT_TRUE(writable_file->GetFileSize() == kGood.size()); @@ -37,7 +35,7 @@ TEST_F(MockEnvTest, Corrupt) { std::string scratch; scratch.resize(kGood.size() + kCorrupted.size() + 16); Slice result; - unique_ptr rand_file; + std::unique_ptr rand_file; ASSERT_OK(env_->NewRandomAccessFile(kFileName, &rand_file, soptions_)); ASSERT_OK(rand_file->Read(0, kGood.size(), &result, &(scratch[0]))); ASSERT_EQ(result.compare(kGood), 0); diff --git a/thirdparty/rocksdb/env/posix_logger.h b/thirdparty/rocksdb/env/posix_logger.h index 3ec6f574a3..401df6a3ff 100644 --- a/thirdparty/rocksdb/env/posix_logger.h +++ b/thirdparty/rocksdb/env/posix_logger.h @@ -24,6 +24,7 @@ #endif #include +#include "env/io_posix.h" #include "monitoring/iostats_context_imp.h" #include "rocksdb/env.h" #include "util/sync_point.h" @@ -32,6 +33,15 @@ namespace rocksdb { class PosixLogger : public Logger { private: + Status PosixCloseHelper() { + int ret; + + ret = fclose(file_); + if (ret) { + return IOError("Unable to close log file", "", ret); + } + return Status::OK(); + } FILE* file_; uint64_t (*gettid_)(); // Return the thread id for the current thread std::atomic_size_t log_size_; @@ -40,6 +50,10 @@ class PosixLogger : public Logger { std::atomic_uint_fast64_t last_flush_micros_; Env* env_; std::atomic flush_pending_; + + protected: + virtual Status CloseImpl() override { return PosixCloseHelper(); } + public: PosixLogger(FILE* f, uint64_t (*gettid)(), Env* env, const InfoLogLevel log_level = InfoLogLevel::ERROR_LEVEL) @@ -52,7 +66,10 @@ class PosixLogger : public Logger { env_(env), flush_pending_(false) {} virtual ~PosixLogger() { - fclose(file_); + if (!closed_) { + closed_ = true; + PosixCloseHelper(); + } } virtual void Flush() override { TEST_SYNC_POINT("PosixLogger::Flush:Begin1"); @@ -148,7 +165,6 @@ class PosixLogger : public Logger { size_t sz = fwrite(base, 1, write_size, file_); flush_pending_ = true; - assert(sz == write_size); if (sz > 0) { log_size_ += write_size; } diff --git a/thirdparty/rocksdb/examples/.gitignore b/thirdparty/rocksdb/examples/.gitignore index b5a05e44a2..823664ae1f 100644 --- a/thirdparty/rocksdb/examples/.gitignore +++ b/thirdparty/rocksdb/examples/.gitignore @@ -2,6 +2,7 @@ c_simple_example column_families_example compact_files_example compaction_filter_example +multi_processes_example optimistic_transaction_example options_file_example simple_example diff --git a/thirdparty/rocksdb/examples/Makefile b/thirdparty/rocksdb/examples/Makefile index 57cd1a75a1..27a6f0f421 100644 --- a/thirdparty/rocksdb/examples/Makefile +++ b/thirdparty/rocksdb/examples/Makefile @@ -43,8 +43,11 @@ transaction_example: librocksdb transaction_example.cc options_file_example: librocksdb options_file_example.cc $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) +multi_processes_example: librocksdb multi_processes_example.cc + $(CXX) $(CXXFLAGS) $@.cc -o$@ ../librocksdb.a -I../include -O2 -std=c++11 $(PLATFORM_LDFLAGS) $(PLATFORM_CXXFLAGS) $(EXEC_LDFLAGS) + clean: - rm -rf ./simple_example ./column_families_example ./compact_files_example ./compaction_filter_example ./c_simple_example c_simple_example.o ./optimistic_transaction_example ./transaction_example ./options_file_example + rm -rf ./simple_example ./column_families_example ./compact_files_example ./compaction_filter_example ./c_simple_example c_simple_example.o ./optimistic_transaction_example ./transaction_example ./options_file_example ./multi_processes_example librocksdb: cd .. && $(MAKE) static_lib diff --git a/thirdparty/rocksdb/examples/multi_processes_example.cc b/thirdparty/rocksdb/examples/multi_processes_example.cc new file mode 100644 index 0000000000..b1c1d02ba2 --- /dev/null +++ b/thirdparty/rocksdb/examples/multi_processes_example.cc @@ -0,0 +1,395 @@ +// Copyright (c) 2011-present, Facebook, 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). + +// How to use this example +// Open two terminals, in one of them, run `./multi_processes_example 0` to +// start a process running the primary instance. This will create a new DB in +// kDBPath. The process will run for a while inserting keys to the normal +// RocksDB database. +// Next, go to the other terminal and run `./multi_processes_example 1` to +// start a process running the secondary instance. This will create a secondary +// instance following the aforementioned primary instance. This process will +// run for a while, tailing the logs of the primary. After process with primary +// instance exits, this process will keep running until you hit 'CTRL+C'. + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(OS_LINUX) +#include +#include +#include +#include +#include +#include +#endif // !OS_LINUX + +#include "rocksdb/db.h" +#include "rocksdb/options.h" +#include "rocksdb/slice.h" + +using rocksdb::ColumnFamilyDescriptor; +using rocksdb::ColumnFamilyHandle; +using rocksdb::ColumnFamilyOptions; +using rocksdb::DB; +using rocksdb::FlushOptions; +using rocksdb::Iterator; +using rocksdb::Options; +using rocksdb::ReadOptions; +using rocksdb::Slice; +using rocksdb::Status; +using rocksdb::WriteOptions; + +const std::string kDBPath = "/tmp/rocksdb_multi_processes_example"; +const std::string kPrimaryStatusFile = + "/tmp/rocksdb_multi_processes_example_primary_status"; +const uint64_t kMaxKey = 600000; +const size_t kMaxValueLength = 256; +const size_t kNumKeysPerFlush = 1000; + +const std::vector& GetColumnFamilyNames() { + static std::vector column_family_names = { + rocksdb::kDefaultColumnFamilyName, "pikachu"}; + return column_family_names; +} + +inline bool IsLittleEndian() { + uint32_t x = 1; + return *reinterpret_cast(&x) != 0; +} + +static std::atomic& ShouldSecondaryWait() { + static std::atomic should_secondary_wait{1}; + return should_secondary_wait; +} + +static std::string Key(uint64_t k) { + std::string ret; + if (IsLittleEndian()) { + ret.append(reinterpret_cast(&k), sizeof(k)); + } else { + char buf[sizeof(k)]; + buf[0] = k & 0xff; + buf[1] = (k >> 8) & 0xff; + buf[2] = (k >> 16) & 0xff; + buf[3] = (k >> 24) & 0xff; + buf[4] = (k >> 32) & 0xff; + buf[5] = (k >> 40) & 0xff; + buf[6] = (k >> 48) & 0xff; + buf[7] = (k >> 56) & 0xff; + ret.append(buf, sizeof(k)); + } + size_t i = 0, j = ret.size() - 1; + while (i < j) { + char tmp = ret[i]; + ret[i] = ret[j]; + ret[j] = tmp; + ++i; + --j; + } + return ret; +} + +static uint64_t Key(std::string key) { + assert(key.size() == sizeof(uint64_t)); + size_t i = 0, j = key.size() - 1; + while (i < j) { + char tmp = key[i]; + key[i] = key[j]; + key[j] = tmp; + ++i; + --j; + } + uint64_t ret = 0; + if (IsLittleEndian()) { + memcpy(&ret, key.c_str(), sizeof(uint64_t)); + } else { + const char* buf = key.c_str(); + ret |= static_cast(buf[0]); + ret |= (static_cast(buf[1]) << 8); + ret |= (static_cast(buf[2]) << 16); + ret |= (static_cast(buf[3]) << 24); + ret |= (static_cast(buf[4]) << 32); + ret |= (static_cast(buf[5]) << 40); + ret |= (static_cast(buf[6]) << 48); + ret |= (static_cast(buf[7]) << 56); + } + return ret; +} + +static Slice GenerateRandomValue(const size_t max_length, char scratch[]) { + size_t sz = 1 + (std::rand() % max_length); + int rnd = std::rand(); + for (size_t i = 0; i != sz; ++i) { + scratch[i] = static_cast(rnd ^ i); + } + return Slice(scratch, sz); +} + +static bool ShouldCloseDB() { return true; } + +// TODO: port this example to other systems. It should be straightforward for +// POSIX-compliant systems. +#if defined(OS_LINUX) +void CreateDB() { + long my_pid = static_cast(getpid()); + Options options; + Status s = rocksdb::DestroyDB(kDBPath, options); + if (!s.ok()) { + fprintf(stderr, "[process %ld] Failed to destroy DB: %s\n", my_pid, + s.ToString().c_str()); + assert(false); + } + options.create_if_missing = true; + DB* db = nullptr; + s = DB::Open(options, kDBPath, &db); + if (!s.ok()) { + fprintf(stderr, "[process %ld] Failed to open DB: %s\n", my_pid, + s.ToString().c_str()); + assert(false); + } + std::vector handles; + ColumnFamilyOptions cf_opts(options); + for (const auto& cf_name : GetColumnFamilyNames()) { + if (rocksdb::kDefaultColumnFamilyName != cf_name) { + ColumnFamilyHandle* handle = nullptr; + s = db->CreateColumnFamily(cf_opts, cf_name, &handle); + if (!s.ok()) { + fprintf(stderr, "[process %ld] Failed to create CF %s: %s\n", my_pid, + cf_name.c_str(), s.ToString().c_str()); + assert(false); + } + handles.push_back(handle); + } + } + fprintf(stdout, "[process %ld] Column families created\n", my_pid); + for (auto h : handles) { + delete h; + } + handles.clear(); + delete db; +} + +void RunPrimary() { + long my_pid = static_cast(getpid()); + fprintf(stdout, "[process %ld] Primary instance starts\n", my_pid); + CreateDB(); + std::srand(time(nullptr)); + DB* db = nullptr; + Options options; + options.create_if_missing = false; + std::vector column_families; + for (const auto& cf_name : GetColumnFamilyNames()) { + column_families.push_back(ColumnFamilyDescriptor(cf_name, options)); + } + std::vector handles; + WriteOptions write_opts; + char val_buf[kMaxValueLength] = {0}; + uint64_t curr_key = 0; + while (curr_key < kMaxKey) { + Status s; + if (nullptr == db) { + s = DB::Open(options, kDBPath, column_families, &handles, &db); + if (!s.ok()) { + fprintf(stderr, "[process %ld] Failed to open DB: %s\n", my_pid, + s.ToString().c_str()); + assert(false); + } + } + assert(nullptr != db); + assert(handles.size() == GetColumnFamilyNames().size()); + for (auto h : handles) { + assert(nullptr != h); + for (size_t i = 0; i != kNumKeysPerFlush; ++i) { + Slice key = Key(curr_key + static_cast(i)); + Slice value = GenerateRandomValue(kMaxValueLength, val_buf); + s = db->Put(write_opts, h, key, value); + if (!s.ok()) { + fprintf(stderr, "[process %ld] Failed to insert\n", my_pid); + assert(false); + } + } + s = db->Flush(FlushOptions(), h); + if (!s.ok()) { + fprintf(stderr, "[process %ld] Failed to flush\n", my_pid); + assert(false); + } + } + curr_key += static_cast(kNumKeysPerFlush); + if (ShouldCloseDB()) { + for (auto h : handles) { + delete h; + } + handles.clear(); + delete db; + db = nullptr; + } + } + if (nullptr != db) { + for (auto h : handles) { + delete h; + } + handles.clear(); + delete db; + db = nullptr; + } + fprintf(stdout, "[process %ld] Finished adding keys\n", my_pid); +} + +void secondary_instance_sigint_handler(int signal) { + ShouldSecondaryWait().store(0, std::memory_order_relaxed); + fprintf(stdout, "\n"); + fflush(stdout); +}; + +void RunSecondary() { + ::signal(SIGINT, secondary_instance_sigint_handler); + long my_pid = static_cast(getpid()); + const std::string kSecondaryPath = + "/tmp/rocksdb_multi_processes_example_secondary"; + // Create directory if necessary + if (nullptr == opendir(kSecondaryPath.c_str())) { + int ret = + mkdir(kSecondaryPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (ret < 0) { + perror("failed to create directory for secondary instance"); + exit(0); + } + } + DB* db = nullptr; + Options options; + options.create_if_missing = false; + options.max_open_files = -1; + Status s = DB::OpenAsSecondary(options, kDBPath, kSecondaryPath, &db); + if (!s.ok()) { + fprintf(stderr, "[process %ld] Failed to open in secondary mode: %s\n", + my_pid, s.ToString().c_str()); + assert(false); + } else { + fprintf(stdout, "[process %ld] Secondary instance starts\n", my_pid); + } + + ReadOptions ropts; + ropts.verify_checksums = true; + ropts.total_order_seek = true; + + std::vector test_threads; + test_threads.emplace_back([&]() { + while (1 == ShouldSecondaryWait().load(std::memory_order_relaxed)) { + std::unique_ptr iter(db->NewIterator(ropts)); + iter->SeekToFirst(); + size_t count = 0; + for (; iter->Valid(); iter->Next()) { + ++count; + } + } + fprintf(stdout, "[process %ld] Range_scan thread finished\n", my_pid); + }); + + test_threads.emplace_back([&]() { + std::srand(time(nullptr)); + while (1 == ShouldSecondaryWait().load(std::memory_order_relaxed)) { + Slice key = Key(std::rand() % kMaxKey); + std::string value; + db->Get(ropts, key, &value); + } + fprintf(stdout, "[process %ld] Point lookup thread finished\n"); + }); + + uint64_t curr_key = 0; + while (1 == ShouldSecondaryWait().load(std::memory_order_relaxed)) { + s = db->TryCatchUpWithPrimary(); + if (!s.ok()) { + fprintf(stderr, + "[process %ld] error while trying to catch up with " + "primary %s\n", + my_pid, s.ToString().c_str()); + assert(false); + } + { + std::unique_ptr iter(db->NewIterator(ropts)); + if (!iter) { + fprintf(stderr, "[process %ld] Failed to create iterator\n", my_pid); + assert(false); + } + iter->SeekToLast(); + if (iter->Valid()) { + uint64_t curr_max_key = Key(iter->key().ToString()); + if (curr_max_key != curr_key) { + fprintf(stdout, "[process %ld] Observed key %" PRIu64 "\n", my_pid, + curr_key); + curr_key = curr_max_key; + } + } + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + s = db->TryCatchUpWithPrimary(); + if (!s.ok()) { + fprintf(stderr, + "[process %ld] error while trying to catch up with " + "primary %s\n", + my_pid, s.ToString().c_str()); + assert(false); + } + + std::vector column_families; + for (const auto& cf_name : GetColumnFamilyNames()) { + column_families.push_back(ColumnFamilyDescriptor(cf_name, options)); + } + std::vector handles; + DB* verification_db = nullptr; + s = DB::OpenForReadOnly(options, kDBPath, column_families, &handles, + &verification_db); + assert(s.ok()); + Iterator* iter1 = verification_db->NewIterator(ropts); + iter1->SeekToFirst(); + + Iterator* iter = db->NewIterator(ropts); + iter->SeekToFirst(); + for (; iter->Valid() && iter1->Valid(); iter->Next(), iter1->Next()) { + if (iter->key().ToString() != iter1->key().ToString()) { + fprintf(stderr, "%" PRIu64 "!= %" PRIu64 "\n", + Key(iter->key().ToString()), Key(iter1->key().ToString())); + assert(false); + } else if (iter->value().ToString() != iter1->value().ToString()) { + fprintf(stderr, "Value mismatch\n"); + assert(false); + } + } + fprintf(stdout, "[process %ld] Verification succeeded\n", my_pid); + for (auto& thr : test_threads) { + thr.join(); + } + delete iter; + delete iter1; + delete db; + delete verification_db; +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "%s <0 for primary, 1 for secondary>\n", argv[0]); + return 0; + } + if (atoi(argv[1]) == 0) { + RunPrimary(); + } else { + RunSecondary(); + } + return 0; +} +#else // OS_LINUX +int main() { + fpritnf(stderr, "Not implemented.\n"); + return 0; +} +#endif // !OS_LINUX diff --git a/thirdparty/rocksdb/examples/rocksdb_option_file_example.ini b/thirdparty/rocksdb/examples/rocksdb_option_file_example.ini index 8e07131b39..351f1ed010 100644 --- a/thirdparty/rocksdb/examples/rocksdb_option_file_example.ini +++ b/thirdparty/rocksdb/examples/rocksdb_option_file_example.ini @@ -138,6 +138,7 @@ block_restart_interval=16 cache_index_and_filter_blocks=false pin_l0_filter_and_index_blocks_in_cache=false + pin_top_level_index_and_filter=false index_type=kBinarySearch hash_index_allow_collision=true flush_block_policy_factory=FlushBlockBySizePolicyFactory diff --git a/thirdparty/rocksdb/hdfs/env_hdfs.h b/thirdparty/rocksdb/hdfs/env_hdfs.h index 3a62bc8cb9..903e32ef92 100644 --- a/thirdparty/rocksdb/hdfs/env_hdfs.h +++ b/thirdparty/rocksdb/hdfs/env_hdfs.h @@ -54,110 +54,109 @@ class HdfsEnv : public Env { hdfsDisconnect(fileSys_); } - virtual Status NewSequentialFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options); + Status NewSequentialFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; - virtual Status NewRandomAccessFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options); + Status NewRandomAccessFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; - virtual Status NewWritableFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options); + Status NewWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; - virtual Status NewDirectory(const std::string& name, - std::unique_ptr* result); + Status NewDirectory(const std::string& name, + std::unique_ptr* result) override; - virtual Status FileExists(const std::string& fname); + Status FileExists(const std::string& fname) override; - virtual Status GetChildren(const std::string& path, - std::vector* result); + Status GetChildren(const std::string& path, + std::vector* result) override; - virtual Status DeleteFile(const std::string& fname); + Status DeleteFile(const std::string& fname) override; - virtual Status CreateDir(const std::string& name); + Status CreateDir(const std::string& name) override; - virtual Status CreateDirIfMissing(const std::string& name); + Status CreateDirIfMissing(const std::string& name) override; - virtual Status DeleteDir(const std::string& name); + Status DeleteDir(const std::string& name) override; - virtual Status GetFileSize(const std::string& fname, uint64_t* size); + Status GetFileSize(const std::string& fname, uint64_t* size) override; - virtual Status GetFileModificationTime(const std::string& fname, - uint64_t* file_mtime); + Status GetFileModificationTime(const std::string& fname, + uint64_t* file_mtime) override; - virtual Status RenameFile(const std::string& src, const std::string& target); + Status RenameFile(const std::string& src, const std::string& target) override; - virtual Status LinkFile(const std::string& src, const std::string& target) { + Status LinkFile(const std::string& /*src*/, + const std::string& /*target*/) override { return Status::NotSupported(); // not supported } - virtual Status LockFile(const std::string& fname, FileLock** lock); + Status LockFile(const std::string& fname, FileLock** lock) override; - virtual Status UnlockFile(FileLock* lock); + Status UnlockFile(FileLock* lock) override; - virtual Status NewLogger(const std::string& fname, - std::shared_ptr* result); + Status NewLogger(const std::string& fname, + std::shared_ptr* result) override; - virtual void Schedule(void (*function)(void* arg), void* arg, - Priority pri = LOW, void* tag = nullptr, void (*unschedFunction)(void* arg) = 0) { + void Schedule(void (*function)(void* arg), void* arg, Priority pri = LOW, + void* tag = nullptr, + void (*unschedFunction)(void* arg) = 0) override { posixEnv->Schedule(function, arg, pri, tag, unschedFunction); } - virtual int UnSchedule(void* tag, Priority pri) { + int UnSchedule(void* tag, Priority pri) override { return posixEnv->UnSchedule(tag, pri); } - virtual void StartThread(void (*function)(void* arg), void* arg) { + void StartThread(void (*function)(void* arg), void* arg) override { posixEnv->StartThread(function, arg); } - virtual void WaitForJoin() { posixEnv->WaitForJoin(); } + void WaitForJoin() override { posixEnv->WaitForJoin(); } - virtual unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const - override { + unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const override { return posixEnv->GetThreadPoolQueueLen(pri); } - virtual Status GetTestDirectory(std::string* path) { + Status GetTestDirectory(std::string* path) override { return posixEnv->GetTestDirectory(path); } - virtual uint64_t NowMicros() { - return posixEnv->NowMicros(); - } + uint64_t NowMicros() override { return posixEnv->NowMicros(); } - virtual void SleepForMicroseconds(int micros) { + void SleepForMicroseconds(int micros) override { posixEnv->SleepForMicroseconds(micros); } - virtual Status GetHostName(char* name, uint64_t len) { + Status GetHostName(char* name, uint64_t len) override { return posixEnv->GetHostName(name, len); } - virtual Status GetCurrentTime(int64_t* unix_time) { + Status GetCurrentTime(int64_t* unix_time) override { return posixEnv->GetCurrentTime(unix_time); } - virtual Status GetAbsolutePath(const std::string& db_path, - std::string* output_path) { + Status GetAbsolutePath(const std::string& db_path, + std::string* output_path) override { return posixEnv->GetAbsolutePath(db_path, output_path); } - virtual void SetBackgroundThreads(int number, Priority pri = LOW) { + void SetBackgroundThreads(int number, Priority pri = LOW) override { posixEnv->SetBackgroundThreads(number, pri); } - virtual int GetBackgroundThreads(Priority pri = LOW) { + int GetBackgroundThreads(Priority pri = LOW) override { return posixEnv->GetBackgroundThreads(pri); } - virtual void IncBackgroundThreadsIfNeeded(int number, Priority pri) override { + void IncBackgroundThreadsIfNeeded(int number, Priority pri) override { posixEnv->IncBackgroundThreadsIfNeeded(number, pri); } - virtual std::string TimeToString(uint64_t number) { + std::string TimeToString(uint64_t number) override { return posixEnv->TimeToString(number); } @@ -166,9 +165,7 @@ class HdfsEnv : public Env { return (uint64_t)pthread_self(); } - virtual uint64_t GetThreadID() const override { - return HdfsEnv::gettid(); - } + uint64_t GetThreadID() const override { return HdfsEnv::gettid(); } private: std::string fsname_; // string of the form "hdfs://hostname:port/" @@ -206,7 +203,7 @@ class HdfsEnv : public Env { std::string host(parts[0]); std::string remaining(parts[1]); - int rem = remaining.find(pathsep); + int rem = static_cast(remaining.find(pathsep)); std::string portStr = (rem == 0 ? remaining : remaining.substr(0, rem)); @@ -245,7 +242,7 @@ static const Status notsup; class HdfsEnv : public Env { public: - explicit HdfsEnv(const std::string& fsname) { + explicit HdfsEnv(const std::string& /*fsname*/) { fprintf(stderr, "You have not build rocksdb with HDFS support\n"); fprintf(stderr, "Please see hdfs/README for details\n"); abort(); @@ -255,115 +252,129 @@ class HdfsEnv : public Env { } virtual Status NewSequentialFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) override; - virtual Status NewRandomAccessFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + virtual Status NewRandomAccessFile( + const std::string& /*fname*/, + std::unique_ptr* /*result*/, + const EnvOptions& /*options*/) override { return notsup; } - virtual Status NewWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override { + virtual Status NewWritableFile(const std::string& /*fname*/, + std::unique_ptr* /*result*/, + const EnvOptions& /*options*/) override { return notsup; } - virtual Status NewDirectory(const std::string& name, - unique_ptr* result) override { + virtual Status NewDirectory(const std::string& /*name*/, + std::unique_ptr* /*result*/) override { return notsup; } - virtual Status FileExists(const std::string& fname) override { + virtual Status FileExists(const std::string& /*fname*/) override { return notsup; } - virtual Status GetChildren(const std::string& path, - std::vector* result) override { + virtual Status GetChildren(const std::string& /*path*/, + std::vector* /*result*/) override { return notsup; } - virtual Status DeleteFile(const std::string& fname) override { + virtual Status DeleteFile(const std::string& /*fname*/) override { return notsup; } - virtual Status CreateDir(const std::string& name) override { return notsup; } + virtual Status CreateDir(const std::string& /*name*/) override { + return notsup; + } - virtual Status CreateDirIfMissing(const std::string& name) override { + virtual Status CreateDirIfMissing(const std::string& /*name*/) override { return notsup; } - virtual Status DeleteDir(const std::string& name) override { return notsup; } + virtual Status DeleteDir(const std::string& /*name*/) override { + return notsup; + } - virtual Status GetFileSize(const std::string& fname, - uint64_t* size) override { + virtual Status GetFileSize(const std::string& /*fname*/, + uint64_t* /*size*/) override { return notsup; } - virtual Status GetFileModificationTime(const std::string& fname, - uint64_t* time) override { + virtual Status GetFileModificationTime(const std::string& /*fname*/, + uint64_t* /*time*/) override { return notsup; } - virtual Status RenameFile(const std::string& src, - const std::string& target) override { + virtual Status RenameFile(const std::string& /*src*/, + const std::string& /*target*/) override { return notsup; } - virtual Status LinkFile(const std::string& src, - const std::string& target) override { + virtual Status LinkFile(const std::string& /*src*/, + const std::string& /*target*/) override { return notsup; } - virtual Status LockFile(const std::string& fname, FileLock** lock) override { + virtual Status LockFile(const std::string& /*fname*/, + FileLock** /*lock*/) override { return notsup; } - virtual Status UnlockFile(FileLock* lock) override { return notsup; } + virtual Status UnlockFile(FileLock* /*lock*/) override { return notsup; } - virtual Status NewLogger(const std::string& fname, - shared_ptr* result) override { + virtual Status NewLogger(const std::string& /*fname*/, + std::shared_ptr* /*result*/) override { return notsup; } - virtual void Schedule(void (*function)(void* arg), void* arg, - Priority pri = LOW, void* tag = nullptr, - void (*unschedFunction)(void* arg) = 0) override {} + virtual void Schedule(void (* /*function*/)(void* arg), void* /*arg*/, + Priority /*pri*/ = LOW, void* /*tag*/ = nullptr, + void (* /*unschedFunction*/)(void* arg) = 0) override {} - virtual int UnSchedule(void* tag, Priority pri) override { return 0; } + virtual int UnSchedule(void* /*tag*/, Priority /*pri*/) override { return 0; } - virtual void StartThread(void (*function)(void* arg), void* arg) override {} + virtual void StartThread(void (* /*function*/)(void* arg), + void* /*arg*/) override {} virtual void WaitForJoin() override {} virtual unsigned int GetThreadPoolQueueLen( - Priority pri = LOW) const override { + Priority /*pri*/ = LOW) const override { return 0; } - virtual Status GetTestDirectory(std::string* path) override { return notsup; } + virtual Status GetTestDirectory(std::string* /*path*/) override { + return notsup; + } virtual uint64_t NowMicros() override { return 0; } - virtual void SleepForMicroseconds(int micros) override {} + virtual void SleepForMicroseconds(int /*micros*/) override {} - virtual Status GetHostName(char* name, uint64_t len) override { + virtual Status GetHostName(char* /*name*/, uint64_t /*len*/) override { return notsup; } - virtual Status GetCurrentTime(int64_t* unix_time) override { return notsup; } + virtual Status GetCurrentTime(int64_t* /*unix_time*/) override { + return notsup; + } - virtual Status GetAbsolutePath(const std::string& db_path, - std::string* outputpath) override { + virtual Status GetAbsolutePath(const std::string& /*db_path*/, + std::string* /*outputpath*/) override { return notsup; } - virtual void SetBackgroundThreads(int number, Priority pri = LOW) override {} - virtual int GetBackgroundThreads(Priority pri = LOW) override { return 0; } - virtual void IncBackgroundThreadsIfNeeded(int number, Priority pri) override { + virtual void SetBackgroundThreads(int /*number*/, + Priority /*pri*/ = LOW) override {} + virtual int GetBackgroundThreads(Priority /*pri*/ = LOW) override { + return 0; } - virtual std::string TimeToString(uint64_t number) override { return ""; } + virtual void IncBackgroundThreadsIfNeeded(int /*number*/, + Priority /*pri*/) override {} + virtual std::string TimeToString(uint64_t /*number*/) override { return ""; } virtual uint64_t GetThreadID() const override { return 0; diff --git a/thirdparty/rocksdb/hdfs/setup.sh b/thirdparty/rocksdb/hdfs/setup.sh old mode 100644 new mode 100755 index ac69b525df..ba76ec2090 --- a/thirdparty/rocksdb/hdfs/setup.sh +++ b/thirdparty/rocksdb/hdfs/setup.sh @@ -1,7 +1,8 @@ +# shellcheck disable=SC2148 export USE_HDFS=1 -export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/jre/lib/amd64:/usr/lib/hadoop/lib/native +export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server:$JAVA_HOME/jre/lib/amd64:$HADOOP_HOME/lib/native -export CLASSPATH= +export CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath --glob` for f in `find /usr/lib/hadoop-hdfs | grep jar`; do export CLASSPATH=$CLASSPATH:$f; done for f in `find /usr/lib/hadoop | grep jar`; do export CLASSPATH=$CLASSPATH:$f; done for f in `find /usr/lib/hadoop/client | grep jar`; do export CLASSPATH=$CLASSPATH:$f; done diff --git a/thirdparty/rocksdb/include/rocksdb/advanced_options.h b/thirdparty/rocksdb/include/rocksdb/advanced_options.h index 6f45134a68..b7ab7c584b 100644 --- a/thirdparty/rocksdb/include/rocksdb/advanced_options.h +++ b/thirdparty/rocksdb/include/rocksdb/advanced_options.h @@ -62,13 +62,6 @@ struct CompactionOptionsFIFO { // Default: 1GB uint64_t max_table_files_size; - // Drop files older than TTL. TTL based deletion will take precedence over - // size based deletion if ttl > 0. - // delete if sst_file_creation_time < (current_time - ttl) - // unit: seconds. Ex: 1 day = 1 * 24 * 60 * 60 - // Default: 0 (disabled) - uint64_t ttl = 0; - // If true, try to do compaction to compact smaller files into larger ones. // Minimum files to compact follows options.level0_file_num_compaction_trigger // and compaction won't trigger if average compact bytes per del file is @@ -78,35 +71,78 @@ struct CompactionOptionsFIFO { bool allow_compaction = false; CompactionOptionsFIFO() : max_table_files_size(1 * 1024 * 1024 * 1024) {} - CompactionOptionsFIFO(uint64_t _max_table_files_size, bool _allow_compaction, - uint64_t _ttl = 0) + CompactionOptionsFIFO(uint64_t _max_table_files_size, bool _allow_compaction) : max_table_files_size(_max_table_files_size), - ttl(_ttl), allow_compaction(_allow_compaction) {} }; // Compression options for different compression algorithms like Zlib struct CompressionOptions { + // RocksDB's generic default compression level. Internally it'll be translated + // to the default compression level specific to the library being used (see + // comment above `ColumnFamilyOptions::compression`). + // + // The default value is the max 16-bit int as it'll be written out in OPTIONS + // file, which should be portable. + const static int kDefaultCompressionLevel = 32767; + int window_bits; int level; int strategy; - // Maximum size of dictionary used to prime the compression library. Currently - // this dictionary will be constructed by sampling the first output file in a - // subcompaction when the target level is bottommost. This dictionary will be - // loaded into the compression library before compressing/uncompressing each - // data block of subsequent files in the subcompaction. Effectively, this - // improves compression ratios when there are repetitions across data blocks. - // A value of 0 indicates the feature is disabled. + + // Maximum size of dictionaries used to prime the compression library. + // Enabling dictionary can improve compression ratios when there are + // repetitions across data blocks. + // + // The dictionary is created by sampling the SST file data. If + // `zstd_max_train_bytes` is nonzero, the samples are passed through zstd's + // dictionary generator. Otherwise, the random samples are used directly as + // the dictionary. + // + // When compression dictionary is disabled, we compress and write each block + // before buffering data for the next one. When compression dictionary is + // enabled, we buffer all SST file data in-memory so we can sample it, as data + // can only be compressed and written after the dictionary has been finalized. + // So users of this feature may see increased memory usage. + // // Default: 0. uint32_t max_dict_bytes; + // Maximum size of training data passed to zstd's dictionary trainer. Using + // zstd's dictionary trainer can achieve even better compression ratio + // improvements than using `max_dict_bytes` alone. + // + // The training data will be used to generate a dictionary of max_dict_bytes. + // + // Default: 0. + uint32_t zstd_max_train_bytes; + + // When the compression options are set by the user, it will be set to "true". + // For bottommost_compression_opts, to enable it, user must set enabled=true. + // Otherwise, bottommost compression will use compression_opts as default + // compression options. + // + // For compression_opts, if compression_opts.enabled=false, it is still + // used as compression options for compression process. + // + // Default: false. + bool enabled; + CompressionOptions() - : window_bits(-14), level(-1), strategy(0), max_dict_bytes(0) {} - CompressionOptions(int wbits, int _lev, int _strategy, int _max_dict_bytes) + : window_bits(-14), + level(kDefaultCompressionLevel), + strategy(0), + max_dict_bytes(0), + zstd_max_train_bytes(0), + enabled(false) {} + CompressionOptions(int wbits, int _lev, int _strategy, int _max_dict_bytes, + int _zstd_max_train_bytes, bool _enabled) : window_bits(wbits), level(_lev), strategy(_strategy), - max_dict_bytes(_max_dict_bytes) {} + max_dict_bytes(_max_dict_bytes), + zstd_max_train_bytes(_zstd_max_train_bytes), + enabled(_enabled) {} }; enum UpdateStatus { // Return status For inplace update callback @@ -229,13 +265,22 @@ struct AdvancedColumnFamilyOptions { // if prefix_extractor is set and memtable_prefix_bloom_size_ratio is not 0, // create prefix bloom for memtable with the size of // write_buffer_size * memtable_prefix_bloom_size_ratio. - // If it is larger than 0.25, it is santinized to 0.25. + // If it is larger than 0.25, it is sanitized to 0.25. // // Default: 0 (disable) // // Dynamically changeable through SetOptions() API double memtable_prefix_bloom_size_ratio = 0.0; + // Enable whole key bloom filter in memtable. Note this will only take effect + // if memtable_prefix_bloom_size_ratio is not 0. Enabling whole key filtering + // can potentially reduce CPU usage for point-look-ups. + // + // Default: false (disable) + // + // Dynamically changeable through SetOptions() API + bool memtable_whole_key_filtering = false; + // Page size for huge page for the arena used by the memtable. If <=0, it // won't allocate from huge page but from malloc. // Users are responsible to reserve huge pages for it to be allocated. For @@ -368,6 +413,7 @@ struct AdvancedColumnFamilyOptions { // of the level. // At the same time max_bytes_for_level_multiplier and // max_bytes_for_level_multiplier_additional are still satisfied. + // (When L0 is too large, we make some adjustment. See below.) // // With this option on, from an empty DB, we make last level the base level, // which means merging L0 data into the last level, until it exceeds @@ -406,13 +452,34 @@ struct AdvancedColumnFamilyOptions { // max_bytes_for_level_base, for a more predictable LSM tree shape. It is // useful to limit worse case space amplification. // + // + // If the compaction from L0 is lagged behind, a special mode will be turned + // on to prioritize write amplification against max_bytes_for_level_multiplier + // or max_bytes_for_level_base. The L0 compaction is lagged behind by looking + // at number of L0 files and total L0 size. If number of L0 files is at least + // the double of level0_file_num_compaction_trigger, or the total size is + // at least max_bytes_for_level_base, this mode is on. The target of L1 grows + // to the actual data size in L0, and then determine the target for each level + // so that each level will have the same level multiplier. + // + // For example, when L0 size is 100MB, the size of last level is 1600MB, + // max_bytes_for_level_base = 80MB, and max_bytes_for_level_multiplier = 10. + // Since L0 size is larger than max_bytes_for_level_base, this is a L0 + // compaction backlogged mode. So that the L1 size is determined to be 100MB. + // Based on max_bytes_for_level_multiplier = 10, at least 3 non-0 levels will + // be needed. The level multiplier will be calculated to be 4 and the three + // levels' target to be [100MB, 400MB, 1600MB]. + // + // In this mode, The number of levels will be no more than the normal mode, + // and the level multiplier will be lower. The write amplification will + // likely to be reduced. + // + // // max_bytes_for_level_multiplier_additional is ignored with this flag on. // // Turning this feature on or off for an existing DB can cause unexpected // LSM tree structure so it's not recommended. // - // NOTE: this option is experimental - // // Default: false bool level_compaction_dynamic_level_bytes = false; @@ -435,19 +502,25 @@ struct AdvancedColumnFamilyOptions { // threshold. But it's not guaranteed. // Value 0 will be sanitized. // - // Default: result.target_file_size_base * 25 + // Default: target_file_size_base * 25 + // + // Dynamically changeable through SetOptions() API uint64_t max_compaction_bytes = 0; // All writes will be slowed down to at least delayed_write_rate if estimated // bytes needed to be compaction exceed this threshold. // // Default: 64GB + // + // Dynamically changeable through SetOptions() API uint64_t soft_pending_compaction_bytes_limit = 64 * 1073741824ull; // All writes are stopped if estimated bytes needed to be compaction exceed // this threshold. // // Default: 256GB + // + // Dynamically changeable through SetOptions() API uint64_t hard_pending_compaction_bytes_limit = 256 * 1073741824ull; // The compaction style. Default: kCompactionStyleLevel @@ -455,13 +528,21 @@ struct AdvancedColumnFamilyOptions { // If level compaction_style = kCompactionStyleLevel, for each level, // which files are prioritized to be picked to compact. - // Default: kByCompensatedSize - CompactionPri compaction_pri = kByCompensatedSize; + // Default: kMinOverlappingRatio + CompactionPri compaction_pri = kMinOverlappingRatio; // The options needed to support Universal Style compactions + // + // Dynamically changeable through SetOptions() API + // Dynamic change example: + // SetOptions("compaction_options_universal", "{size_ratio=2;}") CompactionOptionsUniversal compaction_options_universal; // The options for FIFO compaction style + // + // Dynamically changeable through SetOptions() API + // Dynamic change example: + // SetOptions("compaction_options_fifo", "{max_table_files_size=100;}") CompactionOptionsFIFO compaction_options_fifo; // An iteration->Next() sequentially skips over keys with the same @@ -531,19 +612,44 @@ struct AdvancedColumnFamilyOptions { bool optimize_filters_for_hits = false; // After writing every SST file, reopen it and read all the keys. + // // Default: false + // + // Dynamically changeable through SetOptions() API bool paranoid_file_checks = false; - // In debug mode, RocksDB run consistency checks on the LSM everytime the LSM + // In debug mode, RocksDB run consistency checks on the LSM every time the LSM // change (Flush, Compaction, AddFile). These checks are disabled in release // mode, use this option to enable them in release mode as well. // Default: false bool force_consistency_checks = false; // Measure IO stats in compactions and flushes, if true. + // // Default: false + // + // Dynamically changeable through SetOptions() API bool report_bg_io_stats = false; + // Files older than TTL will go through the compaction process. + // Supported in Level and FIFO compaction. + // Pre-req: This needs max_open_files to be set to -1. + // In Level: Non-bottom-level files older than TTL will go through the + // compation process. + // In FIFO: Files older than TTL will be deleted. + // unit: seconds. Ex: 1 day = 1 * 24 * 60 * 60 + // + // Default: 0 (disabled) + // + // Dynamically changeable through SetOptions() API + uint64_t ttl = 0; + + // If this option is set then 1 in N blocks are compressed + // using a fast (lz4) and slow (zstd) compression algorithm. + // The compressibility is reported as stats and the stored + // data is left uncompressed (unless compression is also requested). + uint64_t sample_for_compression = 0; + // Create ColumnFamilyOptions with default values for all fields AdvancedColumnFamilyOptions(); // Create ColumnFamilyOptions from Options diff --git a/thirdparty/rocksdb/include/rocksdb/c.h b/thirdparty/rocksdb/include/rocksdb/c.h index 2269f7261c..05699492c9 100644 --- a/thirdparty/rocksdb/include/rocksdb/c.h +++ b/thirdparty/rocksdb/include/rocksdb/c.h @@ -42,9 +42,6 @@ (5) All of the pointer arguments must be non-NULL. */ -#ifndef STORAGE_ROCKSDB_INCLUDE_C_H_ -#define STORAGE_ROCKSDB_INCLUDE_C_H_ - #pragma once #ifdef _WIN32 @@ -113,20 +110,30 @@ typedef struct rocksdb_envoptions_t rocksdb_envoptions_t; typedef struct rocksdb_ingestexternalfileoptions_t rocksdb_ingestexternalfileoptions_t; typedef struct rocksdb_sstfilewriter_t rocksdb_sstfilewriter_t; typedef struct rocksdb_ratelimiter_t rocksdb_ratelimiter_t; +typedef struct rocksdb_perfcontext_t rocksdb_perfcontext_t; typedef struct rocksdb_pinnableslice_t rocksdb_pinnableslice_t; typedef struct rocksdb_transactiondb_options_t rocksdb_transactiondb_options_t; typedef struct rocksdb_transactiondb_t rocksdb_transactiondb_t; typedef struct rocksdb_transaction_options_t rocksdb_transaction_options_t; -typedef struct rocksdb_optimistictransactiondb_t rocksdb_optimistictransactiondb_t; -typedef struct rocksdb_optimistictransaction_options_t rocksdb_optimistictransaction_options_t; +typedef struct rocksdb_optimistictransactiondb_t + rocksdb_optimistictransactiondb_t; +typedef struct rocksdb_optimistictransaction_options_t + rocksdb_optimistictransaction_options_t; typedef struct rocksdb_transaction_t rocksdb_transaction_t; typedef struct rocksdb_checkpoint_t rocksdb_checkpoint_t; +typedef struct rocksdb_wal_iterator_t rocksdb_wal_iterator_t; +typedef struct rocksdb_wal_readoptions_t rocksdb_wal_readoptions_t; +typedef struct rocksdb_memory_consumers_t rocksdb_memory_consumers_t; +typedef struct rocksdb_memory_usage_t rocksdb_memory_usage_t; /* DB operations */ extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open( const rocksdb_options_t* options, const char* name, char** errptr); +extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_with_ttl( + const rocksdb_options_t* options, const char* name, int ttl, char** errptr); + extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_for_read_only( const rocksdb_options_t* options, const char* name, unsigned char error_if_log_file_exist, char** errptr); @@ -137,6 +144,10 @@ extern ROCKSDB_LIBRARY_API rocksdb_backup_engine_t* rocksdb_backup_engine_open( extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_create_new_backup( rocksdb_backup_engine_t* be, rocksdb_t* db, char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_create_new_backup_flush( + rocksdb_backup_engine_t* be, rocksdb_t* db, unsigned char flush_before_backup, + char** errptr); + extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_purge_old_backups( rocksdb_backup_engine_t* be, uint32_t num_backups_to_keep, char** errptr); @@ -147,6 +158,10 @@ extern ROCKSDB_LIBRARY_API void rocksdb_restore_options_destroy( extern ROCKSDB_LIBRARY_API void rocksdb_restore_options_set_keep_log_files( rocksdb_restore_options_t* opt, int v); +extern ROCKSDB_LIBRARY_API void +rocksdb_backup_engine_verify_backup(rocksdb_backup_engine_t* be, + uint32_t backup_id, char** errptr); + extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_restore_db_from_latest_backup( rocksdb_backup_engine_t* be, const char* db_dir, const char* wal_dir, @@ -291,6 +306,12 @@ extern ROCKSDB_LIBRARY_API void rocksdb_multi_get_cf( extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_create_iterator( rocksdb_t* db, const rocksdb_readoptions_t* options); +extern ROCKSDB_LIBRARY_API rocksdb_wal_iterator_t* rocksdb_get_updates_since( + rocksdb_t* db, uint64_t seq_number, + const rocksdb_wal_readoptions_t* options, + char** errptr +); + extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_create_iterator_cf( rocksdb_t* db, const rocksdb_readoptions_t* options, rocksdb_column_family_handle_t* column_family); @@ -394,6 +415,14 @@ extern ROCKSDB_LIBRARY_API const char* rocksdb_iter_value( extern ROCKSDB_LIBRARY_API void rocksdb_iter_get_error( const rocksdb_iterator_t*, char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_wal_iter_next(rocksdb_wal_iterator_t* iter); +extern ROCKSDB_LIBRARY_API unsigned char rocksdb_wal_iter_valid( + const rocksdb_wal_iterator_t*); +extern ROCKSDB_LIBRARY_API void rocksdb_wal_iter_status (const rocksdb_wal_iterator_t* iter, char** errptr) ; +extern ROCKSDB_LIBRARY_API rocksdb_writebatch_t* rocksdb_wal_iter_get_batch (const rocksdb_wal_iterator_t* iter, uint64_t* seq) ; +extern ROCKSDB_LIBRARY_API uint64_t rocksdb_get_latest_sequence_number (rocksdb_t *db); +extern ROCKSDB_LIBRARY_API void rocksdb_wal_iter_destroy (const rocksdb_wal_iterator_t* iter) ; + /* Write batch */ extern ROCKSDB_LIBRARY_API rocksdb_writebatch_t* rocksdb_writebatch_create(); @@ -608,7 +637,6 @@ extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_writebatch_wi_create_iter rocksdb_iterator_t* base_iterator, rocksdb_column_family_handle_t* cf); - /* Block based table options */ extern ROCKSDB_LIBRARY_API rocksdb_block_based_table_options_t* @@ -623,6 +651,18 @@ rocksdb_block_based_options_set_block_size_deviation( extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_block_restart_interval( rocksdb_block_based_table_options_t* options, int block_restart_interval); +extern ROCKSDB_LIBRARY_API void +rocksdb_block_based_options_set_index_block_restart_interval( + rocksdb_block_based_table_options_t* options, int index_block_restart_interval); +extern ROCKSDB_LIBRARY_API void +rocksdb_block_based_options_set_metadata_block_size( + rocksdb_block_based_table_options_t* options, uint64_t metadata_block_size); +extern ROCKSDB_LIBRARY_API void +rocksdb_block_based_options_set_partition_filters( + rocksdb_block_based_table_options_t* options, unsigned char partition_filters); +extern ROCKSDB_LIBRARY_API void +rocksdb_block_based_options_set_use_delta_encoding( + rocksdb_block_based_table_options_t* options, unsigned char use_delta_encoding); extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_filter_policy( rocksdb_block_based_table_options_t* options, rocksdb_filterpolicy_t* filter_policy); @@ -653,8 +693,14 @@ extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_cache_index_and_filter_blocks( rocksdb_block_based_table_options_t*, unsigned char); extern ROCKSDB_LIBRARY_API void +rocksdb_block_based_options_set_cache_index_and_filter_blocks_with_high_priority( + rocksdb_block_based_table_options_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void rocksdb_block_based_options_set_pin_l0_filter_and_index_blocks_in_cache( rocksdb_block_based_table_options_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void +rocksdb_block_based_options_set_pin_top_level_index_and_filter( + rocksdb_block_based_table_options_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_block_based_table_factory( rocksdb_options_t* opt, rocksdb_block_based_table_options_t* table_options); @@ -682,6 +728,9 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_cuckoo_table_factory( extern ROCKSDB_LIBRARY_API void rocksdb_set_options( rocksdb_t* db, int count, const char* const keys[], const char* const values[], char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_set_options_cf( + rocksdb_t* db, rocksdb_column_family_handle_t* handle, int count, const char* const keys[], const char* const values[], char** errptr); + extern ROCKSDB_LIBRARY_API rocksdb_options_t* rocksdb_options_create(); extern ROCKSDB_LIBRARY_API void rocksdb_options_destroy(rocksdb_options_t*); extern ROCKSDB_LIBRARY_API void rocksdb_options_increase_parallelism( @@ -693,6 +742,9 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_optimize_level_style_compaction( extern ROCKSDB_LIBRARY_API void rocksdb_options_optimize_universal_style_compaction( rocksdb_options_t* opt, uint64_t memtable_memory_budget); +extern ROCKSDB_LIBRARY_API void +rocksdb_options_set_allow_ingest_behind(rocksdb_options_t*, + unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter( rocksdb_options_t*, rocksdb_compactionfilter_t*); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter_factory( @@ -717,7 +769,7 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_error_if_exists( extern ROCKSDB_LIBRARY_API void rocksdb_options_set_paranoid_checks( rocksdb_options_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_paths(rocksdb_options_t*, - const rocksdb_dbpath_t** path_values, + const rocksdb_dbpath_t** path_values, size_t num_paths); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_env(rocksdb_options_t*, rocksdb_env_t*); @@ -765,8 +817,9 @@ rocksdb_options_set_max_bytes_for_level_multiplier_additional( rocksdb_options_t*, int* level_values, size_t num_levels); extern ROCKSDB_LIBRARY_API void rocksdb_options_enable_statistics( rocksdb_options_t*); -extern ROCKSDB_LIBRARY_API void rocksdb_options_set_skip_stats_update_on_db_open( - rocksdb_options_t* opt, unsigned char val); +extern ROCKSDB_LIBRARY_API void +rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt, + unsigned char val); /* returns a pointer to a malloc()-ed, null terminated string */ extern ROCKSDB_LIBRARY_API char* rocksdb_options_statistics_get_string( @@ -779,6 +832,12 @@ rocksdb_options_set_min_write_buffer_number_to_merge(rocksdb_options_t*, int); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_write_buffer_number_to_maintain(rocksdb_options_t*, int); +extern ROCKSDB_LIBRARY_API void rocksdb_options_set_enable_pipelined_write( + rocksdb_options_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_subcompactions( + rocksdb_options_t*, uint32_t); +extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_background_jobs( + rocksdb_options_t*, int); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_background_compactions( rocksdb_options_t*, int); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_base_background_compactions( @@ -851,6 +910,10 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_use_adaptive_mutex( rocksdb_options_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_bytes_per_sync( rocksdb_options_t*, uint64_t); +extern ROCKSDB_LIBRARY_API void rocksdb_options_set_wal_bytes_per_sync( + rocksdb_options_t*, uint64_t); +extern ROCKSDB_LIBRARY_API void +rocksdb_options_set_writable_file_max_buffer_size(rocksdb_options_t*, uint64_t); extern ROCKSDB_LIBRARY_API void rocksdb_options_set_allow_concurrent_memtable_write(rocksdb_options_t*, unsigned char); @@ -941,6 +1004,99 @@ extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t* rocksdb_ratelimiter_create( int64_t rate_bytes_per_sec, int64_t refill_period_us, int32_t fairness); extern ROCKSDB_LIBRARY_API void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t*); +/* PerfContext */ +enum { + rocksdb_uninitialized = 0, + rocksdb_disable = 1, + rocksdb_enable_count = 2, + rocksdb_enable_time_except_for_mutex = 3, + rocksdb_enable_time = 4, + rocksdb_out_of_bounds = 5 +}; + +enum { + rocksdb_user_key_comparison_count = 0, + rocksdb_block_cache_hit_count, + rocksdb_block_read_count, + rocksdb_block_read_byte, + rocksdb_block_read_time, + rocksdb_block_checksum_time, + rocksdb_block_decompress_time, + rocksdb_get_read_bytes, + rocksdb_multiget_read_bytes, + rocksdb_iter_read_bytes, + rocksdb_internal_key_skipped_count, + rocksdb_internal_delete_skipped_count, + rocksdb_internal_recent_skipped_count, + rocksdb_internal_merge_count, + rocksdb_get_snapshot_time, + rocksdb_get_from_memtable_time, + rocksdb_get_from_memtable_count, + rocksdb_get_post_process_time, + rocksdb_get_from_output_files_time, + rocksdb_seek_on_memtable_time, + rocksdb_seek_on_memtable_count, + rocksdb_next_on_memtable_count, + rocksdb_prev_on_memtable_count, + rocksdb_seek_child_seek_time, + rocksdb_seek_child_seek_count, + rocksdb_seek_min_heap_time, + rocksdb_seek_max_heap_time, + rocksdb_seek_internal_seek_time, + rocksdb_find_next_user_entry_time, + rocksdb_write_wal_time, + rocksdb_write_memtable_time, + rocksdb_write_delay_time, + rocksdb_write_pre_and_post_process_time, + rocksdb_db_mutex_lock_nanos, + rocksdb_db_condition_wait_nanos, + rocksdb_merge_operator_time_nanos, + rocksdb_read_index_block_nanos, + rocksdb_read_filter_block_nanos, + rocksdb_new_table_block_iter_nanos, + rocksdb_new_table_iterator_nanos, + rocksdb_block_seek_nanos, + rocksdb_find_table_nanos, + rocksdb_bloom_memtable_hit_count, + rocksdb_bloom_memtable_miss_count, + rocksdb_bloom_sst_hit_count, + rocksdb_bloom_sst_miss_count, + rocksdb_key_lock_wait_time, + rocksdb_key_lock_wait_count, + rocksdb_env_new_sequential_file_nanos, + rocksdb_env_new_random_access_file_nanos, + rocksdb_env_new_writable_file_nanos, + rocksdb_env_reuse_writable_file_nanos, + rocksdb_env_new_random_rw_file_nanos, + rocksdb_env_new_directory_nanos, + rocksdb_env_file_exists_nanos, + rocksdb_env_get_children_nanos, + rocksdb_env_get_children_file_attributes_nanos, + rocksdb_env_delete_file_nanos, + rocksdb_env_create_dir_nanos, + rocksdb_env_create_dir_if_missing_nanos, + rocksdb_env_delete_dir_nanos, + rocksdb_env_get_file_size_nanos, + rocksdb_env_get_file_modification_time_nanos, + rocksdb_env_rename_file_nanos, + rocksdb_env_link_file_nanos, + rocksdb_env_lock_file_nanos, + rocksdb_env_unlock_file_nanos, + rocksdb_env_new_logger_nanos, + rocksdb_total_metric_count = 68 +}; + +extern ROCKSDB_LIBRARY_API void rocksdb_set_perf_level(int); +extern ROCKSDB_LIBRARY_API rocksdb_perfcontext_t* rocksdb_perfcontext_create(); +extern ROCKSDB_LIBRARY_API void rocksdb_perfcontext_reset( + rocksdb_perfcontext_t* context); +extern ROCKSDB_LIBRARY_API char* rocksdb_perfcontext_report( + rocksdb_perfcontext_t* context, unsigned char exclude_zero_counters); +extern ROCKSDB_LIBRARY_API uint64_t rocksdb_perfcontext_metric( + rocksdb_perfcontext_t* context, int metric); +extern ROCKSDB_LIBRARY_API void rocksdb_perfcontext_destroy( + rocksdb_perfcontext_t* context); + /* Compaction Filter */ extern ROCKSDB_LIBRARY_API rocksdb_compactionfilter_t* @@ -1040,16 +1196,29 @@ extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_snapshot( rocksdb_readoptions_t*, const rocksdb_snapshot_t*); extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_iterate_upper_bound( rocksdb_readoptions_t*, const char* key, size_t keylen); +extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_iterate_lower_bound( + rocksdb_readoptions_t*, const char* key, size_t keylen); extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_read_tier( rocksdb_readoptions_t*, int); extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_tailing( rocksdb_readoptions_t*, unsigned char); +// The functionality that this option controlled has been removed. +extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_managed( + rocksdb_readoptions_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_readahead_size( rocksdb_readoptions_t*, size_t); +extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_prefix_same_as_start( + rocksdb_readoptions_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_pin_data( rocksdb_readoptions_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_total_order_seek( rocksdb_readoptions_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_max_skippable_internal_keys( + rocksdb_readoptions_t*, uint64_t); +extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_background_purge_on_iterator_cleanup( + rocksdb_readoptions_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_ignore_range_deletions( + rocksdb_readoptions_t*, unsigned char); /* Write options */ @@ -1061,6 +1230,12 @@ extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_sync( rocksdb_writeoptions_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_disable_WAL( rocksdb_writeoptions_t* opt, int disable); +extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_ignore_missing_column_families( + rocksdb_writeoptions_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_no_slowdown( + rocksdb_writeoptions_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void rocksdb_writeoptions_set_low_pri( + rocksdb_writeoptions_t*, unsigned char); /* Compact range options */ @@ -1071,6 +1246,9 @@ extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_destroy( extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_set_exclusive_manual_compaction( rocksdb_compactoptions_t*, unsigned char); +extern ROCKSDB_LIBRARY_API void +rocksdb_compactoptions_set_bottommost_level_compaction( + rocksdb_compactoptions_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_set_change_level( rocksdb_compactoptions_t*, unsigned char); extern ROCKSDB_LIBRARY_API void rocksdb_compactoptions_set_target_level( @@ -1143,6 +1321,8 @@ extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_delete( char** errptr); extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_finish( rocksdb_sstfilewriter_t* writer, char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_file_size( + rocksdb_sstfilewriter_t* writer, uint64_t* file_size); extern ROCKSDB_LIBRARY_API void rocksdb_sstfilewriter_destroy( rocksdb_sstfilewriter_t* writer); @@ -1162,6 +1342,10 @@ extern ROCKSDB_LIBRARY_API void rocksdb_ingestexternalfileoptions_set_allow_blocking_flush( rocksdb_ingestexternalfileoptions_t* opt, unsigned char allow_blocking_flush); +extern ROCKSDB_LIBRARY_API void +rocksdb_ingestexternalfileoptions_set_ingest_behind( + rocksdb_ingestexternalfileoptions_t* opt, + unsigned char ingest_behind); extern ROCKSDB_LIBRARY_API void rocksdb_ingestexternalfileoptions_destroy( rocksdb_ingestexternalfileoptions_t* opt); @@ -1240,6 +1424,10 @@ extern ROCKSDB_LIBRARY_API const char* rocksdb_livefiles_smallestkey( const rocksdb_livefiles_t*, int index, size_t* size); extern ROCKSDB_LIBRARY_API const char* rocksdb_livefiles_largestkey( const rocksdb_livefiles_t*, int index, size_t* size); +extern ROCKSDB_LIBRARY_API uint64_t rocksdb_livefiles_entries( + const rocksdb_livefiles_t*, int index); +extern ROCKSDB_LIBRARY_API uint64_t rocksdb_livefiles_deletions( + const rocksdb_livefiles_t*, int index); extern ROCKSDB_LIBRARY_API void rocksdb_livefiles_destroy( const rocksdb_livefiles_t*); @@ -1271,6 +1459,13 @@ extern ROCKSDB_LIBRARY_API rocksdb_transactiondb_t* rocksdb_transactiondb_open( const rocksdb_transactiondb_options_t* txn_db_options, const char* name, char** errptr); +rocksdb_transactiondb_t* rocksdb_transactiondb_open_column_families( + const rocksdb_options_t* options, + const rocksdb_transactiondb_options_t* txn_db_options, const char* name, + int num_column_families, const char** column_family_names, + const rocksdb_options_t** column_family_options, + rocksdb_column_family_handle_t** column_family_handles, char** errptr); + extern ROCKSDB_LIBRARY_API const rocksdb_snapshot_t* rocksdb_transactiondb_create_snapshot(rocksdb_transactiondb_t* txn_db); @@ -1289,6 +1484,12 @@ extern ROCKSDB_LIBRARY_API void rocksdb_transaction_commit( extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback( rocksdb_transaction_t* txn, char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_transaction_set_savepoint( + rocksdb_transaction_t* txn); + +extern ROCKSDB_LIBRARY_API void rocksdb_transaction_rollback_to_savepoint( + rocksdb_transaction_t* txn, char** errptr); + extern ROCKSDB_LIBRARY_API void rocksdb_transaction_destroy( rocksdb_transaction_t* txn); @@ -1310,6 +1511,11 @@ extern ROCKSDB_LIBRARY_API char* rocksdb_transaction_get_for_update( const char* key, size_t klen, size_t* vlen, unsigned char exclusive, char** errptr); +char* rocksdb_transaction_get_for_update_cf( + rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options, + rocksdb_column_family_handle_t* column_family, const char* key, size_t klen, + size_t* vlen, unsigned char exclusive, char** errptr); + extern ROCKSDB_LIBRARY_API char* rocksdb_transactiondb_get( rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options, const char* key, size_t klen, size_t* vlen, char** errptr); @@ -1344,10 +1550,19 @@ extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge( rocksdb_transaction_t* txn, const char* key, size_t klen, const char* val, size_t vlen, char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_transaction_merge_cf( + rocksdb_transaction_t* txn, rocksdb_column_family_handle_t* column_family, + const char* key, size_t klen, const char* val, size_t vlen, char** errptr); + extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge( rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options, const char* key, size_t klen, const char* val, size_t vlen, char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_merge_cf( + rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options, + rocksdb_column_family_handle_t* column_family, const char* key, size_t klen, + const char* val, size_t vlen, char** errptr); + extern ROCKSDB_LIBRARY_API void rocksdb_transaction_delete( rocksdb_transaction_t* txn, const char* key, size_t klen, char** errptr); @@ -1368,10 +1583,20 @@ extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_transaction_create_iterator(rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options); +extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* +rocksdb_transaction_create_iterator_cf( + rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options, + rocksdb_column_family_handle_t* column_family); + extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_transactiondb_create_iterator(rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options); +extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* +rocksdb_transactiondb_create_iterator_cf( + rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options, + rocksdb_column_family_handle_t* column_family); + extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_close( rocksdb_transactiondb_t* txn_db); @@ -1383,6 +1608,20 @@ extern ROCKSDB_LIBRARY_API rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open(const rocksdb_options_t* options, const char* name, char** errptr); +extern ROCKSDB_LIBRARY_API rocksdb_optimistictransactiondb_t* +rocksdb_optimistictransactiondb_open_column_families( + const rocksdb_options_t* options, const char* name, int num_column_families, + const char** column_family_names, + const rocksdb_options_t** column_family_options, + rocksdb_column_family_handle_t** column_family_handles, char** errptr); + +extern ROCKSDB_LIBRARY_API rocksdb_t* +rocksdb_optimistictransactiondb_get_base_db( + rocksdb_optimistictransactiondb_t* otxn_db); + +extern ROCKSDB_LIBRARY_API void rocksdb_optimistictransactiondb_close_base_db( + rocksdb_t* base_db); + extern ROCKSDB_LIBRARY_API rocksdb_transaction_t* rocksdb_optimistictransaction_begin( rocksdb_optimistictransactiondb_t* otxn_db, @@ -1441,7 +1680,6 @@ extern ROCKSDB_LIBRARY_API void rocksdb_transaction_options_set_max_write_batch_size( rocksdb_transaction_options_t* opt, size_t size); - extern ROCKSDB_LIBRARY_API rocksdb_optimistictransaction_options_t* rocksdb_optimistictransaction_options_create(); @@ -1468,8 +1706,33 @@ extern ROCKSDB_LIBRARY_API void rocksdb_pinnableslice_destroy( extern ROCKSDB_LIBRARY_API const char* rocksdb_pinnableslice_value( const rocksdb_pinnableslice_t* t, size_t* vlen); +extern ROCKSDB_LIBRARY_API rocksdb_memory_consumers_t* + rocksdb_memory_consumers_create(); +extern ROCKSDB_LIBRARY_API void rocksdb_memory_consumers_add_db( + rocksdb_memory_consumers_t* consumers, rocksdb_t* db); +extern ROCKSDB_LIBRARY_API void rocksdb_memory_consumers_add_cache( + rocksdb_memory_consumers_t* consumers, rocksdb_cache_t* cache); +extern ROCKSDB_LIBRARY_API void rocksdb_memory_consumers_destroy( + rocksdb_memory_consumers_t* consumers); +extern ROCKSDB_LIBRARY_API rocksdb_memory_usage_t* +rocksdb_approximate_memory_usage_create(rocksdb_memory_consumers_t* consumers, + char** errptr); +extern ROCKSDB_LIBRARY_API void rocksdb_approximate_memory_usage_destroy( + rocksdb_memory_usage_t* usage); + +extern ROCKSDB_LIBRARY_API uint64_t +rocksdb_approximate_memory_usage_get_mem_table_total( + rocksdb_memory_usage_t* memory_usage); +extern ROCKSDB_LIBRARY_API uint64_t +rocksdb_approximate_memory_usage_get_mem_table_unflushed( + rocksdb_memory_usage_t* memory_usage); +extern ROCKSDB_LIBRARY_API uint64_t +rocksdb_approximate_memory_usage_get_mem_table_readers_total( + rocksdb_memory_usage_t* memory_usage); +extern ROCKSDB_LIBRARY_API uint64_t +rocksdb_approximate_memory_usage_get_cache_total( + rocksdb_memory_usage_t* memory_usage); + #ifdef __cplusplus } /* end extern "C" */ #endif - -#endif /* STORAGE_ROCKSDB_INCLUDE_C_H_ */ diff --git a/thirdparty/rocksdb/include/rocksdb/cache.h b/thirdparty/rocksdb/include/rocksdb/cache.h index 5ebd66bde8..ed7790aebb 100644 --- a/thirdparty/rocksdb/include/rocksdb/cache.h +++ b/thirdparty/rocksdb/include/rocksdb/cache.h @@ -25,6 +25,7 @@ #include #include #include +#include "rocksdb/memory_allocator.h" #include "rocksdb/slice.h" #include "rocksdb/statistics.h" #include "rocksdb/status.h" @@ -33,6 +34,61 @@ namespace rocksdb { class Cache; +extern const bool kDefaultToAdaptiveMutex; + +struct LRUCacheOptions { + // Capacity of the cache. + size_t capacity = 0; + + // Cache is sharded into 2^num_shard_bits shards, + // by hash of key. Refer to NewLRUCache for further + // information. + int num_shard_bits = -1; + + // If strict_capacity_limit is set, + // insert to the cache will fail when cache is full. + bool strict_capacity_limit = false; + + // Percentage of cache reserved for high priority entries. + // If greater than zero, the LRU list will be split into a high-pri + // list and a low-pri list. High-pri entries will be insert to the + // tail of high-pri list, while low-pri entries will be first inserted to + // the low-pri list (the midpoint). This is refered to as + // midpoint insertion strategy to make entries never get hit in cache + // age out faster. + // + // See also + // BlockBasedTableOptions::cache_index_and_filter_blocks_with_high_priority. + double high_pri_pool_ratio = 0.0; + + // If non-nullptr will use this allocator instead of system allocator when + // allocating memory for cache blocks. Call this method before you start using + // the cache! + // + // Caveat: when the cache is used as block cache, the memory allocator is + // ignored when dealing with compression libraries that allocate memory + // internally (currently only XPRESS). + std::shared_ptr memory_allocator; + + // Whether to use adaptive mutexes for cache shards. Note that adaptive + // mutexes need to be supported by the platform in order for this to have any + // effect. The default value is true if RocksDB is compiled with + // -DROCKSDB_DEFAULT_TO_ADAPTIVE_MUTEX, false otherwise. + bool use_adaptive_mutex = kDefaultToAdaptiveMutex; + + LRUCacheOptions() {} + LRUCacheOptions(size_t _capacity, int _num_shard_bits, + bool _strict_capacity_limit, double _high_pri_pool_ratio, + std::shared_ptr _memory_allocator = nullptr, + bool _use_adaptive_mutex = kDefaultToAdaptiveMutex) + : capacity(_capacity), + num_shard_bits(_num_shard_bits), + strict_capacity_limit(_strict_capacity_limit), + high_pri_pool_ratio(_high_pri_pool_ratio), + memory_allocator(std::move(_memory_allocator)), + use_adaptive_mutex(_use_adaptive_mutex) {} +}; + // Create a new cache with a fixed size capacity. The cache is sharded // to 2^num_shard_bits shards, by hash of the key. The total capacity // is divided and evenly assigned to each shard. If strict_capacity_limit @@ -41,10 +97,13 @@ class Cache; // high_pri_pool_pct. // num_shard_bits = -1 means it is automatically determined: every shard // will be at least 512KB and number of shard bits will not exceed 6. -extern std::shared_ptr NewLRUCache(size_t capacity, - int num_shard_bits = -1, - bool strict_capacity_limit = false, - double high_pri_pool_ratio = 0.0); +extern std::shared_ptr NewLRUCache( + size_t capacity, int num_shard_bits = -1, + bool strict_capacity_limit = false, double high_pri_pool_ratio = 0.0, + std::shared_ptr memory_allocator = nullptr, + bool use_adaptive_mutex = kDefaultToAdaptiveMutex); + +extern std::shared_ptr NewLRUCache(const LRUCacheOptions& cache_opts); // Similar to NewLRUCache, but create a cache based on CLOCK algorithm with // better concurrent performance in some cases. See util/clock_cache.cc for @@ -61,7 +120,8 @@ class Cache { // likely to get evicted than low priority entries. enum class Priority { HIGH, LOW }; - Cache() {} + Cache(std::shared_ptr allocator = nullptr) + : memory_allocator_(std::move(allocator)) {} // Destroys all existing entries by calling the "deleter" // function that was passed via the Insert() function. @@ -189,12 +249,17 @@ class Cache { // Mark the last inserted object as being a raw data block. This will be used // in tests. The default implementation does nothing. - virtual void TEST_mark_as_data_block(const Slice& key, size_t charge) {} + virtual void TEST_mark_as_data_block(const Slice& /*key*/, + size_t /*charge*/) {} + + MemoryAllocator* memory_allocator() const { return memory_allocator_.get(); } private: // No copying allowed Cache(const Cache&); Cache& operator=(const Cache&); + + std::shared_ptr memory_allocator_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/cleanable.h b/thirdparty/rocksdb/include/rocksdb/cleanable.h index cd2e9425f1..6dba8d9531 100644 --- a/thirdparty/rocksdb/include/rocksdb/cleanable.h +++ b/thirdparty/rocksdb/include/rocksdb/cleanable.h @@ -16,8 +16,7 @@ // non-const method, all threads accessing the same Iterator must use // external synchronization. -#ifndef INCLUDE_ROCKSDB_CLEANABLE_H_ -#define INCLUDE_ROCKSDB_CLEANABLE_H_ +#pragma once namespace rocksdb { @@ -30,7 +29,7 @@ class Cleanable { Cleanable(Cleanable&) = delete; Cleanable& operator=(Cleanable&) = delete; - // Move consturctor and move assignment is allowed. + // Move constructor and move assignment is allowed. Cleanable(Cleanable&&); Cleanable& operator=(Cleanable&&); @@ -78,5 +77,3 @@ class Cleanable { }; } // namespace rocksdb - -#endif // INCLUDE_ROCKSDB_CLEANABLE_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/compaction_filter.h b/thirdparty/rocksdb/include/rocksdb/compaction_filter.h index 64f61a35e0..5d476fb8e6 100644 --- a/thirdparty/rocksdb/include/rocksdb/compaction_filter.h +++ b/thirdparty/rocksdb/include/rocksdb/compaction_filter.h @@ -6,8 +6,7 @@ // 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. -#ifndef STORAGE_ROCKSDB_INCLUDE_COMPACTION_FILTER_H_ -#define STORAGE_ROCKSDB_INCLUDE_COMPACTION_FILTER_H_ +#pragma once #include #include @@ -76,14 +75,11 @@ class CompactionFilter { // to modify the existing_value and pass it back through new_value. // value_changed needs to be set to true in this case. // - // If you use snapshot feature of RocksDB (i.e. call GetSnapshot() API on a - // DB* object), CompactionFilter might not be very useful for you. Due to - // guarantees we need to maintain, compaction process will not call Filter() - // on any keys that were written before the latest snapshot. In other words, - // compaction will only call Filter() on keys written after your most recent - // call to GetSnapshot(). In most cases, Filter() will not be called very - // often. This is something we're fixing. See the discussion at: - // https://www.facebook.com/groups/mysqlonrocksdb/permalink/999723240091865/ + // Note that RocksDB snapshots (i.e. call GetSnapshot() API on a + // DB* object) will not guarantee to preserve the state of the DB with + // CompactionFilter. Data seen from a snapshot might disppear after a + // compaction finishes. If you use snapshots, think twice about whether you + // want to use compaction filter and whether you are using it in a safe way. // // If multithreaded compaction is being used *and* a single CompactionFilter // instance was supplied via Options::compaction_filter, this method may be @@ -94,12 +90,10 @@ class CompactionFilter { // be used by a single thread that is doing the compaction run, and this // call does not need to be thread-safe. However, multiple filters may be // in existence and operating concurrently. - // - // The last paragraph is not true if you set max_subcompactions to more than - // 1. In that case, subcompaction from multiple threads may call a single - // CompactionFilter concurrently. - virtual bool Filter(int level, const Slice& key, const Slice& existing_value, - std::string* new_value, bool* value_changed) const { + virtual bool Filter(int /*level*/, const Slice& /*key*/, + const Slice& /*existing_value*/, + std::string* /*new_value*/, + bool* /*value_changed*/) const { return false; } @@ -112,8 +106,8 @@ class CompactionFilter { // may not realize there is a write conflict and may allow a Transaction to // Commit that should have failed. Instead, it is better to implement any // Merge filtering inside the MergeOperator. - virtual bool FilterMergeOperand(int level, const Slice& key, - const Slice& operand) const { + virtual bool FilterMergeOperand(int /*level*/, const Slice& /*key*/, + const Slice& /*operand*/) const { return false; } @@ -138,9 +132,9 @@ class CompactionFilter { // // Caveats: // - The keys are skipped even if there are snapshots containing them, - // as if IgnoreSnapshots() was true; i.e. values removed - // by kRemoveAndSkipUntil can disappear from a snapshot - beware - // if you're using TransactionDB or DB::GetSnapshot(). + // i.e. values removed by kRemoveAndSkipUntil can disappear from a + // snapshot - beware if you're using TransactionDB or + // DB::GetSnapshot(). // - If value for a key was overwritten or merged into (multiple Put()s // or Merge()s), and compaction filter skips this key with // kRemoveAndSkipUntil, it's possible that it will remove only @@ -158,7 +152,7 @@ class CompactionFilter { // MergeOperator. virtual Decision FilterV2(int level, const Slice& key, ValueType value_type, const Slice& existing_value, std::string* new_value, - std::string* skip_until) const { + std::string* /*skip_until*/) const { switch (value_type) { case ValueType::kValue: { bool value_changed = false; @@ -179,15 +173,12 @@ class CompactionFilter { return Decision::kKeep; } - // By default, compaction will only call Filter() on keys written after the - // most recent call to GetSnapshot(). However, if the compaction filter - // overrides IgnoreSnapshots to make it return true, the compaction filter - // will be called even if the keys were written before the last snapshot. - // This behavior is to be used only when we want to delete a set of keys - // irrespective of snapshots. In particular, care should be taken - // to understand that the values of these keys will change even if we are - // using a snapshot. - virtual bool IgnoreSnapshots() const { return false; } + // This function is deprecated. Snapshots will always be ignored for + // compaction filters, because we realized that not ignoring snapshots doesn't + // provide the gurantee we initially thought it would provide. Repeatable + // reads will not be guaranteed anyway. If you override the function and + // returns false, we will fail the compaction. + virtual bool IgnoreSnapshots() const { return true; } // Returns a name that identifies this compaction filter. // The name will be printed to LOG file on start up for diagnosis. @@ -198,7 +189,7 @@ class CompactionFilter { // application to know about different compactions class CompactionFilterFactory { public: - virtual ~CompactionFilterFactory() { } + virtual ~CompactionFilterFactory() {} virtual std::unique_ptr CreateCompactionFilter( const CompactionFilter::Context& context) = 0; @@ -208,5 +199,3 @@ class CompactionFilterFactory { }; } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_COMPACTION_FILTER_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/compaction_job_stats.h b/thirdparty/rocksdb/include/rocksdb/compaction_job_stats.h index ebb04a46bf..4021fcab20 100644 --- a/thirdparty/rocksdb/include/rocksdb/compaction_job_stats.h +++ b/thirdparty/rocksdb/include/rocksdb/compaction_job_stats.h @@ -18,6 +18,9 @@ struct CompactionJobStats { // the elapsed time of this compaction in microseconds. uint64_t elapsed_micros; + // the elapsed CPU time of this compaction in microseconds. + uint64_t cpu_micros; + // the number of compaction input records. uint64_t num_input_records; // the number of compaction input files. @@ -72,7 +75,7 @@ struct CompactionJobStats { // Time spent on file fsync. uint64_t file_fsync_nanos; - // Time spent on preparing file write (falocate, etc) + // Time spent on preparing file write (fallocate, etc) uint64_t file_prepare_write_nanos; // 0-terminated strings storing the first 8 bytes of the smallest and diff --git a/thirdparty/rocksdb/include/rocksdb/comparator.h b/thirdparty/rocksdb/include/rocksdb/comparator.h index 64db73a724..46279f9a69 100644 --- a/thirdparty/rocksdb/include/rocksdb/comparator.h +++ b/thirdparty/rocksdb/include/rocksdb/comparator.h @@ -6,8 +6,7 @@ // 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. -#ifndef STORAGE_ROCKSDB_INCLUDE_COMPARATOR_H_ -#define STORAGE_ROCKSDB_INCLUDE_COMPARATOR_H_ +#pragma once #include @@ -21,7 +20,7 @@ class Slice; // from multiple threads. class Comparator { public: - virtual ~Comparator(); + virtual ~Comparator() {} // Three-way comparison. Returns value: // < 0 iff "a" < "b", @@ -56,9 +55,8 @@ class Comparator { // If *start < limit, changes *start to a short string in [start,limit). // Simple comparator implementations may return with *start unchanged, // i.e., an implementation of this method that does nothing is correct. - virtual void FindShortestSeparator( - std::string* start, - const Slice& limit) const = 0; + virtual void FindShortestSeparator(std::string* start, + const Slice& limit) const = 0; // Changes *key to a short string >= *key. // Simple comparator implementations may return with *key unchanged, @@ -68,6 +66,18 @@ class Comparator { // if it is a wrapped comparator, may return the root one. // return itself it is not wrapped. virtual const Comparator* GetRootComparator() const { return this; } + + // given two keys, determine if t is the successor of s + virtual bool IsSameLengthImmediateSuccessor(const Slice& /*s*/, + const Slice& /*t*/) const { + return false; + } + + // return true if two keys with different byte sequences can be regarded + // as equal by this comparator. + // The major use case is to determine if DataBlockHashIndex is compatible + // with the customized comparator. + virtual bool CanKeysWithDifferentByteContentsBeEqual() const { return true; } }; // Return a builtin comparator that uses lexicographic byte-wise @@ -80,5 +90,3 @@ extern const Comparator* BytewiseComparator(); extern const Comparator* ReverseBytewiseComparator(); } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_COMPARATOR_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/concurrent_task_limiter.h b/thirdparty/rocksdb/include/rocksdb/concurrent_task_limiter.h new file mode 100644 index 0000000000..2e054efdad --- /dev/null +++ b/thirdparty/rocksdb/include/rocksdb/concurrent_task_limiter.h @@ -0,0 +1,46 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#pragma once + +#include "rocksdb/env.h" +#include "rocksdb/statistics.h" + +namespace rocksdb { + +class ConcurrentTaskLimiter { + public: + virtual ~ConcurrentTaskLimiter() {} + + // Returns a name that identifies this concurrent task limiter. + virtual const std::string& GetName() const = 0; + + // Set max concurrent tasks. + // limit = 0 means no new task allowed. + // limit < 0 means no limitation. + virtual void SetMaxOutstandingTask(int32_t limit) = 0; + + // Reset to unlimited max concurrent task. + virtual void ResetMaxOutstandingTask() = 0; + + // Returns current outstanding task count. + virtual int32_t GetOutstandingTask() const = 0; +}; + +// Create a ConcurrentTaskLimiter that can be shared with mulitple CFs +// across RocksDB instances to control concurrent tasks. +// +// @param name: Name of the limiter. +// @param limit: max concurrent tasks. +// limit = 0 means no new task allowed. +// limit < 0 means no limitation. +extern ConcurrentTaskLimiter* NewConcurrentTaskLimiter(const std::string& name, + int32_t limit); + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/convenience.h b/thirdparty/rocksdb/include/rocksdb/convenience.h index 4a60afb11d..d3cbe6016a 100644 --- a/thirdparty/rocksdb/include/rocksdb/convenience.h +++ b/thirdparty/rocksdb/include/rocksdb/convenience.h @@ -277,15 +277,13 @@ Status GetPlainTableOptionsFromMap( // BlockBasedTableOptions as part of the string for block-based table factory: // "write_buffer_size=1024;block_based_table_factory={block_size=4k};" // "max_write_buffer_num=2" -Status GetColumnFamilyOptionsFromString( - const ColumnFamilyOptions& base_options, - const std::string& opts_str, - ColumnFamilyOptions* new_options); +Status GetColumnFamilyOptionsFromString(const ColumnFamilyOptions& base_options, + const std::string& opts_str, + ColumnFamilyOptions* new_options); -Status GetDBOptionsFromString( - const DBOptions& base_options, - const std::string& opts_str, - DBOptions* new_options); +Status GetDBOptionsFromString(const DBOptions& base_options, + const std::string& opts_str, + DBOptions* new_options); Status GetStringFromDBOptions(std::string* opts_str, const DBOptions& db_options, @@ -301,14 +299,12 @@ Status GetStringFromCompressionType(std::string* compression_str, std::vector GetSupportedCompressions(); Status GetBlockBasedTableOptionsFromString( - const BlockBasedTableOptions& table_options, - const std::string& opts_str, + const BlockBasedTableOptions& table_options, const std::string& opts_str, BlockBasedTableOptions* new_table_options); -Status GetPlainTableOptionsFromString( - const PlainTableOptions& table_options, - const std::string& opts_str, - PlainTableOptions* new_table_options); +Status GetPlainTableOptionsFromString(const PlainTableOptions& table_options, + const std::string& opts_str, + PlainTableOptions* new_table_options); Status GetMemTableRepFactoryFromString( const std::string& opts_str, @@ -325,10 +321,19 @@ void CancelAllBackgroundWork(DB* db, bool wait = false); // Delete files which are entirely in the given range // Could leave some keys in the range which are in files which are not -// entirely in the range. +// entirely in the range. Also leaves L0 files regardless of whether they're +// in the range. // Snapshots before the delete might not see the data in the given range. Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end); + const Slice* begin, const Slice* end, + bool include_end = true); + +// Delete files in multiple ranges at once +// Delete files in a lot of ranges one at a time can be slow, use this API for +// better performance in that case. +Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family, + const RangePtr* ranges, size_t n, + bool include_end = true); // Verify the checksum of file Status VerifySstFileChecksum(const Options& options, diff --git a/thirdparty/rocksdb/include/rocksdb/db.h b/thirdparty/rocksdb/include/rocksdb/db.h index 964f7b1db4..b40af20e27 100644 --- a/thirdparty/rocksdb/include/rocksdb/db.h +++ b/thirdparty/rocksdb/include/rocksdb/db.h @@ -6,8 +6,7 @@ // 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. -#ifndef STORAGE_ROCKSDB_INCLUDE_DB_H_ -#define STORAGE_ROCKSDB_INCLUDE_DB_H_ +#pragma once #include #include @@ -53,8 +52,11 @@ struct ExternalSstFileInfo; class WriteBatch; class Env; class EventListener; - -using std::unique_ptr; +class StatsHistoryIterator; +class TraceWriter; +#ifdef ROCKSDB_LITE +class CompactionJobInfo; +#endif extern const std::string kDefaultColumnFamilyName; struct ColumnFamilyDescriptor { @@ -92,11 +94,25 @@ static const int kMinorVersion = __ROCKSDB_MINOR__; // A range of keys struct Range { - Slice start; // Included in the range - Slice limit; // Not included in the range + Slice start; + Slice limit; + + Range() {} + Range(const Slice& s, const Slice& l) : start(s), limit(l) {} +}; + +struct RangePtr { + const Slice* start; + const Slice* limit; - Range() { } - Range(const Slice& s, const Slice& l) : start(s), limit(l) { } + RangePtr() : start(nullptr), limit(nullptr) {} + RangePtr(const Slice* s, const Slice* l) : start(s), limit(l) {} +}; + +struct IngestExternalFileArg { + ColumnFamilyHandle* column_family = nullptr; + std::vector external_files; + IngestExternalFileOptions options; }; // A collections of table properties objects, where @@ -115,8 +131,7 @@ class DB { // OK on success. // Stores nullptr in *dbptr and returns a non-OK status on error. // Caller should delete *dbptr when it is no longer needed. - static Status Open(const Options& options, - const std::string& name, + static Status Open(const Options& options, const std::string& name, DB** dbptr); // Open the database for read only. All DB interfaces @@ -126,9 +141,9 @@ class DB { // // Not supported in ROCKSDB_LITE, in which case the function will // return Status::NotSupported. - static Status OpenForReadOnly(const Options& options, - const std::string& name, DB** dbptr, - bool error_if_log_file_exist = false); + static Status OpenForReadOnly(const Options& options, const std::string& name, + DB** dbptr, + bool error_if_log_file_exist = false); // Open the database for read only with column families. When opening DB with // read only, you can specify only a subset of column families in the @@ -144,6 +159,54 @@ class DB { std::vector* handles, DB** dbptr, bool error_if_log_file_exist = false); + // The following OpenAsSecondary functions create a secondary instance that + // can dynamically tail the MANIFEST of a primary that must have already been + // created. User can call TryCatchUpWithPrimary to make the secondary + // instance catch up with primary (WAL tailing is NOT supported now) whenever + // the user feels necessary. Column families created by the primary after the + // secondary instance starts are currently ignored by the secondary instance. + // Column families opened by secondary and dropped by the primary will be + // dropped by secondary as well. However the user of the secondary instance + // can still access the data of such dropped column family as long as they + // do not destroy the corresponding column family handle. + // WAL tailing is not supported at present, but will arrive soon. + // + // The options argument specifies the options to open the secondary instance. + // The name argument specifies the name of the primary db that you have used + // to open the primary instance. + // The secondary_path argument points to a directory where the secondary + // instance stores its info log. + // The dbptr is an out-arg corresponding to the opened secondary instance. + // The pointer points to a heap-allocated database, and the user should + // delete it after use. + // Open DB as secondary instance with only the default column family. + // Return OK on success, non-OK on failures. + static Status OpenAsSecondary(const Options& options, const std::string& name, + const std::string& secondary_path, DB** dbptr); + + // Open DB as secondary instance with column families. You can open a subset + // of column families in secondary mode. + // The db_options specify the database specific options. + // The name argument specifies the name of the primary db that you have used + // to open the primary instance. + // The secondary_path argument points to a directory where the secondary + // instance stores its info log. + // The column_families argument specifieds a list of column families to open. + // If any of the column families does not exist, the function returns non-OK + // status. + // The handles is an out-arg corresponding to the opened database column + // familiy handles. + // The dbptr is an out-arg corresponding to the opened secondary instance. + // The pointer points to a heap-allocated database, and the caller should + // delete it after use. Before deleting the dbptr, the user should also + // delete the pointers stored in handles vector. + // Return OK on success, on-OK on failures. + static Status OpenAsSecondary( + const DBOptions& db_options, const std::string& name, + const std::string& secondary_path, + const std::vector& column_families, + std::vector* handles, DB** dbptr); + // Open DB with column families. // db_options specify database specific options // column_families is the vector of all column families in the database, @@ -162,6 +225,18 @@ class DB { const std::vector& column_families, std::vector* handles, DB** dbptr); + virtual Status Resume() { return Status::NotSupported(); } + + // Close the DB by releasing resources, closing files etc. This should be + // called before calling the destructor so that the caller can get back a + // status in case there are any errors. This will not fsync the WAL files. + // If syncing is required, the caller must first call SyncWAL(), or Write() + // using an empty write batch with WriteOptions.sync=true. + // Regardless of the return status, the DB must be freed. If the return + // status is NotSupported(), then the DB implementation does cleanup in the + // destructor + virtual Status Close() { return Status::NotSupported(); } + // ListColumnFamilies will open the DB specified by argument name // and return the list of all column families in that DB // through column_families argument. The ordering of @@ -170,7 +245,7 @@ class DB { const std::string& name, std::vector* column_families); - DB() { } + DB() {} virtual ~DB(); // Create a column_family and return the handle of column family @@ -267,16 +342,12 @@ class DB { // a non-OK status on error. It is not an error if no keys exist in the range // ["begin_key", "end_key"). // - // This feature is currently an experimental performance optimization for - // deleting very large ranges of contiguous keys. Invoking it many times or on - // small ranges may severely degrade read performance; in particular, the - // resulting performance can be worse than calling Delete() for each key in - // the range. Note also the degraded read performance affects keys outside the - // deleted ranges, and affects database operations involving scans, like flush - // and compaction. - // - // Consider setting ReadOptions::ignore_range_deletions = true to speed - // up reads for key(s) that are known to be unaffected by range deletions. + // This feature is now usable in production, with the following caveats: + // 1) Accumulating many range tombstones in the memtable will degrade read + // performance; this can be avoided by manually flushing occasionally. + // 2) Limiting the maximum number of open files in the presence of range + // tombstones can degrade read performance. To avoid this problem, set + // max_open_files to -1 whenever possible. virtual Status DeleteRange(const WriteOptions& options, ColumnFamilyHandle* column_family, const Slice& begin_key, const Slice& end_key); @@ -322,7 +393,8 @@ class DB { virtual Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family, const Slice& key, PinnableSlice* value) = 0; - virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) { + virtual Status Get(const ReadOptions& options, const Slice& key, + std::string* value) { return Get(options, DefaultColumnFamily(), key, value); } @@ -343,9 +415,10 @@ class DB { virtual std::vector MultiGet(const ReadOptions& options, const std::vector& keys, std::vector* values) { - return MultiGet(options, std::vector( - keys.size(), DefaultColumnFamily()), - keys, values); + return MultiGet( + options, + std::vector(keys.size(), DefaultColumnFamily()), + keys, values); } // If the key definitely does not exist in the database, then this method @@ -552,11 +625,20 @@ class DB { // log files that should be kept. static const std::string kMinLogNumberToKeep; + // "rocksdb.min-obsolete-sst-number-to-keep" - return the minimum file + // number for an obsolete SST to be kept. The max value of `uint64_t` + // will be returned if all obsolete files can be deleted. + static const std::string kMinObsoleteSstNumberToKeep; + // "rocksdb.total-sst-files-size" - returns total size (bytes) of all SST // files. // WARNING: may slow down online queries if there are too many files. static const std::string kTotalSstFilesSize; + // "rocksdb.live-sst-files-size" - returns total size (bytes) of all SST + // files belong to the latest LSM tree. + static const std::string kLiveSstFilesSize; + // "rocksdb.base-level" - returns number of level to which L0 data will be // compacted. static const std::string kBaseLevel; @@ -588,6 +670,21 @@ class DB { // FIFO compaction with // compaction_options_fifo.allow_compaction = false. static const std::string kEstimateOldestKeyTime; + + // "rocksdb.block-cache-capacity" - returns block cache capacity. + static const std::string kBlockCacheCapacity; + + // "rocksdb.block-cache-usage" - returns the memory size for the entries + // residing in block cache. + static const std::string kBlockCacheUsage; + + // "rocksdb.block-cache-pinned-usage" - returns the memory size for the + // entries being pinned. + static const std::string kBlockCachePinnedUsage; + + // "rocksdb.options-statistics" - returns multi-line string + // of options.statistics + static const std::string kOptionsStatistics; }; #endif /* ROCKSDB_LITE */ @@ -602,9 +699,9 @@ class DB { } virtual bool GetMapProperty(ColumnFamilyHandle* column_family, const Slice& property, - std::map* value) = 0; + std::map* value) = 0; virtual bool GetMapProperty(const Slice& property, - std::map* value) { + std::map* value) { return GetMapProperty(DefaultColumnFamily(), property, value); } @@ -631,7 +728,9 @@ class DB { // "rocksdb.current-super-version-number" // "rocksdb.estimate-live-data-size" // "rocksdb.min-log-number-to-keep" + // "rocksdb.min-obsolete-sst-number-to-keep" // "rocksdb.total-sst-files-size" + // "rocksdb.live-sst-files-size" // "rocksdb.base-level" // "rocksdb.estimate-pending-compaction-bytes" // "rocksdb.num-running-compactions" @@ -639,6 +738,9 @@ class DB { // "rocksdb.actual-delayed-write-rate" // "rocksdb.is-write-stopped" // "rocksdb.estimate-oldest-key-time" + // "rocksdb.block-cache-capacity" + // "rocksdb.block-cache-usage" + // "rocksdb.block-cache-pinned-usage" virtual bool GetIntProperty(ColumnFamilyHandle* column_family, const Slice& property, uint64_t* value) = 0; virtual bool GetIntProperty(const Slice& property, uint64_t* value) { @@ -678,13 +780,10 @@ class DB { // include_flags should be of type DB::SizeApproximationFlags virtual void GetApproximateSizes(ColumnFamilyHandle* column_family, const Range* range, int n, uint64_t* sizes, - uint8_t include_flags - = INCLUDE_FILES) = 0; + uint8_t include_flags = INCLUDE_FILES) = 0; virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes, - uint8_t include_flags - = INCLUDE_FILES) { - GetApproximateSizes(DefaultColumnFamily(), range, n, sizes, - include_flags); + uint8_t include_flags = INCLUDE_FILES) { + GetApproximateSizes(DefaultColumnFamily(), range, n, sizes, include_flags); } // The method is similar to GetApproximateSizes, except it @@ -701,8 +800,7 @@ class DB { // Deprecated versions of GetApproximateSizes ROCKSDB_DEPRECATED_FUNC virtual void GetApproximateSizes( - const Range* range, int n, uint64_t* sizes, - bool include_memtable) { + const Range* range, int n, uint64_t* sizes, bool include_memtable) { uint8_t include_flags = SizeApproximationFlags::INCLUDE_FILES; if (include_memtable) { include_flags |= SizeApproximationFlags::INCLUDE_MEMTABLES; @@ -710,9 +808,8 @@ class DB { GetApproximateSizes(DefaultColumnFamily(), range, n, sizes, include_flags); } ROCKSDB_DEPRECATED_FUNC virtual void GetApproximateSizes( - ColumnFamilyHandle* column_family, - const Range* range, int n, uint64_t* sizes, - bool include_memtable) { + ColumnFamilyHandle* column_family, const Range* range, int n, + uint64_t* sizes, bool include_memtable) { uint8_t include_flags = SizeApproximationFlags::INCLUDE_FILES; if (include_memtable) { include_flags |= SizeApproximationFlags::INCLUDE_MEMTABLES; @@ -789,20 +886,25 @@ class DB { virtual Status CompactFiles( const CompactionOptions& compact_options, ColumnFamilyHandle* column_family, - const std::vector& input_file_names, - const int output_level, const int output_path_id = -1) = 0; + const std::vector& input_file_names, const int output_level, + const int output_path_id = -1, + std::vector* const output_file_names = nullptr, + CompactionJobInfo* compaction_job_info = nullptr) = 0; virtual Status CompactFiles( const CompactionOptions& compact_options, - const std::vector& input_file_names, - const int output_level, const int output_path_id = -1) { + const std::vector& input_file_names, const int output_level, + const int output_path_id = -1, + std::vector* const output_file_names = nullptr, + CompactionJobInfo* compaction_job_info = nullptr) { return CompactFiles(compact_options, DefaultColumnFamily(), - input_file_names, output_level, output_path_id); + input_file_names, output_level, output_path_id, + output_file_names, compaction_job_info); } // This function will wait until all currently running background processes // finish. After it returns, no background process will be run until - // UnblockBackgroundWork is called + // ContinueBackgroundWork is called virtual Status PauseBackgroundWork() = 0; virtual Status ContinueBackgroundWork() = 0; @@ -854,15 +956,28 @@ class DB { virtual DBOptions GetDBOptions() const = 0; // Flush all mem-table data. + // Flush a single column family, even when atomic flush is enabled. To flush + // multiple column families, use Flush(options, column_families). virtual Status Flush(const FlushOptions& options, ColumnFamilyHandle* column_family) = 0; virtual Status Flush(const FlushOptions& options) { return Flush(options, DefaultColumnFamily()); } + // Flushes multiple column families. + // If atomic flush is not enabled, Flush(options, column_families) is + // equivalent to calling Flush(options, column_family) multiple times. + // If atomic flush is enabled, Flush(options, column_families) will flush all + // column families specified in 'column_families' up to the latest sequence + // number at the time when flush is requested. + // Note that RocksDB 5.15 and earlier may not be able to open later versions + // with atomic flush enabled. + virtual Status Flush( + const FlushOptions& options, + const std::vector& column_families) = 0; // Flush the WAL memory buffer to the file. If sync is true, it calls SyncWAL // afterwards. - virtual Status FlushWAL(bool sync) { + virtual Status FlushWAL(bool /*sync*/) { return Status::NotSupported("FlushWAL not implemented"); } // Sync the wal. Note that Write() followed by SyncWAL() is not exactly the @@ -871,9 +986,27 @@ class DB { // Currently only works if allow_mmap_writes = false in Options. virtual Status SyncWAL() = 0; + // Lock the WAL. Also flushes the WAL after locking. + virtual Status LockWAL() { + return Status::NotSupported("LockWAL not implemented"); + } + + // Unlock the WAL. + virtual Status UnlockWAL() { + return Status::NotSupported("UnlockWAL not implemented"); + } + // The sequence number of the most recent transaction. virtual SequenceNumber GetLatestSequenceNumber() const = 0; + // Instructs DB to preserve deletes with sequence numbers >= passed seqnum. + // Has no effect if DBOptions.preserve_deletes is set to false. + // This function assumes that user calls this function with monotonically + // increasing seqnums (otherwise we can't guarantee that a particular delete + // hasn't been already processed); returns true if the value was successfully + // updated, false if user attempted to call if with seqnum <= current value. + virtual bool SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) = 0; + #ifndef ROCKSDB_LITE // Prevent file deletions. Compactions will continue to occur, @@ -895,14 +1028,14 @@ class DB { // GetLiveFiles followed by GetSortedWalFiles can generate a lossless backup // Retrieve the list of all files in the database. The files are - // relative to the dbname and are not absolute paths. The valid size of the - // manifest file is returned in manifest_file_size. The manifest file is an - // ever growing file, but only the portion specified by manifest_file_size is - // valid for this snapshot. - // Setting flush_memtable to true does Flush before recording the live files. - // Setting flush_memtable to false is useful when we don't want to wait for - // flush which may have to wait for compaction to complete taking an - // indeterminate time. + // relative to the dbname and are not absolute paths. Despite being relative + // paths, the file names begin with "/". The valid size of the manifest file + // is returned in manifest_file_size. The manifest file is an ever growing + // file, but only the portion specified by manifest_file_size is valid for + // this snapshot. Setting flush_memtable to true does Flush before recording + // the live files. Setting flush_memtable to false is useful when we don't + // want to wait for flush which may have to wait for compaction to complete + // taking an indeterminate time. // // In case you have multiple column families, even if flush_memtable is true, // you still need to call GetSortedWalFiles after GetLiveFiles to compensate @@ -915,6 +1048,7 @@ class DB { // Retrieve the sorted list of all wal files with earliest file first virtual Status GetSortedWalFiles(VectorLogPtr& files) = 0; + // Note: this API is not yet consistent with WritePrepared transactions. // Sets iter to an iterator that is positioned at a write-batch containing // seq_number. If the sequence number is non existent, it returns an iterator // at the first available seq_no after the requested seq_no @@ -924,9 +1058,9 @@ class DB { // cleared aggressively and the iterator might keep getting invalid before // an update is read. virtual Status GetUpdatesSince( - SequenceNumber seq_number, unique_ptr* iter, - const TransactionLogIterator::ReadOptions& - read_options = TransactionLogIterator::ReadOptions()) = 0; + SequenceNumber seq_number, std::unique_ptr* iter, + const TransactionLogIterator::ReadOptions& read_options = + TransactionLogIterator::ReadOptions()) = 0; // Windows API macro interference #undef DeleteFile @@ -941,17 +1075,11 @@ class DB { std::vector* /*metadata*/) {} // Obtains the meta data of the specified column family of the DB. - // Status::NotFound() will be returned if the current DB does not have - // any column family match the specified name. - // - // If cf_name is not specified, then the metadata of the default - // column family will be returned. virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/, ColumnFamilyMetaData* /*metadata*/) {} // Get the metadata of the default column family. - void GetColumnFamilyMetaData( - ColumnFamilyMetaData* metadata) { + void GetColumnFamilyMetaData(ColumnFamilyMetaData* metadata) { GetColumnFamilyMetaData(DefaultColumnFamily(), metadata); } @@ -963,7 +1091,7 @@ class DB { // the file can fit in, and ingest the file into this level (2). A file that // have a key range that overlap with the memtable key range will require us // to Flush the memtable first before ingesting the file. - // In the second mode we will always ingest in the bottom mode level (see + // In the second mode we will always ingest in the bottom most level (see // docs to IngestExternalFileOptions::ingest_behind). // // (1) External SST files can be created using SstFileWriter @@ -983,6 +1111,24 @@ class DB { return IngestExternalFile(DefaultColumnFamily(), external_files, options); } + // IngestExternalFiles() will ingest files for multiple column families, and + // record the result atomically to the MANIFEST. + // If this function returns OK, all column families' ingestion must succeed. + // If this function returns NOK, or the process crashes, then non-of the + // files will be ingested into the database after recovery. + // Note that it is possible for application to observe a mixed state during + // the execution of this function. If the user performs range scan over the + // column families with iterators, iterator on one column family may return + // ingested data, while iterator on other column family returns old data. + // Users can use snapshot for a consistent view of data. + // If your db ingests multiple SST files using this API, i.e. args.size() + // > 1, then RocksDB 5.15 and earlier will not be able to open it. + // + // REQUIRES: each arg corresponds to a different column family: namely, for + // 0 <= i < j < len(args), args[i].column_family != args[j].column_family. + virtual Status IngestExternalFiles( + const std::vector& args) = 0; + virtual Status VerifyChecksum() = 0; // AddFile() is deprecated, please use IngestExternalFile() @@ -1107,21 +1253,56 @@ class DB { ColumnFamilyHandle* column_family, const Range* range, std::size_t n, TablePropertiesCollection* props) = 0; - virtual Status SuggestCompactRange(ColumnFamilyHandle* column_family, - const Slice* begin, const Slice* end) { + virtual Status SuggestCompactRange(ColumnFamilyHandle* /*column_family*/, + const Slice* /*begin*/, + const Slice* /*end*/) { return Status::NotSupported("SuggestCompactRange() is not implemented."); } - virtual Status PromoteL0(ColumnFamilyHandle* column_family, - int target_level) { + virtual Status PromoteL0(ColumnFamilyHandle* /*column_family*/, + int /*target_level*/) { return Status::NotSupported("PromoteL0() is not implemented."); } + // Trace DB operations. Use EndTrace() to stop tracing. + virtual Status StartTrace(const TraceOptions& /*options*/, + std::unique_ptr&& /*trace_writer*/) { + return Status::NotSupported("StartTrace() is not implemented."); + } + + virtual Status EndTrace() { + return Status::NotSupported("EndTrace() is not implemented."); + } #endif // ROCKSDB_LITE // Needed for StackableDB virtual DB* GetRootDB() { return this; } + // Given a time window, return an iterator for accessing stats history + // User is responsible for deleting StatsHistoryIterator after use + virtual Status GetStatsHistory( + uint64_t /*start_time*/, uint64_t /*end_time*/, + std::unique_ptr* /*stats_iterator*/) { + return Status::NotSupported("GetStatsHistory() is not implemented."); + } + +#ifndef ROCKSDB_LITE + // Make the secondary instance catch up with the primary by tailing and + // replaying the MANIFEST and WAL of the primary. + // Column families created by the primary after the secondary instance starts + // will be ignored unless the secondary instance closes and restarts with the + // newly created column families. + // Column families that exist before secondary instance starts and dropped by + // the primary afterwards will be marked as dropped. However, as long as the + // secondary instance does not delete the corresponding column family + // handles, the data of the column family is still accessible to the + // secondary. + // TODO: we will support WAL tailing soon. + virtual Status TryCatchUpWithPrimary() { + return Status::NotSupported("Supported only by secondary instance"); + } +#endif // !ROCKSDB_LITE + private: // No copying allowed DB(const DB&); @@ -1130,7 +1311,9 @@ class DB { // Destroy the contents of the specified database. // Be very careful using this method. -Status DestroyDB(const std::string& name, const Options& options); +Status DestroyDB(const std::string& name, const Options& options, + const std::vector& column_families = + std::vector()); #ifndef ROCKSDB_LITE // If a DB cannot be opened, you may attempt to call this method to @@ -1158,5 +1341,3 @@ Status RepairDB(const std::string& dbname, const Options& options); #endif } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_DB_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/db_dump_tool.h b/thirdparty/rocksdb/include/rocksdb/db_dump_tool.h index cb9a265f5c..aeaa3422df 100644 --- a/thirdparty/rocksdb/include/rocksdb/db_dump_tool.h +++ b/thirdparty/rocksdb/include/rocksdb/db_dump_tool.h @@ -17,7 +17,7 @@ struct DumpOptions { std::string db_path; // File location that will contain dump output std::string dump_location; - // Dont include db information header in the dump + // Don't include db information header in the dump bool anonymous = false; }; diff --git a/thirdparty/rocksdb/include/rocksdb/env.h b/thirdparty/rocksdb/include/rocksdb/env.h index 709d503668..4d3a96fe28 100644 --- a/thirdparty/rocksdb/include/rocksdb/env.h +++ b/thirdparty/rocksdb/include/rocksdb/env.h @@ -14,8 +14,7 @@ // All Env implementations are safe for concurrent access from // multiple threads without any external synchronization. -#ifndef STORAGE_ROCKSDB_INCLUDE_ENV_H_ -#define STORAGE_ROCKSDB_INCLUDE_ENV_H_ +#pragma once #include #include @@ -33,6 +32,13 @@ #undef GetCurrentTime #endif +#if defined(__GNUC__) || defined(__clang__) +#define ROCKSDB_PRINTF_FORMAT_ATTR(format_param, dots_param) \ + __attribute__((__format__(__printf__, format_param, dots_param))) +#else +#define ROCKSDB_PRINTF_FORMAT_ATTR(format_param, dots_param) +#endif + namespace rocksdb { class FileLock; @@ -42,31 +48,29 @@ class SequentialFile; class Slice; class WritableFile; class RandomRWFile; +class MemoryMappedFileBuffer; class Directory; struct DBOptions; struct ImmutableDBOptions; +struct MutableDBOptions; class RateLimiter; class ThreadStatusUpdater; struct ThreadStatus; -using std::unique_ptr; -using std::shared_ptr; - const size_t kDefaultPageSize = 4 * 1024; // Options while opening a file to read/write struct EnvOptions { - // Construct with default Options EnvOptions(); // Construct from Options explicit EnvOptions(const DBOptions& options); - // If true, then use mmap to read data + // If true, then use mmap to read data bool use_mmap_reads = false; - // If true, then use mmap to write data + // If true, then use mmap to write data bool use_mmap_writes = true; // If true, then use O_DIRECT for reading data @@ -136,9 +140,8 @@ class Env { // // The returned file will only be accessed by one thread at a time. virtual Status NewSequentialFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) - = 0; + std::unique_ptr* result, + const EnvOptions& options) = 0; // Create a brand new random access read-only file with the // specified name. On success, stores a pointer to the new file in @@ -148,9 +151,18 @@ class Env { // // The returned file may be concurrently accessed by multiple threads. virtual Status NewRandomAccessFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) - = 0; + std::unique_ptr* result, + const EnvOptions& options) = 0; + // These values match Linux definition + // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/fcntl.h#n56 + enum WriteLifeTimeHint { + WLTH_NOT_SET = 0, // No hint information set + WLTH_NONE, // No hints about write life time + WLTH_SHORT, // Data written has a short life time + WLTH_MEDIUM, // Data written has a medium life time + WLTH_LONG, // Data written has a long life time + WLTH_EXTREME, // Data written has an extremely long life time + }; // Create an object that writes to a new file with the specified // name. Deletes any existing file with the same name and creates a @@ -160,7 +172,7 @@ class Env { // // The returned file will only be accessed by one thread at a time. virtual Status NewWritableFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) = 0; // Create an object that writes to a new file with the specified @@ -170,16 +182,16 @@ class Env { // returns non-OK. // // The returned file will only be accessed by one thread at a time. - virtual Status ReopenWritableFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) { + virtual Status ReopenWritableFile(const std::string& /*fname*/, + std::unique_ptr* /*result*/, + const EnvOptions& /*options*/) { return Status::NotSupported(); } // Reuse an existing file by renaming it and opening it as writable. virtual Status ReuseWritableFile(const std::string& fname, const std::string& old_fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options); // Open `fname` for random read and write, if file doesn't exist the file @@ -187,12 +199,22 @@ class Env { // *result and returns OK. On failure returns non-OK. // // The returned file will only be accessed by one thread at a time. - virtual Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) { + virtual Status NewRandomRWFile(const std::string& /*fname*/, + std::unique_ptr* /*result*/, + const EnvOptions& /*options*/) { return Status::NotSupported("RandomRWFile is not implemented in this Env"); } + // Opens `fname` as a memory-mapped file for read and write (in-place updates + // only, i.e., no appends). On success, stores a raw buffer covering the whole + // file in `*result`. The file must exist prior to this call. + virtual Status NewMemoryMappedFileBuffer( + const std::string& /*fname*/, + std::unique_ptr* /*result*/) { + return Status::NotSupported( + "MemoryMappedFileBuffer is not implemented in this Env"); + } + // Create an object that represents a directory. Will fail if directory // doesn't exist. If the directory exists, it will open the directory // and create a new Directory object. @@ -201,7 +223,7 @@ class Env { // *result and returns OK. On failure stores nullptr in *result and // returns non-OK. virtual Status NewDirectory(const std::string& name, - unique_ptr* result) = 0; + std::unique_ptr* result) = 0; // Returns OK if the named file exists. // NotFound if the named file does not exist, @@ -236,6 +258,11 @@ class Env { // Delete the named file. virtual Status DeleteFile(const std::string& fname) = 0; + // Truncate the named file to the specified size. + virtual Status Truncate(const std::string& /*fname*/, size_t /*size*/) { + return Status::NotSupported("Truncate is not supported for this Env"); + } + // Create the specified directory. Returns error if directory exists. virtual Status CreateDir(const std::string& dirname) = 0; @@ -257,10 +284,22 @@ class Env { const std::string& target) = 0; // Hard Link file src to target. - virtual Status LinkFile(const std::string& src, const std::string& target) { + virtual Status LinkFile(const std::string& /*src*/, + const std::string& /*target*/) { return Status::NotSupported("LinkFile is not supported for this Env"); } + virtual Status NumFileLinks(const std::string& /*fname*/, + uint64_t* /*count*/) { + return Status::NotSupported( + "Getting number of file links is not supported for this Env"); + } + + virtual Status AreFilesSame(const std::string& /*first*/, + const std::string& /*second*/, bool* /*res*/) { + return Status::NotSupported("AreFilesSame is not supported for this Env"); + } + // Lock the specified file. Used to prevent concurrent access to // the same db by multiple processes. On failure, stores nullptr in // *lock and returns non-OK. @@ -283,14 +322,12 @@ class Env { virtual Status UnlockFile(FileLock* lock) = 0; // Priority for scheduling job in thread pool - enum Priority { BOTTOM, LOW, HIGH, TOTAL }; + enum Priority { BOTTOM, LOW, HIGH, USER, TOTAL }; + + static std::string PriorityToString(Priority priority); // Priority for requesting bytes in rate limiter scheduler - enum IOPriority { - IO_LOW = 0, - IO_HIGH = 1, - IO_TOTAL = 2 - }; + enum IOPriority { IO_LOW = 0, IO_HIGH = 1, IO_TOTAL = 2 }; // Arrange to run "(*function)(arg)" once in a background thread, in // the thread pool specified by pri. By default, jobs go to the 'LOW' @@ -304,11 +341,11 @@ class Env { // registered at the time of Schedule is invoked with arg as a parameter. virtual void Schedule(void (*function)(void* arg), void* arg, Priority pri = LOW, void* tag = nullptr, - void (*unschedFunction)(void* arg) = 0) = 0; + void (*unschedFunction)(void* arg) = nullptr) = 0; // Arrange to remove jobs for given arg from the queue_ if they are not // already scheduled. Caller is expected to have exclusive lock on arg. - virtual int UnSchedule(void* arg, Priority pri) { return 0; } + virtual int UnSchedule(void* /*arg*/, Priority /*pri*/) { return 0; } // Start a new thread, invoking "function(arg)" within the new thread. // When "function(arg)" returns, the thread will be destroyed. @@ -318,7 +355,7 @@ class Env { virtual void WaitForJoin() {} // Get thread pool queue length for specific thread pool. - virtual unsigned int GetThreadPoolQueueLen(Priority pri = LOW) const { + virtual unsigned int GetThreadPoolQueueLen(Priority /*pri*/ = LOW) const { return 0; } @@ -330,7 +367,7 @@ class Env { // Create and return a log file for storing informational messages. virtual Status NewLogger(const std::string& fname, - shared_ptr* result) = 0; + std::shared_ptr* result) = 0; // Returns the number of micro-seconds since some fixed point in time. // It is often used as system time such as in GenericRateLimiter @@ -342,11 +379,12 @@ class Env { // Default implementation simply relies on NowMicros. // In platform-specific implementations, NowNanos() should return time points // that are MONOTONIC. - virtual uint64_t NowNanos() { - return NowMicros() * 1000; - } + virtual uint64_t NowNanos() { return NowMicros() * 1000; } + + // 0 indicates not supported. + virtual uint64_t NowCPUNanos() { return 0; } - // Sleep/delay the thread for the perscribed number of micro-seconds. + // Sleep/delay the thread for the prescribed number of micro-seconds. virtual void SleepForMicroseconds(int micros) = 0; // Get the current host name. @@ -358,7 +396,7 @@ class Env { // Get full directory name for this db. virtual Status GetAbsolutePath(const std::string& db_path, - std::string* output_path) = 0; + std::string* output_path) = 0; // The number of background worker threads of a specific thread pool // for this environment. 'LOW' is the default pool. @@ -366,13 +404,20 @@ class Env { virtual void SetBackgroundThreads(int number, Priority pri = LOW) = 0; virtual int GetBackgroundThreads(Priority pri = LOW) = 0; + virtual Status SetAllowNonOwnerAccess(bool /*allow_non_owner_access*/) { + return Status::NotSupported("Not supported."); + } + // Enlarge number of background worker threads of a specific thread pool // for this environment if it is smaller than specified. 'LOW' is the default // pool. virtual void IncBackgroundThreadsIfNeeded(int number, Priority pri) = 0; // Lower IO priority for threads from the specified pool. - virtual void LowerThreadPoolIOPriority(Priority pool = LOW) {} + virtual void LowerThreadPoolIOPriority(Priority /*pool*/ = LOW) {} + + // Lower CPU priority for threads from the specified pool. + virtual void LowerThreadPoolCPUPriority(Priority /*pool*/ = LOW) {} // Converts seconds-since-Jan-01-1970 to a printable string virtual std::string TimeToString(uint64_t time) = 0; @@ -406,7 +451,7 @@ class Env { // table files. virtual EnvOptions OptimizeForCompactionTableWrite( const EnvOptions& env_options, - const ImmutableDBOptions& db_options) const; + const ImmutableDBOptions& immutable_ops) const; // OptimizeForCompactionTableWrite will create a new EnvOptions object that // is a copy of the EnvOptions in the parameters, but is optimized for reading @@ -416,7 +461,7 @@ class Env { const ImmutableDBOptions& db_options) const; // Returns the status of all threads that belong to the current Env. - virtual Status GetThreadList(std::vector* thread_list) { + virtual Status GetThreadList(std::vector* /*thread_list*/) { return Status::NotSupported("Not supported."); } @@ -430,6 +475,17 @@ class Env { // Returns the ID of the current thread. virtual uint64_t GetThreadID() const; +// This seems to clash with a macro on Windows, so #undef it here +#undef GetFreeSpace + + // Get the amount of free disk space + virtual Status GetFreeSpace(const std::string& /*path*/, + uint64_t* /*diskfree*/) { + return Status::NotSupported(); + } + + // If you're adding methods here, remember to add them to EnvWrapper too. + protected: // The pointer to an internal structure that will update the // status of each thread. @@ -449,7 +505,7 @@ ThreadStatusUpdater* CreateThreadStatusUpdater(); // A file abstraction for reading sequentially through a file class SequentialFile { public: - SequentialFile() { } + SequentialFile() {} virtual ~SequentialFile(); // Read up to "n" bytes from the file. "scratch[0..n-1]" may be @@ -482,23 +538,25 @@ class SequentialFile { // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. - virtual Status InvalidateCache(size_t offset, size_t length) { + virtual Status InvalidateCache(size_t /*offset*/, size_t /*length*/) { return Status::NotSupported("InvalidateCache not supported."); } // Positioned Read for direct I/O // If Direct I/O enabled, offset, n, and scratch should be properly aligned - virtual Status PositionedRead(uint64_t offset, size_t n, Slice* result, - char* scratch) { + virtual Status PositionedRead(uint64_t /*offset*/, size_t /*n*/, + Slice* /*result*/, char* /*scratch*/) { return Status::NotSupported(); } + + // If you're adding methods here, remember to add them to + // SequentialFileWrapper too. }; // A file abstraction for randomly reading the contents of a file. class RandomAccessFile { public: - - RandomAccessFile() { } + RandomAccessFile() {} virtual ~RandomAccessFile(); // Read up to "n" bytes from the file starting at "offset". @@ -515,7 +573,7 @@ class RandomAccessFile { char* scratch) const = 0; // Readahead the file starting from offset by n bytes for caching. - virtual Status Prefetch(uint64_t offset, size_t n) { + virtual Status Prefetch(uint64_t /*offset*/, size_t /*n*/) { return Status::OK(); } @@ -534,14 +592,14 @@ class RandomAccessFile { // a single varint. // // Note: these IDs are only valid for the duration of the process. - virtual size_t GetUniqueId(char* id, size_t max_size) const { - return 0; // Default implementation to prevent issues with backwards - // compatibility. + virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const { + return 0; // Default implementation to prevent issues with backwards + // compatibility. }; enum AccessPattern { NORMAL, RANDOM, SEQUENTIAL, WILLNEED, DONTNEED }; - virtual void Hint(AccessPattern pattern) {} + virtual void Hint(AccessPattern /*pattern*/) {} // Indicates the upper layers if the current RandomAccessFile implementation // uses direct IO. @@ -554,9 +612,12 @@ class RandomAccessFile { // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. - virtual Status InvalidateCache(size_t offset, size_t length) { + virtual Status InvalidateCache(size_t /*offset*/, size_t /*length*/) { return Status::NotSupported("InvalidateCache not supported."); } + + // If you're adding methods here, remember to add them to + // RandomAccessFileWrapper too. }; // A file abstraction for sequential writing. The implementation @@ -565,10 +626,10 @@ class RandomAccessFile { class WritableFile { public: WritableFile() - : last_preallocated_block_(0), - preallocation_block_size_(0), - io_priority_(Env::IO_TOTAL) { - } + : last_preallocated_block_(0), + preallocation_block_size_(0), + io_priority_(Env::IO_TOTAL), + write_hint_(Env::WLTH_NOT_SET) {} virtual ~WritableFile(); // Append data to the end of the file @@ -596,7 +657,8 @@ class WritableFile { // // PositionedAppend() requires aligned buffer to be passed in. The alignment // required is queried via GetRequiredBufferAlignment() - virtual Status PositionedAppend(const Slice& /* data */, uint64_t /* offset */) { + virtual Status PositionedAppend(const Slice& /* data */, + uint64_t /* offset */) { return Status::NotSupported(); } @@ -604,12 +666,10 @@ class WritableFile { // before closing. It is not always possible to keep track of the file // size due to whole pages writes. The behavior is undefined if called // with other writes to follow. - virtual Status Truncate(uint64_t size) { - return Status::OK(); - } + virtual Status Truncate(uint64_t /*size*/) { return Status::OK(); } virtual Status Close() = 0; virtual Status Flush() = 0; - virtual Status Sync() = 0; // sync data + virtual Status Sync() = 0; // sync data /* * Sync data and/or metadata as well. @@ -617,15 +677,11 @@ class WritableFile { * Override this method for environments where we need to sync * metadata as well. */ - virtual Status Fsync() { - return Sync(); - } + virtual Status Fsync() { return Sync(); } // true if Sync() and Fsync() are safe to call concurrently with Append() // and Flush(). - virtual bool IsSyncThreadSafe() const { - return false; - } + virtual bool IsSyncThreadSafe() const { return false; } // Indicates the upper layers if the current WritableFile implementation // uses direct IO. @@ -638,18 +694,19 @@ class WritableFile { * Change the priority in rate limiter if rate limiting is enabled. * If rate limiting is not enabled, this call has no effect. */ - virtual void SetIOPriority(Env::IOPriority pri) { - io_priority_ = pri; - } + virtual void SetIOPriority(Env::IOPriority pri) { io_priority_ = pri; } virtual Env::IOPriority GetIOPriority() { return io_priority_; } + virtual void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) { + write_hint_ = hint; + } + + virtual Env::WriteLifeTimeHint GetWriteLifeTimeHint() { return write_hint_; } /* * Get the size of valid data in the file. */ - virtual uint64_t GetFileSize() { - return 0; - } + virtual uint64_t GetFileSize() { return 0; } /* * Get and set the default pre-allocation block size for writes to @@ -668,15 +725,15 @@ class WritableFile { } // For documentation, refer to RandomAccessFile::GetUniqueId() - virtual size_t GetUniqueId(char* id, size_t max_size) const { - return 0; // Default implementation to prevent issues with backwards + virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const { + return 0; // Default implementation to prevent issues with backwards } // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. // This call has no effect on dirty pages in the cache. - virtual Status InvalidateCache(size_t offset, size_t length) { + virtual Status InvalidateCache(size_t /*offset*/, size_t /*length*/) { return Status::NotSupported("InvalidateCache not supported."); } @@ -686,7 +743,9 @@ class WritableFile { // This asks the OS to initiate flushing the cached data to disk, // without waiting for completion. // Default implementation does nothing. - virtual Status RangeSync(uint64_t offset, uint64_t nbytes) { return Status::OK(); } + virtual Status RangeSync(uint64_t /*offset*/, uint64_t /*nbytes*/) { + return Status::OK(); + } // PrepareWrite performs any necessary preparation for a write // before the write actually occurs. This allows for pre-allocation @@ -702,10 +761,10 @@ class WritableFile { // cover this write would be and Allocate to that point. const auto block_size = preallocation_block_size_; size_t new_last_preallocated_block = - (offset + len + block_size - 1) / block_size; + (offset + len + block_size - 1) / block_size; if (new_last_preallocated_block > last_preallocated_block_) { size_t num_spanned_blocks = - new_last_preallocated_block - last_preallocated_block_; + new_last_preallocated_block - last_preallocated_block_; Allocate(block_size * last_preallocated_block_, block_size * num_spanned_blocks); last_preallocated_block_ = new_last_preallocated_block; @@ -713,10 +772,13 @@ class WritableFile { } // Pre-allocates space for a file. - virtual Status Allocate(uint64_t offset, uint64_t len) { + virtual Status Allocate(uint64_t /*offset*/, uint64_t /*len*/) { return Status::OK(); } + // If you're adding methods here, remember to add them to + // WritableFileWrapper too. + protected: size_t preallocation_block_size() { return preallocation_block_size_; } @@ -728,10 +790,8 @@ class WritableFile { void operator=(const WritableFile&); protected: - friend class WritableFileWrapper; - friend class WritableFileMirror; - Env::IOPriority io_priority_; + Env::WriteLifeTimeHint write_hint_; }; // A file abstraction for random reading and writing. @@ -766,11 +826,36 @@ class RandomRWFile { virtual Status Close() = 0; + // If you're adding methods here, remember to add them to + // RandomRWFileWrapper too. + // No copying allowed RandomRWFile(const RandomRWFile&) = delete; RandomRWFile& operator=(const RandomRWFile&) = delete; }; +// MemoryMappedFileBuffer object represents a memory-mapped file's raw buffer. +// Subclasses should release the mapping upon destruction. +class MemoryMappedFileBuffer { + public: + MemoryMappedFileBuffer(void* _base, size_t _length) + : base_(_base), length_(_length) {} + + virtual ~MemoryMappedFileBuffer() = 0; + + // We do not want to unmap this twice. We can make this class + // movable if desired, however, since + MemoryMappedFileBuffer(const MemoryMappedFileBuffer&) = delete; + MemoryMappedFileBuffer& operator=(const MemoryMappedFileBuffer&) = delete; + + void* GetBase() const { return base_; } + size_t GetLen() const { return length_; } + + protected: + void* base_; + const size_t length_; +}; + // Directory object represents collection of files and implements // filesystem operations that can be executed on directories. class Directory { @@ -778,6 +863,13 @@ class Directory { virtual ~Directory() {} // Fsync directory. Can be called concurrently from multiple threads. virtual Status Fsync() = 0; + + virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const { + return 0; + } + + // If you're adding methods here, remember to add them to + // DirectoryWrapper too. }; enum InfoLogLevel : unsigned char { @@ -796,9 +888,14 @@ class Logger { size_t kDoNotSupportGetLogFileSize = (std::numeric_limits::max)(); explicit Logger(const InfoLogLevel log_level = InfoLogLevel::INFO_LEVEL) - : log_level_(log_level) {} + : closed_(false), log_level_(log_level) {} virtual ~Logger(); + // Close the log file. Must be called before destructor. If the return + // status is NotSupported(), it means the implementation does cleanup in + // the destructor + virtual Status Close(); + // Write a header to the log file with the specified format // It is recommended that you log all header information at the start of the // application. But it is not enforced. @@ -815,7 +912,8 @@ class Logger { // and format. Any log with level under the internal log level // of *this (see @SetInfoLogLevel and @GetInfoLogLevel) will not be // printed. - virtual void Logv(const InfoLogLevel log_level, const char* format, va_list ap); + virtual void Logv(const InfoLogLevel log_level, const char* format, + va_list ap); virtual size_t GetLogFileSize() const { return kDoNotSupportGetLogFileSize; } // Flush to the OS buffers @@ -825,6 +923,12 @@ class Logger { log_level_ = log_level; } + // If you're adding methods here, remember to add them to LoggerWrapper too. + + protected: + virtual Status CloseImpl(); + bool closed_; + private: // No copying allowed Logger(const Logger&); @@ -832,58 +936,65 @@ class Logger { InfoLogLevel log_level_; }; - // Identifies a locked file. class FileLock { public: - FileLock() { } + FileLock() {} virtual ~FileLock(); + private: // No copying allowed FileLock(const FileLock&); void operator=(const FileLock&); }; -extern void LogFlush(const shared_ptr& info_log); +extern void LogFlush(const std::shared_ptr& info_log); extern void Log(const InfoLogLevel log_level, - const shared_ptr& info_log, const char* format, ...); + const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(3, 4); // a set of log functions with different log levels. -extern void Header(const shared_ptr& info_log, const char* format, ...); -extern void Debug(const shared_ptr& info_log, const char* format, ...); -extern void Info(const shared_ptr& info_log, const char* format, ...); -extern void Warn(const shared_ptr& info_log, const char* format, ...); -extern void Error(const shared_ptr& info_log, const char* format, ...); -extern void Fatal(const shared_ptr& info_log, const char* format, ...); +extern void Header(const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Debug(const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Info(const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Warn(const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Error(const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Fatal(const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); // Log the specified data to *info_log if info_log is non-nullptr. // The default info log level is InfoLogLevel::INFO_LEVEL. -extern void Log(const shared_ptr& info_log, const char* format, ...) -# if defined(__GNUC__) || defined(__clang__) - __attribute__((__format__ (__printf__, 2, 3))) -# endif - ; +extern void Log(const std::shared_ptr& info_log, const char* format, + ...) ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); -extern void LogFlush(Logger *info_log); +extern void LogFlush(Logger* info_log); extern void Log(const InfoLogLevel log_level, Logger* info_log, - const char* format, ...); + const char* format, ...) ROCKSDB_PRINTF_FORMAT_ATTR(3, 4); // The default info log level is InfoLogLevel::INFO_LEVEL. extern void Log(Logger* info_log, const char* format, ...) -# if defined(__GNUC__) || defined(__clang__) - __attribute__((__format__ (__printf__, 2, 3))) -# endif - ; + ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); // a set of log functions with different log levels. -extern void Header(Logger* info_log, const char* format, ...); -extern void Debug(Logger* info_log, const char* format, ...); -extern void Info(Logger* info_log, const char* format, ...); -extern void Warn(Logger* info_log, const char* format, ...); -extern void Error(Logger* info_log, const char* format, ...); -extern void Fatal(Logger* info_log, const char* format, ...); +extern void Header(Logger* info_log, const char* format, ...) + ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Debug(Logger* info_log, const char* format, ...) + ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Info(Logger* info_log, const char* format, ...) + ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Warn(Logger* info_log, const char* format, ...) + ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Error(Logger* info_log, const char* format, ...) + ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); +extern void Fatal(Logger* info_log, const char* format, ...) + ROCKSDB_PRINTF_FORMAT_ATTR(2, 3); // A utility routine: write "data" to the named file. extern Status WriteStringToFile(Env* env, const Slice& data, @@ -894,50 +1005,79 @@ extern Status WriteStringToFile(Env* env, const Slice& data, extern Status ReadFileToString(Env* env, const std::string& fname, std::string* data); +// Below are helpers for wrapping most of the classes in this file. +// They forward all calls to another instance of the class. +// Useful when wrapping the default implementations. +// Typical usage is to inherit your wrapper from *Wrapper, e.g.: +// +// class MySequentialFileWrapper : public rocksdb::SequentialFileWrapper { +// public: +// MySequentialFileWrapper(rocksdb::SequentialFile* target): +// rocksdb::SequentialFileWrapper(target) {} +// Status Read(size_t n, Slice* result, char* scratch) override { +// cout << "Doing a read of size " << n << "!" << endl; +// return rocksdb::SequentialFileWrapper::Read(n, result, scratch); +// } +// // All other methods are forwarded to target_ automatically. +// }; +// +// This is often more convenient than inheriting the class directly because +// (a) Don't have to override and forward all methods - the Wrapper will +// forward everything you're not explicitly overriding. +// (b) Don't need to update the wrapper when more methods are added to the +// rocksdb class. Unless you actually want to override the behavior. +// (And unless rocksdb people forgot to update the *Wrapper class.) + // An implementation of Env that forwards all calls to another Env. // May be useful to clients who wish to override just part of the // functionality of another Env. class EnvWrapper : public Env { public: // Initialize an EnvWrapper that delegates all calls to *t - explicit EnvWrapper(Env* t) : target_(t) { } + explicit EnvWrapper(Env* t) : target_(t) {} ~EnvWrapper() override; // Return the target to which this Env forwards all calls Env* target() const { return target_; } // The following text is boilerplate that forwards all methods to target() - Status NewSequentialFile(const std::string& f, unique_ptr* r, + Status NewSequentialFile(const std::string& f, + std::unique_ptr* r, const EnvOptions& options) override { return target_->NewSequentialFile(f, r, options); } Status NewRandomAccessFile(const std::string& f, - unique_ptr* r, + std::unique_ptr* r, const EnvOptions& options) override { return target_->NewRandomAccessFile(f, r, options); } - Status NewWritableFile(const std::string& f, unique_ptr* r, + Status NewWritableFile(const std::string& f, std::unique_ptr* r, const EnvOptions& options) override { return target_->NewWritableFile(f, r, options); } Status ReopenWritableFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) override { return target_->ReopenWritableFile(fname, result, options); } Status ReuseWritableFile(const std::string& fname, const std::string& old_fname, - unique_ptr* r, + std::unique_ptr* r, const EnvOptions& options) override { return target_->ReuseWritableFile(fname, old_fname, r, options); } Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, + std::unique_ptr* result, const EnvOptions& options) override { return target_->NewRandomRWFile(fname, result, options); } + Status NewMemoryMappedFileBuffer( + const std::string& fname, + std::unique_ptr* result) override { + return target_->NewMemoryMappedFileBuffer(fname, result); + } Status NewDirectory(const std::string& name, - unique_ptr* result) override { + std::unique_ptr* result) override { return target_->NewDirectory(name, result); } Status FileExists(const std::string& f) override { @@ -954,6 +1094,9 @@ class EnvWrapper : public Env { Status DeleteFile(const std::string& f) override { return target_->DeleteFile(f); } + Status Truncate(const std::string& fname, size_t size) override { + return target_->Truncate(fname, size); + } Status CreateDir(const std::string& d) override { return target_->CreateDir(d); } @@ -980,6 +1123,15 @@ class EnvWrapper : public Env { return target_->LinkFile(s, t); } + Status NumFileLinks(const std::string& fname, uint64_t* count) override { + return target_->NumFileLinks(fname, count); + } + + Status AreFilesSame(const std::string& first, const std::string& second, + bool* res) override { + return target_->AreFilesSame(first, second, res); + } + Status LockFile(const std::string& f, FileLock** l) override { return target_->LockFile(f, l); } @@ -987,7 +1139,7 @@ class EnvWrapper : public Env { Status UnlockFile(FileLock* l) override { return target_->UnlockFile(l); } void Schedule(void (*f)(void* arg), void* a, Priority pri, - void* tag = nullptr, void (*u)(void* arg) = 0) override { + void* tag = nullptr, void (*u)(void* arg) = nullptr) override { return target_->Schedule(f, a, pri, tag, u); } @@ -1006,10 +1158,12 @@ class EnvWrapper : public Env { return target_->GetTestDirectory(path); } Status NewLogger(const std::string& fname, - shared_ptr* result) override { + std::shared_ptr* result) override { return target_->NewLogger(fname, result); } uint64_t NowMicros() override { return target_->NowMicros(); } + uint64_t NowNanos() override { return target_->NowNanos(); } + uint64_t NowCPUNanos() override { return target_->NowCPUNanos(); } void SleepForMicroseconds(int micros) override { target_->SleepForMicroseconds(micros); @@ -1031,6 +1185,10 @@ class EnvWrapper : public Env { return target_->GetBackgroundThreads(pri); } + Status SetAllowNonOwnerAccess(bool allow_non_owner_access) override { + return target_->SetAllowNonOwnerAccess(allow_non_owner_access); + } + void IncBackgroundThreadsIfNeeded(int num, Priority pri) override { return target_->IncBackgroundThreadsIfNeeded(num, pri); } @@ -1039,6 +1197,10 @@ class EnvWrapper : public Env { target_->LowerThreadPoolIOPriority(pool); } + void LowerThreadPoolCPUPriority(Priority pool = LOW) override { + target_->LowerThreadPoolCPUPriority(pool); + } + std::string TimeToString(uint64_t time) override { return target_->TimeToString(time); } @@ -1051,26 +1213,100 @@ class EnvWrapper : public Env { return target_->GetThreadStatusUpdater(); } - uint64_t GetThreadID() const override { - return target_->GetThreadID(); - } + uint64_t GetThreadID() const override { return target_->GetThreadID(); } std::string GenerateUniqueId() override { return target_->GenerateUniqueId(); } + EnvOptions OptimizeForLogRead(const EnvOptions& env_options) const override { + return target_->OptimizeForLogRead(env_options); + } + EnvOptions OptimizeForManifestRead( + const EnvOptions& env_options) const override { + return target_->OptimizeForManifestRead(env_options); + } + EnvOptions OptimizeForLogWrite(const EnvOptions& env_options, + const DBOptions& db_options) const override { + return target_->OptimizeForLogWrite(env_options, db_options); + } + EnvOptions OptimizeForManifestWrite( + const EnvOptions& env_options) const override { + return target_->OptimizeForManifestWrite(env_options); + } + EnvOptions OptimizeForCompactionTableWrite( + const EnvOptions& env_options, + const ImmutableDBOptions& immutable_ops) const override { + return target_->OptimizeForCompactionTableWrite(env_options, immutable_ops); + } + EnvOptions OptimizeForCompactionTableRead( + const EnvOptions& env_options, + const ImmutableDBOptions& db_options) const override { + return target_->OptimizeForCompactionTableRead(env_options, db_options); + } + Status GetFreeSpace(const std::string& path, uint64_t* diskfree) override { + return target_->GetFreeSpace(path, diskfree); + } + private: Env* target_; }; -// An implementation of WritableFile that forwards all calls to another -// WritableFile. May be useful to clients who wish to override just part of the -// functionality of another WritableFile. -// It's declared as friend of WritableFile to allow forwarding calls to -// protected virtual methods. +class SequentialFileWrapper : public SequentialFile { + public: + explicit SequentialFileWrapper(SequentialFile* target) : target_(target) {} + + Status Read(size_t n, Slice* result, char* scratch) override { + return target_->Read(n, result, scratch); + } + Status Skip(uint64_t n) override { return target_->Skip(n); } + bool use_direct_io() const override { return target_->use_direct_io(); } + size_t GetRequiredBufferAlignment() const override { + return target_->GetRequiredBufferAlignment(); + } + Status InvalidateCache(size_t offset, size_t length) override { + return target_->InvalidateCache(offset, length); + } + Status PositionedRead(uint64_t offset, size_t n, Slice* result, + char* scratch) override { + return target_->PositionedRead(offset, n, result, scratch); + } + + private: + SequentialFile* target_; +}; + +class RandomAccessFileWrapper : public RandomAccessFile { + public: + explicit RandomAccessFileWrapper(RandomAccessFile* target) + : target_(target) {} + + Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override { + return target_->Read(offset, n, result, scratch); + } + Status Prefetch(uint64_t offset, size_t n) override { + return target_->Prefetch(offset, n); + } + size_t GetUniqueId(char* id, size_t max_size) const override { + return target_->GetUniqueId(id, max_size); + }; + void Hint(AccessPattern pattern) override { target_->Hint(pattern); } + bool use_direct_io() const override { return target_->use_direct_io(); } + size_t GetRequiredBufferAlignment() const override { + return target_->GetRequiredBufferAlignment(); + } + Status InvalidateCache(size_t offset, size_t length) override { + return target_->InvalidateCache(offset, length); + } + + private: + RandomAccessFile* target_; +}; + class WritableFileWrapper : public WritableFile { public: - explicit WritableFileWrapper(WritableFile* t) : target_(t) { } + explicit WritableFileWrapper(WritableFile* t) : target_(t) {} Status Append(const Slice& data) override { return target_->Append(data); } Status PositionedAppend(const Slice& data, uint64_t offset) override { @@ -1082,41 +1318,127 @@ class WritableFileWrapper : public WritableFile { Status Sync() override { return target_->Sync(); } Status Fsync() override { return target_->Fsync(); } bool IsSyncThreadSafe() const override { return target_->IsSyncThreadSafe(); } + + bool use_direct_io() const override { return target_->use_direct_io(); } + + size_t GetRequiredBufferAlignment() const override { + return target_->GetRequiredBufferAlignment(); + } + void SetIOPriority(Env::IOPriority pri) override { target_->SetIOPriority(pri); } + Env::IOPriority GetIOPriority() override { return target_->GetIOPriority(); } + + void SetWriteLifeTimeHint(Env::WriteLifeTimeHint hint) override { + target_->SetWriteLifeTimeHint(hint); + } + + Env::WriteLifeTimeHint GetWriteLifeTimeHint() override { + return target_->GetWriteLifeTimeHint(); + } + uint64_t GetFileSize() override { return target_->GetFileSize(); } + + void SetPreallocationBlockSize(size_t size) override { + target_->SetPreallocationBlockSize(size); + } + void GetPreallocationStatus(size_t* block_size, size_t* last_allocated_block) override { target_->GetPreallocationStatus(block_size, last_allocated_block); } + size_t GetUniqueId(char* id, size_t max_size) const override { return target_->GetUniqueId(id, max_size); } + Status InvalidateCache(size_t offset, size_t length) override { return target_->InvalidateCache(offset, length); } - void SetPreallocationBlockSize(size_t size) override { - target_->SetPreallocationBlockSize(size); + Status RangeSync(uint64_t offset, uint64_t nbytes) override { + return target_->RangeSync(offset, nbytes); } + void PrepareWrite(size_t offset, size_t len) override { target_->PrepareWrite(offset, len); } - protected: Status Allocate(uint64_t offset, uint64_t len) override { return target_->Allocate(offset, len); } - Status RangeSync(uint64_t offset, uint64_t nbytes) override { - return target_->RangeSync(offset, nbytes); - } private: WritableFile* target_; }; +class RandomRWFileWrapper : public RandomRWFile { + public: + explicit RandomRWFileWrapper(RandomRWFile* target) : target_(target) {} + + bool use_direct_io() const override { return target_->use_direct_io(); } + size_t GetRequiredBufferAlignment() const override { + return target_->GetRequiredBufferAlignment(); + } + Status Write(uint64_t offset, const Slice& data) override { + return target_->Write(offset, data); + } + Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override { + return target_->Read(offset, n, result, scratch); + } + Status Flush() override { return target_->Flush(); } + Status Sync() override { return target_->Sync(); } + Status Fsync() override { return target_->Fsync(); } + Status Close() override { return target_->Close(); } + + private: + RandomRWFile* target_; +}; + +class DirectoryWrapper : public Directory { + public: + explicit DirectoryWrapper(Directory* target) : target_(target) {} + + Status Fsync() override { return target_->Fsync(); } + size_t GetUniqueId(char* id, size_t max_size) const override { + return target_->GetUniqueId(id, max_size); + } + + private: + Directory* target_; +}; + +class LoggerWrapper : public Logger { + public: + explicit LoggerWrapper(Logger* target) : target_(target) {} + + Status Close() override { return target_->Close(); } + void LogHeader(const char* format, va_list ap) override { + return target_->LogHeader(format, ap); + } + void Logv(const char* format, va_list ap) override { + return target_->Logv(format, ap); + } + void Logv(const InfoLogLevel log_level, const char* format, + va_list ap) override { + return target_->Logv(log_level, format, ap); + } + size_t GetLogFileSize() const override { return target_->GetLogFileSize(); } + void Flush() override { return target_->Flush(); } + InfoLogLevel GetInfoLogLevel() const override { + return target_->GetInfoLogLevel(); + } + void SetInfoLogLevel(const InfoLogLevel log_level) override { + return target_->SetInfoLogLevel(log_level); + } + + private: + Logger* target_; +}; + // Returns a new environment that stores its data in memory and delegates // all non-file-storage tasks to base_env. The caller must delete the result // when it is no longer needed. @@ -1133,5 +1455,3 @@ Status NewHdfsEnv(Env** hdfs_env, const std::string& fsname); Env* NewTimedEnv(Env* base_env); } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_ENV_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/env_encryption.h b/thirdparty/rocksdb/include/rocksdb/env_encryption.h index e4c924a4b4..a80da963a3 100644 --- a/thirdparty/rocksdb/include/rocksdb/env_encryption.h +++ b/thirdparty/rocksdb/include/rocksdb/env_encryption.h @@ -5,7 +5,7 @@ #pragma once -#if !defined(ROCKSDB_LITE) +#if !defined(ROCKSDB_LITE) #include @@ -15,180 +15,190 @@ namespace rocksdb { class EncryptionProvider; -// Returns an Env that encrypts data when stored on disk and decrypts data when +// Returns an Env that encrypts data when stored on disk and decrypts data when // read from disk. Env* NewEncryptedEnv(Env* base_env, EncryptionProvider* provider); -// BlockAccessCipherStream is the base class for any cipher stream that -// supports random access at block level (without requiring data from other blocks). -// E.g. CTR (Counter operation mode) supports this requirement. +// BlockAccessCipherStream is the base class for any cipher stream that +// supports random access at block level (without requiring data from other +// blocks). E.g. CTR (Counter operation mode) supports this requirement. class BlockAccessCipherStream { - public: - virtual ~BlockAccessCipherStream() {}; + public: + virtual ~BlockAccessCipherStream(){}; - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() = 0; + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() = 0; - // Encrypt one or more (partial) blocks of data at the file offset. - // Length of data is given in dataSize. - virtual Status Encrypt(uint64_t fileOffset, char *data, size_t dataSize); + // Encrypt one or more (partial) blocks of data at the file offset. + // Length of data is given in dataSize. + virtual Status Encrypt(uint64_t fileOffset, char* data, size_t dataSize); - // Decrypt one or more (partial) blocks of data at the file offset. - // Length of data is given in dataSize. - virtual Status Decrypt(uint64_t fileOffset, char *data, size_t dataSize); + // Decrypt one or more (partial) blocks of data at the file offset. + // Length of data is given in dataSize. + virtual Status Decrypt(uint64_t fileOffset, char* data, size_t dataSize); - protected: - // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. - virtual void AllocateScratch(std::string&) = 0; + protected: + // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. + virtual void AllocateScratch(std::string&) = 0; - // Encrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status EncryptBlock(uint64_t blockIndex, char *data, char* scratch) = 0; + // Encrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status EncryptBlock(uint64_t blockIndex, char* data, + char* scratch) = 0; - // Decrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status DecryptBlock(uint64_t blockIndex, char *data, char* scratch) = 0; + // Decrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status DecryptBlock(uint64_t blockIndex, char* data, + char* scratch) = 0; }; -// BlockCipher +// BlockCipher class BlockCipher { - public: - virtual ~BlockCipher() {}; + public: + virtual ~BlockCipher(){}; - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() = 0; + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() = 0; - // Encrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Encrypt(char *data) = 0; + // Encrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Encrypt(char* data) = 0; - // Decrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Decrypt(char *data) = 0; + // Decrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Decrypt(char* data) = 0; }; // Implements a BlockCipher using ROT13. // -// Note: This is a sample implementation of BlockCipher, +// Note: This is a sample implementation of BlockCipher, // it is NOT considered safe and should NOT be used in production. class ROT13BlockCipher : public BlockCipher { - private: - size_t blockSize_; - public: - ROT13BlockCipher(size_t blockSize) - : blockSize_(blockSize) {} - virtual ~ROT13BlockCipher() {}; - - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() override { return blockSize_; } - - // Encrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Encrypt(char *data) override; - - // Decrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Decrypt(char *data) override; + private: + size_t blockSize_; + + public: + ROT13BlockCipher(size_t blockSize) : blockSize_(blockSize) {} + virtual ~ROT13BlockCipher(){}; + + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() override { return blockSize_; } + + // Encrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Encrypt(char* data) override; + + // Decrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Decrypt(char* data) override; }; -// CTRCipherStream implements BlockAccessCipherStream using an -// Counter operations mode. +// CTRCipherStream implements BlockAccessCipherStream using an +// Counter operations mode. // See https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation // -// Note: This is a possible implementation of BlockAccessCipherStream, +// Note: This is a possible implementation of BlockAccessCipherStream, // it is considered suitable for use. class CTRCipherStream final : public BlockAccessCipherStream { - private: - BlockCipher& cipher_; - std::string iv_; - uint64_t initialCounter_; - public: - CTRCipherStream(BlockCipher& c, const char *iv, uint64_t initialCounter) - : cipher_(c), iv_(iv, c.BlockSize()), initialCounter_(initialCounter) {}; - virtual ~CTRCipherStream() {}; - - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() override { return cipher_.BlockSize(); } - - protected: - // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. - virtual void AllocateScratch(std::string&) override; - - // Encrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status EncryptBlock(uint64_t blockIndex, char *data, char *scratch) override; - - // Decrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status DecryptBlock(uint64_t blockIndex, char *data, char *scratch) override; + private: + BlockCipher& cipher_; + std::string iv_; + uint64_t initialCounter_; + + public: + CTRCipherStream(BlockCipher& c, const char* iv, uint64_t initialCounter) + : cipher_(c), iv_(iv, c.BlockSize()), initialCounter_(initialCounter){}; + virtual ~CTRCipherStream(){}; + + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() override { return cipher_.BlockSize(); } + + protected: + // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. + virtual void AllocateScratch(std::string&) override; + + // Encrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status EncryptBlock(uint64_t blockIndex, char* data, + char* scratch) override; + + // Decrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status DecryptBlock(uint64_t blockIndex, char* data, + char* scratch) override; }; -// The encryption provider is used to create a cipher stream for a specific file. -// The returned cipher stream will be used for actual encryption/decryption -// actions. +// The encryption provider is used to create a cipher stream for a specific +// file. The returned cipher stream will be used for actual +// encryption/decryption actions. class EncryptionProvider { public: - virtual ~EncryptionProvider() {}; - - // GetPrefixLength returns the length of the prefix that is added to every file - // and used for storing encryption options. - // For optimal performance, the prefix length should be a multiple of - // the a page size. - virtual size_t GetPrefixLength() = 0; - - // CreateNewPrefix initialized an allocated block of prefix memory - // for a new file. - virtual Status CreateNewPrefix(const std::string& fname, char *prefix, size_t prefixLength) = 0; - - // CreateCipherStream creates a block access cipher stream for a file given - // given name and options. - virtual Status CreateCipherStream(const std::string& fname, const EnvOptions& options, - Slice& prefix, unique_ptr* result) = 0; + virtual ~EncryptionProvider(){}; + + // GetPrefixLength returns the length of the prefix that is added to every + // file and used for storing encryption options. For optimal performance, the + // prefix length should be a multiple of the page size. + virtual size_t GetPrefixLength() = 0; + + // CreateNewPrefix initialized an allocated block of prefix memory + // for a new file. + virtual Status CreateNewPrefix(const std::string& fname, char* prefix, + size_t prefixLength) = 0; + + // CreateCipherStream creates a block access cipher stream for a file given + // given name and options. + virtual Status CreateCipherStream( + const std::string& fname, const EnvOptions& options, Slice& prefix, + std::unique_ptr* result) = 0; }; -// This encryption provider uses a CTR cipher stream, with a given block cipher +// This encryption provider uses a CTR cipher stream, with a given block cipher // and IV. // -// Note: This is a possible implementation of EncryptionProvider, +// Note: This is a possible implementation of EncryptionProvider, // it is considered suitable for use, provided a safe BlockCipher is used. class CTREncryptionProvider : public EncryptionProvider { - private: - BlockCipher& cipher_; - protected: - const static size_t defaultPrefixLength = 4096; + private: + BlockCipher& cipher_; + + protected: + const static size_t defaultPrefixLength = 4096; public: - CTREncryptionProvider(BlockCipher& c) - : cipher_(c) {}; - virtual ~CTREncryptionProvider() {} - - // GetPrefixLength returns the length of the prefix that is added to every file - // and used for storing encryption options. - // For optimal performance, the prefix length should be a multiple of - // the a page size. - virtual size_t GetPrefixLength() override; - - // CreateNewPrefix initialized an allocated block of prefix memory - // for a new file. - virtual Status CreateNewPrefix(const std::string& fname, char *prefix, size_t prefixLength) override; - - // CreateCipherStream creates a block access cipher stream for a file given - // given name and options. - virtual Status CreateCipherStream(const std::string& fname, const EnvOptions& options, - Slice& prefix, unique_ptr* result) override; - - protected: - // PopulateSecretPrefixPart initializes the data into a new prefix block - // that will be encrypted. This function will store the data in plain text. - // It will be encrypted later (before written to disk). - // Returns the amount of space (starting from the start of the prefix) - // that has been initialized. - virtual size_t PopulateSecretPrefixPart(char *prefix, size_t prefixLength, size_t blockSize); - - // CreateCipherStreamFromPrefix creates a block access cipher stream for a file given - // given name and options. The given prefix is already decrypted. - virtual Status CreateCipherStreamFromPrefix(const std::string& fname, const EnvOptions& options, - uint64_t initialCounter, const Slice& iv, const Slice& prefix, unique_ptr* result); + CTREncryptionProvider(BlockCipher& c) : cipher_(c){}; + virtual ~CTREncryptionProvider() {} + + // GetPrefixLength returns the length of the prefix that is added to every + // file and used for storing encryption options. For optimal performance, the + // prefix length should be a multiple of the page size. + virtual size_t GetPrefixLength() override; + + // CreateNewPrefix initialized an allocated block of prefix memory + // for a new file. + virtual Status CreateNewPrefix(const std::string& fname, char* prefix, + size_t prefixLength) override; + + // CreateCipherStream creates a block access cipher stream for a file given + // given name and options. + virtual Status CreateCipherStream( + const std::string& fname, const EnvOptions& options, Slice& prefix, + std::unique_ptr* result) override; + + protected: + // PopulateSecretPrefixPart initializes the data into a new prefix block + // that will be encrypted. This function will store the data in plain text. + // It will be encrypted later (before written to disk). + // Returns the amount of space (starting from the start of the prefix) + // that has been initialized. + virtual size_t PopulateSecretPrefixPart(char* prefix, size_t prefixLength, + size_t blockSize); + + // CreateCipherStreamFromPrefix creates a block access cipher stream for a + // file given given name and options. The given prefix is already decrypted. + virtual Status CreateCipherStreamFromPrefix( + const std::string& fname, const EnvOptions& options, + uint64_t initialCounter, const Slice& iv, const Slice& prefix, + std::unique_ptr* result); }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/filter_policy.h b/thirdparty/rocksdb/include/rocksdb/filter_policy.h index 8add48e496..5d465b7820 100644 --- a/thirdparty/rocksdb/include/rocksdb/filter_policy.h +++ b/thirdparty/rocksdb/include/rocksdb/filter_policy.h @@ -17,12 +17,11 @@ // Most people will want to use the builtin bloom filter support (see // NewBloomFilterPolicy() below). -#ifndef STORAGE_ROCKSDB_INCLUDE_FILTER_POLICY_H_ -#define STORAGE_ROCKSDB_INCLUDE_FILTER_POLICY_H_ +#pragma once +#include #include #include -#include #include #include @@ -46,7 +45,11 @@ class FilterBitsBuilder { virtual Slice Finish(std::unique_ptr* buf) = 0; // Calculate num of entries fit into a space. - virtual int CalculateNumEntry(const uint32_t space) { +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4702) // unreachable code +#endif + virtual int CalculateNumEntry(const uint32_t /*space*/) { #ifndef ROCKSDB_LITE throw std::runtime_error("CalculateNumEntry not Implemented"); #else @@ -54,6 +57,9 @@ class FilterBitsBuilder { #endif return 0; } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif }; // A class that checks if a key can be in filter @@ -96,8 +102,8 @@ class FilterPolicy { // // Warning: do not change the initial contents of *dst. Instead, // append the newly constructed filter to *dst. - virtual void CreateFilter(const Slice* keys, int n, std::string* dst) - const = 0; + virtual void CreateFilter(const Slice* keys, int n, + std::string* dst) const = 0; // "filter" contains the data appended by a preceding call to // CreateFilter() on this class. This method must return true if @@ -108,14 +114,13 @@ class FilterPolicy { // Get the FilterBitsBuilder, which is ONLY used for full filter block // It contains interface to take individual key, then generate filter - virtual FilterBitsBuilder* GetFilterBitsBuilder() const { - return nullptr; - } + virtual FilterBitsBuilder* GetFilterBitsBuilder() const { return nullptr; } // Get the FilterBitsReader, which is ONLY used for full filter block // It contains interface to tell if key can be in filter // The input slice should NOT be deleted by FilterPolicy - virtual FilterBitsReader* GetFilterBitsReader(const Slice& contents) const { + virtual FilterBitsReader* GetFilterBitsReader( + const Slice& /*contents*/) const { return nullptr; } }; @@ -138,8 +143,6 @@ class FilterPolicy { // ignores trailing spaces, it would be incorrect to use a // FilterPolicy (like NewBloomFilterPolicy) that does not ignore // trailing spaces in keys. -extern const FilterPolicy* NewBloomFilterPolicy(int bits_per_key, - bool use_block_based_builder = true); -} - -#endif // STORAGE_ROCKSDB_INCLUDE_FILTER_POLICY_H_ +extern const FilterPolicy* NewBloomFilterPolicy( + int bits_per_key, bool use_block_based_builder = false); +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/flush_block_policy.h b/thirdparty/rocksdb/include/rocksdb/flush_block_policy.h index 5daa967624..38807249ce 100644 --- a/thirdparty/rocksdb/include/rocksdb/flush_block_policy.h +++ b/thirdparty/rocksdb/include/rocksdb/flush_block_policy.h @@ -20,10 +20,9 @@ class FlushBlockPolicy { public: // Keep track of the key/value sequences and return the boolean value to // determine if table builder should flush current data block. - virtual bool Update(const Slice& key, - const Slice& value) = 0; + virtual bool Update(const Slice& key, const Slice& value) = 0; - virtual ~FlushBlockPolicy() { } + virtual ~FlushBlockPolicy() {} }; class FlushBlockPolicyFactory { @@ -41,7 +40,7 @@ class FlushBlockPolicyFactory { const BlockBasedTableOptions& table_options, const BlockBuilder& data_block_builder) const = 0; - virtual ~FlushBlockPolicyFactory() { } + virtual ~FlushBlockPolicyFactory() {} }; class FlushBlockBySizePolicyFactory : public FlushBlockPolicyFactory { @@ -59,4 +58,4 @@ class FlushBlockBySizePolicyFactory : public FlushBlockPolicyFactory { const BlockBuilder& data_block_builder); }; -} // rocksdb +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/iostats_context.h b/thirdparty/rocksdb/include/rocksdb/iostats_context.h index 77a59643a1..67f2b32177 100644 --- a/thirdparty/rocksdb/include/rocksdb/iostats_context.h +++ b/thirdparty/rocksdb/include/rocksdb/iostats_context.h @@ -44,6 +44,10 @@ struct IOStatsContext { uint64_t prepare_write_nanos; // time spent in Logger::Logv(). uint64_t logger_nanos; + // CPU time spent in write() and pwrite() + uint64_t cpu_write_nanos; + // CPU time spent in read() and pread() + uint64_t cpu_read_nanos; }; // Get Thread-local IOStatsContext object pointer diff --git a/thirdparty/rocksdb/include/rocksdb/iterator.h b/thirdparty/rocksdb/include/rocksdb/iterator.h index d4ac528181..e99b434a01 100644 --- a/thirdparty/rocksdb/include/rocksdb/iterator.h +++ b/thirdparty/rocksdb/include/rocksdb/iterator.h @@ -16,8 +16,7 @@ // non-const method, all threads accessing the same Iterator must use // external synchronization. -#ifndef STORAGE_ROCKSDB_INCLUDE_ITERATOR_H_ -#define STORAGE_ROCKSDB_INCLUDE_ITERATOR_H_ +#pragma once #include #include "rocksdb/cleanable.h" @@ -33,6 +32,7 @@ class Iterator : public Cleanable { // An iterator is either positioned at a key/value pair, or // not valid. This method returns true iff the iterator is valid. + // Always returns false if !status().ok(). virtual bool Valid() const = 0; // Position at the first key in the source. The iterator is Valid() @@ -43,15 +43,18 @@ class Iterator : public Cleanable { // Valid() after this call iff the source is not empty. virtual void SeekToLast() = 0; - // Position at the first key in the source that at or past target + // Position at the first key in the source that at or past target. // The iterator is Valid() after this call iff the source contains // an entry that comes at or past target. + // All Seek*() methods clear any error status() that the iterator had prior to + // the call; after the seek, status() indicates only the error (if any) that + // happened during the seek, not any past errors. virtual void Seek(const Slice& target) = 0; - // Position at the last key in the source that at or before target + // Position at the last key in the source that at or before target. // The iterator is Valid() after this call iff the source contains // an entry that comes at or before target. - virtual void SeekForPrev(const Slice& target) {} + virtual void SeekForPrev(const Slice& target) = 0; // Moves to the next entry in the source. After this call, Valid() is // true iff the iterator was not positioned at the last entry in the source. @@ -72,7 +75,7 @@ class Iterator : public Cleanable { // Return the value for the current entry. The underlying storage for // the returned slice is valid only until the next modification of // the iterator. - // REQUIRES: !AtEnd() && !AtStart() + // REQUIRES: Valid() virtual Slice value() const = 0; // If an error has occurred, return it. Else return an ok status. @@ -97,6 +100,9 @@ class Iterator : public Cleanable { // Property "rocksdb.iterator.super-version-number": // LSM version used by the iterator. The same format as DB Property // kCurrentSuperVersionNumber. See its comment for more information. + // Property "rocksdb.iterator.internal-key": + // Get the user-key portion of the internal key at which the iteration + // stopped. virtual Status GetProperty(std::string prop_name, std::string* prop); private: @@ -112,5 +118,3 @@ extern Iterator* NewEmptyIterator(); extern Iterator* NewErrorIterator(const Status& status); } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_ITERATOR_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/ldb_tool.h b/thirdparty/rocksdb/include/rocksdb/ldb_tool.h index 0ec2da9fc0..636605ff7f 100644 --- a/thirdparty/rocksdb/include/rocksdb/ldb_tool.h +++ b/thirdparty/rocksdb/include/rocksdb/ldb_tool.h @@ -2,8 +2,8 @@ // 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 #pragma once +#ifndef ROCKSDB_LITE #include #include #include "rocksdb/db.h" @@ -38,6 +38,6 @@ class LDBTool { const std::vector* column_families = nullptr); }; -} // namespace rocksdb +} // namespace rocksdb #endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/listener.h b/thirdparty/rocksdb/include/rocksdb/listener.h index e132033db2..d4a61c20e3 100644 --- a/thirdparty/rocksdb/include/rocksdb/listener.h +++ b/thirdparty/rocksdb/include/rocksdb/listener.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -27,6 +28,7 @@ enum class TableFileCreationReason { kFlush, kCompaction, kRecovery, + kMisc, }; struct TableFileCreationBriefInfo { @@ -55,8 +57,8 @@ struct TableFileCreationInfo : public TableFileCreationBriefInfo { Status status; }; -enum class CompactionReason { - kUnknown, +enum class CompactionReason : int { + kUnknown = 0, // [Level] number of L0 files > level0_file_num_compaction_trigger kLevelL0FilesNum, // [Level] total size of level > MaxBytesForLevel() @@ -77,6 +79,33 @@ enum class CompactionReason { kManualCompaction, // DB::SuggestCompactRange() marked files for compaction kFilesMarkedForCompaction, + // [Level] Automatic compaction within bottommost level to cleanup duplicate + // versions of same user key, usually due to a released snapshot. + kBottommostFiles, + // Compaction based on TTL + kTtl, + // According to the comments in flush_job.cc, RocksDB treats flush as + // a level 0 compaction in internal stats. + kFlush, + // Compaction caused by external sst file ingestion + kExternalSstIngestion, + // total number of compaction reasons, new reasons must be added above this. + kNumOfReasons, +}; + +enum class FlushReason : int { + kOthers = 0x00, + kGetLiveFiles = 0x01, + kShutDown = 0x02, + kExternalFileIngestion = 0x03, + kManualCompaction = 0x04, + kWriteBufferManager = 0x05, + kWriteBufferFull = 0x06, + kTest = 0x07, + kDeleteFiles = 0x08, + kAutoCompaction = 0x09, + kManualFlush = 0x0a, + kErrorRecovery = 0xb, }; enum class BackgroundErrorReason { @@ -86,6 +115,22 @@ enum class BackgroundErrorReason { kMemTable, }; +enum class WriteStallCondition { + kNormal, + kDelayed, + kStopped, +}; + +struct WriteStallInfo { + // the name of the column family + std::string cf_name; + // state of the write controller + struct { + WriteStallCondition cur; + WriteStallCondition prev; + } condition; +}; + #ifndef ROCKSDB_LITE struct TableFileDeletionInfo { @@ -99,7 +144,24 @@ struct TableFileDeletionInfo { Status status; }; +struct FileOperationInfo { + using TimePoint = std::chrono::time_point; + + const std::string& path; + uint64_t offset; + size_t length; + const TimePoint& start_timestamp; + const TimePoint& finish_timestamp; + Status status; + FileOperationInfo(const std::string& _path, const TimePoint& start, + const TimePoint& finish) + : path(_path), start_timestamp(start), finish_timestamp(finish) {} +}; + struct FlushJobInfo { + // the id of the column family + uint32_t cf_id; // the name of the column family std::string cf_name; // the path to the newly created file @@ -124,13 +186,17 @@ struct FlushJobInfo { SequenceNumber largest_seqno; // Table properties of the table being flushed TableProperties table_properties; + + FlushReason flush_reason; }; struct CompactionJobInfo { CompactionJobInfo() = default; - explicit CompactionJobInfo(const CompactionJobStats& _stats) : - stats(_stats) {} + explicit CompactionJobInfo(const CompactionJobStats& _stats) + : stats(_stats) {} + // the id of the column family where the compaction happened. + uint32_t cf_id; // the name of the column family where the compaction happened. std::string cf_name; // the status indicating whether the compaction was successful or not. @@ -178,7 +244,6 @@ struct MemTableInfo { uint64_t num_entries; // Total number of deletes in memtable uint64_t num_deletes; - }; struct ExternalFileIngestionInfo { @@ -194,36 +259,12 @@ struct ExternalFileIngestionInfo { TableProperties table_properties; }; -// A call-back function to RocksDB which will be called when the compaction -// iterator is compacting values. It is mean to be returned from -// EventListner::GetCompactionEventListner() at the beginning of compaction -// job. -class CompactionEventListener { - public: - enum CompactionListenerValueType { - kValue, - kMergeOperand, - kDelete, - kSingleDelete, - kRangeDelete, - kBlobIndex, - kInvalid, - }; - - virtual void OnCompaction(int level, const Slice& key, - CompactionListenerValueType value_type, - const Slice& existing_value, - const SequenceNumber& sn, bool is_new) = 0; - - virtual ~CompactionEventListener() = default; -}; - -// EventListener class contains a set of call-back functions that will +// EventListener class contains a set of callback functions that will // be called when specific RocksDB event happens such as flush. It can // be used as a building block for developing custom features such as // stats-collector or external compaction algorithm. // -// Note that call-back functions should not run for an extended period of +// Note that callback functions should not run for an extended period of // time before the function returns, otherwise RocksDB may be blocked. // For example, it is not suggested to do DB::CompactFiles() (as it may // run for a long while) or issue many of DB::Put() (as Put may be blocked @@ -239,17 +280,10 @@ class CompactionEventListener { // [Locking] All EventListener callbacks are designed to be called without // the current thread holding any DB mutex. This is to prevent potential // deadlock and performance issue when using EventListener callback -// in a complex way. However, all EventListener call-back functions -// should not run for an extended period of time before the function -// returns, otherwise RocksDB may be blocked. For example, it is not -// suggested to do DB::CompactFiles() (as it may run for a long while) -// or issue many of DB::Put() (as Put may be blocked in certain cases) -// in the same thread in the EventListener callback. However, doing -// DB::CompactFiles() and DB::Put() in a thread other than the -// EventListener callback thread is considered safe. +// in a complex way. class EventListener { public: - // A call-back function to RocksDB which will be called whenever a + // A callback function to RocksDB which will be called whenever a // registered RocksDB flushes a file. The default implementation is // no-op. // @@ -259,7 +293,7 @@ class EventListener { virtual void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& /*flush_job_info*/) {} - // A call-back function to RocksDB which will be called before a + // A callback function to RocksDB which will be called before a // RocksDB starts to flush memtables. The default implementation is // no-op. // @@ -269,9 +303,9 @@ class EventListener { virtual void OnFlushBegin(DB* /*db*/, const FlushJobInfo& /*flush_job_info*/) {} - // A call-back function for RocksDB which will be called whenever + // A callback function for RocksDB which will be called whenever // a SST file is deleted. Different from OnCompactionCompleted and - // OnFlushCompleted, this call-back is designed for external logging + // OnFlushCompleted, this callback is designed for external logging // service and thus only provide string parameters instead // of a pointer to DB. Applications that build logic basic based // on file creations and deletions is suggested to implement @@ -282,7 +316,16 @@ class EventListener { // returned value. virtual void OnTableFileDeleted(const TableFileDeletionInfo& /*info*/) {} - // A call-back function for RocksDB which will be called whenever + // A callback function to RocksDB which will be called before a + // RocksDB starts to compact. The default implementation is + // no-op. + // + // Note that the this function must be implemented in a way such that + // it should not run for an extended period of time before the function + // returns. Otherwise, RocksDB may be blocked. + virtual void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& /*ci*/) {} + + // A callback function for RocksDB which will be called whenever // a registered RocksDB compacts a file. The default implementation // is a no-op. // @@ -298,9 +341,9 @@ class EventListener { virtual void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& /*ci*/) {} - // A call-back function for RocksDB which will be called whenever + // A callback function for RocksDB which will be called whenever // a SST file is created. Different from OnCompactionCompleted and - // OnFlushCompleted, this call-back is designed for external logging + // OnFlushCompleted, this callback is designed for external logging // service and thus only provide string parameters instead // of a pointer to DB. Applications that build logic basic based // on file creations and deletions is suggested to implement @@ -315,7 +358,7 @@ class EventListener { // returned value. virtual void OnTableFileCreated(const TableFileCreationInfo& /*info*/) {} - // A call-back function for RocksDB which will be called before + // A callback function for RocksDB which will be called before // a SST file is being created. It will follow by OnTableFileCreated after // the creation finishes. // @@ -325,7 +368,7 @@ class EventListener { virtual void OnTableFileCreationStarted( const TableFileCreationBriefInfo& /*info*/) {} - // A call-back function for RocksDB which will be called before + // A callback function for RocksDB which will be called before // a memtable is made immutable. // // Note that the this function must be implemented in a way such that @@ -335,10 +378,9 @@ class EventListener { // Note that if applications would like to use the passed reference // outside this function call, they should make copies from these // returned value. - virtual void OnMemTableSealed( - const MemTableInfo& /*info*/) {} + virtual void OnMemTableSealed(const MemTableInfo& /*info*/) {} - // A call-back function for RocksDB which will be called before + // A callback function for RocksDB which will be called before // a column family handle is deleted. // // Note that the this function must be implemented in a way such that @@ -346,10 +388,10 @@ class EventListener { // returns. Otherwise, RocksDB may be blocked. // @param handle is a pointer to the column family handle to be deleted // which will become a dangling pointer after the deletion. - virtual void OnColumnFamilyHandleDeletionStarted(ColumnFamilyHandle* handle) { - } + virtual void OnColumnFamilyHandleDeletionStarted( + ColumnFamilyHandle* /*handle*/) {} - // A call-back function for RocksDB which will be called after an external + // A callback function for RocksDB which will be called after an external // file is ingested using IngestExternalFile. // // Note that the this function will run on the same thread as @@ -358,7 +400,7 @@ class EventListener { virtual void OnExternalFileIngested( DB* /*db*/, const ExternalFileIngestionInfo& /*info*/) {} - // A call-back function for RocksDB which will be called before setting the + // A callback function for RocksDB which will be called before setting the // background error status to a non-OK value. The new background error status // is provided in `bg_error` and can be modified by the callback. E.g., a // callback can suppress errors by resetting it to Status::OK(), thus @@ -372,19 +414,47 @@ class EventListener { virtual void OnBackgroundError(BackgroundErrorReason /* reason */, Status* /* bg_error */) {} - // Factory method to return CompactionEventListener. If multiple listeners - // provides CompactionEventListner, only the first one will be used. - virtual CompactionEventListener* GetCompactionEventListener() { - return nullptr; - } + // A callback function for RocksDB which will be called whenever a change + // of superversion triggers a change of the stall conditions. + // + // Note that the this function must be implemented in a way such that + // it should not run for an extended period of time before the function + // returns. Otherwise, RocksDB may be blocked. + virtual void OnStallConditionsChanged(const WriteStallInfo& /*info*/) {} + + // A callback function for RocksDB which will be called whenever a file read + // operation finishes. + virtual void OnFileReadFinish(const FileOperationInfo& /* info */) {} + + // A callback function for RocksDB which will be called whenever a file write + // operation finishes. + virtual void OnFileWriteFinish(const FileOperationInfo& /* info */) {} + + // If true, the OnFileReadFinish and OnFileWriteFinish will be called. If + // false, then they won't be called. + virtual bool ShouldBeNotifiedOnFileIO() { return false; } + + // A callback function for RocksDB which will be called just before + // starting the automatic recovery process for recoverable background + // errors, such as NoSpace(). The callback can suppress the automatic + // recovery by setting *auto_recovery to false. The database will then + // have to be transitioned out of read-only mode by calling DB::Resume() + virtual void OnErrorRecoveryBegin(BackgroundErrorReason /* reason */, + Status /* bg_error */, + bool* /* auto_recovery */) {} + + // A callback function for RocksDB which will be called once the database + // is recovered from read-only mode after an error. When this is called, it + // means normal writes to the database can be issued and the user can + // initiate any further recovery actions needed + virtual void OnErrorRecoveryCompleted(Status /* old_bg_error */) {} virtual ~EventListener() {} }; #else -class EventListener { -}; +class EventListener {}; #endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/memory_allocator.h b/thirdparty/rocksdb/include/rocksdb/memory_allocator.h new file mode 100644 index 0000000000..889c0e9218 --- /dev/null +++ b/thirdparty/rocksdb/include/rocksdb/memory_allocator.h @@ -0,0 +1,77 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#include "rocksdb/status.h" + +#include + +namespace rocksdb { + +// MemoryAllocator is an interface that a client can implement to supply custom +// memory allocation and deallocation methods. See rocksdb/cache.h for more +// information. +// All methods should be thread-safe. +class MemoryAllocator { + public: + virtual ~MemoryAllocator() = default; + + // Name of the cache allocator, printed in the log + virtual const char* Name() const = 0; + + // Allocate a block of at least size. Has to be thread-safe. + virtual void* Allocate(size_t size) = 0; + + // Deallocate previously allocated block. Has to be thread-safe. + virtual void Deallocate(void* p) = 0; + + // Returns the memory size of the block allocated at p. The default + // implementation that just returns the original allocation_size is fine. + virtual size_t UsableSize(void* /*p*/, size_t allocation_size) const { + // default implementation just returns the allocation size + return allocation_size; + } +}; + +struct JemallocAllocatorOptions { + // Jemalloc tcache cache allocations by size class. For each size class, + // it caches between 20 (for large size classes) to 200 (for small size + // classes). To reduce tcache memory usage in case the allocator is access + // by large number of threads, we can control whether to cache an allocation + // by its size. + bool limit_tcache_size = false; + + // Lower bound of allocation size to use tcache, if limit_tcache_size=true. + // When used with block cache, it is recommneded to set it to block_size/4. + size_t tcache_size_lower_bound = 1024; + + // Upper bound of allocation size to use tcache, if limit_tcache_size=true. + // When used with block cache, it is recommneded to set it to block_size. + size_t tcache_size_upper_bound = 16 * 1024; +}; + +// Generate memory allocators which allocates through Jemalloc and utilize +// MADV_DONTDUMP through madvice to exclude cache items from core dump. +// Applications can use the allocator with block cache to exclude block cache +// usage from core dump. +// +// Implementation details: +// The JemallocNodumpAllocator creates a delicated jemalloc arena, and all +// allocations of the JemallocNodumpAllocator is through the same arena. +// The memory allocator hooks memory allocation of the arena, and call +// madvice() with MADV_DONTDUMP flag to exclude the piece of memory from +// core dump. Side benefit of using single arena would be reduce of jemalloc +// metadata for some workload. +// +// To mitigate mutex contention for using one single arena, jemalloc tcache +// (thread-local cache) is enabled to cache unused allocations for future use. +// The tcache normally incur 0.5M extra memory usage per-thread. The usage +// can be reduce by limitting allocation sizes to cache. +extern Status NewJemallocNodumpAllocator( + JemallocAllocatorOptions& options, + std::shared_ptr* memory_allocator); + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/memtablerep.h b/thirdparty/rocksdb/include/rocksdb/memtablerep.h index 347dd3096c..328422f570 100644 --- a/thirdparty/rocksdb/include/rocksdb/memtablerep.h +++ b/thirdparty/rocksdb/include/rocksdb/memtablerep.h @@ -35,28 +35,38 @@ #pragma once -#include -#include +#include #include #include +#include +#include namespace rocksdb { class Arena; class Allocator; class LookupKey; -class Slice; class SliceTransform; class Logger; typedef void* KeyHandle; +extern Slice GetLengthPrefixedSlice(const char* data); + class MemTableRep { public: // KeyComparator provides a means to compare keys, which are internal keys // concatenated with values. class KeyComparator { public: + typedef rocksdb::Slice DecodedType; + + virtual DecodedType decode_key(const char* key) const { + // The format of key is frozen and can be terated as a part of the API + // contract. Refer to MemTable::Add for details. + return GetLengthPrefixedSlice(key); + } + // Compare a and b. Return a negative value if a is less than b, 0 if they // are equal, and a positive value if a is greater than b virtual int operator()(const char* prefix_len_key1, @@ -65,7 +75,7 @@ class MemTableRep { virtual int operator()(const char* prefix_len_key, const Slice& key) const = 0; - virtual ~KeyComparator() { } + virtual ~KeyComparator() {} }; explicit MemTableRep(Allocator* allocator) : allocator_(allocator) {} @@ -83,25 +93,46 @@ class MemTableRep { // collection, and no concurrent modifications to the table in progress virtual void Insert(KeyHandle handle) = 0; + // Same as ::Insert + // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and + // the already exists. + virtual bool InsertKey(KeyHandle handle) { + Insert(handle); + return true; + } + // Same as Insert(), but in additional pass a hint to insert location for // the key. If hint points to nullptr, a new hint will be populated. // otherwise the hint will be updated to reflect the last insert location. // // Currently only skip-list based memtable implement the interface. Other // implementations will fallback to Insert() by default. - virtual void InsertWithHint(KeyHandle handle, void** hint) { + virtual void InsertWithHint(KeyHandle handle, void** /*hint*/) { // Ignore the hint by default. Insert(handle); } + // Same as ::InsertWithHint + // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and + // the already exists. + virtual bool InsertKeyWithHint(KeyHandle handle, void** hint) { + InsertWithHint(handle, hint); + return true; + } + // Like Insert(handle), but may be called concurrent with other calls - // to InsertConcurrently for other handles - virtual void InsertConcurrently(KeyHandle handle) { -#ifndef ROCKSDB_LITE - throw std::runtime_error("concurrent insert not supported"); -#else - abort(); -#endif + // to InsertConcurrently for other handles. + // + // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and + // the already exists. + virtual void InsertConcurrently(KeyHandle handle); + + // Same as ::InsertConcurrently + // Returns false if MemTableRepFactory::CanHandleDuplicatedKey() is true and + // the already exists. + virtual bool InsertKeyConcurrently(KeyHandle handle) { + InsertConcurrently(handle); + return true; } // Returns true iff an entry that compares equal to key is in the collection. @@ -111,7 +142,15 @@ class MemTableRep { // does nothing. After MarkReadOnly() is called, this table rep will // not be written to (ie No more calls to Allocate(), Insert(), // or any writes done directly to entries accessed through the iterator.) - virtual void MarkReadOnly() { } + virtual void MarkReadOnly() {} + + // Notify this table rep that it has been flushed to stable storage. + // By default, does nothing. + // + // Invariant: MarkReadOnly() is called, before MarkFlushed(). + // Note that this method if overridden, should not run for an extended period + // of time. Otherwise, RocksDB may be blocked. + virtual void MarkFlushed() {} // Look up key from the mem table, since the first key in the mem table whose // user_key matches the one given k, call the function callback_func(), with @@ -128,8 +167,8 @@ class MemTableRep { virtual void Get(const LookupKey& k, void* callback_args, bool (*callback_func)(void* arg, const char* entry)); - virtual uint64_t ApproximateNumEntries(const Slice& start_ikey, - const Slice& end_key) { + virtual uint64_t ApproximateNumEntries(const Slice& /*start_ikey*/, + const Slice& /*end_key*/) { return 0; } @@ -137,7 +176,7 @@ class MemTableRep { // that was allocated through the allocator. Safe to call from any thread. virtual size_t ApproximateMemoryUsage() = 0; - virtual ~MemTableRep() { } + virtual ~MemTableRep() {} // Iteration over the contents of a skip collection class Iterator { @@ -232,6 +271,12 @@ class MemTableRepFactory { // Return true if the current MemTableRep supports concurrent inserts // Default: false virtual bool IsInsertConcurrentlySupported() const { return false; } + + // Return true if the current MemTableRep supports detecting duplicate + // at insertion time. If true, then MemTableRep::Insert* returns + // false when if the already exists. + // Default: false + virtual bool CanHandleDuplicatedKey() const { return false; } }; // This uses a skip list to store keys. It is the default. @@ -253,6 +298,8 @@ class SkipListFactory : public MemTableRepFactory { bool IsInsertConcurrentlySupported() const override { return true; } + bool CanHandleDuplicatedKey() const override { return true; } + private: const size_t lookahead_; }; @@ -270,16 +317,14 @@ class VectorRepFactory : public MemTableRepFactory { const size_t count_; public: - explicit VectorRepFactory(size_t count = 0) : count_(count) { } + explicit VectorRepFactory(size_t count = 0) : count_(count) {} using MemTableRepFactory::CreateMemTableRep; virtual MemTableRep* CreateMemTableRep(const MemTableRep::KeyComparator&, Allocator*, const SliceTransform*, Logger* logger) override; - virtual const char* Name() const override { - return "VectorRepFactory"; - } + virtual const char* Name() const override { return "VectorRepFactory"; } }; // This class contains a fixed array of buckets, each @@ -290,8 +335,7 @@ class VectorRepFactory : public MemTableRepFactory { // link lists in the skiplist extern MemTableRepFactory* NewHashSkipListRepFactory( size_t bucket_count = 1000000, int32_t skiplist_height = 4, - int32_t skiplist_branching_factor = 4 -); + int32_t skiplist_branching_factor = 4); // The factory is to create memtables based on a hash table: // it contains a fixed array of buckets, each pointing to either a linked list @@ -315,39 +359,5 @@ extern MemTableRepFactory* NewHashLinkListRepFactory( bool if_log_bucket_dist_when_flash = true, uint32_t threshold_use_skiplist = 256); -// This factory creates a cuckoo-hashing based mem-table representation. -// Cuckoo-hash is a closed-hash strategy, in which all key/value pairs -// are stored in the bucket array itself intead of in some data structures -// external to the bucket array. In addition, each key in cuckoo hash -// has a constant number of possible buckets in the bucket array. These -// two properties together makes cuckoo hash more memory efficient and -// a constant worst-case read time. Cuckoo hash is best suitable for -// point-lookup workload. -// -// When inserting a key / value, it first checks whether one of its possible -// buckets is empty. If so, the key / value will be inserted to that vacant -// bucket. Otherwise, one of the keys originally stored in one of these -// possible buckets will be "kicked out" and move to one of its possible -// buckets (and possibly kicks out another victim.) In the current -// implementation, such "kick-out" path is bounded. If it cannot find a -// "kick-out" path for a specific key, this key will be stored in a backup -// structure, and the current memtable to be forced to immutable. -// -// Note that currently this mem-table representation does not support -// snapshot (i.e., it only queries latest state) and iterators. In addition, -// MultiGet operation might also lose its atomicity due to the lack of -// snapshot support. -// -// Parameters: -// write_buffer_size: the write buffer size in bytes. -// average_data_size: the average size of key + value in bytes. This value -// together with write_buffer_size will be used to compute the number -// of buckets. -// hash_function_count: the number of hash functions that will be used by -// the cuckoo-hash. The number also equals to the number of possible -// buckets each key will have. -extern MemTableRepFactory* NewHashCuckooRepFactory( - size_t write_buffer_size, size_t average_data_size = 64, - unsigned int hash_function_count = 4); #endif // ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/merge_operator.h b/thirdparty/rocksdb/include/rocksdb/merge_operator.h index f294710055..d8ddcc6a09 100644 --- a/thirdparty/rocksdb/include/rocksdb/merge_operator.h +++ b/thirdparty/rocksdb/include/rocksdb/merge_operator.h @@ -3,8 +3,7 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef STORAGE_ROCKSDB_INCLUDE_MERGE_OPERATOR_H_ -#define STORAGE_ROCKSDB_INCLUDE_MERGE_OPERATOR_H_ +#pragma once #include #include @@ -66,11 +65,9 @@ class MergeOperator { // internal corruption. This will be treated as an error by the library. // // Also make use of the *logger for error messages. - virtual bool FullMerge(const Slice& key, - const Slice* existing_value, - const std::deque& operand_list, - std::string* new_value, - Logger* logger) const { + virtual bool FullMerge(const Slice& /*key*/, const Slice* /*existing_value*/, + const std::deque& /*operand_list*/, + std::string* /*new_value*/, Logger* /*logger*/) const { // deprecated, please use FullMergeV2() assert(false); return false; @@ -89,7 +86,7 @@ class MergeOperator { // The key associated with the merge operation. const Slice& key; // The existing value of the current key, nullptr means that the - // value dont exist. + // value doesn't exist. const Slice* existing_value; // A list of operands to apply. const std::vector& operand_list; @@ -111,6 +108,23 @@ class MergeOperator { Slice& existing_operand; }; + // This function applies a stack of merge operands in chrionological order + // on top of an existing value. There are two ways in which this method is + // being used: + // a) During Get() operation, it used to calculate the final value of a key + // b) During compaction, in order to collapse some operands with the based + // value. + // + // Note: The name of the method is somewhat misleading, as both in the cases + // of Get() or compaction it may be called on a subset of operands: + // K: 0 +1 +2 +7 +4 +5 2 +1 +2 + // ^ + // | + // snapshot + // In the example above, Get(K) operation will call FullMerge with a base + // value of 2 and operands [+1, +2]. Compaction process might decide to + // collapse the beginning of the history up to the snapshot by performing + // full Merge with base value of 0 and operands [+1, +2, +7, +3]. virtual bool FullMergeV2(const MergeOperationInput& merge_in, MergeOperationOutput* merge_out) const; @@ -145,9 +159,10 @@ class MergeOperator { // If there is corruption in the data, handle it in the FullMergeV2() function // and return false there. The default implementation of PartialMerge will // always return false. - virtual bool PartialMerge(const Slice& key, const Slice& left_operand, - const Slice& right_operand, std::string* new_value, - Logger* logger) const { + virtual bool PartialMerge(const Slice& /*key*/, const Slice& /*left_operand*/, + const Slice& /*right_operand*/, + std::string* /*new_value*/, + Logger* /*logger*/) const { return false; } @@ -184,12 +199,26 @@ class MergeOperator { // consistent MergeOperator between DB opens. virtual const char* Name() const = 0; - // Determines whether the MergeOperator can be called with just a single + // Determines whether the PartialMerge can be called with just a single // merge operand. - // Override and return true for allowing a single operand. FullMergeV2 and - // PartialMerge/PartialMergeMulti should be implemented accordingly to handle - // a single operand. + // Override and return true for allowing a single operand. PartialMerge + // and PartialMergeMulti should be overridden and implemented + // correctly to properly handle a single operand. virtual bool AllowSingleOperand() const { return false; } + + // Allows to control when to invoke a full merge during Get. + // This could be used to limit the number of merge operands that are looked at + // during a point lookup, thereby helping in limiting the number of levels to + // read from. + // Doesn't help with iterators. + // + // Note: the merge operands are passed to this function in the reversed order + // relative to how they were merged (passed to FullMerge or FullMergeV2) + // for performance reasons, see also: + // https://github.com/facebook/rocksdb/issues/3865 + virtual bool ShouldMerge(const std::vector& /*operands*/) const { + return false; + } }; // The simpler, associative merge operator. @@ -210,13 +239,10 @@ class AssociativeMergeOperator : public MergeOperator { // returns false, it is because client specified bad data or there was // internal corruption. The client should assume that this will be treated // as an error by the library. - virtual bool Merge(const Slice& key, - const Slice* existing_value, - const Slice& value, - std::string* new_value, + virtual bool Merge(const Slice& key, const Slice* existing_value, + const Slice& value, std::string* new_value, Logger* logger) const = 0; - private: // Default implementations of the MergeOperator functions bool FullMergeV2(const MergeOperationInput& merge_in, @@ -228,5 +254,3 @@ class AssociativeMergeOperator : public MergeOperator { }; } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_MERGE_OPERATOR_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/metadata.h b/thirdparty/rocksdb/include/rocksdb/metadata.h index 37e7b50b9b..a0ab41efdf 100644 --- a/thirdparty/rocksdb/include/rocksdb/metadata.h +++ b/thirdparty/rocksdb/include/rocksdb/metadata.h @@ -20,10 +20,10 @@ struct SstFileMetaData; // The metadata that describes a column family. struct ColumnFamilyMetaData { - ColumnFamilyMetaData() : size(0), name("") {} + ColumnFamilyMetaData() : size(0), file_count(0), name("") {} ColumnFamilyMetaData(const std::string& _name, uint64_t _size, - const std::vector&& _levels) : - size(_size), name(_name), levels(_levels) {} + const std::vector&& _levels) + : size(_size), name(_name), levels(_levels) {} // The size of this column family in bytes, which is equal to the sum of // the file size of its "levels". @@ -39,9 +39,8 @@ struct ColumnFamilyMetaData { // The metadata that describes a level. struct LevelMetaData { LevelMetaData(int _level, uint64_t _size, - const std::vector&& _files) : - level(_level), size(_size), - files(_files) {} + const std::vector&& _files) + : level(_level), size(_size), files(_files) {} // The level which this meta data describes. const int level; @@ -54,9 +53,21 @@ struct LevelMetaData { // The metadata that describes a SST file. struct SstFileMetaData { - SstFileMetaData() {} + SstFileMetaData() + : size(0), + name(""), + db_path(""), + smallest_seqno(0), + largest_seqno(0), + smallestkey(""), + largestkey(""), + num_reads_sampled(0), + being_compacted(false), + num_entries(0), + num_deletions(0) {} + SstFileMetaData(const std::string& _file_name, const std::string& _path, - uint64_t _size, SequenceNumber _smallest_seqno, + size_t _size, SequenceNumber _smallest_seqno, SequenceNumber _largest_seqno, const std::string& _smallestkey, const std::string& _largestkey, uint64_t _num_reads_sampled, @@ -69,10 +80,12 @@ struct SstFileMetaData { smallestkey(_smallestkey), largestkey(_largestkey), num_reads_sampled(_num_reads_sampled), - being_compacted(_being_compacted) {} + being_compacted(_being_compacted), + num_entries(0), + num_deletions(0) {} // File size in bytes. - uint64_t size; + size_t size; // The name of the file. std::string name; // The full path where the file locates. @@ -80,15 +93,19 @@ struct SstFileMetaData { SequenceNumber smallest_seqno; // Smallest sequence number in file. SequenceNumber largest_seqno; // Largest sequence number in file. - std::string smallestkey; // Smallest user defined key in the file. - std::string largestkey; // Largest user defined key in the file. - uint64_t num_reads_sampled; // How many times the file is read. + std::string smallestkey; // Smallest user defined key in the file. + std::string largestkey; // Largest user defined key in the file. + uint64_t num_reads_sampled; // How many times the file is read. bool being_compacted; // true if the file is currently being compacted. + + uint64_t num_entries; + uint64_t num_deletions; }; // The full set of metadata associated with each SST file. struct LiveFileMetaData : SstFileMetaData { std::string column_family_name; // Name of the column family - int level; // Level at which this file resides. + int level; // Level at which this file resides. + LiveFileMetaData() : column_family_name(), level(0) {} }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/options.h b/thirdparty/rocksdb/include/rocksdb/options.h index 4d2f143a0f..f7d6dfaf58 100644 --- a/thirdparty/rocksdb/include/rocksdb/options.h +++ b/thirdparty/rocksdb/include/rocksdb/options.h @@ -6,16 +6,15 @@ // 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. -#ifndef STORAGE_ROCKSDB_INCLUDE_OPTIONS_H_ -#define STORAGE_ROCKSDB_INCLUDE_OPTIONS_H_ +#pragma once #include #include -#include -#include -#include #include +#include +#include #include +#include #include "rocksdb/advanced_options.h" #include "rocksdb/comparator.h" @@ -35,6 +34,7 @@ class Cache; class CompactionFilter; class CompactionFilterFactory; class Comparator; +class ConcurrentTaskLimiter; class Env; enum InfoLogLevel : unsigned char; class SstFileManager; @@ -77,6 +77,7 @@ enum CompressionType : unsigned char { }; struct Options; +struct DbPath; struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions { // The function recovers options to a previous version. Only 4.6 or later @@ -93,8 +94,7 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions { // an iterator, only Put() and Get() API calls // // Not supported in ROCKSDB_LITE - ColumnFamilyOptions* OptimizeForPointLookup( - uint64_t block_cache_size_mb); + ColumnFamilyOptions* OptimizeForPointLookup(uint64_t block_cache_size_mb); // Default values for some parameters in ColumnFamilyOptions are not // optimized for heavy workloads and big datasets, which means you might @@ -188,8 +188,7 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions { // Dynamically changeable through SetOptions() API size_t write_buffer_size = 64 << 20; - // Compress blocks using the specified compression algorithm. This - // parameter can be changed dynamically. + // Compress blocks using the specified compression algorithm. // // Default: kSnappyCompression, if it's supported. If snappy is not linked // with the library, the default is kNoCompression. @@ -197,20 +196,36 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions { // Typical speeds of kSnappyCompression on an Intel(R) Core(TM)2 2.4GHz: // ~200-500MB/s compression // ~400-800MB/s decompression + // // Note that these speeds are significantly faster than most // persistent storage speeds, and therefore it is typically never // worth switching to kNoCompression. Even if the input data is // incompressible, the kSnappyCompression implementation will // efficiently detect that and will switch to uncompressed mode. + // + // If you do not set `compression_opts.level`, or set it to + // `CompressionOptions::kDefaultCompressionLevel`, we will attempt to pick the + // default corresponding to `compression` as follows: + // + // - kZSTD: 3 + // - kZlibCompression: Z_DEFAULT_COMPRESSION (currently -1) + // - kLZ4HCCompression: 0 + // - For all others, we do not specify a compression level + // + // Dynamically changeable through SetOptions() API CompressionType compression; // Compression algorithm that will be used for the bottommost level that - // contain files. If level-compaction is used, this option will only affect - // levels after base level. + // contain files. // // Default: kDisableCompressionOption (Disabled) CompressionType bottommost_compression = kDisableCompressionOption; + // different options for compression algorithms used by bottommost_compression + // if it is enabled. To enable it, please see the definition of + // CompressionOptions. + CompressionOptions bottommost_compression_opts; + // different options for compression algorithms CompressionOptions compression_opts; @@ -264,6 +279,28 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions { // BlockBasedTableOptions. std::shared_ptr table_factory; + // A list of paths where SST files for this column family + // can be put into, with its target size. Similar to db_paths, + // newer data is placed into paths specified earlier in the + // vector while older data gradually moves to paths specified + // later in the vector. + // Note that, if a path is supplied to multiple column + // families, it would have files and total size from all + // the column families combined. User should provision for the + // total size(from all the column families) in such cases. + // + // If left empty, db_paths will be used. + // Default: empty + std::vector cf_paths; + + // Compaction concurrent thread limiter for the column family. + // If non-nullptr, use given concurrent thread limiter to control + // the max outstanding compaction tasks. Limiter can be shared with + // multiple column families across db instances. + // + // Default: nullptr + std::shared_ptr compaction_thread_limiter = nullptr; + // Create ColumnFamilyOptions with default values for all fields ColumnFamilyOptions(); // Create ColumnFamilyOptions from Options @@ -275,14 +312,14 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions { enum class WALRecoveryMode : char { // Original levelDB recovery // We tolerate incomplete record in trailing data on all logs - // Use case : This is legacy behavior (default) + // Use case : This is legacy behavior kTolerateCorruptedTailRecords = 0x00, // Recover from clean shutdown // We don't expect to find any corruption in the WAL // Use case : This is ideal for unit tests and rare applications that // can require high consistency guarantee kAbsoluteConsistency = 0x01, - // Recover to point-in-time consistency + // Recover to point-in-time consistency (default) // We stop the WAL playback on discovering WAL inconsistency // Use case : Ideal for systems that have disk controller cache like // hard disk, SSD without super capacitor that store related data @@ -303,7 +340,6 @@ struct DbPath { DbPath(const std::string& p, uint64_t t) : path(p), target_size(t) {} }; - struct DBOptions { // The function recovers options to the option as in version 4.6. DBOptions* OldDefaults(int rocksdb_major_version = 4, @@ -378,9 +414,9 @@ struct DBOptions { std::shared_ptr info_log = nullptr; #ifdef NDEBUG - InfoLogLevel info_log_level = INFO_LEVEL; + InfoLogLevel info_log_level = INFO_LEVEL; #else - InfoLogLevel info_log_level = DEBUG_LEVEL; + InfoLogLevel info_log_level = DEBUG_LEVEL; #endif // NDEBUG // Number of open files that can be used by the DB. You may need to @@ -388,7 +424,10 @@ struct DBOptions { // files opened are always kept open. You can estimate number of files based // on target_file_size_base and target_file_size_multiplier for level-based // compaction. For universal-style compaction, you can usually set it to -1. + // // Default: -1 + // + // Dynamically changeable through SetDBOptions() API. int max_open_files = -1; // If max_open_files is -1, DB will open all files on DB::Open(). You can @@ -401,19 +440,25 @@ struct DBOptions { // (i.e. the ones that are causing all the space amplification). If set to 0 // (default), we will dynamically choose the WAL size limit to be // [sum of all write_buffer_size * max_write_buffer_number] * 4 + // This option takes effect only when there are more than one column family as + // otherwise the wal size is dictated by the write_buffer_size. + // // Default: 0 + // + // Dynamically changeable through SetDBOptions() API. uint64_t max_total_wal_size = 0; // If non-null, then we should collect metrics about database operations std::shared_ptr statistics = nullptr; - // If true, then every store to stable storage will issue a fsync. - // If false, then every store to stable storage will issue a fdatasync. - // This parameter should be set to true while storing data to - // filesystem like ext3 that can lose files after a reboot. - // Default: false - // Note: on many platforms fdatasync is defined as fsync, so this parameter - // would make no difference. Refer to fdatasync definition in this code base. + // By default, writes to stable storage use fdatasync (on platforms + // where this function is available). If this option is true, + // fsync is used instead. + // + // fsync and fdatasync are equally safe for our purposes and fdatasync is + // faster, so it is rarely necessary to set this option. It is provided + // as a workaround for kernel/filesystem bugs, such as one that affected + // fdatasync with ext4 in kernel versions prior to 3.7. bool use_fsync = false; // A list of paths where SST files can be put into, with its target size. @@ -461,13 +506,23 @@ struct DBOptions { // value is 6 hours. The files that get out of scope by compaction // process will still get automatically delete on every compaction, // regardless of this setting + // + // Default: 6 hours + // + // Dynamically changeable through SetDBOptions() API. uint64_t delete_obsolete_files_period_micros = 6ULL * 60 * 60 * 1000000; // Maximum number of concurrent background jobs (compactions and flushes). + // + // Default: 2 + // + // Dynamically changeable through SetDBOptions() API. int max_background_jobs = 2; // NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the // value of max_background_jobs. This option is ignored. + // + // Dynamically changeable through SetDBOptions() API. int base_background_compactions = -1; // NOT SUPPORTED ANYMORE: RocksDB automatically decides this based on the @@ -482,7 +537,10 @@ struct DBOptions { // If you're increasing this, also consider increasing number of threads in // LOW priority thread pool. For more information, see // Env::SetBackgroundThreads + // // Default: -1 + // + // Dynamically changeable through SetDBOptions() API. int max_background_compactions = -1; // This value represents the maximum number of threads that will @@ -543,8 +601,9 @@ struct DBOptions { // manifest file is rolled over on reaching this limit. // The older manifest file be deleted. - // The default value is MAX_INT so that roll-over does not take place. - uint64_t max_manifest_file_size = std::numeric_limits::max(); + // The default value is 1GB so that the manifest file can grow, but not + // reach the limit of storage capacity. + uint64_t max_manifest_file_size = 1024 * 1024 * 1024; // Number of shards used for table cache. int table_cache_numshardbits = 6; @@ -560,7 +619,7 @@ struct DBOptions { // then WAL_size_limit_MB, they will be deleted starting with the // earliest until size_limit is met. All empty files will be deleted. // 3. If WAL_ttl_seconds is not 0 and WAL_size_limit_MB is 0, then - // WAL files will be checked every WAL_ttl_secondsi / 2 and those that + // WAL files will be checked every WAL_ttl_seconds / 2 and those that // are older than WAL_ttl_seconds will be deleted. // 4. If both are not 0, WAL files will be checked every 10 min and both // checks will be performed with ttl being first. @@ -589,13 +648,13 @@ struct DBOptions { // buffered. The hardware buffer of the devices may however still // be used. Memory mapped files are not impacted by these parameters. - // Use O_DIRECT for user reads + // Use O_DIRECT for user and compaction reads. + // When true, we also force new_table_reader_for_compaction_inputs to true. // Default: false // Not supported in ROCKSDB_LITE mode! bool use_direct_reads = false; - // Use O_DIRECT for both reads and writes in background flush and compactions - // When true, we also force new_table_reader_for_compaction_inputs to true. + // Use O_DIRECT for writes in background flush and compactions. // Default: false // Not supported in ROCKSDB_LITE mode! bool use_direct_io_for_flush_and_compaction = false; @@ -610,9 +669,21 @@ struct DBOptions { bool skip_log_error_on_recovery = false; // if not zero, dump rocksdb.stats to LOG every stats_dump_period_sec + // // Default: 600 (10 min) + // + // Dynamically changeable through SetDBOptions() API. unsigned int stats_dump_period_sec = 600; + // if not zero, dump rocksdb.stats to RocksDB every stats_persist_period_sec + // Default: 600 + unsigned int stats_persist_period_sec = 600; + + // if not zero, periodically take stats snapshots and store in memory, the + // memory size for stats snapshots is capped at stats_history_buffer_size + // Default: 1MB + size_t stats_history_buffer_size = 1024 * 1024; + // If set true, will hint the underlying file system that the file // access pattern is random, when a sst file is opened. // Default: true @@ -636,7 +707,7 @@ struct DBOptions { // a limit, a flush will be triggered in the next DB to which the next write // is issued. // - // If the object is only passed to on DB, the behavior is the same as + // If the object is only passed to one DB, the behavior is the same as // db_write_buffer_size. When write_buffer_manager is set, the value set will // override db_write_buffer_size. // @@ -649,12 +720,7 @@ struct DBOptions { // Specify the file access pattern once a compaction is started. // It will be applied to all input files of a compaction. // Default: NORMAL - enum AccessHint { - NONE, - NORMAL, - SEQUENTIAL, - WILLNEED - }; + enum AccessHint { NONE, NORMAL, SEQUENTIAL, WILLNEED }; AccessHint access_hint_on_compaction_start = NORMAL; // If true, always create a new file descriptor and new table reader @@ -677,6 +743,8 @@ struct DBOptions { // true. // // Default: 0 + // + // Dynamically changeable through SetDBOptions() API. size_t compaction_readahead_size = 0; // This is a maximum buffer size that is used by WinMmapReadableFile in @@ -703,9 +771,10 @@ struct DBOptions { // write requests if the logical sector size is unusual // // Default: 1024 * 1024 (1 MB) + // + // Dynamically changeable through SetDBOptions() API. size_t writable_file_max_buffer_size = 1024 * 1024; - // Use adaptive mutex, which spins in the user space before resorting // to kernel. This could reduce context switch when the mutex is not // heavily contended. However, if the mutex is hot, we could end up @@ -725,20 +794,27 @@ struct DBOptions { // to smooth out write I/Os over time. Users shouldn't rely on it for // persistency guarantee. // Issue one request for every bytes_per_sync written. 0 turns it off. - // Default: 0 // // You may consider using rate_limiter to regulate write rate to device. // When rate limiter is enabled, it automatically enables bytes_per_sync // to 1MB. // // This option applies to table files + // + // Default: 0, turned off + // + // Note: DOES NOT apply to WAL files. See wal_bytes_per_sync instead + // Dynamically changeable through SetDBOptions() API. uint64_t bytes_per_sync = 0; // Same as bytes_per_sync, but applies to WAL files + // // Default: 0, turned off + // + // Dynamically changeable through SetDBOptions() API. uint64_t wal_bytes_per_sync = 0; - // A vector of EventListeners which call-back functions will be called + // A vector of EventListeners whose callback functions will be called // when specific RocksDB event happens. std::vector> listeners; @@ -762,6 +838,8 @@ struct DBOptions { // Unit: byte per second. // // Default: 0 + // + // Dynamically changeable through SetDBOptions() API. uint64_t delayed_write_rate = 0; // By default, a single write thread queue is maintained. The thread gets @@ -888,17 +966,51 @@ struct DBOptions { // Immutable. bool allow_ingest_behind = false; + // Needed to support differential snapshots. + // If set to true then DB will only process deletes with sequence number + // less than what was set by SetPreserveDeletesSequenceNumber(uint64_t ts). + // Clients are responsible to periodically call this method to advance + // the cutoff time. If this method is never called and preserve_deletes + // is set to true NO deletes will ever be processed. + // At the moment this only keeps normal deletes, SingleDeletes will + // not be preserved. + // DEFAULT: false + // Immutable (TODO: make it dynamically changeable) + bool preserve_deletes = false; + // If enabled it uses two queues for writes, one for the ones with // disable_memtable and one for the ones that also write to memtable. This // allows the memtable writes not to lag behind other writes. It can be used // to optimize MySQL 2PC in which only the commits, which are serial, write to // memtable. - bool concurrent_prepare = false; + bool two_write_queues = false; // If true WAL is not flushed automatically after each write. Instead it // relies on manual invocation of FlushWAL to write the WAL buffer to its // file. bool manual_wal_flush = false; + + // If true, RocksDB supports flushing multiple column families and committing + // their results atomically to MANIFEST. Note that it is not + // necessary to set atomic_flush to true if WAL is always enabled since WAL + // allows the database to be restored to the last persistent state in WAL. + // This option is useful when there are column families with writes NOT + // protected by WAL. + // For manual flush, application has to specify which column families to + // flush atomically in DB::Flush. + // For auto-triggered flush, RocksDB atomically flushes ALL column families. + // + // Currently, any WAL-enabled writes after atomic flush may be replayed + // independently if the process crashes later and tries to recover. + bool atomic_flush = false; + + // If true, ColumnFamilyHandle's and Iterator's destructors won't delete + // obsolete files directly and will instead schedule a background job + // to do it. Use it if you're destroying iterators or ColumnFamilyHandle-s + // from latency-sensitive threads. + // If set to true, takes precedence over + // ReadOptions::background_purge_on_iterator_cleanup. + bool avoid_unnecessary_blocking_io = false; }; // Options to control the behavior of a database (passed to DB::Open) @@ -961,14 +1073,24 @@ struct ReadOptions { // Default: nullptr const Snapshot* snapshot; + // `iterate_lower_bound` defines the smallest key at which the backward + // iterator can return an entry. Once the bound is passed, Valid() will be + // false. `iterate_lower_bound` is inclusive ie the bound value is a valid + // entry. + // + // If prefix_extractor is not null, the Seek target and `iterate_lower_bound` + // need to have the same prefix. This is because ordering is not guaranteed + // outside of prefix domain. + // + // Default: nullptr + const Slice* iterate_lower_bound; + // "iterate_upper_bound" defines the extent upto which the forward iterator // can returns entries. Once the bound is reached, Valid() will be false. // "iterate_upper_bound" is exclusive ie the bound value is // not a valid entry. If iterator_extractor is not null, the Seek target - // and iterator_upper_bound need to have the same prefix. + // and iterate_upper_bound need to have the same prefix. // This is because ordering is not guaranteed outside of prefix domain. - // There is no lower bound on the iterator. If needed, that can be easily - // implemented. // // Default: nullptr const Slice* iterate_upper_bound; @@ -996,10 +1118,11 @@ struct ReadOptions { // Default: true bool verify_checksums; - // Should the "data block"/"index block"/"filter block" read for this - // iteration be cached in memory? + // Should the "data block"/"index block"" read for this iteration be placed in + // block cache? // Callers may wish to set this field to false for bulk scans. - // Default: true + // This would help not to the change eviction order of existing items in the + // block cache. Default: true bool fill_cache; // Specify to create a tailing iterator -- a special iterator that has a @@ -1010,11 +1133,8 @@ struct ReadOptions { // Not supported in ROCKSDB_LITE mode! bool tailing; - // Specify to create a managed iterator -- a special iterator that - // uses less resources by having the ability to free its underlying - // resources on request. - // Default: false - // Not supported in ROCKSDB_LITE mode! + // This options is not used anymore. It was to turn on a functionality that + // has been removed. bool managed; // Enable a total order seek regardless of index format (e.g. hash index) @@ -1053,6 +1173,21 @@ struct ReadOptions { // Default: false bool ignore_range_deletions; + // A callback to determine whether relevant keys for this scan exist in a + // given table based on the table's properties. The callback is passed the + // properties of each table during iteration. If the callback returns false, + // the table will not be scanned. This option only affects Iterators and has + // no impact on point lookups. + // Default: empty (every table will be scanned) + std::function table_filter; + + // Needed to support differential snapshots. Has 2 effects: + // 1) Iterator will skip all internal keys with seqnum < iter_start_seqnum + // 2) if this param > 0 iterator will return INTERNAL keys instead of + // user keys; e.g. return tombstones as well. + // Default: 0 (don't filter by seqnum, return user keys) + SequenceNumber iter_start_seqnum; + ReadOptions(); ReadOptions(bool cksum, bool cache); }; @@ -1078,7 +1213,11 @@ struct WriteOptions { bool sync; // If true, writes will not first go to the write ahead log, - // and the write may got lost after a crash. + // and the write may get lost after a crash. The backup engine + // relies on write-ahead logs to back up the memtable, so if + // you disable write-ahead logs, you must create backups with + // flush_before_backup=true to avoid losing unflushed memtable data. + // Default: false bool disableWAL; // If true and if user is trying to write to column families that don't exist @@ -1089,6 +1228,7 @@ struct WriteOptions { // If true and we need to wait or sleep for the write request, fails // immediately with Status::Incomplete(). + // Default: false bool no_slowdown; // If true, this write request is of lower priority if compaction is @@ -1113,8 +1253,13 @@ struct FlushOptions { // If true, the flush will wait until the flush is done. // Default: true bool wait; - - FlushOptions() : wait(true) {} + // If true, the flush would proceed immediately even it means writes will + // stall for the duration of the flush; if false the operation will wait + // until it's possible to do flush w/o causing stall or until required flush + // is performed by someone else (foreground call or background thread). + // Default: false + bool allow_write_stall; + FlushOptions() : wait(true), allow_write_stall(false) {} }; // Create a Logger from provided DBOptions @@ -1126,14 +1271,20 @@ extern Status CreateLoggerFromOptions(const std::string& dbname, struct CompactionOptions { // Compaction output compression type // Default: snappy + // If set to `kDisableCompressionOption`, RocksDB will choose compression type + // according to the `ColumnFamilyOptions`, taking into account the output + // level if `compression_per_level` is specified. CompressionType compression; // Compaction will create files of size `output_file_size_limit`. // Default: MAX, which means that compaction will create a single file uint64_t output_file_size_limit; + // If > 0, it will replace the option in the DBOptions for this compaction. + uint32_t max_subcompactions; CompactionOptions() : compression(kSnappyCompression), - output_file_size_limit(std::numeric_limits::max()) {} + output_file_size_limit(std::numeric_limits::max()), + max_subcompactions(0) {} }; // For level based compaction, we can configure if we want to skip/force @@ -1166,6 +1317,11 @@ struct CompactRangeOptions { // if there is a compaction filter BottommostLevelCompaction bottommost_level_compaction = BottommostLevelCompaction::kIfHaveCompactionFilter; + // If true, will execute immediately even if doing so would cause the DB to + // enter write stall mode. Otherwise, it'll sleep until load is low enough. + bool allow_write_stall = false; + // If > 0, it will replace the option in the DBOptions for this compaction. + uint32_t max_subcompactions = 0; }; // IngestExternalFileOptions is used by IngestExternalFile() @@ -1189,8 +1345,44 @@ struct IngestExternalFileOptions { // with allow_ingest_behind=true since the dawn of time. // All files will be ingested at the bottommost level with seqno=0. bool ingest_behind = false; + // Set to true if you would like to write global_seqno to a given offset in + // the external SST file for backward compatibility. Older versions of + // RocksDB writes a global_seqno to a given offset within ingested SST files, + // and new versions of RocksDB do not. If you ingest an external SST using + // new version of RocksDB and would like to be able to downgrade to an + // older version of RocksDB, you should set 'write_global_seqno' to true. If + // your service is just starting to use the new RocksDB, we recommend that + // you set this option to false, which brings two benefits: + // 1. No extra random write for global_seqno during ingestion. + // 2. Without writing external SST file, it's possible to do checksum. + // We have a plan to set this option to false by default in the future. + bool write_global_seqno = true; + // Set to true if you would like to verify the checksums of each block of the + // external SST file before ingestion. + // Warning: setting this to true causes slowdown in file ingestion because + // the external SST file has to be read. + bool verify_checksums_before_ingest = false; }; -} // namespace rocksdb +enum TraceFilterType : uint64_t { + // Trace all the operations + kTraceFilterNone = 0x0, + // Do not trace the get operations + kTraceFilterGet = 0x1 << 0, + // Do not trace the write operations + kTraceFilterWrite = 0x1 << 1 +}; + +// TraceOptions is used for StartTrace +struct TraceOptions { + // To avoid the trace file size grows large than the storage space, + // user can set the max trace file size in Bytes. Default is 64GB + uint64_t max_trace_file_size = uint64_t{64} * 1024 * 1024 * 1024; + // Specify trace sampling option, i.e. capture one per how many requests. + // Default to 1 (capture every request). + uint64_t sampling_frequency = 1; + // Note: The filtering happens before sampling. + uint64_t filter = kTraceFilterNone; +}; -#endif // STORAGE_ROCKSDB_INCLUDE_OPTIONS_H_ +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/perf_context.h b/thirdparty/rocksdb/include/rocksdb/perf_context.h index 1095d063bd..a1d803c2c2 100644 --- a/thirdparty/rocksdb/include/rocksdb/perf_context.h +++ b/thirdparty/rocksdb/include/rocksdb/perf_context.h @@ -3,10 +3,10 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef STORAGE_ROCKSDB_INCLUDE_PERF_CONTEXT_H -#define STORAGE_ROCKSDB_INCLUDE_PERF_CONTEXT_H +#pragma once #include +#include #include #include "rocksdb/perf_level.h" @@ -17,18 +17,64 @@ namespace rocksdb { // and transparently. // Use SetPerfLevel(PerfLevel::kEnableTime) to enable time stats. +// Break down performance counters by level and store per-level perf context in +// PerfContextByLevel +struct PerfContextByLevel { + // # of times bloom filter has avoided file reads, i.e., negatives. + uint64_t bloom_filter_useful = 0; + // # of times bloom FullFilter has not avoided the reads. + uint64_t bloom_filter_full_positive = 0; + // # of times bloom FullFilter has not avoided the reads and data actually + // exist. + uint64_t bloom_filter_full_true_positive = 0; + + // total number of user key returned (only include keys that are found, does + // not include keys that are deleted or merged without a final put + uint64_t user_key_return_count; + + // total nanos spent on reading data from SST files + uint64_t get_from_table_nanos; + + uint64_t block_cache_hit_count = 0; // total number of block cache hits + uint64_t block_cache_miss_count = 0; // total number of block cache misses + + void Reset(); // reset all performance counters to zero +}; + struct PerfContext { + ~PerfContext(); + + PerfContext() {} - void Reset(); // reset all performance counters to zero + PerfContext(const PerfContext&); + PerfContext& operator=(const PerfContext&); + PerfContext(PerfContext&&) noexcept; + + void Reset(); // reset all performance counters to zero std::string ToString(bool exclude_zero_counters = false) const; - uint64_t user_key_comparison_count; // total number of user key comparisons - uint64_t block_cache_hit_count; // total number of block cache hits - uint64_t block_read_count; // total number of block reads (with IO) - uint64_t block_read_byte; // total number of bytes from block reads - uint64_t block_read_time; // total nanos spent on block reads - uint64_t block_checksum_time; // total nanos spent on block checksum + // enable per level perf context and allocate storage for PerfContextByLevel + void EnablePerLevelPerfContext(); + + // temporarily disable per level perf contxt by setting the flag to false + void DisablePerLevelPerfContext(); + + // free the space for PerfContextByLevel, also disable per level perf context + void ClearPerLevelPerfContext(); + + uint64_t user_key_comparison_count; // total number of user key comparisons + uint64_t block_cache_hit_count; // total number of block cache hits + uint64_t block_read_count; // total number of block reads (with IO) + uint64_t block_read_byte; // total number of bytes from block reads + uint64_t block_read_time; // total nanos spent on block reads + uint64_t block_cache_index_hit_count; // total number of index block hits + uint64_t index_block_read_count; // total number of index block reads + uint64_t block_cache_filter_hit_count; // total number of filter block hits + uint64_t filter_block_read_count; // total number of filter block reads + uint64_t compression_dict_block_read_count; // total number of compression + // dictionary block reads + uint64_t block_checksum_time; // total nanos spent on block checksum uint64_t block_decompress_time; // total nanos spent on block decompression uint64_t get_read_bytes; // bytes for vals returned by Get @@ -69,9 +115,9 @@ struct PerfContext { // uint64_t internal_merge_count; - uint64_t get_snapshot_time; // total nanos spent on getting snapshot - uint64_t get_from_memtable_time; // total nanos spent on querying memtables - uint64_t get_from_memtable_count; // number of mem tables queried + uint64_t get_snapshot_time; // total nanos spent on getting snapshot + uint64_t get_from_memtable_time; // total nanos spent on querying memtables + uint64_t get_from_memtable_count; // number of mem tables queried // total nanos spent after Get() finds a key uint64_t get_post_process_time; uint64_t get_from_output_files_time; // total nanos reading from output files @@ -95,16 +141,27 @@ struct PerfContext { // total nanos spent on iterating internal entries to find the next user entry uint64_t find_next_user_entry_time; + // This group of stats provide a breakdown of time spent by Write(). + // May be inaccurate when 2PC, two_write_queues or enable_pipelined_write + // are enabled. + // // total nanos spent on writing to WAL uint64_t write_wal_time; // total nanos spent on writing to mem tables uint64_t write_memtable_time; - // total nanos spent on delaying write + // total nanos spent on delaying or throttling write uint64_t write_delay_time; - // total nanos spent on writing a record, excluding the above three times + // total nanos spent on switching memtable/wal and scheduling + // flushes/compactions. + uint64_t write_scheduling_flushes_compactions_time; + // total nanos spent on writing a record, excluding the above four things uint64_t write_pre_and_post_process_time; - uint64_t db_mutex_lock_nanos; // time spent on acquiring DB mutex. + // time spent waiting for other threads of the batch group + uint64_t write_thread_wait_nanos; + + // time spent on acquiring DB mutex. + uint64_t db_mutex_lock_nanos; // Time spent on waiting with a condition variable created with DB mutex. uint64_t db_condition_wait_nanos; // Time spent on merge operator. @@ -131,6 +188,11 @@ struct PerfContext { // total number of SST table bloom misses uint64_t bloom_sst_miss_count; + // Time spent waiting on key locks in transaction lock manager. + uint64_t key_lock_wait_time; + // number of times acquiring a lock was blocked by another transaction. + uint64_t key_lock_wait_count; + // Total time spent in Env filesystem operations. These are only populated // when TimedEnv is used. uint64_t env_new_sequential_file_nanos; @@ -153,12 +215,18 @@ struct PerfContext { uint64_t env_lock_file_nanos; uint64_t env_unlock_file_nanos; uint64_t env_new_logger_nanos; + + uint64_t get_cpu_nanos; + uint64_t iter_next_cpu_nanos; + uint64_t iter_prev_cpu_nanos; + uint64_t iter_seek_cpu_nanos; + + std::map* level_to_perf_context = nullptr; + bool per_level_perf_context_enabled = false; }; // Get Thread-local PerfContext object pointer // if defined(NPERF_CONTEXT), then the pointer is not thread-local PerfContext* get_perf_context(); -} - -#endif +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/perf_level.h b/thirdparty/rocksdb/include/rocksdb/perf_level.h index 84a331c355..de0a214d6a 100644 --- a/thirdparty/rocksdb/include/rocksdb/perf_level.h +++ b/thirdparty/rocksdb/include/rocksdb/perf_level.h @@ -3,8 +3,7 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef INCLUDE_ROCKSDB_PERF_LEVEL_H_ -#define INCLUDE_ROCKSDB_PERF_LEVEL_H_ +#pragma once #include #include @@ -18,8 +17,11 @@ enum PerfLevel : unsigned char { kEnableCount = 2, // enable only count stats kEnableTimeExceptForMutex = 3, // Other than count stats, also enable time // stats except for mutexes - kEnableTime = 4, // enable count and time stats - kOutOfBounds = 5 // N.B. Must always be the last value! + // Other than time, also measure CPU time counters. Still don't measure + // time (neither wall time nor CPU time) for mutexes. + kEnableTimeAndCPUTimeExceptForMutex = 4, + kEnableTime = 5, // enable count and time stats + kOutOfBounds = 6 // N.B. Must always be the last value! }; // set the perf stats level for current thread @@ -29,5 +31,3 @@ void SetPerfLevel(PerfLevel level); PerfLevel GetPerfLevel(); } // namespace rocksdb - -#endif // INCLUDE_ROCKSDB_PERF_LEVEL_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/rate_limiter.h b/thirdparty/rocksdb/include/rocksdb/rate_limiter.h index 838c98a6de..57b1169b63 100644 --- a/thirdparty/rocksdb/include/rocksdb/rate_limiter.h +++ b/thirdparty/rocksdb/include/rocksdb/rate_limiter.h @@ -45,7 +45,7 @@ class RateLimiter { // Request for token for bytes. If this request can not be satisfied, the call // is blocked. Caller is responsible to make sure // bytes <= GetSingleBurstBytes() - virtual void Request(const int64_t bytes, const Env::IOPriority pri) { + virtual void Request(const int64_t /*bytes*/, const Env::IOPriority /*pri*/) { assert(false); } @@ -81,11 +81,11 @@ class RateLimiter { // Max bytes can be granted in a single burst virtual int64_t GetSingleBurstBytes() const = 0; - // Total bytes that go though rate limiter + // Total bytes that go through rate limiter virtual int64_t GetTotalBytesThrough( const Env::IOPriority pri = Env::IO_TOTAL) const = 0; - // Total # of requests that go though rate limiter + // Total # of requests that go through rate limiter virtual int64_t GetTotalRequests( const Env::IOPriority pri = Env::IO_TOTAL) const = 0; @@ -127,9 +127,13 @@ class RateLimiter { // 1/fairness chance even though high-pri requests exist to avoid starvation. // You should be good by leaving it at default 10. // @mode: Mode indicates which types of operations count against the limit. +// @auto_tuned: Enables dynamic adjustment of rate limit within the range +// `[rate_bytes_per_sec / 20, rate_bytes_per_sec]`, according to +// the recent demand for background I/O. extern RateLimiter* NewGenericRateLimiter( int64_t rate_bytes_per_sec, int64_t refill_period_us = 100 * 1000, int32_t fairness = 10, - RateLimiter::Mode mode = RateLimiter::Mode::kWritesOnly); + RateLimiter::Mode mode = RateLimiter::Mode::kWritesOnly, + bool auto_tuned = false); } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/slice.h b/thirdparty/rocksdb/include/rocksdb/slice.h index 4f24c8a221..2b01e6d9a6 100644 --- a/thirdparty/rocksdb/include/rocksdb/slice.h +++ b/thirdparty/rocksdb/include/rocksdb/slice.h @@ -16,15 +16,18 @@ // non-const method, all threads accessing the same Slice must use // external synchronization. -#ifndef STORAGE_ROCKSDB_INCLUDE_SLICE_H_ -#define STORAGE_ROCKSDB_INCLUDE_SLICE_H_ +#pragma once #include -#include #include #include +#include #include +#ifdef __cpp_lib_string_view +#include +#endif + #include "rocksdb/cleanable.h" namespace rocksdb { @@ -32,18 +35,24 @@ namespace rocksdb { class Slice { public: // Create an empty slice. - Slice() : data_(""), size_(0) { } + Slice() : data_(""), size_(0) {} // Create a slice that refers to d[0,n-1]. - Slice(const char* d, size_t n) : data_(d), size_(n) { } + Slice(const char* d, size_t n) : data_(d), size_(n) {} // Create a slice that refers to the contents of "s" /* implicit */ - Slice(const std::string& s) : data_(s.data()), size_(s.size()) { } + Slice(const std::string& s) : data_(s.data()), size_(s.size()) {} + +#ifdef __cpp_lib_string_view + // Create a slice that refers to the same contents as "sv" + /* implicit */ + Slice(std::string_view sv) : data_(sv.data()), size_(sv.size()) {} +#endif // Create a slice that refers to s[0,strlen(s)-1] /* implicit */ - Slice(const char* s) : data_(s), size_(strlen(s)) { } + Slice(const char* s) : data_(s) { size_ = (s == nullptr) ? 0 : strlen(s); } // Create a single slice from SliceParts using buf as storage. // buf must exist as long as the returned Slice exists. @@ -66,7 +75,10 @@ class Slice { } // Change this slice to refer to an empty array - void clear() { data_ = ""; size_ = 0; } + void clear() { + data_ = ""; + size_ = 0; + } // Drop the first "n" bytes from this slice. void remove_prefix(size_t n) { @@ -84,6 +96,13 @@ class Slice { // when hex is true, returns a string of twice the length hex encoded (0-9A-F) std::string ToString(bool hex = false) const; +#ifdef __cpp_lib_string_view + // Return a string_view that references the same data as this slice. + std::string_view ToStringView() const { + return std::string_view(data_, size_); + } +#endif + // Decodes the current slice interpreted as an hexadecimal string into result, // if successful returns true, if this isn't a valid hex string // (e.g not coming from Slice::ToString(true)) DecodeHex returns false. @@ -99,8 +118,7 @@ class Slice { // Return true iff "x" is a prefix of "*this" bool starts_with(const Slice& x) const { - return ((size_ >= x.size_) && - (memcmp(data_, x.data_, x.size_) == 0)); + return ((size_ >= x.size_) && (memcmp(data_, x.data_, x.size_) == 0)); } bool ends_with(const Slice& x) const { @@ -111,7 +129,7 @@ class Slice { // Compare two slices and returns the first byte where they differ size_t difference_offset(const Slice& b) const; - // private: make these public for rocksdbjni access + // private: make these public for rocksdbjni access const char* data_; size_t size_; @@ -121,7 +139,7 @@ class Slice { /** * A Slice that can be pinned with some cleanup tasks, which will be run upon * ::Reset() or object destruction, whichever is invoked first. This can be used - * to avoid memcpy by having the PinnsableSlice object referring to the data + * to avoid memcpy by having the PinnableSlice object referring to the data * that is locked in the memory and release them after the data is consumed. */ class PinnableSlice : public Slice, public Cleanable { @@ -177,13 +195,14 @@ class PinnableSlice : public Slice, public Cleanable { } } - void remove_prefix(size_t n) { + void remove_prefix(size_t /*n*/) { assert(0); // Not implemented } void Reset() { Cleanable::Reset(); pinned_ = false; + size_ = 0; } inline std::string* GetSelf() { return buf_; } @@ -200,8 +219,8 @@ class PinnableSlice : public Slice, public Cleanable { // A set of Slices that are virtually concatenated together. 'parts' points // to an array of Slices. The number of elements in the array is 'num_parts'. struct SliceParts { - SliceParts(const Slice* _parts, int _num_parts) : - parts(_parts), num_parts(_num_parts) { } + SliceParts(const Slice* _parts, int _num_parts) + : parts(_parts), num_parts(_num_parts) {} SliceParts() : parts(nullptr), num_parts(0) {} const Slice* parts; @@ -213,17 +232,17 @@ inline bool operator==(const Slice& x, const Slice& y) { (memcmp(x.data(), y.data(), x.size()) == 0)); } -inline bool operator!=(const Slice& x, const Slice& y) { - return !(x == y); -} +inline bool operator!=(const Slice& x, const Slice& y) { return !(x == y); } inline int Slice::compare(const Slice& b) const { assert(data_ != nullptr && b.data_ != nullptr); const size_t min_len = (size_ < b.size_) ? size_ : b.size_; int r = memcmp(data_, b.data_, min_len); if (r == 0) { - if (size_ < b.size_) r = -1; - else if (size_ > b.size_) r = +1; + if (size_ < b.size_) + r = -1; + else if (size_ > b.size_) + r = +1; } return r; } @@ -238,5 +257,3 @@ inline size_t Slice::difference_offset(const Slice& b) const { } } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_SLICE_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/slice_transform.h b/thirdparty/rocksdb/include/rocksdb/slice_transform.h index fc82bf5845..39e3d5fa13 100644 --- a/thirdparty/rocksdb/include/rocksdb/slice_transform.h +++ b/thirdparty/rocksdb/include/rocksdb/slice_transform.h @@ -12,8 +12,7 @@ // define InDomain and InRange to determine which slices are in either // of these sets respectively. -#ifndef STORAGE_ROCKSDB_INCLUDE_SLICE_TRANSFORM_H_ -#define STORAGE_ROCKSDB_INCLUDE_SLICE_TRANSFORM_H_ +#pragma once #include @@ -22,14 +21,14 @@ namespace rocksdb { class Slice; /* - * A SliceTranform is a generic pluggable way of transforming one string + * A SliceTransform is a generic pluggable way of transforming one string * to another. Its primary use-case is in configuring rocksdb * to store prefix blooms by setting prefix_extractor in * ColumnFamilyOptions. */ class SliceTransform { public: - virtual ~SliceTransform() {}; + virtual ~SliceTransform(){}; // Return the name of this transformation. virtual const char* Name() const = 0; @@ -58,7 +57,12 @@ class SliceTransform { virtual bool InDomain(const Slice& key) const = 0; // This is currently not used and remains here for backward compatibility. - virtual bool InRange(const Slice& dst) const { return false; } + virtual bool InRange(const Slice& /*dst*/) const { return false; } + + // Some SliceTransform will have a full length which can be used to + // determine if two keys are consecuitive. Can be disabled by always + // returning 0 + virtual bool FullLengthEnabled(size_t* /*len*/) const { return false; } // Transform(s)=Transform(`prefix`) for any s with `prefix` as a prefix. // @@ -72,7 +76,7 @@ class SliceTransform { // by setting ReadOptions.total_order_seek = true. // // Here is an example: Suppose we implement a slice transform that returns - // the first part of the string after spliting it using delimiter ",": + // the first part of the string after splitting it using delimiter ",": // 1. SameResultWhenAppended("abc,") should return true. If applying prefix // bloom filter using it, all slices matching "abc:.*" will be extracted // to "abc,", so any SST file or memtable containing any of those key @@ -83,7 +87,7 @@ class SliceTransform { // "abcd,e", the file can be filtered out and the key will be invisible. // // i.e., an implementation always returning false is safe. - virtual bool SameResultWhenAppended(const Slice& prefix) const { + virtual bool SameResultWhenAppended(const Slice& /*prefix*/) const { return false; } }; @@ -94,6 +98,4 @@ extern const SliceTransform* NewCappedPrefixTransform(size_t cap_len); extern const SliceTransform* NewNoopTransform(); -} - -#endif // STORAGE_ROCKSDB_INCLUDE_SLICE_TRANSFORM_H_ +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/sst_dump_tool.h b/thirdparty/rocksdb/include/rocksdb/sst_dump_tool.h index 021faa019c..c7cc4a0fc4 100644 --- a/thirdparty/rocksdb/include/rocksdb/sst_dump_tool.h +++ b/thirdparty/rocksdb/include/rocksdb/sst_dump_tool.h @@ -5,11 +5,13 @@ #ifndef ROCKSDB_LITE #pragma once +#include "rocksdb/options.h" + namespace rocksdb { class SSTDumpTool { public: - int Run(int argc, char** argv); + int Run(int argc, char** argv, Options options = Options()); }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/sst_file_manager.h b/thirdparty/rocksdb/include/rocksdb/sst_file_manager.h index 692007d31a..3e3ef859b5 100644 --- a/thirdparty/rocksdb/include/rocksdb/sst_file_manager.h +++ b/thirdparty/rocksdb/include/rocksdb/sst_file_manager.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "rocksdb/status.h" @@ -16,9 +17,10 @@ namespace rocksdb { class Env; class Logger; -// SstFileManager is used to track SST files in the DB and control there +// SstFileManager is used to track SST files in the DB and control their // deletion rate. // All SstFileManager public functions are thread-safe. +// SstFileManager is not extensible. class SstFileManager { public: virtual ~SstFileManager() {} @@ -27,23 +29,33 @@ class SstFileManager { // the total size of the SST files exceeds max_allowed_space, writes to // RocksDB will fail. // - // Setting max_allowed_space to 0 will disable this feature, maximum allowed + // Setting max_allowed_space to 0 will disable this feature; maximum allowed // space will be infinite (Default value). // // thread-safe. virtual void SetMaxAllowedSpaceUsage(uint64_t max_allowed_space) = 0; + // Set the amount of buffer room each compaction should be able to leave. + // In other words, at its maximum disk space consumption, the compaction + // should still leave compaction_buffer_size available on the disk so that + // other background functions may continue, such as logging and flushing. + virtual void SetCompactionBufferSize(uint64_t compaction_buffer_size) = 0; + // Return true if the total size of SST files exceeded the maximum allowed // space usage. // // thread-safe. virtual bool IsMaxAllowedSpaceReached() = 0; + // Returns true if the total size of SST files as well as estimated size + // of ongoing compactions exceeds the maximums allowed space usage. + virtual bool IsMaxAllowedSpaceReachedIncludingCompactions() = 0; + // Return the total size of all tracked files. // thread-safe virtual uint64_t GetTotalSize() = 0; - // Return a map containing all tracked files and there corresponding sizes. + // Return a map containing all tracked files and their corresponding sizes. // thread-safe virtual std::unordered_map GetTrackedFiles() = 0; @@ -55,31 +67,54 @@ class SstFileManager { // zero means disable delete rate limiting and delete files immediately // thread-safe virtual void SetDeleteRateBytesPerSecond(int64_t delete_rate) = 0; + + // Return trash/DB size ratio where new files will be deleted immediately + // thread-safe + virtual double GetMaxTrashDBRatio() = 0; + + // Update trash/DB size ratio where new files will be deleted immediately + // thread-safe + virtual void SetMaxTrashDBRatio(double ratio) = 0; + + // Return the total size of trash files + // thread-safe + virtual uint64_t GetTotalTrashSize() = 0; }; // Create a new SstFileManager that can be shared among multiple RocksDB // instances to track SST file and control there deletion rate. +// Even though SstFileManager don't track WAL files but it still control +// there deletion rate. // // @param env: Pointer to Env object, please see "rocksdb/env.h". // @param info_log: If not nullptr, info_log will be used to log errors. // // == Deletion rate limiting specific arguments == -// @param trash_dir: Path to the directory where deleted files will be moved -// to be deleted in a background thread while applying rate limiting. If this -// directory doesn't exist, it will be created. This directory should not be -// used by any other process or any other SstFileManager, Set to "" to -// disable deletion rate limiting. +// @param trash_dir: Deprecated, this argument have no effect // @param rate_bytes_per_sec: How many bytes should be deleted per second, If // this value is set to 1024 (1 Kb / sec) and we deleted a file of size 4 Kb // in 1 second, we will wait for another 3 seconds before we delete other // files, Set to 0 to disable deletion rate limiting. -// @param delete_existing_trash: If set to true, the newly created -// SstFileManager will delete files that already exist in trash_dir. +// This option also affect the delete rate of WAL files in the DB. +// @param delete_existing_trash: Deprecated, this argument have no effect, but +// if user provide trash_dir we will schedule deletes for files in the dir // @param status: If not nullptr, status will contain any errors that happened // during creating the missing trash_dir or deleting existing files in trash. +// @param max_trash_db_ratio: If the trash size constitutes for more than this +// fraction of the total DB size we will start deleting new files passed to +// DeleteScheduler immediately +// @param bytes_max_delete_chunk: if a file to delete is larger than delete +// chunk, ftruncate the file by this size each time, rather than dropping the +// whole file. 0 means to always delete the whole file. If the file has more +// than one linked names, the file will be deleted as a whole. Either way, +// `rate_bytes_per_sec` will be appreciated. NOTE that with this option, +// files already renamed as a trash may be partial, so users should not +// directly recover them without checking. extern SstFileManager* NewSstFileManager( Env* env, std::shared_ptr info_log = nullptr, std::string trash_dir = "", int64_t rate_bytes_per_sec = 0, - bool delete_existing_trash = true, Status* status = nullptr); + bool delete_existing_trash = true, Status* status = nullptr, + double max_trash_db_ratio = 0.25, + uint64_t bytes_max_delete_chunk = 64 * 1024 * 1024); } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/sst_file_reader.h b/thirdparty/rocksdb/include/rocksdb/sst_file_reader.h new file mode 100644 index 0000000000..517907dd50 --- /dev/null +++ b/thirdparty/rocksdb/include/rocksdb/sst_file_reader.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#ifndef ROCKSDB_LITE + +#include "rocksdb/iterator.h" +#include "rocksdb/options.h" +#include "rocksdb/slice.h" +#include "rocksdb/table_properties.h" + +namespace rocksdb { + +// SstFileReader is used to read sst files that are generated by DB or +// SstFileWriter. +class SstFileReader { + public: + SstFileReader(const Options& options); + + ~SstFileReader(); + + // Prepares to read from the file located at "file_path". + Status Open(const std::string& file_path); + + // Returns a new iterator over the table contents. + // Most read options provide the same control as we read from DB. + // If "snapshot" is nullptr, the iterator returns only the latest keys. + Iterator* NewIterator(const ReadOptions& options); + + std::shared_ptr GetTableProperties() const; + + // Verifies whether there is corruption in this table. + Status VerifyChecksum(); + + private: + struct Rep; + std::unique_ptr rep_; +}; + +} // namespace rocksdb + +#endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/sst_file_writer.h b/thirdparty/rocksdb/include/rocksdb/sst_file_writer.h index 04d5c271a0..273c913e4f 100644 --- a/thirdparty/rocksdb/include/rocksdb/sst_file_writer.h +++ b/thirdparty/rocksdb/include/rocksdb/sst_file_writer.h @@ -3,10 +3,10 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef ROCKSDB_LITE - #pragma once +#ifndef ROCKSDB_LITE + #include #include @@ -28,7 +28,18 @@ class Comparator; // ExternalSstFileInfo include information about sst files created // using SstFileWriter. struct ExternalSstFileInfo { - ExternalSstFileInfo() {} + ExternalSstFileInfo() + : file_path(""), + smallest_key(""), + largest_key(""), + smallest_range_del_key(""), + largest_range_del_key(""), + sequence_number(0), + file_size(0), + num_entries(0), + num_range_del_entries(0), + version(0) {} + ExternalSstFileInfo(const std::string& _file_path, const std::string& _smallest_key, const std::string& _largest_key, @@ -37,17 +48,24 @@ struct ExternalSstFileInfo { : file_path(_file_path), smallest_key(_smallest_key), largest_key(_largest_key), + smallest_range_del_key(""), + largest_range_del_key(""), sequence_number(_sequence_number), file_size(_file_size), num_entries(_num_entries), + num_range_del_entries(0), version(_version) {} - std::string file_path; // external sst file path - std::string smallest_key; // smallest user key in file - std::string largest_key; // largest user key in file - SequenceNumber sequence_number; // sequence number of all keys in file - uint64_t file_size; // file size in bytes - uint64_t num_entries; // number of entries in file + std::string file_path; // external sst file path + std::string smallest_key; // smallest user key in file + std::string largest_key; // largest user key in file + std::string + smallest_range_del_key; // smallest range deletion user key in file + std::string largest_range_del_key; // largest range deletion user key in file + SequenceNumber sequence_number; // sequence number of all keys in file + uint64_t file_size; // file size in bytes + uint64_t num_entries; // number of entries in file + uint64_t num_range_del_entries; // number of range deletion entries in file int32_t version; // file version }; @@ -59,21 +77,24 @@ class SstFileWriter { // be ingested into this column_family, note that passing nullptr means that // the column_family is unknown. // If invalidate_page_cache is set to true, SstFileWriter will give the OS a - // hint that this file pages is not needed everytime we write 1MB to the file. - // To use the rate limiter an io_priority smaller than IO_TOTAL can be passed. + // hint that this file pages is not needed every time we write 1MB to the + // file. To use the rate limiter an io_priority smaller than IO_TOTAL can be + // passed. SstFileWriter(const EnvOptions& env_options, const Options& options, ColumnFamilyHandle* column_family = nullptr, bool invalidate_page_cache = true, - Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL) + Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL, + bool skip_filters = false) : SstFileWriter(env_options, options, options.comparator, column_family, - invalidate_page_cache, io_priority) {} + invalidate_page_cache, io_priority, skip_filters) {} // Deprecated API SstFileWriter(const EnvOptions& env_options, const Options& options, const Comparator* user_comparator, ColumnFamilyHandle* column_family = nullptr, bool invalidate_page_cache = true, - Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL); + Env::IOPriority io_priority = Env::IOPriority::IO_TOTAL, + bool skip_filters = false); ~SstFileWriter(); @@ -96,6 +117,9 @@ class SstFileWriter { // REQUIRES: key is after any previously added key according to comparator. Status Delete(const Slice& user_key); + // Add a range deletion tombstone to currently opened file + Status DeleteRange(const Slice& begin_key, const Slice& end_key); + // Finalize writing to sst file and close file. // // An optional ExternalSstFileInfo pointer can be passed to the function diff --git a/thirdparty/rocksdb/include/rocksdb/statistics.h b/thirdparty/rocksdb/include/rocksdb/statistics.h index 731ff78096..bad1c87ec5 100644 --- a/thirdparty/rocksdb/include/rocksdb/statistics.h +++ b/thirdparty/rocksdb/include/rocksdb/statistics.h @@ -3,14 +3,14 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef STORAGE_ROCKSDB_INCLUDE_STATISTICS_H_ -#define STORAGE_ROCKSDB_INCLUDE_STATISTICS_H_ +#pragma once #include #include #include -#include +#include #include +#include #include #include "rocksdb/status.h" @@ -22,6 +22,8 @@ namespace rocksdb { * 1. Any ticker should be added before TICKER_ENUM_MAX. * 2. Add a readable string in TickersNameMap below for the newly added ticker. * 3. Add a corresponding enum value to TickerType.java in the java API + * 4. Add the enum conversions from Java and C++ to portal.h's toJavaTickerType + * and toCppTickers */ enum Tickers : uint32_t { // total block cache misses @@ -71,8 +73,13 @@ enum Tickers : uint32_t { // # of bytes written into cache. BLOCK_CACHE_BYTES_WRITE, - // # of times bloom filter has avoided file reads. + // # of times bloom filter has avoided file reads, i.e., negatives. BLOOM_FILTER_USEFUL, + // # of times bloom FullFilter has not avoided the reads. + BLOOM_FILTER_FULL_POSITIVE, + // # of times bloom FullFilter has not avoided the reads and data actually + // exist. + BLOOM_FILTER_FULL_TRUE_POSITIVE, // # persistent cache hit PERSISTENT_CACHE_HIT, @@ -108,6 +115,8 @@ enum Tickers : uint32_t { COMPACTION_RANGE_DEL_DROP_OBSOLETE, // all keys in range were deleted. // Deletions obsoleted before bottom level due to file gap optimization. COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE, + // If a compaction was cancelled in sfm to prevent ENOSPC + COMPACTION_CANCELLED, // Number of keys written to the database via the Put and Write call's NUMBER_KEYS_WRITTEN, @@ -149,7 +158,8 @@ enum Tickers : uint32_t { // Disabled by default. To enable it set stats level to kAll DB_MUTEX_WAIT_MICROS, RATE_LIMIT_DELAY_MILLIS, - NO_ITERATORS, // number of iterators currently open + // DEPRECATED number of iterators currently open + NO_ITERATORS, // Number of MultiGet calls, keys read, and bytes read NUMBER_MULTIGET_CALLS, @@ -223,112 +233,115 @@ enum Tickers : uint32_t { // Number of refill intervals where rate limiter's bytes are fully consumed. NUMBER_RATE_LIMITER_DRAINS, + // Number of internal keys skipped by Iterator + NUMBER_ITER_SKIP, + + // BlobDB specific stats + // # of Put/PutTTL/PutUntil to BlobDB. + BLOB_DB_NUM_PUT, + // # of Write to BlobDB. + BLOB_DB_NUM_WRITE, + // # of Get to BlobDB. + BLOB_DB_NUM_GET, + // # of MultiGet to BlobDB. + BLOB_DB_NUM_MULTIGET, + // # of Seek/SeekToFirst/SeekToLast/SeekForPrev to BlobDB iterator. + BLOB_DB_NUM_SEEK, + // # of Next to BlobDB iterator. + BLOB_DB_NUM_NEXT, + // # of Prev to BlobDB iterator. + BLOB_DB_NUM_PREV, + // # of keys written to BlobDB. + BLOB_DB_NUM_KEYS_WRITTEN, + // # of keys read from BlobDB. + BLOB_DB_NUM_KEYS_READ, + // # of bytes (key + value) written to BlobDB. + BLOB_DB_BYTES_WRITTEN, + // # of bytes (keys + value) read from BlobDB. + BLOB_DB_BYTES_READ, + // # of keys written by BlobDB as non-TTL inlined value. + BLOB_DB_WRITE_INLINED, + // # of keys written by BlobDB as TTL inlined value. + BLOB_DB_WRITE_INLINED_TTL, + // # of keys written by BlobDB as non-TTL blob value. + BLOB_DB_WRITE_BLOB, + // # of keys written by BlobDB as TTL blob value. + BLOB_DB_WRITE_BLOB_TTL, + // # of bytes written to blob file. + BLOB_DB_BLOB_FILE_BYTES_WRITTEN, + // # of bytes read from blob file. + BLOB_DB_BLOB_FILE_BYTES_READ, + // # of times a blob files being synced. + BLOB_DB_BLOB_FILE_SYNCED, + // # of blob index evicted from base DB by BlobDB compaction filter because + // of expiration. + BLOB_DB_BLOB_INDEX_EXPIRED_COUNT, + // size of blob index evicted from base DB by BlobDB compaction filter + // because of expiration. + BLOB_DB_BLOB_INDEX_EXPIRED_SIZE, + // # of blob index evicted from base DB by BlobDB compaction filter because + // of corresponding file deleted. + BLOB_DB_BLOB_INDEX_EVICTED_COUNT, + // size of blob index evicted from base DB by BlobDB compaction filter + // because of corresponding file deleted. + BLOB_DB_BLOB_INDEX_EVICTED_SIZE, + // # of blob files being garbage collected. + BLOB_DB_GC_NUM_FILES, + // # of blob files generated by garbage collection. + BLOB_DB_GC_NUM_NEW_FILES, + // # of BlobDB garbage collection failures. + BLOB_DB_GC_FAILURES, + // # of keys drop by BlobDB garbage collection because they had been + // overwritten. + BLOB_DB_GC_NUM_KEYS_OVERWRITTEN, + // # of keys drop by BlobDB garbage collection because of expiration. + BLOB_DB_GC_NUM_KEYS_EXPIRED, + // # of keys relocated to new blob file by garbage collection. + BLOB_DB_GC_NUM_KEYS_RELOCATED, + // # of bytes drop by BlobDB garbage collection because they had been + // overwritten. + BLOB_DB_GC_BYTES_OVERWRITTEN, + // # of bytes drop by BlobDB garbage collection because of expiration. + BLOB_DB_GC_BYTES_EXPIRED, + // # of bytes relocated to new blob file by garbage collection. + BLOB_DB_GC_BYTES_RELOCATED, + // # of blob files evicted because of BlobDB is full. + BLOB_DB_FIFO_NUM_FILES_EVICTED, + // # of keys in the blob files evicted because of BlobDB is full. + BLOB_DB_FIFO_NUM_KEYS_EVICTED, + // # of bytes in the blob files evicted because of BlobDB is full. + BLOB_DB_FIFO_BYTES_EVICTED, + + // These counters indicate a performance issue in WritePrepared transactions. + // We should not seem them ticking them much. + // # of times prepare_mutex_ is acquired in the fast path. + TXN_PREPARE_MUTEX_OVERHEAD, + // # of times old_commit_map_mutex_ is acquired in the fast path. + TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD, + // # of times we checked a batch for duplicate keys. + TXN_DUPLICATE_KEY_OVERHEAD, + // # of times snapshot_mutex_ is acquired in the fast path. + TXN_SNAPSHOT_MUTEX_OVERHEAD, + + // Number of keys actually found in MultiGet calls (vs number requested by + // caller) + // NUMBER_MULTIGET_KEYS_READ gives the number requested by caller + NUMBER_MULTIGET_KEYS_FOUND, + + NO_ITERATOR_CREATED, // number of iterators created + NO_ITERATOR_DELETED, // number of iterators deleted + + BLOCK_CACHE_COMPRESSION_DICT_MISS, + BLOCK_CACHE_COMPRESSION_DICT_HIT, + BLOCK_CACHE_COMPRESSION_DICT_ADD, + BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT, + BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT, TICKER_ENUM_MAX }; // The order of items listed in Tickers should be the same as // the order listed in TickersNameMap -const std::vector> TickersNameMap = { - {BLOCK_CACHE_MISS, "rocksdb.block.cache.miss"}, - {BLOCK_CACHE_HIT, "rocksdb.block.cache.hit"}, - {BLOCK_CACHE_ADD, "rocksdb.block.cache.add"}, - {BLOCK_CACHE_ADD_FAILURES, "rocksdb.block.cache.add.failures"}, - {BLOCK_CACHE_INDEX_MISS, "rocksdb.block.cache.index.miss"}, - {BLOCK_CACHE_INDEX_HIT, "rocksdb.block.cache.index.hit"}, - {BLOCK_CACHE_INDEX_ADD, "rocksdb.block.cache.index.add"}, - {BLOCK_CACHE_INDEX_BYTES_INSERT, "rocksdb.block.cache.index.bytes.insert"}, - {BLOCK_CACHE_INDEX_BYTES_EVICT, "rocksdb.block.cache.index.bytes.evict"}, - {BLOCK_CACHE_FILTER_MISS, "rocksdb.block.cache.filter.miss"}, - {BLOCK_CACHE_FILTER_HIT, "rocksdb.block.cache.filter.hit"}, - {BLOCK_CACHE_FILTER_ADD, "rocksdb.block.cache.filter.add"}, - {BLOCK_CACHE_FILTER_BYTES_INSERT, - "rocksdb.block.cache.filter.bytes.insert"}, - {BLOCK_CACHE_FILTER_BYTES_EVICT, "rocksdb.block.cache.filter.bytes.evict"}, - {BLOCK_CACHE_DATA_MISS, "rocksdb.block.cache.data.miss"}, - {BLOCK_CACHE_DATA_HIT, "rocksdb.block.cache.data.hit"}, - {BLOCK_CACHE_DATA_ADD, "rocksdb.block.cache.data.add"}, - {BLOCK_CACHE_DATA_BYTES_INSERT, "rocksdb.block.cache.data.bytes.insert"}, - {BLOCK_CACHE_BYTES_READ, "rocksdb.block.cache.bytes.read"}, - {BLOCK_CACHE_BYTES_WRITE, "rocksdb.block.cache.bytes.write"}, - {BLOOM_FILTER_USEFUL, "rocksdb.bloom.filter.useful"}, - {PERSISTENT_CACHE_HIT, "rocksdb.persistent.cache.hit"}, - {PERSISTENT_CACHE_MISS, "rocksdb.persistent.cache.miss"}, - {SIM_BLOCK_CACHE_HIT, "rocksdb.sim.block.cache.hit"}, - {SIM_BLOCK_CACHE_MISS, "rocksdb.sim.block.cache.miss"}, - {MEMTABLE_HIT, "rocksdb.memtable.hit"}, - {MEMTABLE_MISS, "rocksdb.memtable.miss"}, - {GET_HIT_L0, "rocksdb.l0.hit"}, - {GET_HIT_L1, "rocksdb.l1.hit"}, - {GET_HIT_L2_AND_UP, "rocksdb.l2andup.hit"}, - {COMPACTION_KEY_DROP_NEWER_ENTRY, "rocksdb.compaction.key.drop.new"}, - {COMPACTION_KEY_DROP_OBSOLETE, "rocksdb.compaction.key.drop.obsolete"}, - {COMPACTION_KEY_DROP_RANGE_DEL, "rocksdb.compaction.key.drop.range_del"}, - {COMPACTION_KEY_DROP_USER, "rocksdb.compaction.key.drop.user"}, - {COMPACTION_RANGE_DEL_DROP_OBSOLETE, - "rocksdb.compaction.range_del.drop.obsolete"}, - {COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE, - "rocksdb.compaction.optimized.del.drop.obsolete"}, - {NUMBER_KEYS_WRITTEN, "rocksdb.number.keys.written"}, - {NUMBER_KEYS_READ, "rocksdb.number.keys.read"}, - {NUMBER_KEYS_UPDATED, "rocksdb.number.keys.updated"}, - {BYTES_WRITTEN, "rocksdb.bytes.written"}, - {BYTES_READ, "rocksdb.bytes.read"}, - {NUMBER_DB_SEEK, "rocksdb.number.db.seek"}, - {NUMBER_DB_NEXT, "rocksdb.number.db.next"}, - {NUMBER_DB_PREV, "rocksdb.number.db.prev"}, - {NUMBER_DB_SEEK_FOUND, "rocksdb.number.db.seek.found"}, - {NUMBER_DB_NEXT_FOUND, "rocksdb.number.db.next.found"}, - {NUMBER_DB_PREV_FOUND, "rocksdb.number.db.prev.found"}, - {ITER_BYTES_READ, "rocksdb.db.iter.bytes.read"}, - {NO_FILE_CLOSES, "rocksdb.no.file.closes"}, - {NO_FILE_OPENS, "rocksdb.no.file.opens"}, - {NO_FILE_ERRORS, "rocksdb.no.file.errors"}, - {STALL_L0_SLOWDOWN_MICROS, "rocksdb.l0.slowdown.micros"}, - {STALL_MEMTABLE_COMPACTION_MICROS, "rocksdb.memtable.compaction.micros"}, - {STALL_L0_NUM_FILES_MICROS, "rocksdb.l0.num.files.stall.micros"}, - {STALL_MICROS, "rocksdb.stall.micros"}, - {DB_MUTEX_WAIT_MICROS, "rocksdb.db.mutex.wait.micros"}, - {RATE_LIMIT_DELAY_MILLIS, "rocksdb.rate.limit.delay.millis"}, - {NO_ITERATORS, "rocksdb.num.iterators"}, - {NUMBER_MULTIGET_CALLS, "rocksdb.number.multiget.get"}, - {NUMBER_MULTIGET_KEYS_READ, "rocksdb.number.multiget.keys.read"}, - {NUMBER_MULTIGET_BYTES_READ, "rocksdb.number.multiget.bytes.read"}, - {NUMBER_FILTERED_DELETES, "rocksdb.number.deletes.filtered"}, - {NUMBER_MERGE_FAILURES, "rocksdb.number.merge.failures"}, - {BLOOM_FILTER_PREFIX_CHECKED, "rocksdb.bloom.filter.prefix.checked"}, - {BLOOM_FILTER_PREFIX_USEFUL, "rocksdb.bloom.filter.prefix.useful"}, - {NUMBER_OF_RESEEKS_IN_ITERATION, "rocksdb.number.reseeks.iteration"}, - {GET_UPDATES_SINCE_CALLS, "rocksdb.getupdatessince.calls"}, - {BLOCK_CACHE_COMPRESSED_MISS, "rocksdb.block.cachecompressed.miss"}, - {BLOCK_CACHE_COMPRESSED_HIT, "rocksdb.block.cachecompressed.hit"}, - {BLOCK_CACHE_COMPRESSED_ADD, "rocksdb.block.cachecompressed.add"}, - {BLOCK_CACHE_COMPRESSED_ADD_FAILURES, - "rocksdb.block.cachecompressed.add.failures"}, - {WAL_FILE_SYNCED, "rocksdb.wal.synced"}, - {WAL_FILE_BYTES, "rocksdb.wal.bytes"}, - {WRITE_DONE_BY_SELF, "rocksdb.write.self"}, - {WRITE_DONE_BY_OTHER, "rocksdb.write.other"}, - {WRITE_TIMEDOUT, "rocksdb.write.timeout"}, - {WRITE_WITH_WAL, "rocksdb.write.wal"}, - {COMPACT_READ_BYTES, "rocksdb.compact.read.bytes"}, - {COMPACT_WRITE_BYTES, "rocksdb.compact.write.bytes"}, - {FLUSH_WRITE_BYTES, "rocksdb.flush.write.bytes"}, - {NUMBER_DIRECT_LOAD_TABLE_PROPERTIES, - "rocksdb.number.direct.load.table.properties"}, - {NUMBER_SUPERVERSION_ACQUIRES, "rocksdb.number.superversion_acquires"}, - {NUMBER_SUPERVERSION_RELEASES, "rocksdb.number.superversion_releases"}, - {NUMBER_SUPERVERSION_CLEANUPS, "rocksdb.number.superversion_cleanups"}, - {NUMBER_BLOCK_COMPRESSED, "rocksdb.number.block.compressed"}, - {NUMBER_BLOCK_DECOMPRESSED, "rocksdb.number.block.decompressed"}, - {NUMBER_BLOCK_NOT_COMPRESSED, "rocksdb.number.block.not_compressed"}, - {MERGE_OPERATION_TOTAL_TIME, "rocksdb.merge.operation.time.nanos"}, - {FILTER_OPERATION_TOTAL_TIME, "rocksdb.filter.operation.time.nanos"}, - {ROW_CACHE_HIT, "rocksdb.row.cache.hit"}, - {ROW_CACHE_MISS, "rocksdb.row.cache.miss"}, - {READ_AMP_ESTIMATE_USEFUL_BYTES, "rocksdb.read.amp.estimate.useful.bytes"}, - {READ_AMP_TOTAL_READ_BYTES, "rocksdb.read.amp.total.read.bytes"}, - {NUMBER_RATE_LIMITER_DRAINS, "rocksdb.number.rate_limiter.drains"}, -}; +extern const std::vector> TickersNameMap; /** * Keep adding histogram's here. @@ -342,6 +355,7 @@ enum Histograms : uint32_t { DB_GET = 0, DB_WRITE, COMPACTION_TIME, + COMPACTION_CPU_TIME, SUBCOMPACTION_SETUP_TIME, TABLE_SYNC_MICROS, COMPACTION_OUTFILE_SYNC_MICROS, @@ -379,42 +393,42 @@ enum Histograms : uint32_t { // requests. READ_NUM_MERGE_OPERANDS, - HISTOGRAM_ENUM_MAX, // TODO(ldemailly): enforce HistogramsNameMap match + // BlobDB specific stats + // Size of keys written to BlobDB. + BLOB_DB_KEY_SIZE, + // Size of values written to BlobDB. + BLOB_DB_VALUE_SIZE, + // BlobDB Put/PutWithTTL/PutUntil/Write latency. + BLOB_DB_WRITE_MICROS, + // BlobDB Get lagency. + BLOB_DB_GET_MICROS, + // BlobDB MultiGet latency. + BLOB_DB_MULTIGET_MICROS, + // BlobDB Seek/SeekToFirst/SeekToLast/SeekForPrev latency. + BLOB_DB_SEEK_MICROS, + // BlobDB Next latency. + BLOB_DB_NEXT_MICROS, + // BlobDB Prev latency. + BLOB_DB_PREV_MICROS, + // Blob file write latency. + BLOB_DB_BLOB_FILE_WRITE_MICROS, + // Blob file read latency. + BLOB_DB_BLOB_FILE_READ_MICROS, + // Blob file sync latency. + BLOB_DB_BLOB_FILE_SYNC_MICROS, + // BlobDB garbage collection time. + BLOB_DB_GC_MICROS, + // BlobDB compression time. + BLOB_DB_COMPRESSION_MICROS, + // BlobDB decompression time. + BLOB_DB_DECOMPRESSION_MICROS, + // Time spent flushing memtable to disk + FLUSH_TIME, + + HISTOGRAM_ENUM_MAX, }; -const std::vector> HistogramsNameMap = { - {DB_GET, "rocksdb.db.get.micros"}, - {DB_WRITE, "rocksdb.db.write.micros"}, - {COMPACTION_TIME, "rocksdb.compaction.times.micros"}, - {SUBCOMPACTION_SETUP_TIME, "rocksdb.subcompaction.setup.times.micros"}, - {TABLE_SYNC_MICROS, "rocksdb.table.sync.micros"}, - {COMPACTION_OUTFILE_SYNC_MICROS, "rocksdb.compaction.outfile.sync.micros"}, - {WAL_FILE_SYNC_MICROS, "rocksdb.wal.file.sync.micros"}, - {MANIFEST_FILE_SYNC_MICROS, "rocksdb.manifest.file.sync.micros"}, - {TABLE_OPEN_IO_MICROS, "rocksdb.table.open.io.micros"}, - {DB_MULTIGET, "rocksdb.db.multiget.micros"}, - {READ_BLOCK_COMPACTION_MICROS, "rocksdb.read.block.compaction.micros"}, - {READ_BLOCK_GET_MICROS, "rocksdb.read.block.get.micros"}, - {WRITE_RAW_BLOCK_MICROS, "rocksdb.write.raw.block.micros"}, - {STALL_L0_SLOWDOWN_COUNT, "rocksdb.l0.slowdown.count"}, - {STALL_MEMTABLE_COMPACTION_COUNT, "rocksdb.memtable.compaction.count"}, - {STALL_L0_NUM_FILES_COUNT, "rocksdb.num.files.stall.count"}, - {HARD_RATE_LIMIT_DELAY_COUNT, "rocksdb.hard.rate.limit.delay.count"}, - {SOFT_RATE_LIMIT_DELAY_COUNT, "rocksdb.soft.rate.limit.delay.count"}, - {NUM_FILES_IN_SINGLE_COMPACTION, "rocksdb.numfiles.in.singlecompaction"}, - {DB_SEEK, "rocksdb.db.seek.micros"}, - {WRITE_STALL, "rocksdb.db.write.stall"}, - {SST_READ_MICROS, "rocksdb.sst.read.micros"}, - {NUM_SUBCOMPACTIONS_SCHEDULED, "rocksdb.num.subcompactions.scheduled"}, - {BYTES_PER_READ, "rocksdb.bytes.per.read"}, - {BYTES_PER_WRITE, "rocksdb.bytes.per.write"}, - {BYTES_PER_MULTIGET, "rocksdb.bytes.per.multiget"}, - {BYTES_COMPRESSED, "rocksdb.bytes.compressed"}, - {BYTES_DECOMPRESSED, "rocksdb.bytes.decompressed"}, - {COMPRESSION_TIMES_NANOS, "rocksdb.compression.times.nanos"}, - {DECOMPRESSION_TIMES_NANOS, "rocksdb.decompression.times.nanos"}, - {READ_NUM_MERGE_OPERANDS, "rocksdb.read.num.merge_operands"}, -}; +extern const std::vector> HistogramsNameMap; struct HistogramData { double median; @@ -425,9 +439,16 @@ struct HistogramData { // zero-initialize new members since old Statistics::histogramData() // implementations won't write them. double max = 0.0; + uint64_t count = 0; + uint64_t sum = 0; + double min = 0.0; }; -enum StatsLevel { +enum StatsLevel : uint8_t { + // Disable timer stats, and skip histogram stats + kExceptHistogramOrTimers, + // Skip timer stats + kExceptTimers, // Collect all stats except time inside mutex lock AND time spent on // compression. kExceptDetailedTimers, @@ -448,16 +469,33 @@ class Statistics { virtual uint64_t getTickerCount(uint32_t tickerType) const = 0; virtual void histogramData(uint32_t type, HistogramData* const data) const = 0; - virtual std::string getHistogramString(uint32_t type) const { return ""; } + virtual std::string getHistogramString(uint32_t /*type*/) const { return ""; } virtual void recordTick(uint32_t tickerType, uint64_t count = 0) = 0; virtual void setTickerCount(uint32_t tickerType, uint64_t count) = 0; virtual uint64_t getAndResetTickerCount(uint32_t tickerType) = 0; - virtual void measureTime(uint32_t histogramType, uint64_t time) = 0; + virtual void reportTimeToHistogram(uint32_t histogramType, uint64_t time) { + if (get_stats_level() <= StatsLevel::kExceptTimers) { + return; + } + recordInHistogram(histogramType, time); + } + // The function is here only for backward compatibility reason. + // Users implementing their own Statistics class should override + // recordInHistogram() instead and leave measureTime() as it is. + virtual void measureTime(uint32_t /*histogramType*/, uint64_t /*time*/) { + // This is not supposed to be called. + assert(false); + } + virtual void recordInHistogram(uint32_t histogramType, uint64_t time) { + // measureTime() is the old and inaccurate function name. + // To keep backward compatible. If users implement their own + // statistics, which overrides meareTime() but doesn't override + // this function. We forward to measureTime(). + measureTime(histogramType, time); + } // Resets all ticker and histogram stats - virtual Status Reset() { - return Status::NotSupported("Not implemented"); - } + virtual Status Reset() { return Status::NotSupported("Not implemented"); } // String representation of the statistic object. virtual std::string ToString() const { @@ -465,17 +503,27 @@ class Statistics { return std::string("ToString(): not implemented"); } + virtual bool getTickerMap(std::map*) const { + // Do nothing by default + return false; + }; + // Override this function to disable particular histogram collection virtual bool HistEnabledForType(uint32_t type) const { return type < HISTOGRAM_ENUM_MAX; } + void set_stats_level(StatsLevel sl) { + stats_level_.store(sl, std::memory_order_relaxed); + } + StatsLevel get_stats_level() const { + return stats_level_.load(std::memory_order_relaxed); + } - StatsLevel stats_level_ = kExceptDetailedTimers; + private: + std::atomic stats_level_{kExceptDetailedTimers}; }; // Create a concrete DBStatistics object std::shared_ptr CreateDBStatistics(); } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_STATISTICS_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/stats_history.h b/thirdparty/rocksdb/include/rocksdb/stats_history.h new file mode 100644 index 0000000000..40ea51d1ff --- /dev/null +++ b/thirdparty/rocksdb/include/rocksdb/stats_history.h @@ -0,0 +1,49 @@ +// Copyright (c) 2011-present, Facebook, 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). +// 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. + +#pragma once + +#include +#include + +// #include "db/db_impl.h" +#include "rocksdb/statistics.h" +#include "rocksdb/status.h" + +namespace rocksdb { + +class DBImpl; + +class StatsHistoryIterator { + public: + StatsHistoryIterator() {} + virtual ~StatsHistoryIterator() {} + + virtual bool Valid() const = 0; + + // Moves to the next stats history record. After this call, Valid() is + // true iff the iterator was not positioned at the last entry in the source. + // REQUIRES: Valid() + virtual void Next() = 0; + + // Return the time stamp (in microseconds) when stats history is recorded. + // REQUIRES: Valid() + virtual uint64_t GetStatsTime() const = 0; + + // Return the current stats history as an std::map which specifies the + // mapping from stats name to stats value . The underlying storage + // for the returned map is valid only until the next modification of + // the iterator. + // REQUIRES: Valid() + virtual const std::map& GetStatsMap() const = 0; + + // If an error has occurred, return it. Else return an ok status. + virtual Status status() const = 0; +}; + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/status.h b/thirdparty/rocksdb/include/rocksdb/status.h index 709f383709..12e8070d1e 100644 --- a/thirdparty/rocksdb/include/rocksdb/status.h +++ b/thirdparty/rocksdb/include/rocksdb/status.h @@ -14,8 +14,7 @@ // non-const method, all threads accessing the same Status must use // external synchronization. -#ifndef STORAGE_ROCKSDB_INCLUDE_STATUS_H_ -#define STORAGE_ROCKSDB_INCLUDE_STATUS_H_ +#pragma once #include #include "rocksdb/slice.h" @@ -25,7 +24,7 @@ namespace rocksdb { class Status { public: // Create a success status. - Status() : code_(kOk), subcode_(kNone), state_(nullptr) {} + Status() : code_(kOk), subcode_(kNone), sev_(kNoError), state_(nullptr) {} ~Status() { delete[] state_; } // Copy the specified status. @@ -44,7 +43,7 @@ class Status { bool operator==(const Status& rhs) const; bool operator!=(const Status& rhs) const; - enum Code { + enum Code : unsigned char { kOk = 0, kNotFound = 1, kCorruption = 2, @@ -58,12 +57,13 @@ class Status { kAborted = 10, kBusy = 11, kExpired = 12, - kTryAgain = 13 + kTryAgain = 13, + kCompactionTooLarge = 14 }; Code code() const { return code_; } - enum SubCode { + enum SubCode : unsigned char { kNone = 0, kMutexTimeout = 1, kLockTimeout = 2, @@ -72,11 +72,25 @@ class Status { kDeadlock = 5, kStaleFile = 6, kMemoryLimit = 7, + kSpaceLimit = 8, + kPathNotFound = 9, kMaxSubCode }; SubCode subcode() const { return subcode_; } + enum Severity : unsigned char { + kNoError = 0, + kSoftError = 1, + kHardError = 2, + kFatalError = 3, + kUnrecoverableError = 4, + kMaxSeverity + }; + + Status(const Status& s, Severity sev); + Severity severity() const { return sev_; } + // Returns a C style string indicating the message of the Status const char* getState() const { return state_; } @@ -162,6 +176,14 @@ class Status { return Status(kTryAgain, msg, msg2); } + static Status CompactionTooLarge(SubCode msg = kNone) { + return Status(kCompactionTooLarge, msg); + } + static Status CompactionTooLarge(const Slice& msg, + const Slice& msg2 = Slice()) { + return Status(kCompactionTooLarge, msg, msg2); + } + static Status NoSpace() { return Status(kIOError, kNoSpace); } static Status NoSpace(const Slice& msg, const Slice& msg2 = Slice()) { return Status(kIOError, kNoSpace, msg, msg2); @@ -172,6 +194,16 @@ class Status { return Status(kAborted, kMemoryLimit, msg, msg2); } + static Status SpaceLimit() { return Status(kIOError, kSpaceLimit); } + static Status SpaceLimit(const Slice& msg, const Slice& msg2 = Slice()) { + return Status(kIOError, kSpaceLimit, msg, msg2); + } + + static Status PathNotFound() { return Status(kIOError, kPathNotFound); } + static Status PathNotFound(const Slice& msg, const Slice& msg2 = Slice()) { + return Status(kIOError, kPathNotFound, msg, msg2); + } + // Returns true iff the status indicates success. bool ok() const { return code() == kOk; } @@ -221,6 +253,9 @@ class Status { // re-attempted. bool IsTryAgain() const { return code() == kTryAgain; } + // Returns true iff the status indicates the proposed compaction is too large + bool IsCompactionTooLarge() const { return code() == kCompactionTooLarge; } + // Returns true iff the status indicates a NoSpace error // This is caused by an I/O error returning the specific "out of space" // error condition. Stricto sensu, an NoSpace error is an I/O error @@ -237,6 +272,14 @@ class Status { return (code() == kAborted) && (subcode() == kMemoryLimit); } + // Returns true iff the status indicates a PathNotFound error + // This is caused by an I/O error returning the specific "no such file or + // directory" error condition. A PathNotFound error is an I/O error with + // a specific subcode, enabling users to take appropriate action if necessary + bool IsPathNotFound() const { + return (code() == kIOError) && (subcode() == kPathNotFound); + } + // Return a string representation of this status suitable for printing. // Returns the string "OK" for success. std::string ToString() const; @@ -249,12 +292,11 @@ class Status { // state_[4..] == message Code code_; SubCode subcode_; + Severity sev_; const char* state_; - static const char* msgs[static_cast(kMaxSubCode)]; - explicit Status(Code _code, SubCode _subcode = kNone) - : code_(_code), subcode_(_subcode), state_(nullptr) {} + : code_(_code), subcode_(_subcode), sev_(kNoError), state_(nullptr) {} Status(Code _code, SubCode _subcode, const Slice& msg, const Slice& msg2); Status(Code _code, const Slice& msg, const Slice& msg2) @@ -263,7 +305,12 @@ class Status { static const char* CopyState(const char* s); }; -inline Status::Status(const Status& s) : code_(s.code_), subcode_(s.subcode_) { +inline Status::Status(const Status& s) + : code_(s.code_), subcode_(s.subcode_), sev_(s.sev_) { + state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_); +} +inline Status::Status(const Status& s, Severity sev) + : code_(s.code_), subcode_(s.subcode_), sev_(sev) { state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_); } inline Status& Status::operator=(const Status& s) { @@ -272,6 +319,7 @@ inline Status& Status::operator=(const Status& s) { if (this != &s) { code_ = s.code_; subcode_ = s.subcode_; + sev_ = s.sev_; delete[] state_; state_ = (s.state_ == nullptr) ? nullptr : CopyState(s.state_); } @@ -296,6 +344,8 @@ inline Status& Status::operator=(Status&& s) s.code_ = kOk; subcode_ = std::move(s.subcode_); s.subcode_ = kNone; + sev_ = std::move(s.sev_); + s.sev_ = kNoError; delete[] state_; state_ = nullptr; std::swap(state_, s.state_); @@ -312,5 +362,3 @@ inline bool Status::operator!=(const Status& rhs) const { } } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_STATUS_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/table.h b/thirdparty/rocksdb/include/rocksdb/table.h index 1b4c0ced90..6c584375cc 100644 --- a/thirdparty/rocksdb/include/rocksdb/table.h +++ b/thirdparty/rocksdb/include/rocksdb/table.h @@ -16,6 +16,7 @@ // https://github.com/facebook/rocksdb/wiki/A-Tutorial-of-RocksDB-SST-formats#wiki-examples #pragma once + #include #include #include @@ -40,12 +41,11 @@ class WritableFileWriter; struct EnvOptions; struct Options; -using std::unique_ptr; - enum ChecksumType : char { kNoChecksum = 0x0, kCRC32c = 0x1, kxxHash = 0x2, + kxxHash64 = 0x3, }; // For advanced user only @@ -60,6 +60,10 @@ struct BlockBasedTableOptions { // TODO(kailiu) Temporarily disable this feature by making the default value // to be false. // + // TODO(ajkr) we need to update names of variables controlling meta-block + // caching as they should now apply to range tombstone and compression + // dictionary meta-blocks, in addition to index and filter meta-blocks. + // // Indicating if we'd put index/filter blocks to the block cache. // If not specified, each "table reader" object will pre-load index/filter // block during table initialization. @@ -77,6 +81,13 @@ struct BlockBasedTableOptions { // evicted from cache when the table reader is freed. bool pin_l0_filter_and_index_blocks_in_cache = false; + // If cache_index_and_filter_blocks is true and the below is true, then + // the top-level index of partitioned filter and index blocks are stored in + // the cache, but a reference is held in the "table reader" object so the + // blocks are pinned and only evicted from cache when the table reader is + // freed. This is not limited to l0 in LSM tree. + bool pin_top_level_index_and_filter = true; + // The index type that will be used for this table. enum IndexType : char { // A space efficient index block that is optimized for @@ -87,15 +98,24 @@ struct BlockBasedTableOptions { // `Options.prefix_extractor` is provided. kHashSearch, - // TODO(myabandeh): this feature is in experimental phase and shall not be - // used in production; either remove the feature or remove this comment if - // it is ready to be used in production. // A two-level index implementation. Both levels are binary search indexes. kTwoLevelIndexSearch, }; IndexType index_type = kBinarySearch; + // The index type that will be used for the data block. + enum DataBlockIndexType : char { + kDataBlockBinarySearch = 0, // traditional block type + kDataBlockBinaryAndHash = 1, // additional hash index + }; + + DataBlockIndexType data_block_index_type = kDataBlockBinarySearch; + + // #entries/#buckets. It is valid only when data_block_hash_index_type is + // kDataBlockBinaryAndHash. + double data_block_hash_table_util_ratio = 0.75; + // This option is now deprecated. No matter what value it is set to, // it will behave as if hash_index_allow_collision=true. bool hash_index_allow_collision = true; @@ -120,6 +140,8 @@ struct BlockBasedTableOptions { // If non-NULL use the specified cache for compressed blocks. // If NULL, rocksdb will not use a compressed block cache. + // Note: though it looks similar to `block_cache`, RocksDB doesn't put the + // same type of object there. std::shared_ptr block_cache_compressed = nullptr; // Approximate size of user data packed per block. Note that the @@ -158,10 +180,8 @@ struct BlockBasedTableOptions { // Note: currently this option requires kTwoLevelIndexSearch to be set as // well. // TODO(myabandeh): remove the note above once the limitation is lifted - // TODO(myabandeh): this feature is in experimental phase and shall not be - // used in production; either remove the feature or remove this comment if - // it is ready to be used in production. - // Use partitioned full filters for each SST file + // Use partitioned full filters for each SST file. This option is + // incompatible with block-based filters. bool partition_filters = false; // Use delta encoding to compress keys in blocks. @@ -207,7 +227,7 @@ struct BlockBasedTableOptions { // Default: 0 (disabled) uint32_t read_amp_bytes_per_bit = 0; - // We currently have three versions: + // We currently have five versions: // 0 -- This version is currently written out by all RocksDB's versions by // default. Can be read by really old RocksDB's. Doesn't support changing // checksum (default is CRC32). @@ -219,14 +239,31 @@ struct BlockBasedTableOptions { // encode compressed blocks with LZ4, BZip2 and Zlib compression. If you // don't plan to run RocksDB before version 3.10, you should probably use // this. - // This option only affects newly written tables. When reading exising tables, - // the information about version is read from the footer. + // 3 -- Can be read by RocksDB's versions since 5.15. Changes the way we + // encode the keys in index blocks. If you don't plan to run RocksDB before + // version 5.15, you should probably use this. + // This option only affects newly written tables. When reading existing + // tables, the information about version is read from the footer. + // 4 -- Can be read by RocksDB's versions since 5.16. Changes the way we + // encode the values in index blocks. If you don't plan to run RocksDB before + // version 5.16 and you are using index_block_restart_interval > 1, you should + // probably use this as it would reduce the index size. + // This option only affects newly written tables. When reading existing + // tables, the information about version is read from the footer. uint32_t format_version = 2; + + // Store index blocks on disk in compressed format. Changing this option to + // false will avoid the overhead of decompression if index blocks are evicted + // and read back + bool enable_index_compression = true; + + // Align data blocks on lesser of page size and block size + bool block_align = false; }; // Table Properties that are specific to block-based table properties. struct BlockBasedTablePropertyNames { - // value of this propertis is a fixed int32 number. + // value of this properties is a fixed int32 number. static const std::string kIndexType; // value is "1" for true and "0" for false. static const std::string kWholeKeyFiltering; @@ -319,13 +356,13 @@ struct PlainTableOptions { }; // -- Plain Table with prefix-only seek -// For this factory, you need to set Options.prefix_extrator properly to make it -// work. Look-up will starts with prefix hash lookup for key prefix. Inside the -// hash bucket found, a binary search is executed for hash conflicts. Finally, -// a linear search is used. +// For this factory, you need to set Options.prefix_extractor properly to make +// it work. Look-up will starts with prefix hash lookup for key prefix. Inside +// the hash bucket found, a binary search is executed for hash conflicts. +// Finally, a linear search is used. -extern TableFactory* NewPlainTableFactory(const PlainTableOptions& options = - PlainTableOptions()); +extern TableFactory* NewPlainTableFactory( + const PlainTableOptions& options = PlainTableOptions()); struct CuckooTablePropertyNames { // The key that is used to fill empty buckets. @@ -382,7 +419,7 @@ struct CuckooTableOptions { bool identity_as_first_hash = false; // If this option is set to true, module is used during hash calculation. // This often yields better space efficiency at the cost of performance. - // If this optino is set to false, # of entries in table is constrained to be + // If this option is set to false, # of entries in table is constrained to be // power of two, and bit and is used to calculate hash, which is faster in // general. bool use_module_hash = true; @@ -417,10 +454,10 @@ class TableFactory { // NewTableReader() is called in three places: // (1) TableCache::FindTable() calls the function when table cache miss // and cache the table object returned. - // (2) SstFileReader (for SST Dump) opens the table and dump the table + // (2) SstFileDumper (for SST Dump) opens the table and dump the table // contents using the iterator of the table. - // (3) DBImpl::AddFile() calls this function to read the contents of - // the sst file it's attempting to add + // (3) DBImpl::IngestExternalFile() calls this function to read the contents + // of the sst file it's attempting to add // // table_reader_options is a TableReaderOptions which contain all the // needed parameters and configuration to open the table. @@ -429,8 +466,8 @@ class TableFactory { // table_reader is the output table reader. virtual Status NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table_reader, + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table_reader, bool prefetch_index_and_filter_in_cache = true) const = 0; // Return a table builder to write to a file for this table type. @@ -459,16 +496,15 @@ class TableFactory { // // If the function cannot find a way to sanitize the input DB Options, // a non-ok Status will be returned. - virtual Status SanitizeOptions( - const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const = 0; + virtual Status SanitizeOptions(const DBOptions& db_opts, + const ColumnFamilyOptions& cf_opts) const = 0; // Return a string that contains printable format of table configurations. // RocksDB prints configurations at DB Open(). virtual std::string GetPrintableTableOptions() const = 0; - virtual Status GetOptionString(std::string* opt_string, - const std::string& delimiter) const { + virtual Status GetOptionString(std::string* /*opt_string*/, + const std::string& /*delimiter*/) const { return Status::NotSupported( "The table factory doesn't implement GetOptionString()."); } @@ -501,7 +537,8 @@ class TableFactory { // @block_based_table_factory: block based table factory to use. If NULL, use // a default one. // @plain_table_factory: plain table factory to use. If NULL, use a default one. -// @cuckoo_table_factory: cuckoo table factory to use. If NULL, use a default one. +// @cuckoo_table_factory: cuckoo table factory to use. If NULL, use a default +// one. extern TableFactory* NewAdaptiveTableFactory( std::shared_ptr table_factory_to_write = nullptr, std::shared_ptr block_based_table_factory = nullptr, diff --git a/thirdparty/rocksdb/include/rocksdb/table_properties.h b/thirdparty/rocksdb/include/rocksdb/table_properties.h index 2605fadd25..70e8d2cba7 100644 --- a/thirdparty/rocksdb/include/rocksdb/table_properties.h +++ b/thirdparty/rocksdb/include/rocksdb/table_properties.h @@ -15,7 +15,7 @@ namespace rocksdb { // Other than basic table properties, each table may also have the user // collected properties. // The value of the user-collected properties are encoded as raw bytes -- -// users have to interprete these values by themselves. +// users have to interpret these values by themselves. // Note: To do prefix seek/scan in `UserCollectedProperties`, you can do // something similar to: // @@ -33,11 +33,16 @@ struct TablePropertiesNames { static const std::string kIndexSize; static const std::string kIndexPartitions; static const std::string kTopLevelIndexSize; + static const std::string kIndexKeyIsUserKey; + static const std::string kIndexValueIsDeltaEncoded; static const std::string kFilterSize; static const std::string kRawKeySize; static const std::string kRawValueSize; static const std::string kNumDataBlocks; static const std::string kNumEntries; + static const std::string kDeletedKeys; + static const std::string kMergeOperands; + static const std::string kNumRangeDeletions; static const std::string kFormatVersion; static const std::string kFixedKeyLen; static const std::string kFilterPolicy; @@ -48,6 +53,7 @@ struct TablePropertiesNames { static const std::string kPrefixExtractorName; static const std::string kPropertyCollectors; static const std::string kCompression; + static const std::string kCompressionOptions; static const std::string kCreationTime; static const std::string kOldestKeyTime; }; @@ -56,18 +62,10 @@ extern const std::string kPropertiesBlock; extern const std::string kCompressionDictBlock; extern const std::string kRangeDelBlock; -enum EntryType { - kEntryPut, - kEntryDelete, - kEntrySingleDelete, - kEntryMerge, - kEntryOther, -}; - // `TablePropertiesCollector` provides the mechanism for users to collect // their own properties that they are interested in. This class is essentially // a collection of callback functions that will be invoked during table -// building. It is construced with TablePropertiesCollectorFactory. The methods +// building. It is constructed with TablePropertiesCollectorFactory. The methods // don't need to be thread-safe, as we will create exactly one // TablePropertiesCollector object per table and then call it sequentially class TablePropertiesCollector { @@ -95,6 +93,14 @@ class TablePropertiesCollector { return Add(key, value); } + // Called after each new block is cut + virtual void BlockAdd(uint64_t /* blockRawBytes */, + uint64_t /* blockCompressedBytesFast */, + uint64_t /* blockCompressedBytesSlow */) { + // Nothing to do here. Callback registers can override. + return; + } + // Finish() will be called when a table has already been built and is ready // for writing the properties block. // @params properties User will add their collected statistics to @@ -142,6 +148,11 @@ struct TableProperties { uint64_t index_partitions = 0; // Size of the top-level index if kTwoLevelIndexSearch is used uint64_t top_level_index_size = 0; + // Whether the index key is user key. Otherwise it includes 8 byte of sequence + // number added by internal key format. + uint64_t index_key_is_user_key = 0; + // Whether delta encoding is used to encode the index values. + uint64_t index_value_is_delta_encoded = 0; // the size of filter block. uint64_t filter_size = 0; // total raw key size @@ -152,6 +163,12 @@ struct TableProperties { uint64_t num_data_blocks = 0; // the number of entries in this table uint64_t num_entries = 0; + // the number of deletions in the table + uint64_t num_deletions = 0; + // the number of merge operands in the table + uint64_t num_merge_operands = 0; + // the number of range deletions in this table + uint64_t num_range_deletions = 0; // format version, reserved for backward compatibility uint64_t format_version = 0; // If 0, key is variable length. Otherwise number of bytes for each key. @@ -193,6 +210,9 @@ struct TableProperties { // The compression algo used to compress the SST files. std::string compression_name; + // Compression options used to compress the SST files. + std::string compression_options; + // user collected properties UserCollectedProperties user_collected_properties; UserCollectedProperties readable_properties; @@ -214,6 +234,10 @@ struct TableProperties { // Below is a list of non-basic properties that are collected by database // itself. Especially some properties regarding to the internal keys (which // is unknown to `table`). +// +// DEPRECATED: these properties now belong as TableProperties members. Please +// use TableProperties::num_deletions and TableProperties::num_merge_operands, +// respectively. extern uint64_t GetDeletedKeys(const UserCollectedProperties& props); extern uint64_t GetMergeOperands(const UserCollectedProperties& props, bool* property_present); diff --git a/thirdparty/rocksdb/include/rocksdb/thread_status.h b/thirdparty/rocksdb/include/rocksdb/thread_status.h index 55c32ed6d2..b81c1c284e 100644 --- a/thirdparty/rocksdb/include/rocksdb/thread_status.h +++ b/thirdparty/rocksdb/include/rocksdb/thread_status.h @@ -20,8 +20,7 @@ #include #include -#if !defined(ROCKSDB_LITE) && \ - !defined(NROCKSDB_THREAD_STATUS) && \ +#if !defined(ROCKSDB_LITE) && !defined(NROCKSDB_THREAD_STATUS) && \ defined(ROCKSDB_SUPPORT_THREAD_LOCAL) #define ROCKSDB_USING_THREAD_STATUS #endif @@ -43,8 +42,9 @@ struct ThreadStatus { // The type of a thread. enum ThreadType : int { HIGH_PRIORITY = 0, // RocksDB BG thread in high-pri thread pool - LOW_PRIORITY, // RocksDB BG thread in low-pri thread pool - USER, // User thread (Non-RocksDB BG thread) + LOW_PRIORITY, // RocksDB BG thread in low-pri thread pool + USER, // User thread (Non-RocksDB BG thread) + BOTTOM_PRIORITY, // RocksDB BG thread in bottom-pri thread pool NUM_THREAD_TYPES }; @@ -104,22 +104,20 @@ struct ThreadStatus { NUM_STATE_TYPES }; - ThreadStatus(const uint64_t _id, - const ThreadType _thread_type, - const std::string& _db_name, - const std::string& _cf_name, + ThreadStatus(const uint64_t _id, const ThreadType _thread_type, + const std::string& _db_name, const std::string& _cf_name, const OperationType _operation_type, const uint64_t _op_elapsed_micros, const OperationStage _operation_stage, - const uint64_t _op_props[], - const StateType _state_type) : - thread_id(_id), thread_type(_thread_type), - db_name(_db_name), - cf_name(_cf_name), - operation_type(_operation_type), - op_elapsed_micros(_op_elapsed_micros), - operation_stage(_operation_stage), - state_type(_state_type) { + const uint64_t _op_props[], const StateType _state_type) + : thread_id(_id), + thread_type(_thread_type), + db_name(_db_name), + cf_name(_cf_name), + operation_type(_operation_type), + op_elapsed_micros(_op_elapsed_micros), + operation_stage(_operation_stage), + state_type(_state_type) { for (int i = 0; i < kNumOperationProperties; ++i) { op_properties[i] = _op_props[i]; } @@ -163,7 +161,7 @@ struct ThreadStatus { // The followings are a set of utility functions for interpreting // the information of ThreadStatus - static const std::string& GetThreadTypeName(ThreadType thread_type); + static std::string GetThreadTypeName(ThreadType thread_type); // Obtain the name of an operation given its type. static const std::string& GetOperationName(OperationType op_type); @@ -171,23 +169,20 @@ struct ThreadStatus { static const std::string MicrosToString(uint64_t op_elapsed_time); // Obtain a human-readable string describing the specified operation stage. - static const std::string& GetOperationStageName( - OperationStage stage); + static const std::string& GetOperationStageName(OperationStage stage); // Obtain the name of the "i"th operation property of the // specified operation. - static const std::string& GetOperationPropertyName( - OperationType op_type, int i); + static const std::string& GetOperationPropertyName(OperationType op_type, + int i); // Translate the "i"th property of the specified operation given // a property value. - static std::map - InterpretOperationProperties( - OperationType op_type, const uint64_t* op_properties); + static std::map InterpretOperationProperties( + OperationType op_type, const uint64_t* op_properties); // Obtain the name of a state given its type. static const std::string& GetStateName(StateType state_type); }; - } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/threadpool.h b/thirdparty/rocksdb/include/rocksdb/threadpool.h index e871ee18c7..2e2f2b44fe 100644 --- a/thirdparty/rocksdb/include/rocksdb/threadpool.h +++ b/thirdparty/rocksdb/include/rocksdb/threadpool.h @@ -47,7 +47,6 @@ class ThreadPool { virtual void SubmitJob(const std::function&) = 0; // This moves the function in for efficiency virtual void SubmitJob(std::function&&) = 0; - }; // NewThreadPool() is a function that could be used to create a ThreadPool diff --git a/thirdparty/rocksdb/include/rocksdb/trace_reader_writer.h b/thirdparty/rocksdb/include/rocksdb/trace_reader_writer.h new file mode 100644 index 0000000000..28919a0fad --- /dev/null +++ b/thirdparty/rocksdb/include/rocksdb/trace_reader_writer.h @@ -0,0 +1,48 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#include "rocksdb/env.h" + +namespace rocksdb { + +// Allow custom implementations of TraceWriter and TraceReader. +// By default, RocksDB provides a way to capture the traces to a file using the +// factory NewFileTraceWriter(). But users could also choose to export traces to +// any other system by providing custom implementations of TraceWriter and +// TraceReader. + +// TraceWriter allows exporting RocksDB traces to any system, one operation at +// a time. +class TraceWriter { + public: + TraceWriter() {} + virtual ~TraceWriter() {} + + virtual Status Write(const Slice& data) = 0; + virtual Status Close() = 0; + virtual uint64_t GetFileSize() = 0; +}; + +// TraceReader allows reading RocksDB traces from any system, one operation at +// a time. A RocksDB Replayer could depend on this to replay opertions. +class TraceReader { + public: + TraceReader() {} + virtual ~TraceReader() {} + + virtual Status Read(std::string* data) = 0; + virtual Status Close() = 0; +}; + +// Factory methods to read/write traces from/to a file. +Status NewFileTraceWriter(Env* env, const EnvOptions& env_options, + const std::string& trace_filename, + std::unique_ptr* trace_writer); +Status NewFileTraceReader(Env* env, const EnvOptions& env_options, + const std::string& trace_filename, + std::unique_ptr* trace_reader); +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/transaction_log.h b/thirdparty/rocksdb/include/rocksdb/transaction_log.h index 7fc46ae264..80f373b247 100644 --- a/thirdparty/rocksdb/include/rocksdb/transaction_log.h +++ b/thirdparty/rocksdb/include/rocksdb/transaction_log.h @@ -3,21 +3,20 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef STORAGE_ROCKSDB_INCLUDE_TRANSACTION_LOG_ITERATOR_H_ -#define STORAGE_ROCKSDB_INCLUDE_TRANSACTION_LOG_ITERATOR_H_ +#pragma once +#include +#include #include "rocksdb/status.h" #include "rocksdb/types.h" #include "rocksdb/write_batch.h" -#include -#include namespace rocksdb { class LogFile; typedef std::vector> VectorLogPtr; -enum WalFileType { +enum WalFileType { /* Indicates that WAL file is in archive directory. WAL files are moved from * the main db directory to archive directory once they are not live and stay * there until cleaned up. Files are cleaned depending on archive size @@ -28,7 +27,7 @@ enum WalFileType { /* Indicates that WAL file is live and resides in the main db directory */ kAliveLogFile = 1 -} ; +}; class LogFile { public: @@ -40,7 +39,6 @@ class LogFile { // For an archived-log-file = /archive/000003.log virtual std::string PathName() const = 0; - // Primary identifier for log file. // This is directly proportional to creation time of the log file virtual uint64_t LogNumber() const = 0; @@ -61,7 +59,7 @@ struct BatchResult { // Add empty __ctor and __dtor for the rule of five // However, preserve the original semantics and prohibit copying - // as the unique_ptr member does not copy. + // as the std::unique_ptr member does not copy. BatchResult() {} ~BatchResult() {} @@ -120,6 +118,4 @@ class TransactionLogIterator { : verify_checksums_(verify_checksums) {} }; }; -} // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_TRANSACTION_LOG_ITERATOR_H_ +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/types.h b/thirdparty/rocksdb/include/rocksdb/types.h index 106ac2f76b..2cd4039bd7 100644 --- a/thirdparty/rocksdb/include/rocksdb/types.h +++ b/thirdparty/rocksdb/include/rocksdb/types.h @@ -3,10 +3,10 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef STORAGE_ROCKSDB_INCLUDE_TYPES_H_ -#define STORAGE_ROCKSDB_INCLUDE_TYPES_H_ +#pragma once #include +#include "rocksdb/slice.h" namespace rocksdb { @@ -15,6 +15,40 @@ namespace rocksdb { // Represents a sequence number in a WAL file. typedef uint64_t SequenceNumber; -} // namespace rocksdb +const SequenceNumber kMinUnCommittedSeq = 1; // 0 is always committed + +// User-oriented representation of internal key types. +enum EntryType { + kEntryPut, + kEntryDelete, + kEntrySingleDelete, + kEntryMerge, + kEntryRangeDeletion, + kEntryBlobIndex, + kEntryOther, +}; + +// tuple. +struct FullKey { + Slice user_key; + SequenceNumber sequence; + EntryType type; + + FullKey() : sequence(0) {} // Intentionally left uninitialized (for speed) + FullKey(const Slice& u, const SequenceNumber& seq, EntryType t) + : user_key(u), sequence(seq), type(t) {} + std::string DebugString(bool hex = false) const; -#endif // STORAGE_ROCKSDB_INCLUDE_TYPES_H_ + void clear() { + user_key.clear(); + sequence = 0; + type = EntryType::kEntryPut; + } +}; + +// Parse slice representing internal key to FullKey +// Parsed FullKey is valid for as long as the memory pointed to by +// internal_key is alive. +bool ParseFullKey(const Slice& internal_key, FullKey* result); + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/universal_compaction.h b/thirdparty/rocksdb/include/rocksdb/universal_compaction.h index ed2220873c..e219694b3f 100644 --- a/thirdparty/rocksdb/include/rocksdb/universal_compaction.h +++ b/thirdparty/rocksdb/include/rocksdb/universal_compaction.h @@ -3,8 +3,7 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#ifndef STORAGE_ROCKSDB_UNIVERSAL_COMPACTION_OPTIONS_H -#define STORAGE_ROCKSDB_UNIVERSAL_COMPACTION_OPTIONS_H +#pragma once #include #include @@ -17,13 +16,12 @@ namespace rocksdb { // into a single compaction run // enum CompactionStopStyle { - kCompactionStopStyleSimilarSize, // pick files of similar size - kCompactionStopStyleTotalSize // total size of picked files > next file + kCompactionStopStyleSimilarSize, // pick files of similar size + kCompactionStopStyleTotalSize // total size of picked files > next file }; class CompactionOptionsUniversal { public: - // Percentage flexibility while comparing file size. If the candidate file(s) // size is 1% smaller than the next file's size, then include next file into // this candidate set. // Default: 1 @@ -86,5 +84,3 @@ class CompactionOptionsUniversal { }; } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_UNIVERSAL_COMPACTION_OPTIONS_H diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/backupable_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/backupable_db.h index fc2b6ba43f..7817c56496 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/backupable_db.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/backupable_db.h @@ -15,10 +15,10 @@ #endif #include -#include +#include #include +#include #include -#include #include "rocksdb/utilities/stackable_db.h" @@ -109,8 +109,14 @@ struct BackupableDBOptions { uint64_t callback_trigger_interval_size; // When Open() is called, it will open at most this many of the latest - // non-corrupted backups. If 0, it will open all available backups. - // Default: 0 + // non-corrupted backups. + // + // Note setting this to a non-default value prevents old files from being + // deleted in the shared directory, as we can't do proper ref-counting. If + // using this option, make sure to occasionally disable it (by resetting to + // INT_MAX) and run GarbageCollect to clean accumulated stale files. + // + // Default: INT_MAX int max_valid_backups_to_open; void Dump(Logger* logger) const; @@ -122,7 +128,7 @@ struct BackupableDBOptions { bool _backup_log_files = true, uint64_t _backup_rate_limit = 0, uint64_t _restore_rate_limit = 0, int _max_background_operations = 1, uint64_t _callback_trigger_interval_size = 4 * 1024 * 1024, - int _max_valid_backups_to_open = 0) + int _max_valid_backups_to_open = INT_MAX) : backup_dir(_backup_dir), backup_env(_backup_env), share_table_files(_share_table_files), @@ -251,12 +257,13 @@ class BackupEngine { // BackupableDBOptions have to be the same as the ones used in previous // BackupEngines for the same backup directory. - static Status Open(Env* db_env, - const BackupableDBOptions& options, + static Status Open(Env* db_env, const BackupableDBOptions& options, BackupEngine** backup_engine_ptr); // same as CreateNewBackup, but stores extra application metadata // Flush will always trigger if 2PC is enabled. + // If write-ahead logs are disabled, set flush_before_backup=true to + // avoid losing unflushed key/value pairs from the memtable. virtual Status CreateNewBackupWithMetadata( DB* db, const std::string& app_metadata, bool flush_before_backup = false, std::function progress_callback = []() {}) = 0; @@ -264,6 +271,8 @@ class BackupEngine { // Captures the state of the database in the latest backup // NOT a thread safe call // Flush will always trigger if 2PC is enabled. + // If write-ahead logs are disabled, set flush_before_backup=true to + // avoid losing unflushed key/value pairs from the memtable. virtual Status CreateNewBackup(DB* db, bool flush_before_backup = false, std::function progress_callback = []() {}) { diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/date_tiered_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/date_tiered_db.h deleted file mode 100644 index f259b05a8a..0000000000 --- a/thirdparty/rocksdb/include/rocksdb/utilities/date_tiered_db.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -#pragma once -#ifndef ROCKSDB_LITE - -#include -#include -#include - -#include "rocksdb/db.h" - -namespace rocksdb { - -// Date tiered database is a wrapper of DB that implements -// a simplified DateTieredCompactionStrategy by using multiple column famillies -// as time windows. -// -// DateTieredDB provides an interface similar to DB, but it assumes that user -// provides keys with last 8 bytes encoded as timestamp in seconds. DateTieredDB -// is assigned with a TTL to declare when data should be deleted. -// -// DateTieredDB hides column families layer from standard RocksDB instance. It -// uses multiple column families to manage time series data, each containing a -// specific range of time. Column families are named by its maximum possible -// timestamp. A column family is created automatically when data newer than -// latest timestamp of all existing column families. The time range of a column -// family is configurable by `column_family_interval`. By doing this, we -// guarantee that compaction will only happen in a column family. -// -// DateTieredDB is assigned with a TTL. When all data in a column family are -// expired (CF_Timestamp <= CUR_Timestamp - TTL), we directly drop the whole -// column family. -// -// TODO(jhli): This is only a simplified version of DTCS. In a complete DTCS, -// time windows can be merged over time, so that older time windows will have -// larger time range. Also, compaction are executed only for adjacent SST files -// to guarantee there is no time overlap between SST files. - -class DateTieredDB { - public: - // Open a DateTieredDB whose name is `dbname`. - // Similar to DB::Open(), created database object is stored in dbptr. - // - // Two parameters can be configured: `ttl` to specify the length of time that - // keys should exist in the database, and `column_family_interval` to specify - // the time range of a column family interval. - // - // Open a read only database if read only is set as true. - // TODO(jhli): Should use an option object that includes ttl and - // column_family_interval. - static Status Open(const Options& options, const std::string& dbname, - DateTieredDB** dbptr, int64_t ttl, - int64_t column_family_interval, bool read_only = false); - - explicit DateTieredDB() {} - - virtual ~DateTieredDB() {} - - // Wrapper for Put method. Similar to DB::Put(), but column family to be - // inserted is decided by the timestamp in keys, i.e. the last 8 bytes of user - // key. If key is already obsolete, it will not be inserted. - // - // When client put a key value pair in DateTieredDB, it assumes last 8 bytes - // of keys are encoded as timestamp. Timestamp is a 64-bit signed integer - // encoded as the number of seconds since 1970-01-01 00:00:00 (UTC) (Same as - // Env::GetCurrentTime()). Timestamp should be encoded in big endian. - virtual Status Put(const WriteOptions& options, const Slice& key, - const Slice& val) = 0; - - // Wrapper for Get method. Similar to DB::Get() but column family is decided - // by timestamp in keys. If key is already obsolete, it will not be found. - virtual Status Get(const ReadOptions& options, const Slice& key, - std::string* value) = 0; - - // Wrapper for Delete method. Similar to DB::Delete() but column family is - // decided by timestamp in keys. If key is already obsolete, return NotFound - // status. - virtual Status Delete(const WriteOptions& options, const Slice& key) = 0; - - // Wrapper for KeyMayExist method. Similar to DB::KeyMayExist() but column - // family is decided by timestamp in keys. Return false when key is already - // obsolete. - virtual bool KeyMayExist(const ReadOptions& options, const Slice& key, - std::string* value, bool* value_found = nullptr) = 0; - - // Wrapper for Merge method. Similar to DB::Merge() but column family is - // decided by timestamp in keys. - virtual Status Merge(const WriteOptions& options, const Slice& key, - const Slice& value) = 0; - - // Create an iterator that hides low level details. This iterator internally - // merge results from all active time series column families. Note that - // column families are not deleted until all data are obsolete, so this - // iterator can possibly access obsolete key value pairs. - virtual Iterator* NewIterator(const ReadOptions& opts) = 0; - - // Explicitly drop column families in which all keys are obsolete. This - // process is also inplicitly done in Put() operation. - virtual Status DropObsoleteColumnFamilies() = 0; - - static const uint64_t kTSLength = sizeof(int64_t); // size of timestamp -}; - -} // namespace rocksdb -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/db_ttl.h b/thirdparty/rocksdb/include/rocksdb/utilities/db_ttl.h index 7c9c0cc55a..227796cbe2 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/db_ttl.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/db_ttl.h @@ -9,8 +9,8 @@ #include #include -#include "rocksdb/utilities/stackable_db.h" #include "rocksdb/db.h" +#include "rocksdb/utilities/stackable_db.h" namespace rocksdb { @@ -60,6 +60,10 @@ class DBWithTTL : public StackableDB { DBWithTTL** dbptr, std::vector ttls, bool read_only = false); + virtual void SetTtl(int32_t ttl) = 0; + + virtual void SetTtl(ColumnFamilyHandle* h, int32_t ttl) = 0; + protected: explicit DBWithTTL(DB* db) : StackableDB(db) {} }; diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/debug.h b/thirdparty/rocksdb/include/rocksdb/utilities/debug.h index bc5b9bf03d..50645423d0 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/debug.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/debug.h @@ -31,9 +31,13 @@ struct KeyVersion { }; // Returns listing of all versions of keys in the provided user key range. -// The range is inclusive-inclusive, i.e., [`begin_key`, `end_key`]. +// The range is inclusive-inclusive, i.e., [`begin_key`, `end_key`], or +// `max_num_ikeys` has been reached. Since all those keys returned will be +// copied to memory, if the range covers too many keys, the memory usage +// may be huge. `max_num_ikeys` can be used to cap the memory usage. // The result is inserted into the provided vector, `key_versions`. Status GetAllKeyVersions(DB* db, Slice begin_key, Slice end_key, + size_t max_num_ikeys, std::vector* key_versions); } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/document_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/document_db.h deleted file mode 100644 index 3668a50b9d..0000000000 --- a/thirdparty/rocksdb/include/rocksdb/utilities/document_db.h +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -#pragma once -#ifndef ROCKSDB_LITE - -#include -#include - -#include "rocksdb/utilities/stackable_db.h" -#include "rocksdb/utilities/json_document.h" -#include "rocksdb/db.h" - -namespace rocksdb { - -// IMPORTANT: DocumentDB is a work in progress. It is unstable and we might -// change the API without warning. Talk to RocksDB team before using this in -// production ;) - -// DocumentDB is a layer on top of RocksDB that provides a very simple JSON API. -// When creating a DB, you specify a list of indexes you want to keep on your -// data. You can insert a JSON document to the DB, which is automatically -// indexed. Every document added to the DB needs to have "_id" field which is -// automatically indexed and is an unique primary key. All other indexes are -// non-unique. - -// NOTE: field names in the JSON are NOT allowed to start with '$' or -// contain '.'. We don't currently enforce that rule, but will start behaving -// badly. - -// Cursor is what you get as a result of executing query. To get all -// results from a query, call Next() on a Cursor while Valid() returns true -class Cursor { - public: - Cursor() = default; - virtual ~Cursor() {} - - virtual bool Valid() const = 0; - virtual void Next() = 0; - // Lifecycle of the returned JSONDocument is until the next Next() call - virtual const JSONDocument& document() const = 0; - virtual Status status() const = 0; - - private: - // No copying allowed - Cursor(const Cursor&); - void operator=(const Cursor&); -}; - -struct DocumentDBOptions { - int background_threads = 4; - uint64_t memtable_size = 128 * 1024 * 1024; // 128 MB - uint64_t cache_size = 1 * 1024 * 1024 * 1024; // 1 GB -}; - -// TODO(icanadi) Add `JSONDocument* info` parameter to all calls that can be -// used by the caller to get more information about the call execution (number -// of dropped records, number of updated records, etc.) -class DocumentDB : public StackableDB { - public: - struct IndexDescriptor { - // Currently, you can only define an index on a single field. To specify an - // index on a field X, set index description to JSON "{X: 1}" - // Currently the value needs to be 1, which means ascending. - // In the future, we plan to also support indexes on multiple keys, where - // you could mix ascending sorting (1) with descending sorting indexes (-1) - JSONDocument* description; - std::string name; - }; - - // Open DocumentDB with specified indexes. The list of indexes has to be - // complete, i.e. include all indexes present in the DB, except the primary - // key index. - // Otherwise, Open() will return an error - static Status Open(const DocumentDBOptions& options, const std::string& name, - const std::vector& indexes, - DocumentDB** db, bool read_only = false); - - explicit DocumentDB(DB* db) : StackableDB(db) {} - - // Create a new index. It will stop all writes for the duration of the call. - // All current documents in the DB are scanned and corresponding index entries - // are created - virtual Status CreateIndex(const WriteOptions& write_options, - const IndexDescriptor& index) = 0; - - // Drop an index. Client is responsible to make sure that index is not being - // used by currently executing queries - virtual Status DropIndex(const std::string& name) = 0; - - // Insert a document to the DB. The document needs to have a primary key "_id" - // which can either be a string or an integer. Otherwise the write will fail - // with InvalidArgument. - virtual Status Insert(const WriteOptions& options, - const JSONDocument& document) = 0; - - // Deletes all documents matching a filter atomically - virtual Status Remove(const ReadOptions& read_options, - const WriteOptions& write_options, - const JSONDocument& query) = 0; - - // Does this sequence of operations: - // 1. Find all documents matching a filter - // 2. For all documents, atomically: - // 2.1. apply the update operators - // 2.2. update the secondary indexes - // - // Currently only $set update operator is supported. - // Syntax is: {$set: {key1: value1, key2: value2, etc...}} - // This operator will change a document's key1 field to value1, key2 to - // value2, etc. New values will be set even if a document didn't have an entry - // for the specified key. - // - // You can not change a primary key of a document. - // - // Update example: Update({id: {$gt: 5}, $index: id}, {$set: {enabled: true}}) - virtual Status Update(const ReadOptions& read_options, - const WriteOptions& write_options, - const JSONDocument& filter, - const JSONDocument& updates) = 0; - - // query has to be an array in which every element is an operator. Currently - // only $filter operator is supported. Syntax of $filter operator is: - // {$filter: {key1: condition1, key2: condition2, etc.}} where conditions can - // be either: - // 1) a single value in which case the condition is equality condition, or - // 2) a defined operators, like {$gt: 4}, which will match all documents that - // have key greater than 4. - // - // Supported operators are: - // 1) $gt -- greater than - // 2) $gte -- greater than or equal - // 3) $lt -- less than - // 4) $lte -- less than or equal - // If you want the filter to use an index, you need to specify it like this: - // {$filter: {...(conditions)..., $index: index_name}} - // - // Example query: - // * [{$filter: {name: John, age: {$gte: 18}, $index: age}}] - // will return all Johns whose age is greater or equal to 18 and it will use - // index "age" to satisfy the query. - virtual Cursor* Query(const ReadOptions& read_options, - const JSONDocument& query) = 0; -}; - -} // namespace rocksdb -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/env_librados.h b/thirdparty/rocksdb/include/rocksdb/utilities/env_librados.h index 272365f0c6..7be75878d9 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/env_librados.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/env_librados.h @@ -2,8 +2,8 @@ // 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_UTILITIES_ENV_LIBRADOS_H -#define ROCKSDB_UTILITIES_ENV_LIBRADOS_H + +#pragma once #include #include @@ -172,5 +172,4 @@ class EnvLibrados : public EnvWrapper { librados::IoCtx* _GetIoctx(const std::string& prefix); friend class LibradosWritableFile; }; -} -#endif +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/env_mirror.h b/thirdparty/rocksdb/include/rocksdb/utilities/env_mirror.h index ffd175ae5e..6d513fc791 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/env_mirror.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/env_mirror.h @@ -19,8 +19,8 @@ #ifndef ROCKSDB_LITE -#include #include +#include #include #include "rocksdb/env.h" @@ -31,37 +31,32 @@ class RandomAccessFileMirror; class WritableFileMirror; class EnvMirror : public EnvWrapper { - Env* a_, *b_; + Env *a_, *b_; bool free_a_, free_b_; public: - EnvMirror(Env* a, Env* b, bool free_a=false, bool free_b=false) - : EnvWrapper(a), - a_(a), - b_(b), - free_a_(free_a), - free_b_(free_b) {} + EnvMirror(Env* a, Env* b, bool free_a = false, bool free_b = false) + : EnvWrapper(a), a_(a), b_(b), free_a_(free_a), free_b_(free_b) {} ~EnvMirror() { - if (free_a_) - delete a_; - if (free_b_) - delete b_; + if (free_a_) delete a_; + if (free_b_) delete b_; } - Status NewSequentialFile(const std::string& f, unique_ptr* r, + Status NewSequentialFile(const std::string& f, + std::unique_ptr* r, const EnvOptions& options) override; Status NewRandomAccessFile(const std::string& f, - unique_ptr* r, + std::unique_ptr* r, const EnvOptions& options) override; - Status NewWritableFile(const std::string& f, unique_ptr* r, + Status NewWritableFile(const std::string& f, std::unique_ptr* r, const EnvOptions& options) override; Status ReuseWritableFile(const std::string& fname, const std::string& old_fname, - unique_ptr* r, + std::unique_ptr* r, const EnvOptions& options) override; virtual Status NewDirectory(const std::string& name, - unique_ptr* result) override { - unique_ptr br; + std::unique_ptr* result) override { + std::unique_ptr br; Status as = a_->NewDirectory(name, result); Status bs = b_->NewDirectory(name, &br); assert(as == bs); @@ -73,6 +68,11 @@ class EnvMirror : public EnvWrapper { assert(as == bs); return as; } +#if defined(_MSC_VER) +#pragma warning(push) +// logical operation on address of string constant +#pragma warning(disable : 4130) +#endif Status GetChildren(const std::string& dir, std::vector* r) override { std::vector ar, br; @@ -87,6 +87,9 @@ class EnvMirror : public EnvWrapper { *r = ar; return as; } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif Status DeleteFile(const std::string& f) override { Status as = a_->DeleteFile(f); Status bs = b_->DeleteFile(f); @@ -148,12 +151,12 @@ class EnvMirror : public EnvWrapper { class FileLockMirror : public FileLock { public: - FileLock* a_, *b_; + FileLock *a_, *b_; FileLockMirror(FileLock* a, FileLock* b) : a_(a), b_(b) {} }; Status LockFile(const std::string& f, FileLock** l) override { - FileLock* al, *bl; + FileLock *al, *bl; Status as = a_->LockFile(f, &al); Status bs = b_->LockFile(f, &bl); assert(as == bs); diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/geo_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/geo_db.h deleted file mode 100644 index 408774c599..0000000000 --- a/thirdparty/rocksdb/include/rocksdb/utilities/geo_db.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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 -#pragma once -#include -#include - -#include "rocksdb/utilities/stackable_db.h" -#include "rocksdb/status.h" - -namespace rocksdb { - -// -// Configurable options needed for setting up a Geo database -// -struct GeoDBOptions { - // Backup info and error messages will be written to info_log - // if non-nullptr. - // Default: nullptr - Logger* info_log; - - explicit GeoDBOptions(Logger* _info_log = nullptr):info_log(_info_log) { } -}; - -// -// A position in the earth's geoid -// -class GeoPosition { - public: - double latitude; - double longitude; - - explicit GeoPosition(double la = 0, double lo = 0) : - latitude(la), longitude(lo) { - } -}; - -// -// Description of an object on the Geoid. It is located by a GPS location, -// and is identified by the id. The value associated with this object is -// an opaque string 'value'. Different objects identified by unique id's -// can have the same gps-location associated with them. -// -class GeoObject { - public: - GeoPosition position; - std::string id; - std::string value; - - GeoObject() {} - - GeoObject(const GeoPosition& pos, const std::string& i, - const std::string& val) : - position(pos), id(i), value(val) { - } -}; - -class GeoIterator { - public: - GeoIterator() = default; - virtual ~GeoIterator() {} - virtual void Next() = 0; - virtual bool Valid() const = 0; - virtual const GeoObject& geo_object() = 0; - virtual Status status() const = 0; -}; - -// -// Stack your DB with GeoDB to be able to get geo-spatial support -// -class GeoDB : public StackableDB { - public: - // GeoDBOptions have to be the same as the ones used in a previous - // incarnation of the DB - // - // GeoDB owns the pointer `DB* db` now. You should not delete it or - // use it after the invocation of GeoDB - // GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {} - GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {} - virtual ~GeoDB() {} - - // Insert a new object into the location database. The object is - // uniquely identified by the id. If an object with the same id already - // exists in the db, then the old one is overwritten by the new - // object being inserted here. - virtual Status Insert(const GeoObject& object) = 0; - - // Retrieve the value of the object located at the specified GPS - // location and is identified by the 'id'. - virtual Status GetByPosition(const GeoPosition& pos, - const Slice& id, std::string* value) = 0; - - // Retrieve the value of the object identified by the 'id'. This method - // could be potentially slower than GetByPosition - virtual Status GetById(const Slice& id, GeoObject* object) = 0; - - // Delete the specified object - virtual Status Remove(const Slice& id) = 0; - - // Returns an iterator for the items within a circular radius from the - // specified gps location. If 'number_of_values' is specified, - // then the iterator is capped to that number of objects. - // The radius is specified in 'meters'. - virtual GeoIterator* SearchRadial(const GeoPosition& pos, - double radius, - int number_of_values = INT_MAX) = 0; -}; - -} // namespace rocksdb -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/json_document.h b/thirdparty/rocksdb/include/rocksdb/utilities/json_document.h deleted file mode 100644 index 5d841f9515..0000000000 --- a/thirdparty/rocksdb/include/rocksdb/utilities/json_document.h +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). -#pragma once -#ifndef ROCKSDB_LITE - -#include -#include -#include -#include -#include -#include -#include - -#include "rocksdb/slice.h" - -// We use JSONDocument for DocumentDB API -// Implementation inspired by folly::dynamic, rapidjson and fbson - -namespace fbson { - class FbsonValue; - class ObjectVal; - template - class FbsonWriterT; - class FbsonOutStream; - typedef FbsonWriterT FbsonWriter; -} // namespace fbson - -namespace rocksdb { - -// NOTE: none of this is thread-safe -class JSONDocument { - public: - // return nullptr on parse failure - static JSONDocument* ParseJSON(const char* json); - - enum Type { - kNull, - kArray, - kBool, - kDouble, - kInt64, - kObject, - kString, - }; - - /* implicit */ JSONDocument(); // null - /* implicit */ JSONDocument(bool b); - /* implicit */ JSONDocument(double d); - /* implicit */ JSONDocument(int8_t i); - /* implicit */ JSONDocument(int16_t i); - /* implicit */ JSONDocument(int32_t i); - /* implicit */ JSONDocument(int64_t i); - /* implicit */ JSONDocument(const std::string& s); - /* implicit */ JSONDocument(const char* s); - // constructs JSONDocument of specific type with default value - explicit JSONDocument(Type _type); - - JSONDocument(const JSONDocument& json_document); - - JSONDocument(JSONDocument&& json_document); - - Type type() const; - - // REQUIRES: IsObject() - bool Contains(const std::string& key) const; - // REQUIRES: IsObject() - // Returns non-owner object - JSONDocument operator[](const std::string& key) const; - - // REQUIRES: IsArray() == true || IsObject() == true - size_t Count() const; - - // REQUIRES: IsArray() - // Returns non-owner object - JSONDocument operator[](size_t i) const; - - JSONDocument& operator=(JSONDocument jsonDocument); - - bool IsNull() const; - bool IsArray() const; - bool IsBool() const; - bool IsDouble() const; - bool IsInt64() const; - bool IsObject() const; - bool IsString() const; - - // REQUIRES: IsBool() == true - bool GetBool() const; - // REQUIRES: IsDouble() == true - double GetDouble() const; - // REQUIRES: IsInt64() == true - int64_t GetInt64() const; - // REQUIRES: IsString() == true - std::string GetString() const; - - bool operator==(const JSONDocument& rhs) const; - - bool operator!=(const JSONDocument& rhs) const; - - JSONDocument Copy() const; - - bool IsOwner() const; - - std::string DebugString() const; - - private: - class ItemsIteratorGenerator; - - public: - // REQUIRES: IsObject() - ItemsIteratorGenerator Items() const; - - // appends serialized object to dst - void Serialize(std::string* dst) const; - // returns nullptr if Slice doesn't represent valid serialized JSONDocument - static JSONDocument* Deserialize(const Slice& src); - - private: - friend class JSONDocumentBuilder; - - JSONDocument(fbson::FbsonValue* val, bool makeCopy); - - void InitFromValue(const fbson::FbsonValue* val); - - // iteration on objects - class const_item_iterator { - private: - class Impl; - public: - typedef std::pair value_type; - explicit const_item_iterator(Impl* impl); - const_item_iterator(const_item_iterator&&); - const_item_iterator& operator++(); - bool operator!=(const const_item_iterator& other); - value_type operator*(); - ~const_item_iterator(); - private: - friend class ItemsIteratorGenerator; - std::unique_ptr it_; - }; - - class ItemsIteratorGenerator { - public: - explicit ItemsIteratorGenerator(const fbson::ObjectVal& object); - const_item_iterator begin() const; - - const_item_iterator end() const; - - private: - const fbson::ObjectVal& object_; - }; - - std::unique_ptr data_; - mutable fbson::FbsonValue* value_; - - // Our serialization format's first byte specifies the encoding version. That - // way, we can easily change our format while providing backwards - // compatibility. This constant specifies the current version of the - // serialization format - static const char kSerializationFormatVersion; -}; - -class JSONDocumentBuilder { - public: - JSONDocumentBuilder(); - - explicit JSONDocumentBuilder(fbson::FbsonOutStream* out); - - void Reset(); - - bool WriteStartArray(); - - bool WriteEndArray(); - - bool WriteStartObject(); - - bool WriteEndObject(); - - bool WriteKeyValue(const std::string& key, const JSONDocument& value); - - bool WriteJSONDocument(const JSONDocument& value); - - JSONDocument GetJSONDocument(); - - ~JSONDocumentBuilder(); - - private: - std::unique_ptr writer_; -}; - -} // namespace rocksdb - -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd.h b/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd.h index b9eb1035fb..57ab88a34e 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd.h @@ -96,6 +96,12 @@ class LDBCommand { ldb_options_ = ldb_options; } + const std::map& TEST_GetOptionMap() { + return option_map_; + } + + const std::vector& TEST_GetFlags() { return flags_; } + virtual bool NoDBOpen() { return false; } virtual ~LDBCommand() { CloseDB(); } @@ -210,6 +216,15 @@ class LDBCommand { bool ParseStringOption(const std::map& options, const std::string& option, std::string* value); + /** + * Returns the value of the specified option as a boolean. + * default_val is used if the option is not found in options. + * Throws an exception if the value of the option is not + * "true" or "false" (case insensitive). + */ + bool ParseBooleanOption(const std::map& options, + const std::string& option, bool default_val); + Options options_; std::vector column_families_; LDBOptions ldb_options_; @@ -229,15 +244,6 @@ class LDBCommand { bool IsValueHex(const std::map& options, const std::vector& flags); - /** - * Returns the value of the specified option as a boolean. - * default_val is used if the option is not found in options. - * Throws an exception if the value of the option is not - * "true" or "false" (case insensitive). - */ - bool ParseBooleanOption(const std::map& options, - const std::string& option, bool default_val); - /** * Converts val to a boolean. * val must be either true or false (case insensitive). diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h b/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h index 5ddc6feb69..85c219542d 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/ldb_cmd_execute_result.h @@ -12,26 +12,28 @@ namespace rocksdb { class LDBCommandExecuteResult { -public: + public: enum State { - EXEC_NOT_STARTED = 0, EXEC_SUCCEED = 1, EXEC_FAILED = 2, + EXEC_NOT_STARTED = 0, + EXEC_SUCCEED = 1, + EXEC_FAILED = 2, }; LDBCommandExecuteResult() : state_(EXEC_NOT_STARTED), message_("") {} - LDBCommandExecuteResult(State state, std::string& msg) : - state_(state), message_(msg) {} + LDBCommandExecuteResult(State state, std::string& msg) + : state_(state), message_(msg) {} std::string ToString() { std::string ret; switch (state_) { - case EXEC_SUCCEED: - break; - case EXEC_FAILED: - ret.append("Failed: "); - break; - case EXEC_NOT_STARTED: - ret.append("Not started: "); + case EXEC_SUCCEED: + break; + case EXEC_FAILED: + ret.append("Failed: "); + break; + case EXEC_NOT_STARTED: + ret.append("Not started: "); } if (!message_.empty()) { ret.append(message_); @@ -44,17 +46,11 @@ class LDBCommandExecuteResult { message_ = ""; } - bool IsSucceed() { - return state_ == EXEC_SUCCEED; - } + bool IsSucceed() { return state_ == EXEC_SUCCEED; } - bool IsNotStarted() { - return state_ == EXEC_NOT_STARTED; - } + bool IsNotStarted() { return state_ == EXEC_NOT_STARTED; } - bool IsFailed() { - return state_ == EXEC_FAILED; - } + bool IsFailed() { return state_ == EXEC_FAILED; } static LDBCommandExecuteResult Succeed(std::string msg) { return LDBCommandExecuteResult(EXEC_SUCCEED, msg); @@ -64,7 +60,7 @@ class LDBCommandExecuteResult { return LDBCommandExecuteResult(EXEC_FAILED, msg); } -private: + private: State state_; std::string message_; @@ -72,4 +68,4 @@ class LDBCommandExecuteResult { bool operator!=(const LDBCommandExecuteResult&); }; -} +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/lua/rocks_lua_compaction_filter.h b/thirdparty/rocksdb/include/rocksdb/utilities/lua/rocks_lua_compaction_filter.h deleted file mode 100644 index a7af592d8c..0000000000 --- a/thirdparty/rocksdb/include/rocksdb/utilities/lua/rocks_lua_compaction_filter.h +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2016, Facebook, 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). - -#pragma once - -#if defined(LUA) && !defined(ROCKSDB_LITE) -// lua headers -extern "C" { -#include -#include -#include -} - -#include -#include -#include - -#include "rocksdb/compaction_filter.h" -#include "rocksdb/env.h" -#include "rocksdb/slice.h" -#include "rocksdb/utilities/lua/rocks_lua_custom_library.h" -#include "rocksdb/utilities/lua/rocks_lua_util.h" - -namespace rocksdb { -namespace lua { - -struct RocksLuaCompactionFilterOptions { - // The lua script in string that implements all necessary CompactionFilter - // virtual functions. The specified lua_script must implement the following - // functions, which are Name and Filter, as described below. - // - // 0. The Name function simply returns a string representing the name of - // the lua script. If there's any erorr in the Name function, an - // empty string will be used. - // --- Example - // function Name() - // return "DefaultLuaCompactionFilter" - // end - // - // - // 1. The script must contains a function called Filter, which implements - // CompactionFilter::Filter() , takes three input arguments, and returns - // three values as the following API: - // - // function Filter(level, key, existing_value) - // ... - // return is_filtered, is_changed, new_value - // end - // - // Note that if ignore_value is set to true, then Filter should implement - // the following API: - // - // function Filter(level, key) - // ... - // return is_filtered - // end - // - // If there're any error in the Filter() function, then it will keep - // the input key / value pair. - // - // -- Input - // The function must take three arguments (integer, string, string), - // which map to "level", "key", and "existing_value" passed from - // RocksDB. - // - // -- Output - // The function must return three values (boolean, boolean, string). - // - is_filtered: if the first return value is true, then it indicates - // the input key / value pair should be filtered. - // - is_changed: if the second return value is true, then it indicates - // the existing_value needs to be changed, and the resulting value - // is stored in the third return value. - // - new_value: if the second return value is true, then this third - // return value stores the new value of the input key / value pair. - // - // -- Examples - // -- a filter that keeps all key-value pairs - // function Filter(level, key, existing_value) - // return false, false, "" - // end - // - // -- a filter that keeps all keys and change their values to "Rocks" - // function Filter(level, key, existing_value) - // return false, true, "Rocks" - // end - - std::string lua_script; - - // If set to true, then existing_value will not be passed to the Filter - // function, and the Filter function only needs to return a single boolean - // flag indicating whether to filter out this key or not. - // - // function Filter(level, key) - // ... - // return is_filtered - // end - bool ignore_value = false; - - // A boolean flag to determine whether to ignore snapshots. - bool ignore_snapshots = false; - - // When specified a non-null pointer, the first "error_limit_per_filter" - // errors of each CompactionFilter that is lua related will be included - // in this log. - std::shared_ptr error_log; - - // The number of errors per CompactionFilter will be printed - // to error_log. - int error_limit_per_filter = 1; - - // A string to luaL_reg array map that allows the Lua CompactionFilter - // to use custom C library. The string will be used as the library - // name in Lua. - std::vector> libraries; - - /////////////////////////////////////////////////////////////////////////// - // NOT YET SUPPORTED - // The name of the Lua function in "lua_script" that implements - // CompactionFilter::FilterMergeOperand(). The function must take - // three input arguments (integer, string, string), which map to "level", - // "key", and "operand" passed from the RocksDB. In addition, the - // function must return a single boolean value, indicating whether - // to filter the input key / operand. - // - // DEFAULT: the default implementation always returns false. - // @see CompactionFilter::FilterMergeOperand -}; - -class RocksLuaCompactionFilterFactory : public CompactionFilterFactory { - public: - explicit RocksLuaCompactionFilterFactory( - const RocksLuaCompactionFilterOptions opt); - - virtual ~RocksLuaCompactionFilterFactory() {} - - std::unique_ptr CreateCompactionFilter( - const CompactionFilter::Context& context) override; - - // Change the Lua script so that the next compaction after this - // function call will use the new Lua script. - void SetScript(const std::string& new_script); - - // Obtain the current Lua script - std::string GetScript(); - - const char* Name() const override; - - private: - RocksLuaCompactionFilterOptions opt_; - std::string name_; - // A lock to protect "opt_" to make it dynamically changeable. - std::mutex opt_mutex_; -}; - -// A wrapper class that invokes Lua script to perform CompactionFilter -// functions. -class RocksLuaCompactionFilter : public rocksdb::CompactionFilter { - public: - explicit RocksLuaCompactionFilter(const RocksLuaCompactionFilterOptions& opt) - : options_(opt), - lua_state_wrapper_(opt.lua_script, opt.libraries), - error_count_(0), - name_("") {} - - virtual bool Filter(int level, const Slice& key, const Slice& existing_value, - std::string* new_value, - bool* value_changed) const override; - // Not yet supported - virtual bool FilterMergeOperand(int level, const Slice& key, - const Slice& operand) const override { - return false; - } - virtual bool IgnoreSnapshots() const override; - virtual const char* Name() const override; - - protected: - void LogLuaError(const char* format, ...) const; - - RocksLuaCompactionFilterOptions options_; - LuaStateWrapper lua_state_wrapper_; - mutable int error_count_; - mutable std::string name_; -}; - -} // namespace lua -} // namespace rocksdb -#endif // defined(LUA) && !defined(ROCKSDB_LITE) diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h b/thirdparty/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h index 3ca8b32f3e..471ae12a6d 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/lua/rocks_lua_custom_library.h @@ -36,7 +36,7 @@ class RocksLuaCustomLibrary { // and pushed on the top of the lua_State. This custom setup function // allows developers to put additional table or constant values inside // the same table / namespace. - virtual void CustomSetup(lua_State* L) const {} + virtual void CustomSetup(lua_State* /*L*/) const {} }; } // namespace lua } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/object_registry.h b/thirdparty/rocksdb/include/rocksdb/utilities/object_registry.h index b046ba7c1f..86a51b92ea 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/object_registry.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/object_registry.h @@ -27,8 +27,8 @@ namespace rocksdb { template T* NewCustomObject(const std::string& target, std::unique_ptr* res_guard); -// Returns a new T when called with a string. Populates the unique_ptr argument -// if granting ownership to caller. +// Returns a new T when called with a string. Populates the std::unique_ptr +// argument if granting ownership to caller. template using FactoryFunc = std::function*)>; diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h index 02917ff583..28b24083e2 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/optimistic_transaction_db.h @@ -11,6 +11,7 @@ #include "rocksdb/comparator.h" #include "rocksdb/db.h" +#include "rocksdb/utilities/stackable_db.h" namespace rocksdb { @@ -30,7 +31,7 @@ struct OptimisticTransactionOptions { const Comparator* cmp = BytewiseComparator(); }; -class OptimisticTransactionDB { +class OptimisticTransactionDB : public StackableDB { public: // Open an OptimisticTransactionDB similar to DB::Open(). static Status Open(const Options& options, const std::string& dbname, @@ -57,18 +58,12 @@ class OptimisticTransactionDB { OptimisticTransactionOptions(), Transaction* old_txn = nullptr) = 0; - // Return the underlying Database that was opened - virtual DB* GetBaseDB() = 0; + OptimisticTransactionDB(const OptimisticTransactionDB&) = delete; + void operator=(const OptimisticTransactionDB&) = delete; protected: // To Create an OptimisticTransactionDB, call Open() - explicit OptimisticTransactionDB(DB* db) {} - OptimisticTransactionDB() {} - - private: - // No copying allowed - OptimisticTransactionDB(const OptimisticTransactionDB&); - void operator=(const OptimisticTransactionDB&); + explicit OptimisticTransactionDB(DB* db) : StackableDB(db) {} }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/options_util.h b/thirdparty/rocksdb/include/rocksdb/utilities/options_util.h index d02c574104..d97b394eac 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/options_util.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/options_util.h @@ -33,6 +33,12 @@ namespace rocksdb { // * merge_operator // * compaction_filter // +// User can also choose to load customized comparator and/or merge_operator +// through object registry: +// * comparator needs to be registered through Registrar +// * merge operator needs to be registered through +// Registrar>. +// // For table_factory, this function further supports deserializing // BlockBasedTableFactory and its BlockBasedTableOptions except the // pointer options of BlockBasedTableOptions (flush_block_policy_factory, @@ -58,7 +64,8 @@ namespace rocksdb { Status LoadLatestOptions(const std::string& dbpath, Env* env, DBOptions* db_options, std::vector* cf_descs, - bool ignore_unknown_options = false); + bool ignore_unknown_options = false, + std::shared_ptr* cache = {}); // Similar to LoadLatestOptions, this function constructs the DBOptions // and ColumnFamilyDescriptors based on the specified RocksDB Options file. @@ -67,7 +74,8 @@ Status LoadLatestOptions(const std::string& dbpath, Env* env, Status LoadOptionsFromFile(const std::string& options_file_name, Env* env, DBOptions* db_options, std::vector* cf_descs, - bool ignore_unknown_options = false); + bool ignore_unknown_options = false, + std::shared_ptr* cache = {}); // Returns the latest options file name under the specified db path. Status GetLatestOptionsFileName(const std::string& dbpath, Env* env, diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/sim_cache.h b/thirdparty/rocksdb/include/rocksdb/utilities/sim_cache.h index f29fd5e8f6..bc2a7bc13d 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/sim_cache.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/sim_cache.h @@ -73,7 +73,8 @@ class SimCache : public Cache { // stop logging to the file automatically after reaching a specific size in // bytes, a values of 0 disable this feature virtual Status StartActivityLogging(const std::string& activity_log_file, - Env* env, uint64_t max_logging_size = 0) = 0; + Env* env, + uint64_t max_logging_size = 0) = 0; // Stop cache activity logging if any virtual void StopActivityLogging() = 0; diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/spatial_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/spatial_db.h deleted file mode 100644 index 477b77cf62..0000000000 --- a/thirdparty/rocksdb/include/rocksdb/utilities/spatial_db.h +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -#pragma once -#ifndef ROCKSDB_LITE - -#include -#include - -#include "rocksdb/db.h" -#include "rocksdb/slice.h" -#include "rocksdb/utilities/stackable_db.h" - -namespace rocksdb { -namespace spatial { - -// NOTE: SpatialDB is experimental and we might change its API without warning. -// Please talk to us before developing against SpatialDB API. -// -// SpatialDB is a support for spatial indexes built on top of RocksDB. -// When creating a new SpatialDB, clients specifies a list of spatial indexes to -// build on their data. Each spatial index is defined by the area and -// granularity. If you're storing map data, different spatial index -// granularities can be used for different zoom levels. -// -// Each element inserted into SpatialDB has: -// * a bounding box, which determines how will the element be indexed -// * string blob, which will usually be WKB representation of the polygon -// (http://en.wikipedia.org/wiki/Well-known_text) -// * feature set, which is a map of key-value pairs, where value can be null, -// int, double, bool, string -// * a list of indexes to insert the element in -// -// Each query is executed on a single spatial index. Query guarantees that it -// will return all elements intersecting the specified bounding box, but it -// might also return some extra non-intersecting elements. - -// Variant is a class that can be many things: null, bool, int, double or string -// It is used to store different value types in FeatureSet (see below) -struct Variant { - // Don't change the values here, they are persisted on disk - enum Type { - kNull = 0x0, - kBool = 0x1, - kInt = 0x2, - kDouble = 0x3, - kString = 0x4, - }; - - Variant() : type_(kNull) {} - /* implicit */ Variant(bool b) : type_(kBool) { data_.b = b; } - /* implicit */ Variant(uint64_t i) : type_(kInt) { data_.i = i; } - /* implicit */ Variant(double d) : type_(kDouble) { data_.d = d; } - /* implicit */ Variant(const std::string& s) : type_(kString) { - new (&data_.s) std::string(s); - } - - Variant(const Variant& v) : type_(v.type_) { Init(v, data_); } - - Variant& operator=(const Variant& v); - - Variant(Variant&& rhs) : type_(kNull) { *this = std::move(rhs); } - - Variant& operator=(Variant&& v); - - ~Variant() { Destroy(type_, data_); } - - Type type() const { return type_; } - bool get_bool() const { return data_.b; } - uint64_t get_int() const { return data_.i; } - double get_double() const { return data_.d; } - const std::string& get_string() const { return *GetStringPtr(data_); } - - bool operator==(const Variant& other) const; - bool operator!=(const Variant& other) const { return !(*this == other); } - - private: - Type type_; - - union Data { - bool b; - uint64_t i; - double d; - // Current version of MS compiler not C++11 compliant so can not put - // std::string - // however, even then we still need the rest of the maintenance. - char s[sizeof(std::string)]; - } data_; - - // Avoid type_punned aliasing problem - static std::string* GetStringPtr(Data& d) { - void* p = d.s; - return reinterpret_cast(p); - } - - static const std::string* GetStringPtr(const Data& d) { - const void* p = d.s; - return reinterpret_cast(p); - } - - static void Init(const Variant&, Data&); - - static void Destroy(Type t, Data& d) { - if (t == kString) { - using std::string; - GetStringPtr(d)->~string(); - } - } -}; - -// FeatureSet is a map of key-value pairs. One feature set is associated with -// each element in SpatialDB. It can be used to add rich data about the element. -class FeatureSet { - private: - typedef std::unordered_map map; - - public: - class iterator { - public: - /* implicit */ iterator(const map::const_iterator itr) : itr_(itr) {} - iterator& operator++() { - ++itr_; - return *this; - } - bool operator!=(const iterator& other) { return itr_ != other.itr_; } - bool operator==(const iterator& other) { return itr_ == other.itr_; } - map::value_type operator*() { return *itr_; } - - private: - map::const_iterator itr_; - }; - FeatureSet() = default; - - FeatureSet* Set(const std::string& key, const Variant& value); - bool Contains(const std::string& key) const; - // REQUIRES: Contains(key) - const Variant& Get(const std::string& key) const; - iterator Find(const std::string& key) const; - - iterator begin() const { return map_.begin(); } - iterator end() const { return map_.end(); } - - void Clear(); - size_t Size() const { return map_.size(); } - - void Serialize(std::string* output) const; - // REQUIRED: empty FeatureSet - bool Deserialize(const Slice& input); - - std::string DebugString() const; - - private: - map map_; -}; - -// BoundingBox is a helper structure for defining rectangles representing -// bounding boxes of spatial elements. -template -struct BoundingBox { - T min_x, min_y, max_x, max_y; - BoundingBox() = default; - BoundingBox(T _min_x, T _min_y, T _max_x, T _max_y) - : min_x(_min_x), min_y(_min_y), max_x(_max_x), max_y(_max_y) {} - - bool Intersects(const BoundingBox& a) const { - return !(min_x > a.max_x || min_y > a.max_y || a.min_x > max_x || - a.min_y > max_y); - } -}; - -struct SpatialDBOptions { - uint64_t cache_size = 1 * 1024 * 1024 * 1024LL; // 1GB - int num_threads = 16; - bool bulk_load = true; -}; - -// Cursor is used to return data from the query to the client. To get all the -// data from the query, just call Next() while Valid() is true -class Cursor { - public: - Cursor() = default; - virtual ~Cursor() {} - - virtual bool Valid() const = 0; - // REQUIRES: Valid() - virtual void Next() = 0; - - // Lifetime of the underlying storage until the next call to Next() - // REQUIRES: Valid() - virtual const Slice blob() = 0; - // Lifetime of the underlying storage until the next call to Next() - // REQUIRES: Valid() - virtual const FeatureSet& feature_set() = 0; - - virtual Status status() const = 0; - - private: - // No copying allowed - Cursor(const Cursor&); - void operator=(const Cursor&); -}; - -// SpatialIndexOptions defines a spatial index that will be built on the data -struct SpatialIndexOptions { - // Spatial indexes are referenced by names - std::string name; - // An area that is indexed. If the element is not intersecting with spatial - // index's bbox, it will not be inserted into the index - BoundingBox bbox; - // tile_bits control the granularity of the spatial index. Each dimension of - // the bbox will be split into (1 << tile_bits) tiles, so there will be a - // total of (1 << tile_bits)^2 tiles. It is recommended to configure a size of - // each tile to be approximately the size of the query on that spatial index - uint32_t tile_bits; - SpatialIndexOptions() {} - SpatialIndexOptions(const std::string& _name, - const BoundingBox& _bbox, uint32_t _tile_bits) - : name(_name), bbox(_bbox), tile_bits(_tile_bits) {} -}; - -class SpatialDB : public StackableDB { - public: - // Creates the SpatialDB with specified list of indexes. - // REQUIRED: db doesn't exist - static Status Create(const SpatialDBOptions& options, const std::string& name, - const std::vector& spatial_indexes); - - // Open the existing SpatialDB. The resulting db object will be returned - // through db parameter. - // REQUIRED: db was created using SpatialDB::Create - static Status Open(const SpatialDBOptions& options, const std::string& name, - SpatialDB** db, bool read_only = false); - - explicit SpatialDB(DB* db) : StackableDB(db) {} - - // Insert the element into the DB. Element will be inserted into specified - // spatial_indexes, based on specified bbox. - // REQUIRES: spatial_indexes.size() > 0 - virtual Status Insert(const WriteOptions& write_options, - const BoundingBox& bbox, const Slice& blob, - const FeatureSet& feature_set, - const std::vector& spatial_indexes) = 0; - - // Calling Compact() after inserting a bunch of elements should speed up - // reading. This is especially useful if you use SpatialDBOptions::bulk_load - // Num threads determines how many threads we'll use for compactions. Setting - // this to bigger number will use more IO and CPU, but finish faster - virtual Status Compact(int num_threads = 1) = 0; - - // Query the specified spatial_index. Query will return all elements that - // intersect bbox, but it may also return some extra elements. - virtual Cursor* Query(const ReadOptions& read_options, - const BoundingBox& bbox, - const std::string& spatial_index) = 0; -}; - -} // namespace spatial -} // namespace rocksdb -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/stackable_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/stackable_db.h index 991de90aab..8fef9b3e85 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/stackable_db.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/stackable_db.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "rocksdb/db.h" @@ -12,22 +13,30 @@ #undef DeleteFile #endif - namespace rocksdb { // This class contains APIs to stack rocksdb wrappers.Eg. Stack TTL over base d class StackableDB : public DB { public: - // StackableDB is the owner of db now! + // StackableDB take sole ownership of the underlying db. explicit StackableDB(DB* db) : db_(db) {} + // StackableDB take shared ownership of the underlying db. + explicit StackableDB(std::shared_ptr db) + : db_(db.get()), shared_db_ptr_(db) {} + ~StackableDB() { - delete db_; + if (shared_db_ptr_ == nullptr) { + delete db_; + } else { + assert(shared_db_ptr_.get() == db_); + } + db_ = nullptr; } - virtual DB* GetBaseDB() { - return db_; - } + virtual Status Close() override { return db_->Close(); } + + virtual DB* GetBaseDB() { return db_; } virtual DB* GetRootDB() override { return db_->GetRootDB(); } @@ -95,6 +104,12 @@ class StackableDB : public DB { return db_->IngestExternalFile(column_family, external_files, options); } + using DB::IngestExternalFiles; + virtual Status IngestExternalFiles( + const std::vector& args) override { + return db_->IngestExternalFiles(args); + } + virtual Status VerifyChecksum() override { return db_->VerifyChecksum(); } using DB::KeyMayExist; @@ -126,10 +141,8 @@ class StackableDB : public DB { return db_->Merge(options, column_family, key, value); } - - virtual Status Write(const WriteOptions& opts, WriteBatch* updates) - override { - return db_->Write(opts, updates); + virtual Status Write(const WriteOptions& opts, WriteBatch* updates) override { + return db_->Write(opts, updates); } using DB::NewIterator; @@ -145,10 +158,7 @@ class StackableDB : public DB { return db_->NewIterators(options, column_families, iterators); } - - virtual const Snapshot* GetSnapshot() override { - return db_->GetSnapshot(); - } + virtual const Snapshot* GetSnapshot() override { return db_->GetSnapshot(); } virtual void ReleaseSnapshot(const Snapshot* snapshot) override { return db_->ReleaseSnapshot(snapshot); @@ -160,9 +170,9 @@ class StackableDB : public DB { const Slice& property, std::string* value) override { return db_->GetProperty(column_family, property, value); } - virtual bool GetMapProperty(ColumnFamilyHandle* column_family, - const Slice& property, - std::map* value) override { + virtual bool GetMapProperty( + ColumnFamilyHandle* column_family, const Slice& property, + std::map* value) override { return db_->GetMapProperty(column_family, property, value); } @@ -179,12 +189,10 @@ class StackableDB : public DB { } using DB::GetApproximateSizes; - virtual void GetApproximateSizes(ColumnFamilyHandle* column_family, - const Range* r, int n, uint64_t* sizes, - uint8_t include_flags - = INCLUDE_FILES) override { - return db_->GetApproximateSizes(column_family, r, n, sizes, - include_flags); + virtual void GetApproximateSizes( + ColumnFamilyHandle* column_family, const Range* r, int n, uint64_t* sizes, + uint8_t include_flags = INCLUDE_FILES) override { + return db_->GetApproximateSizes(column_family, r, n, sizes, include_flags); } using DB::GetApproximateMemTableStats; @@ -206,11 +214,13 @@ class StackableDB : public DB { virtual Status CompactFiles( const CompactionOptions& compact_options, ColumnFamilyHandle* column_family, - const std::vector& input_file_names, - const int output_level, const int output_path_id = -1) override { - return db_->CompactFiles( - compact_options, column_family, input_file_names, - output_level, output_path_id); + const std::vector& input_file_names, const int output_level, + const int output_path_id = -1, + std::vector* const output_file_names = nullptr, + CompactionJobInfo* compaction_job_info = nullptr) override { + return db_->CompactFiles(compact_options, column_family, input_file_names, + output_level, output_path_id, output_file_names, + compaction_job_info); } virtual Status PauseBackgroundWork() override { @@ -231,24 +241,20 @@ class StackableDB : public DB { } using DB::MaxMemCompactionLevel; - virtual int MaxMemCompactionLevel(ColumnFamilyHandle* column_family) - override { + virtual int MaxMemCompactionLevel( + ColumnFamilyHandle* column_family) override { return db_->MaxMemCompactionLevel(column_family); } using DB::Level0StopWriteTrigger; - virtual int Level0StopWriteTrigger(ColumnFamilyHandle* column_family) - override { + virtual int Level0StopWriteTrigger( + ColumnFamilyHandle* column_family) override { return db_->Level0StopWriteTrigger(column_family); } - virtual const std::string& GetName() const override { - return db_->GetName(); - } + virtual const std::string& GetName() const override { return db_->GetName(); } - virtual Env* GetEnv() const override { - return db_->GetEnv(); - } + virtual Env* GetEnv() const override { return db_->GetEnv(); } using DB::GetOptions; virtual Options GetOptions(ColumnFamilyHandle* column_family) const override { @@ -265,13 +271,20 @@ class StackableDB : public DB { ColumnFamilyHandle* column_family) override { return db_->Flush(fopts, column_family); } - - virtual Status SyncWAL() override { - return db_->SyncWAL(); + virtual Status Flush( + const FlushOptions& fopts, + const std::vector& column_families) override { + return db_->Flush(fopts, column_families); } + virtual Status SyncWAL() override { return db_->SyncWAL(); } + virtual Status FlushWAL(bool sync) override { return db_->FlushWAL(sync); } + virtual Status LockWAL() override { return db_->LockWAL(); } + + virtual Status UnlockWAL() override { return db_->UnlockWAL(); } + #ifndef ROCKSDB_LITE virtual Status DisableFileDeletions() override { @@ -287,9 +300,8 @@ class StackableDB : public DB { db_->GetLiveFilesMetaData(metadata); } - virtual void GetColumnFamilyMetaData( - ColumnFamilyHandle *column_family, - ColumnFamilyMetaData* cf_meta) override { + virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family, + ColumnFamilyMetaData* cf_meta) override { db_->GetColumnFamilyMetaData(column_family, cf_meta); } @@ -297,13 +309,18 @@ class StackableDB : public DB { virtual Status GetLiveFiles(std::vector& vec, uint64_t* mfs, bool flush_memtable = true) override { - return db_->GetLiveFiles(vec, mfs, flush_memtable); + return db_->GetLiveFiles(vec, mfs, flush_memtable); } virtual SequenceNumber GetLatestSequenceNumber() const override { return db_->GetLatestSequenceNumber(); } + virtual bool SetPreserveDeletesSequenceNumber( + SequenceNumber seqnum) override { + return db_->SetPreserveDeletesSequenceNumber(seqnum); + } + virtual Status GetSortedWalFiles(VectorLogPtr& files) override { return db_->GetSortedWalFiles(files); } @@ -347,7 +364,7 @@ class StackableDB : public DB { } virtual Status GetUpdatesSince( - SequenceNumber seq_number, unique_ptr* iter, + SequenceNumber seq_number, std::unique_ptr* iter, const TransactionLogIterator::ReadOptions& read_options) override { return db_->GetUpdatesSince(seq_number, iter, read_options); } @@ -369,6 +386,7 @@ class StackableDB : public DB { protected: DB* db_; + std::shared_ptr shared_db_ptr_; }; -} // namespace rocksdb +} // namespace rocksdb diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/table_properties_collectors.h b/thirdparty/rocksdb/include/rocksdb/utilities/table_properties_collectors.h index 0f8827037b..bb350bcf9c 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/table_properties_collectors.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/table_properties_collectors.h @@ -5,12 +5,58 @@ #pragma once #ifndef ROCKSDB_LITE +#include #include #include "rocksdb/table_properties.h" namespace rocksdb { +// A factory of a table property collector that marks a SST +// file as need-compaction when it observe at least "D" deletion +// entries in any "N" consecutive entires. +class CompactOnDeletionCollectorFactory + : public TablePropertiesCollectorFactory { + public: + virtual ~CompactOnDeletionCollectorFactory() {} + + virtual TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollectorFactory::Context context) override; + + // Change the value of sliding_window_size "N" + // Setting it to 0 disables the delete triggered compaction + void SetWindowSize(size_t sliding_window_size) { + sliding_window_size_.store(sliding_window_size); + } + + // Change the value of deletion_trigger "D" + void SetDeletionTrigger(size_t deletion_trigger) { + deletion_trigger_.store(deletion_trigger); + } + + virtual const char* Name() const override { + return "CompactOnDeletionCollector"; + } + + private: + friend std::shared_ptr + NewCompactOnDeletionCollectorFactory(size_t sliding_window_size, + size_t deletion_trigger); + // A factory of a table property collector that marks a SST + // file as need-compaction when it observe at least "D" deletion + // entries in any "N" consecutive entires. + // + // @param sliding_window_size "N" + // @param deletion_trigger "D" + CompactOnDeletionCollectorFactory(size_t sliding_window_size, + size_t deletion_trigger) + : sliding_window_size_(sliding_window_size), + deletion_trigger_(deletion_trigger) {} + + std::atomic sliding_window_size_; + std::atomic deletion_trigger_; +}; + // Creates a factory of a table property collector that marks a SST // file as need-compaction when it observe at least "D" deletion // entries in any "N" consecutive entires. @@ -20,10 +66,9 @@ namespace rocksdb { // than the specified size. // @param deletion_trigger "D". Note that even when "N" is changed, // the specified number for "D" will not be changed. -extern std::shared_ptr - NewCompactOnDeletionCollectorFactory( - size_t sliding_window_size, - size_t deletion_trigger); +extern std::shared_ptr +NewCompactOnDeletionCollectorFactory(size_t sliding_window_size, + size_t deletion_trigger); } // namespace rocksdb #endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/transaction.h b/thirdparty/rocksdb/include/rocksdb/utilities/transaction.h index a3519739c2..ce67248227 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/transaction.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/transaction.h @@ -118,7 +118,7 @@ class Transaction { // longer be valid and should be discarded after a call to ClearSnapshot(). virtual void ClearSnapshot() = 0; - // Prepare the current transation for 2PC + // Prepare the current transaction for 2PC virtual Status Prepare() = 0; // Write all batched keys to the db atomically. @@ -152,6 +152,12 @@ class Transaction { // If there is no previous call to SetSavePoint(), returns Status::NotFound() virtual Status RollbackToSavePoint() = 0; + // Pop the most recent save point. + // If there is no previous call to SetSavePoint(), Status::NotFound() + // will be returned. + // Otherwise returns Status::OK(). + virtual Status PopSavePoint() = 0; + // This function is similar to DB::Get() except it will also read pending // changes in this transaction. Currently, this function will return // Status::MergeInProgress if the most recent write to the queried key in @@ -169,8 +175,8 @@ class Transaction { ColumnFamilyHandle* column_family, const Slice& key, std::string* value) = 0; - // An overload of the the above method that receives a PinnableSlice - // For backward compatiblity a default implementation is provided + // An overload of the above method that receives a PinnableSlice + // For backward compatibility a default implementation is provided virtual Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family, const Slice& key, PinnableSlice* pinnable_val) { @@ -202,8 +208,10 @@ class Transaction { // Read this key and ensure that this transaction will only // be able to be committed if this key is not written outside this // transaction after it has first been read (or after the snapshot if a - // snapshot is set in this transaction). The transaction behavior is the - // same regardless of whether the key exists or not. + // snapshot is set in this transaction and do_validate is true). If + // do_validate is false, ReadOptions::snapshot is expected to be nullptr so + // that GetForUpdate returns the latest committed value. The transaction + // behavior is the same regardless of whether the key exists or not. // // Note: Currently, this function will return Status::MergeInProgress // if the most recent write to the queried key in this batch is a Merge. @@ -228,26 +236,31 @@ class Transaction { virtual Status GetForUpdate(const ReadOptions& options, ColumnFamilyHandle* column_family, const Slice& key, std::string* value, - bool exclusive = true) = 0; + bool exclusive = true, + const bool do_validate = true) = 0; - // An overload of the the above method that receives a PinnableSlice - // For backward compatiblity a default implementation is provided + // An overload of the above method that receives a PinnableSlice + // For backward compatibility a default implementation is provided virtual Status GetForUpdate(const ReadOptions& options, ColumnFamilyHandle* column_family, const Slice& key, PinnableSlice* pinnable_val, - bool exclusive = true) { + bool exclusive = true, + const bool do_validate = true) { if (pinnable_val == nullptr) { std::string* null_str = nullptr; - return GetForUpdate(options, key, null_str); + return GetForUpdate(options, column_family, key, null_str, exclusive, + do_validate); } else { - auto s = GetForUpdate(options, key, pinnable_val->GetSelf()); + auto s = GetForUpdate(options, column_family, key, + pinnable_val->GetSelf(), exclusive, do_validate); pinnable_val->PinSelf(); return s; } } virtual Status GetForUpdate(const ReadOptions& options, const Slice& key, - std::string* value, bool exclusive = true) = 0; + std::string* value, bool exclusive = true, + const bool do_validate = true) = 0; virtual std::vector MultiGetForUpdate( const ReadOptions& options, @@ -280,6 +293,9 @@ class Transaction { // functions in WriteBatch, but will also do conflict checking on the // keys being written. // + // assume_tracked=true expects the key be already tracked. If valid then it + // skips ValidateSnapshot. Returns error otherwise. + // // If this Transaction was created on an OptimisticTransactionDB, these // functions should always return Status::OK(). // @@ -292,28 +308,33 @@ class Transaction { // (See max_write_buffer_number_to_maintain) // or other errors on unexpected failures. virtual Status Put(ColumnFamilyHandle* column_family, const Slice& key, - const Slice& value) = 0; + const Slice& value, const bool assume_tracked = false) = 0; virtual Status Put(const Slice& key, const Slice& value) = 0; virtual Status Put(ColumnFamilyHandle* column_family, const SliceParts& key, - const SliceParts& value) = 0; + const SliceParts& value, + const bool assume_tracked = false) = 0; virtual Status Put(const SliceParts& key, const SliceParts& value) = 0; virtual Status Merge(ColumnFamilyHandle* column_family, const Slice& key, - const Slice& value) = 0; + const Slice& value, + const bool assume_tracked = false) = 0; virtual Status Merge(const Slice& key, const Slice& value) = 0; - virtual Status Delete(ColumnFamilyHandle* column_family, - const Slice& key) = 0; + virtual Status Delete(ColumnFamilyHandle* column_family, const Slice& key, + const bool assume_tracked = false) = 0; virtual Status Delete(const Slice& key) = 0; virtual Status Delete(ColumnFamilyHandle* column_family, - const SliceParts& key) = 0; + const SliceParts& key, + const bool assume_tracked = false) = 0; virtual Status Delete(const SliceParts& key) = 0; virtual Status SingleDelete(ColumnFamilyHandle* column_family, - const Slice& key) = 0; + const Slice& key, + const bool assume_tracked = false) = 0; virtual Status SingleDelete(const Slice& key) = 0; virtual Status SingleDelete(ColumnFamilyHandle* column_family, - const SliceParts& key) = 0; + const SliceParts& key, + const bool assume_tracked = false) = 0; virtual Status SingleDelete(const SliceParts& key) = 0; // PutUntracked() will write a Put to the batch of operations to be committed @@ -321,9 +342,9 @@ class Transaction { // gets committed successfully. But unlike Transaction::Put(), // no conflict checking will be done for this key. // - // If this Transaction was created on a TransactionDB, this function will - // still acquire locks necessary to make sure this write doesn't cause - // conflicts in other transactions and may return Status::Busy(). + // If this Transaction was created on a PessimisticTransactionDB, this + // function will still acquire locks necessary to make sure this write doesn't + // cause conflicts in other transactions and may return Status::Busy(). virtual Status PutUntracked(ColumnFamilyHandle* column_family, const Slice& key, const Slice& value) = 0; virtual Status PutUntracked(const Slice& key, const Slice& value) = 0; @@ -344,6 +365,10 @@ class Transaction { virtual Status DeleteUntracked(ColumnFamilyHandle* column_family, const SliceParts& key) = 0; virtual Status DeleteUntracked(const SliceParts& key) = 0; + virtual Status SingleDeleteUntracked(ColumnFamilyHandle* column_family, + const Slice& key) = 0; + + virtual Status SingleDeleteUntracked(const Slice& key) = 0; // Similar to WriteBatch::PutLogData virtual void PutLogData(const Slice& blob) = 0; @@ -364,7 +389,7 @@ class Transaction { virtual void EnableIndexing() = 0; // Returns the number of distinct Keys being tracked by this transaction. - // If this transaction was created by a TransactinDB, this is the number of + // If this transaction was created by a TransactionDB, this is the number of // keys that are currently locked by this transaction. // If this transaction was created by an OptimisticTransactionDB, this is the // number of keys that need to be checked for conflicts at commit time. @@ -436,8 +461,8 @@ class Transaction { virtual bool IsDeadlockDetect() const { return false; } - virtual std::vector GetWaitingTxns(uint32_t* column_family_id, - std::string* key) const { + virtual std::vector GetWaitingTxns( + uint32_t* /*column_family_id*/, std::string* /*key*/) const { assert(false); return std::vector(); } @@ -456,9 +481,17 @@ class Transaction { TransactionState GetState() const { return txn_state_; } void SetState(TransactionState state) { txn_state_ = state; } + // NOTE: Experimental feature + // The globally unique id with which the transaction is identified. This id + // might or might not be set depending on the implementation. Similarly the + // implementation decides the point in lifetime of a transaction at which it + // assigns the id. Although currently it is the case, the id is not guaranteed + // to remain the same across restarts. + uint64_t GetId() { return id_; } + protected: - explicit Transaction(const TransactionDB* db) {} - Transaction() {} + explicit Transaction(const TransactionDB* /*db*/) {} + Transaction() : log_number_(0), txn_state_(STARTED) {} // the log in which the prepared section for this txn resides // (for two phase commit) @@ -468,7 +501,15 @@ class Transaction { // Execution status of the transaction. std::atomic txn_state_; + uint64_t id_ = 0; + virtual void SetId(uint64_t id) { + assert(id_ == 0); + id_ = id; + } + private: + friend class PessimisticTransactionDB; + friend class WriteUnpreparedTxnDB; // No copying allowed Transaction(const Transaction&); void operator=(const Transaction&); diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/transaction_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/transaction_db.h index 77043897a7..6c4346ff3e 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/transaction_db.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/transaction_db.h @@ -75,7 +75,7 @@ struct TransactionDBOptions { // expiration set. int64_t default_lock_timeout = 1000; // 1 second - // If set, the TransactionDB will use this implemenation of a mutex and + // If set, the TransactionDB will use this implementation of a mutex and // condition variable for all transaction locking instead of the default // mutex/condvar implementation. std::shared_ptr custom_mutex_factory; @@ -85,6 +85,24 @@ struct TransactionDBOptions { // before the commit phase. The DB then needs to provide the mechanisms to // tell apart committed from uncommitted data. TxnDBWritePolicy write_policy = TxnDBWritePolicy::WRITE_COMMITTED; + + // TODO(myabandeh): remove this option + // Note: this is a temporary option as a hot fix in rollback of writeprepared + // txns in myrocks. MyRocks uses merge operands for autoinc column id without + // however obtaining locks. This breaks the assumption behind the rollback + // logic in myrocks. This hack of simply not rolling back merge operands works + // for the special way that myrocks uses this operands. + bool rollback_merge_operands = false; + + private: + // 128 entries + size_t wp_snapshot_cache_bits = static_cast(7); + // 8m entry, 64MB size + size_t wp_commit_cache_bits = static_cast(23); + + friend class WritePreparedTxnDB; + friend class WritePreparedTransactionTestBase; + friend class MySQLStyleTransactionTest; }; struct TransactionOptions { @@ -97,12 +115,18 @@ struct TransactionOptions { // Status::Busy. The user should retry their transaction. bool deadlock_detect = false; + // If set, it states that the CommitTimeWriteBatch represents the latest state + // of the application, has only one sub-batch, i.e., no duplicate keys, and + // meant to be used later during recovery. It enables an optimization to + // postpone updating the memtable with CommitTimeWriteBatch to only + // SwitchMemtable or recovery. + bool use_only_the_last_commit_time_batch_for_recovery = false; + // TODO(agiardullo): TransactionDB does not yet support comparators that allow // two non-equal keys to be equivalent. Ie, cmp->Compare(a,b) should only // return 0 if // a.compare(b) returns 0. - // If positive, specifies the wait timeout in milliseconds when // a transaction attempts to lock a key. // @@ -122,6 +146,29 @@ struct TransactionOptions { // The maximum number of bytes used for the write batch. 0 means no limit. size_t max_write_batch_size = 0; + + // Skip Concurrency Control. This could be as an optimization if the + // application knows that the transaction would not have any conflict with + // concurrent transactions. It could also be used during recovery if (i) + // application guarantees no conflict between prepared transactions in the WAL + // (ii) application guarantees that recovered transactions will be rolled + // back/commit before new transactions start. + // Default: false + bool skip_concurrency_control = false; +}; + +// The per-write optimizations that do not involve transactions. TransactionDB +// implementation might or might not make use of the specified optimizations. +struct TransactionDBWriteOptimizations { + // If it is true it means that the application guarantees that the + // key-set in the write batch do not conflict with any concurrent transaction + // and hence the concurrency control mechanism could be skipped for this + // write. + bool skip_concurrency_control = false; + // If true, the application guarantees that there is no duplicate in the write batch and any employed mechanism to handle + // duplicate keys could be skipped. + bool skip_duplicate_key_check = false; }; struct KeyLockInfo { @@ -133,27 +180,41 @@ struct KeyLockInfo { struct DeadlockInfo { TransactionID m_txn_id; uint32_t m_cf_id; - std::string m_waiting_key; bool m_exclusive; + std::string m_waiting_key; }; struct DeadlockPath { std::vector path; bool limit_exceeded; + int64_t deadlock_time; - explicit DeadlockPath(std::vector path_entry) - : path(path_entry), limit_exceeded(false) {} + explicit DeadlockPath(std::vector path_entry, + const int64_t& dl_time) + : path(path_entry), limit_exceeded(false), deadlock_time(dl_time) {} // empty path, limit exceeded constructor and default constructor - explicit DeadlockPath(bool limit = false) : path(0), limit_exceeded(limit) {} + explicit DeadlockPath(const int64_t& dl_time = 0, bool limit = false) + : path(0), limit_exceeded(limit), deadlock_time(dl_time) {} bool empty() { return path.empty() && !limit_exceeded; } }; class TransactionDB : public StackableDB { public: + // Optimized version of ::Write that receives more optimization request such + // as skip_concurrency_control. + using StackableDB::Write; + virtual Status Write(const WriteOptions& opts, + const TransactionDBWriteOptimizations&, + WriteBatch* updates) { + // The default implementation ignores TransactionDBWriteOptimizations and + // falls back to the un-optimized version of ::Write + return Write(opts, updates); + } // Open a TransactionDB similar to DB::Open(). // Internally call PrepareWrap() and WrapDB() + // If the return status is not ok, then dbptr is set to nullptr. static Status Open(const Options& options, const TransactionDBOptions& txn_db_options, const std::string& dbname, TransactionDB** dbptr); @@ -164,27 +225,29 @@ class TransactionDB : public StackableDB { const std::vector& column_families, std::vector* handles, TransactionDB** dbptr); - // The following functions are used to open a TransactionDB internally using - // an opened DB or StackableDB. - // 1. Call prepareWrap(), passing an empty std::vector to - // compaction_enabled_cf_indices. - // 2. Open DB or Stackable DB with db_options and column_families passed to - // prepareWrap() // Note: PrepareWrap() may change parameters, make copies before the // invocation if needed. - // 3. Call Wrap*DB() with compaction_enabled_cf_indices in step 1 and handles - // of the opened DB/StackableDB in step 2 static void PrepareWrap(DBOptions* db_options, std::vector* column_families, std::vector* compaction_enabled_cf_indices); + // If the return status is not ok, then dbptr will bet set to nullptr. The + // input db parameter might or might not be deleted as a result of the + // failure. If it is properly deleted it will be set to nullptr. If the return + // status is ok, the ownership of db is transferred to dbptr. static Status WrapDB(DB* db, const TransactionDBOptions& txn_db_options, const std::vector& compaction_enabled_cf_indices, const std::vector& handles, TransactionDB** dbptr); + // If the return status is not ok, then dbptr will bet set to nullptr. The + // input db parameter might or might not be deleted as a result of the + // failure. If it is properly deleted it will be set to nullptr. If the return + // status is ok, the ownership of db is transferred to dbptr. static Status WrapStackableDB( StackableDB* db, const TransactionDBOptions& txn_db_options, const std::vector& compaction_enabled_cf_indices, const std::vector& handles, TransactionDB** dbptr); + // Since the destructor in StackableDB is virtual, this destructor is virtual + // too. The root db will be deleted by the base's destructor. ~TransactionDB() override {} // Starts a new Transaction. @@ -213,6 +276,7 @@ class TransactionDB : public StackableDB { protected: // To Create an TransactionDB, call Open() + // The ownership of db is transferred to the base StackableDB explicit TransactionDB(DB* db) : StackableDB(db) {} private: diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/utility_db.h b/thirdparty/rocksdb/include/rocksdb/utilities/utility_db.h index a34a638980..3008fee1a9 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/utility_db.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/utility_db.h @@ -4,12 +4,12 @@ #pragma once #ifndef ROCKSDB_LITE -#include #include +#include -#include "rocksdb/utilities/stackable_db.h" -#include "rocksdb/utilities/db_ttl.h" #include "rocksdb/db.h" +#include "rocksdb/utilities/db_ttl.h" +#include "rocksdb/utilities/stackable_db.h" namespace rocksdb { @@ -22,14 +22,12 @@ class UtilityDB { #if defined(__GNUC__) || defined(__clang__) __attribute__((deprecated)) #elif _WIN32 - __declspec(deprecated) + __declspec(deprecated) #endif - static Status OpenTtlDB(const Options& options, - const std::string& name, - StackableDB** dbptr, - int32_t ttl = 0, - bool read_only = false); + static Status + OpenTtlDB(const Options& options, const std::string& name, + StackableDB** dbptr, int32_t ttl = 0, bool read_only = false); }; -} // namespace rocksdb +} // namespace rocksdb #endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/include/rocksdb/utilities/write_batch_with_index.h b/thirdparty/rocksdb/include/rocksdb/utilities/write_batch_with_index.h index 24d8f30aa5..d25b9513ba 100644 --- a/thirdparty/rocksdb/include/rocksdb/utilities/write_batch_with_index.h +++ b/thirdparty/rocksdb/include/rocksdb/utilities/write_batch_with_index.h @@ -27,6 +27,7 @@ namespace rocksdb { class ColumnFamilyHandle; class Comparator; class DB; +class ReadCallback; struct ReadOptions; struct DBOptions; @@ -154,6 +155,12 @@ class WriteBatchWithIndex : public WriteBatchBase { // The returned iterator should be deleted by the caller. // The base_iterator is now 'owned' by the returned iterator. Deleting the // returned iterator will also delete the base_iterator. + // + // Updating write batch with the current key of the iterator is not safe. + // We strongly recommand users not to do it. It will invalidate the current + // key() and value() of the iterator. This invalidation happens even before + // the write batch update finishes. The state may recover after Next() is + // called. Iterator* NewIteratorWithBase(ColumnFamilyHandle* column_family, Iterator* base_iterator); // default column family @@ -187,7 +194,7 @@ class WriteBatchWithIndex : public WriteBatchBase { Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options, const Slice& key, std::string* value); - // An overload of the the above method that receives a PinnableSlice + // An overload of the above method that receives a PinnableSlice Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options, const Slice& key, PinnableSlice* value); @@ -195,7 +202,7 @@ class WriteBatchWithIndex : public WriteBatchBase { ColumnFamilyHandle* column_family, const Slice& key, std::string* value); - // An overload of the the above method that receives a PinnableSlice + // An overload of the above method that receives a PinnableSlice Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options, ColumnFamilyHandle* column_family, const Slice& key, PinnableSlice* value); @@ -224,8 +231,21 @@ class WriteBatchWithIndex : public WriteBatchBase { Status PopSavePoint() override; void SetMaxBytes(size_t max_bytes) override; + size_t GetDataSize() const; private: + friend class PessimisticTransactionDB; + friend class WritePreparedTxn; + friend class WriteUnpreparedTxn; + friend class WriteBatchWithIndex_SubBatchCnt_Test; + // Returns the number of sub-batches inside the write batch. A sub-batch + // starts right before inserting a key that is a duplicate of a key in the + // last sub-batch. + size_t SubBatchCnt(); + + Status GetFromBatchAndDB(DB* db, const ReadOptions& read_options, + ColumnFamilyHandle* column_family, const Slice& key, + PinnableSlice* value, ReadCallback* callback); struct Rep; std::unique_ptr rep; }; diff --git a/thirdparty/rocksdb/include/rocksdb/version.h b/thirdparty/rocksdb/include/rocksdb/version.h index b48732d75f..d72f6b649d 100644 --- a/thirdparty/rocksdb/include/rocksdb/version.h +++ b/thirdparty/rocksdb/include/rocksdb/version.h @@ -4,9 +4,9 @@ // (found in the LICENSE.Apache file in the root directory). #pragma once -#define ROCKSDB_MAJOR 5 -#define ROCKSDB_MINOR 8 -#define ROCKSDB_PATCH 6 +#define ROCKSDB_MAJOR 6 +#define ROCKSDB_MINOR 1 +#define ROCKSDB_PATCH 2 // Do not use these. We made the mistake of declaring macros starting with // double underscore. Now we have to live with our choice. We'll deprecate these diff --git a/thirdparty/rocksdb/include/rocksdb/wal_filter.h b/thirdparty/rocksdb/include/rocksdb/wal_filter.h index 686fa49989..e25746dba4 100644 --- a/thirdparty/rocksdb/include/rocksdb/wal_filter.h +++ b/thirdparty/rocksdb/include/rocksdb/wal_filter.h @@ -4,8 +4,9 @@ // (found in the LICENSE.Apache file in the root directory). #pragma once -#include + #include +#include namespace rocksdb { @@ -33,7 +34,7 @@ class WalFilter { virtual ~WalFilter() {} // Provide ColumnFamily->LogNumber map to filter - // so that filter can determine whether a log number applies to a given + // so that filter can determine whether a log number applies to a given // column family (i.e. that log hasn't been flushed to SST already for the // column family). // We also pass in name->id map as only name is known during @@ -44,8 +45,8 @@ class WalFilter { // @params cf_name_id_map column_family_name to column_family_id map virtual void ColumnFamilyLogNumberMap( - const std::map& cf_lognumber_map, - const std::map& cf_name_id_map) {} + const std::map& /*cf_lognumber_map*/, + const std::map& /*cf_name_id_map*/) {} // LogRecord is invoked for each log record encountered for all the logs // during replay on logs on recovery. This method can be used to: @@ -75,21 +76,19 @@ class WalFilter { // @returns Processing option for the current record. // Please see WalProcessingOption enum above for // details. - virtual WalProcessingOption LogRecordFound(unsigned long long log_number, - const std::string& log_file_name, - const WriteBatch& batch, - WriteBatch* new_batch, - bool* batch_changed) { + virtual WalProcessingOption LogRecordFound( + unsigned long long /*log_number*/, const std::string& /*log_file_name*/, + const WriteBatch& batch, WriteBatch* new_batch, bool* batch_changed) { // Default implementation falls back to older function for compatibility return LogRecord(batch, new_batch, batch_changed); } - // Please see the comments for LogRecord above. This function is for - // compatibility only and contains a subset of parameters. + // Please see the comments for LogRecord above. This function is for + // compatibility only and contains a subset of parameters. // New code should use the function above. - virtual WalProcessingOption LogRecord(const WriteBatch& batch, - WriteBatch* new_batch, - bool* batch_changed) const { + virtual WalProcessingOption LogRecord(const WriteBatch& /*batch*/, + WriteBatch* /*new_batch*/, + bool* /*batch_changed*/) const { return WalProcessingOption::kContinueProcessing; } diff --git a/thirdparty/rocksdb/include/rocksdb/write_batch.h b/thirdparty/rocksdb/include/rocksdb/write_batch.h index 336391ead5..8782d08f1f 100644 --- a/thirdparty/rocksdb/include/rocksdb/write_batch.h +++ b/thirdparty/rocksdb/include/rocksdb/write_batch.h @@ -22,13 +22,12 @@ // non-const method, all threads accessing the same WriteBatch must use // external synchronization. -#ifndef STORAGE_ROCKSDB_INCLUDE_WRITE_BATCH_H_ -#define STORAGE_ROCKSDB_INCLUDE_WRITE_BATCH_H_ +#pragma once +#include #include #include #include -#include #include "rocksdb/status.h" #include "rocksdb/write_batch_base.h" @@ -217,8 +216,9 @@ class WriteBatch : public WriteBatchBase { } virtual void SingleDelete(const Slice& /*key*/) {} - virtual Status DeleteRangeCF(uint32_t column_family_id, - const Slice& begin_key, const Slice& end_key) { + virtual Status DeleteRangeCF(uint32_t /*column_family_id*/, + const Slice& /*begin_key*/, + const Slice& /*end_key*/) { return Status::InvalidArgument("DeleteRangeCF not implemented"); } @@ -242,20 +242,24 @@ class WriteBatch : public WriteBatchBase { // The default implementation of LogData does nothing. virtual void LogData(const Slice& blob); - virtual Status MarkBeginPrepare() { + virtual Status MarkBeginPrepare(bool = false) { return Status::InvalidArgument("MarkBeginPrepare() handler not defined."); } - virtual Status MarkEndPrepare(const Slice& xid) { + virtual Status MarkEndPrepare(const Slice& /*xid*/) { return Status::InvalidArgument("MarkEndPrepare() handler not defined."); } - virtual Status MarkRollback(const Slice& xid) { + virtual Status MarkNoop(bool /*empty_batch*/) { + return Status::InvalidArgument("MarkNoop() handler not defined."); + } + + virtual Status MarkRollback(const Slice& /*xid*/) { return Status::InvalidArgument( "MarkRollbackPrepare() handler not defined."); } - virtual Status MarkCommit(const Slice& xid) { + virtual Status MarkCommit(const Slice& /*xid*/) { return Status::InvalidArgument("MarkCommit() handler not defined."); } @@ -263,6 +267,11 @@ class WriteBatch : public WriteBatchBase { // iteration is halted. Otherwise, it continues iterating. The default // implementation always returns true. virtual bool Continue(); + + protected: + friend class WriteBatch; + virtual bool WriteAfterCommit() const { return true; } + virtual bool WriteBeforePrepare() const { return false; } }; Status Iterate(Handler* handler) const; @@ -307,9 +316,10 @@ class WriteBatch : public WriteBatchBase { // Constructor with a serialized string object explicit WriteBatch(const std::string& rep); + explicit WriteBatch(std::string&& rep); WriteBatch(const WriteBatch& src); - WriteBatch(WriteBatch&& src); + WriteBatch(WriteBatch&& src) noexcept; WriteBatch& operator=(const WriteBatch& src); WriteBatch& operator=(WriteBatch&& src); @@ -323,6 +333,10 @@ class WriteBatch : public WriteBatchBase { private: friend class WriteBatchInternal; friend class LocalSavePoint; + // TODO(myabandeh): this is needed for a hack to collapse the write batch and + // remove duplicate keys. Remove it when the hack is replaced with a proper + // solution. + friend class WriteBatchWithIndex; SavePoints* save_points_; // When sending a WriteBatch through WriteImpl we might want to @@ -339,6 +353,12 @@ class WriteBatch : public WriteBatchBase { // Maximum size of rep_. size_t max_bytes_; + // Is the content of the batch the application's latest state that meant only + // to be used for recovery? Refer to + // TransactionOptions::use_only_the_last_commit_time_batch_for_recovery for + // more details. + bool is_latest_persistent_state_ = false; + protected: std::string rep_; // See comment in write_batch.cc for the format of rep_ @@ -346,5 +366,3 @@ class WriteBatch : public WriteBatchBase { }; } // namespace rocksdb - -#endif // STORAGE_ROCKSDB_INCLUDE_WRITE_BATCH_H_ diff --git a/thirdparty/rocksdb/include/rocksdb/write_batch_base.h b/thirdparty/rocksdb/include/rocksdb/write_batch_base.h index 3e6d011bd5..a7747a7c84 100644 --- a/thirdparty/rocksdb/include/rocksdb/write_batch_base.h +++ b/thirdparty/rocksdb/include/rocksdb/write_batch_base.h @@ -20,7 +20,7 @@ struct SliceParts; // Abstract base class that defines the basic interface for a write batch. // See WriteBatch for a basic implementation and WrithBatchWithIndex for an -// indexed implemenation. +// indexed implementation. class WriteBatchBase { public: virtual ~WriteBatchBase() {} @@ -69,7 +69,7 @@ class WriteBatchBase { const SliceParts& key); virtual Status SingleDelete(const SliceParts& key); - // If the database contains mappings in the range ["begin_key", "end_key"], + // If the database contains mappings in the range ["begin_key", "end_key"), // erase them. Else do nothing. virtual Status DeleteRange(ColumnFamilyHandle* column_family, const Slice& begin_key, const Slice& end_key) = 0; diff --git a/thirdparty/rocksdb/include/rocksdb/write_buffer_manager.h b/thirdparty/rocksdb/include/rocksdb/write_buffer_manager.h index 856cf4b246..dea904c187 100644 --- a/thirdparty/rocksdb/include/rocksdb/write_buffer_manager.h +++ b/thirdparty/rocksdb/include/rocksdb/write_buffer_manager.h @@ -30,6 +30,8 @@ class WriteBufferManager { bool enabled() const { return buffer_size_ != 0; } + bool cost_to_cache() const { return cache_rep_ != nullptr; } + // Only valid if enabled() size_t memory_usage() const { return memory_used_.load(std::memory_order_relaxed); diff --git a/thirdparty/rocksdb/issue_template.md b/thirdparty/rocksdb/issue_template.md new file mode 100644 index 0000000000..3e16c99a61 --- /dev/null +++ b/thirdparty/rocksdb/issue_template.md @@ -0,0 +1,7 @@ +> Note: Please use Issues only for bug reports. For questions, discussions, feature requests, etc. post to dev group: https://www.facebook.com/groups/rocksdb.dev + +### Expected behavior + +### Actual behavior + +### Steps to reproduce the behavior diff --git a/thirdparty/rocksdb/java/CMakeLists.txt b/thirdparty/rocksdb/java/CMakeLists.txt index d67896c2cd..360951834a 100644 --- a/thirdparty/rocksdb/java/CMakeLists.txt +++ b/thirdparty/rocksdb/java/CMakeLists.txt @@ -1,14 +1,22 @@ -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 3.4) set(JNI_NATIVE_SOURCES rocksjni/backupablejni.cc rocksjni/backupenginejni.cc + rocksjni/cassandra_compactionfilterjni.cc + rocksjni/cassandra_value_operator.cc rocksjni/checkpoint.cc rocksjni/clock_cache.cc rocksjni/columnfamilyhandle.cc rocksjni/compaction_filter.cc + rocksjni/compaction_filter_factory.cc + rocksjni/compaction_filter_factory_jnicallback.cc + rocksjni/compaction_job_info.cc + rocksjni/compaction_job_stats.cc + rocksjni/compaction_options.cc rocksjni/compaction_options_fifo.cc rocksjni/compaction_options_universal.cc + rocksjni/compact_range_options.cc rocksjni/comparator.cc rocksjni/comparatorjnicallback.cc rocksjni/compression_options.cc @@ -17,53 +25,81 @@ set(JNI_NATIVE_SOURCES rocksjni/filter.cc rocksjni/ingest_external_file_options.cc rocksjni/iterator.cc + rocksjni/jnicallback.cc rocksjni/loggerjnicallback.cc rocksjni/lru_cache.cc + rocksjni/memory_util.cc rocksjni/memtablejni.cc rocksjni/merge_operator.cc + rocksjni/native_comparator_wrapper_test.cc + rocksjni/optimistic_transaction_db.cc + rocksjni/optimistic_transaction_options.cc rocksjni/options.cc + rocksjni/options_util.cc + rocksjni/persistent_cache.cc rocksjni/ratelimiterjni.cc rocksjni/remove_emptyvalue_compactionfilterjni.cc - rocksjni/cassandra_compactionfilterjni.cc rocksjni/restorejni.cc + rocksjni/rocks_callback_object.cc rocksjni/rocksdb_exception_test.cc rocksjni/rocksjni.cc rocksjni/slice.cc rocksjni/snapshot.cc + rocksjni/sst_file_manager.cc rocksjni/sst_file_writerjni.cc rocksjni/statistics.cc rocksjni/statisticsjni.cc rocksjni/table.cc + rocksjni/table_filter.cc + rocksjni/table_filter_jnicallback.cc + rocksjni/thread_status.cc + rocksjni/trace_writer.cc + rocksjni/trace_writer_jnicallback.cc + rocksjni/transaction.cc + rocksjni/transaction_db.cc + rocksjni/transaction_db_options.cc rocksjni/transaction_log.cc + rocksjni/transaction_notifier.cc + rocksjni/transaction_notifier_jnicallback.cc + rocksjni/transaction_options.cc rocksjni/ttl.cc + rocksjni/wal_filter.cc + rocksjni/wal_filter_jnicallback.cc rocksjni/write_batch.cc + rocksjni/writebatchhandlerjnicallback.cc rocksjni/write_batch_test.cc rocksjni/write_batch_with_index.cc - rocksjni/writebatchhandlerjnicallback.cc + rocksjni/write_buffer_manager.cc ) set(NATIVE_JAVA_CLASSES org.rocksdb.AbstractCompactionFilter + org.rocksdb.AbstractCompactionFilterFactory org.rocksdb.AbstractComparator org.rocksdb.AbstractImmutableNativeReference org.rocksdb.AbstractNativeReference org.rocksdb.AbstractRocksIterator org.rocksdb.AbstractSlice - org.rocksdb.AbstractWriteBatch + org.rocksdb.AbstractTableFilter + org.rocksdb.AbstractTraceWriter + org.rocksdb.AbstractTransactionNotifier + org.rocksdb.AbstractWalFilter org.rocksdb.BackupableDBOptions org.rocksdb.BackupEngine - org.rocksdb.BackupEngineTest org.rocksdb.BlockBasedTableConfig org.rocksdb.BloomFilter - org.rocksdb.Cache org.rocksdb.CassandraCompactionFilter org.rocksdb.CassandraValueMergeOperator org.rocksdb.Checkpoint org.rocksdb.ClockCache org.rocksdb.ColumnFamilyHandle org.rocksdb.ColumnFamilyOptions + org.rocksdb.CompactionJobInfo + org.rocksdb.CompactionJobStats + org.rocksdb.CompactionOptions org.rocksdb.CompactionOptionsFIFO org.rocksdb.CompactionOptionsUniversal + org.rocksdb.CompactRangeOptions org.rocksdb.Comparator org.rocksdb.ComparatorOptions org.rocksdb.CompressionOptions @@ -72,25 +108,30 @@ set(NATIVE_JAVA_CLASSES org.rocksdb.DirectSlice org.rocksdb.Env org.rocksdb.EnvOptions - org.rocksdb.ExternalSstFileInfo org.rocksdb.Filter org.rocksdb.FlushOptions org.rocksdb.HashLinkedListMemTableConfig org.rocksdb.HashSkipListMemTableConfig + org.rocksdb.HdfsEnv org.rocksdb.IngestExternalFileOptions org.rocksdb.Logger org.rocksdb.LRUCache + org.rocksdb.MemoryUtil org.rocksdb.MemTableConfig - org.rocksdb.MergeOperator + org.rocksdb.NativeComparatorWrapper org.rocksdb.NativeLibraryLoader + org.rocksdb.OptimisticTransactionDB + org.rocksdb.OptimisticTransactionOptions org.rocksdb.Options + org.rocksdb.OptionsUtil + org.rocksdb.PersistentCache org.rocksdb.PlainTableConfig org.rocksdb.RateLimiter org.rocksdb.ReadOptions org.rocksdb.RemoveEmptyValueCompactionFilter org.rocksdb.RestoreOptions + org.rocksdb.RocksCallbackObject org.rocksdb.RocksDB - org.rocksdb.RocksDBExceptionTest org.rocksdb.RocksEnv org.rocksdb.RocksIterator org.rocksdb.RocksIteratorInterface @@ -100,25 +141,40 @@ set(NATIVE_JAVA_CLASSES org.rocksdb.SkipListMemTableConfig org.rocksdb.Slice org.rocksdb.Snapshot - org.rocksdb.SnapshotTest + org.rocksdb.SstFileManager org.rocksdb.SstFileWriter org.rocksdb.Statistics org.rocksdb.StringAppendOperator org.rocksdb.TableFormatConfig + org.rocksdb.ThreadStatus + org.rocksdb.TimedEnv + org.rocksdb.Transaction + org.rocksdb.TransactionDB + org.rocksdb.TransactionDBOptions org.rocksdb.TransactionLogIterator + org.rocksdb.TransactionOptions org.rocksdb.TtlDB + org.rocksdb.UInt64AddOperator org.rocksdb.VectorMemTableConfig org.rocksdb.WBWIRocksIterator org.rocksdb.WriteBatch org.rocksdb.WriteBatch.Handler - org.rocksdb.WriteBatchTest - org.rocksdb.WriteBatchTestInternalHelper + org.rocksdb.WriteBatchInterface org.rocksdb.WriteBatchWithIndex org.rocksdb.WriteOptions + org.rocksdb.NativeComparatorWrapperTest + org.rocksdb.RocksDBExceptionTest + org.rocksdb.SnapshotTest + org.rocksdb.WriteBatchTest + org.rocksdb.WriteBatchTestInternalHelper + org.rocksdb.WriteBufferManager ) -include_directories($ENV{JAVA_HOME}/include) -include_directories($ENV{JAVA_HOME}/include/win32) +include(FindJava) +include(UseJava) +include(FindJNI) + +include_directories(${JNI_INCLUDE_DIRS}) include_directories(${PROJECT_SOURCE_DIR}/java) set(JAVA_TEST_LIBDIR ${PROJECT_SOURCE_DIR}/java/test-libs) @@ -128,7 +184,183 @@ set(JAVA_HAMCR_JAR ${JAVA_TEST_LIBDIR}/hamcrest-core-1.3.jar) set(JAVA_MOCKITO_JAR ${JAVA_TEST_LIBDIR}/mockito-all-1.10.19.jar) set(JAVA_CGLIB_JAR ${JAVA_TEST_LIBDIR}/cglib-2.2.2.jar) set(JAVA_ASSERTJ_JAR ${JAVA_TEST_LIBDIR}/assertj-core-1.7.1.jar) -set(JAVA_TESTCLASSPATH "${JAVA_JUNIT_JAR}\;${JAVA_HAMCR_JAR}\;${JAVA_MOCKITO_JAR}\;${JAVA_CGLIB_JAR}\;${JAVA_ASSERTJ_JAR}") +set(JAVA_TESTCLASSPATH ${JAVA_JUNIT_JAR} ${JAVA_HAMCR_JAR} ${JAVA_MOCKITO_JAR} ${JAVA_CGLIB_JAR} ${JAVA_ASSERTJ_JAR}) + +add_jar( + rocksdbjni_classes + SOURCES + src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java + src/main/java/org/rocksdb/AbstractCompactionFilter.java + src/main/java/org/rocksdb/AbstractComparator.java + src/main/java/org/rocksdb/AbstractImmutableNativeReference.java + src/main/java/org/rocksdb/AbstractMutableOptions.java + src/main/java/org/rocksdb/AbstractNativeReference.java + src/main/java/org/rocksdb/AbstractRocksIterator.java + src/main/java/org/rocksdb/AbstractSlice.java + src/main/java/org/rocksdb/AbstractTableFilter.java + src/main/java/org/rocksdb/AbstractTraceWriter.java + src/main/java/org/rocksdb/AbstractTransactionNotifier.java + src/main/java/org/rocksdb/AbstractWalFilter.java + src/main/java/org/rocksdb/AbstractWriteBatch.java + src/main/java/org/rocksdb/AccessHint.java + src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java + src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java + src/main/java/org/rocksdb/BackupableDBOptions.java + src/main/java/org/rocksdb/BackupEngine.java + src/main/java/org/rocksdb/BackupInfo.java + src/main/java/org/rocksdb/BlockBasedTableConfig.java + src/main/java/org/rocksdb/BloomFilter.java + src/main/java/org/rocksdb/BuiltinComparator.java + src/main/java/org/rocksdb/Cache.java + src/main/java/org/rocksdb/CassandraCompactionFilter.java + src/main/java/org/rocksdb/CassandraValueMergeOperator.java + src/main/java/org/rocksdb/Checkpoint.java + src/main/java/org/rocksdb/ChecksumType.java + src/main/java/org/rocksdb/ClockCache.java + src/main/java/org/rocksdb/ColumnFamilyDescriptor.java + src/main/java/org/rocksdb/ColumnFamilyHandle.java + src/main/java/org/rocksdb/ColumnFamilyMetaData.java + src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java + src/main/java/org/rocksdb/ColumnFamilyOptions.java + src/main/java/org/rocksdb/CompactionJobInfo.java + src/main/java/org/rocksdb/CompactionJobStats.java + src/main/java/org/rocksdb/CompactionOptions.java + src/main/java/org/rocksdb/CompactionOptionsFIFO.java + src/main/java/org/rocksdb/CompactionOptionsUniversal.java + src/main/java/org/rocksdb/CompactionPriority.java + src/main/java/org/rocksdb/CompactionReason.java + src/main/java/org/rocksdb/CompactRangeOptions.java + src/main/java/org/rocksdb/CompactionStopStyle.java + src/main/java/org/rocksdb/CompactionStyle.java + src/main/java/org/rocksdb/Comparator.java + src/main/java/org/rocksdb/ComparatorOptions.java + src/main/java/org/rocksdb/ComparatorType.java + src/main/java/org/rocksdb/CompressionOptions.java + src/main/java/org/rocksdb/CompressionType.java + src/main/java/org/rocksdb/DataBlockIndexType.java + src/main/java/org/rocksdb/DBOptionsInterface.java + src/main/java/org/rocksdb/DBOptions.java + src/main/java/org/rocksdb/DbPath.java + src/main/java/org/rocksdb/DirectComparator.java + src/main/java/org/rocksdb/DirectSlice.java + src/main/java/org/rocksdb/EncodingType.java + src/main/java/org/rocksdb/Env.java + src/main/java/org/rocksdb/EnvOptions.java + src/main/java/org/rocksdb/Experimental.java + src/main/java/org/rocksdb/Filter.java + src/main/java/org/rocksdb/FlushOptions.java + src/main/java/org/rocksdb/HashLinkedListMemTableConfig.java + src/main/java/org/rocksdb/HashSkipListMemTableConfig.java + src/main/java/org/rocksdb/HdfsEnv.java + src/main/java/org/rocksdb/HistogramData.java + src/main/java/org/rocksdb/HistogramType.java + src/main/java/org/rocksdb/IndexType.java + src/main/java/org/rocksdb/InfoLogLevel.java + src/main/java/org/rocksdb/IngestExternalFileOptions.java + src/main/java/org/rocksdb/LevelMetaData.java + src/main/java/org/rocksdb/LiveFileMetaData.java + src/main/java/org/rocksdb/LogFile.java + src/main/java/org/rocksdb/Logger.java + src/main/java/org/rocksdb/LRUCache.java + src/main/java/org/rocksdb/MemoryUsageType.java + src/main/java/org/rocksdb/MemoryUtil.java + src/main/java/org/rocksdb/MemTableConfig.java + src/main/java/org/rocksdb/MergeOperator.java + src/main/java/org/rocksdb/MutableColumnFamilyOptions.java + src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java + src/main/java/org/rocksdb/MutableDBOptions.java + src/main/java/org/rocksdb/MutableDBOptionsInterface.java + src/main/java/org/rocksdb/MutableOptionKey.java + src/main/java/org/rocksdb/MutableOptionValue.java + src/main/java/org/rocksdb/NativeComparatorWrapper.java + src/main/java/org/rocksdb/NativeLibraryLoader.java + src/main/java/org/rocksdb/OperationStage.java + src/main/java/org/rocksdb/OperationType.java + src/main/java/org/rocksdb/OptimisticTransactionDB.java + src/main/java/org/rocksdb/OptimisticTransactionOptions.java + src/main/java/org/rocksdb/Options.java + src/main/java/org/rocksdb/OptionsUtil.java + src/main/java/org/rocksdb/PersistentCache.java + src/main/java/org/rocksdb/PlainTableConfig.java + src/main/java/org/rocksdb/Priority.java + src/main/java/org/rocksdb/Range.java + src/main/java/org/rocksdb/RateLimiter.java + src/main/java/org/rocksdb/RateLimiterMode.java + src/main/java/org/rocksdb/ReadOptions.java + src/main/java/org/rocksdb/ReadTier.java + src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java + src/main/java/org/rocksdb/RestoreOptions.java + src/main/java/org/rocksdb/RocksCallbackObject.java + src/main/java/org/rocksdb/RocksDBException.java + src/main/java/org/rocksdb/RocksDB.java + src/main/java/org/rocksdb/RocksEnv.java + src/main/java/org/rocksdb/RocksIteratorInterface.java + src/main/java/org/rocksdb/RocksIterator.java + src/main/java/org/rocksdb/RocksMemEnv.java + src/main/java/org/rocksdb/RocksMutableObject.java + src/main/java/org/rocksdb/RocksObject.java + src/main/java/org/rocksdb/SizeApproximationFlag.java + src/main/java/org/rocksdb/SkipListMemTableConfig.java + src/main/java/org/rocksdb/Slice.java + src/main/java/org/rocksdb/Snapshot.java + src/main/java/org/rocksdb/SstFileManager.java + src/main/java/org/rocksdb/SstFileMetaData.java + src/main/java/org/rocksdb/SstFileWriter.java + src/main/java/org/rocksdb/StateType.java + src/main/java/org/rocksdb/StatisticsCollectorCallback.java + src/main/java/org/rocksdb/StatisticsCollector.java + src/main/java/org/rocksdb/Statistics.java + src/main/java/org/rocksdb/StatsCollectorInput.java + src/main/java/org/rocksdb/StatsLevel.java + src/main/java/org/rocksdb/Status.java + src/main/java/org/rocksdb/StringAppendOperator.java + src/main/java/org/rocksdb/TableFilter.java + src/main/java/org/rocksdb/TableProperties.java + src/main/java/org/rocksdb/TableFormatConfig.java + src/main/java/org/rocksdb/ThreadType.java + src/main/java/org/rocksdb/ThreadStatus.java + src/main/java/org/rocksdb/TickerType.java + src/main/java/org/rocksdb/TimedEnv.java + src/main/java/org/rocksdb/TraceOptions.java + src/main/java/org/rocksdb/TraceWriter.java + src/main/java/org/rocksdb/TransactionalDB.java + src/main/java/org/rocksdb/TransactionalOptions.java + src/main/java/org/rocksdb/TransactionDB.java + src/main/java/org/rocksdb/TransactionDBOptions.java + src/main/java/org/rocksdb/Transaction.java + src/main/java/org/rocksdb/TransactionLogIterator.java + src/main/java/org/rocksdb/TransactionOptions.java + src/main/java/org/rocksdb/TtlDB.java + src/main/java/org/rocksdb/TxnDBWritePolicy.java + src/main/java/org/rocksdb/VectorMemTableConfig.java + src/main/java/org/rocksdb/WalFileType.java + src/main/java/org/rocksdb/WalFilter.java + src/main/java/org/rocksdb/WalProcessingOption.java + src/main/java/org/rocksdb/WALRecoveryMode.java + src/main/java/org/rocksdb/WBWIRocksIterator.java + src/main/java/org/rocksdb/WriteBatchInterface.java + src/main/java/org/rocksdb/WriteBatch.java + src/main/java/org/rocksdb/WriteBatchWithIndex.java + src/main/java/org/rocksdb/WriteOptions.java + src/main/java/org/rocksdb/WriteBufferManager.java + src/main/java/org/rocksdb/util/BytewiseComparator.java + src/main/java/org/rocksdb/util/DirectBytewiseComparator.java + src/main/java/org/rocksdb/util/Environment.java + src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java + src/main/java/org/rocksdb/util/SizeUnit.java + src/test/java/org/rocksdb/BackupEngineTest.java + src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java + src/test/java/org/rocksdb/NativeComparatorWrapperTest.java + src/test/java/org/rocksdb/PlatformRandomHelper.java + src/test/java/org/rocksdb/RocksDBExceptionTest.java + src/test/java/org/rocksdb/RocksMemoryResource.java + src/test/java/org/rocksdb/SnapshotTest.java + src/main/java/org/rocksdb/UInt64AddOperator.java + src/test/java/org/rocksdb/WriteBatchTest.java + src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java + src/test/java/org/rocksdb/util/WriteBatchGetter.java + INCLUDE_JARS ${JAVA_TESTCLASSPATH} +) if(NOT EXISTS ${PROJECT_SOURCE_DIR}/java/classes) file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/java/classes) @@ -192,16 +424,35 @@ if(NOT EXISTS ${JAVA_ASSERTJ_JAR}) file(RENAME ${JAVA_TMP_JAR} ${JAVA_ASSERTJ_JAR}) endif() -if(WIN32) - set(JAVAC cmd /c javac) - set(JAVAH cmd /c javah) -else() - set(JAVAC javac) - set(JAVAH javah) +set(JNI_OUTPUT_DIR ${PROJECT_SOURCE_DIR}/java/include) + +file(MAKE_DIRECTORY ${JNI_OUTPUT_DIR}) +create_javah( + TARGET rocksdbjni_headers + CLASSES ${NATIVE_JAVA_CLASSES} + CLASSPATH rocksdbjni_classes ${JAVA_TESTCLASSPATH} + OUTPUT_DIR ${JNI_OUTPUT_DIR} +) + +if(NOT MSVC) + set_property(TARGET ${ROCKSDB_STATIC_LIB} PROPERTY POSITION_INDEPENDENT_CODE ON) endif() -execute_process(COMMAND ${JAVAC} ${JAVAC_ARGS} -cp ${JAVA_TESTCLASSPATH} -d ${PROJECT_SOURCE_DIR}/java/classes ${PROJECT_SOURCE_DIR}/java/src/main/java/org/rocksdb/util/*.java ${PROJECT_SOURCE_DIR}/java/src/main/java/org/rocksdb/*.java ${PROJECT_SOURCE_DIR}/java/src/test/java/org/rocksdb/*.java) -execute_process(COMMAND ${JAVAH} -cp ${PROJECT_SOURCE_DIR}/java/classes -d ${PROJECT_SOURCE_DIR}/java/include -jni ${NATIVE_JAVA_CLASSES}) -add_library(rocksdbjni${ARTIFACT_SUFFIX} SHARED ${JNI_NATIVE_SOURCES}) -set_target_properties(rocksdbjni${ARTIFACT_SUFFIX} PROPERTIES COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/rocksdbjni${ARTIFACT_SUFFIX}.pdb") -target_link_libraries(rocksdbjni${ARTIFACT_SUFFIX} rocksdb${ARTIFACT_SUFFIX} ${LIBS}) +set(ROCKSDBJNI_STATIC_LIB rocksdbjni${ARTIFACT_SUFFIX}) +add_library(${ROCKSDBJNI_STATIC_LIB} ${JNI_NATIVE_SOURCES}) +add_dependencies(${ROCKSDBJNI_STATIC_LIB} rocksdbjni_headers) +target_link_libraries(${ROCKSDBJNI_STATIC_LIB} ${ROCKSDB_STATIC_LIB} ${LIBS}) + +if(NOT MINGW) + set(ROCKSDBJNI_SHARED_LIB rocksdbjni-shared${ARTIFACT_SUFFIX}) + add_library(${ROCKSDBJNI_SHARED_LIB} SHARED ${JNI_NATIVE_SOURCES}) + add_dependencies(${ROCKSDBJNI_SHARED_LIB} rocksdbjni_headers) + target_link_libraries(${ROCKSDBJNI_SHARED_LIB} ${ROCKSDB_STATIC_LIB} ${LIBS}) + + set_target_properties( + ${ROCKSDBJNI_SHARED_LIB} + PROPERTIES + COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_CFG_INTDIR} + COMPILE_PDB_NAME ${ROCKSDBJNI_STATIC_LIB}.pdb + ) +endif() diff --git a/thirdparty/rocksdb/java/Makefile b/thirdparty/rocksdb/java/Makefile index b29447bd8a..efc9d2b4e1 100644 --- a/thirdparty/rocksdb/java/Makefile +++ b/thirdparty/rocksdb/java/Makefile @@ -1,6 +1,10 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractCompactionFilter\ - org.rocksdb.AbstractComparator\ + org.rocksdb.AbstractCompactionFilterFactory\ org.rocksdb.AbstractSlice\ + org.rocksdb.AbstractTableFilter\ + org.rocksdb.AbstractTraceWriter\ + org.rocksdb.AbstractTransactionNotifier\ + org.rocksdb.AbstractWalFilter\ org.rocksdb.BackupEngine\ org.rocksdb.BackupableDBOptions\ org.rocksdb.BlockBasedTableConfig\ @@ -11,8 +15,12 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractCompactionFilter\ org.rocksdb.CassandraValueMergeOperator\ org.rocksdb.ColumnFamilyHandle\ org.rocksdb.ColumnFamilyOptions\ + org.rocksdb.CompactionJobInfo\ + org.rocksdb.CompactionJobStats\ + org.rocksdb.CompactionOptions\ org.rocksdb.CompactionOptionsFIFO\ org.rocksdb.CompactionOptionsUniversal\ + org.rocksdb.CompactRangeOptions\ org.rocksdb.Comparator\ org.rocksdb.ComparatorOptions\ org.rocksdb.CompressionOptions\ @@ -26,35 +34,54 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractCompactionFilter\ org.rocksdb.IngestExternalFileOptions\ org.rocksdb.HashLinkedListMemTableConfig\ org.rocksdb.HashSkipListMemTableConfig\ + org.rocksdb.HdfsEnv\ org.rocksdb.Logger\ org.rocksdb.LRUCache\ + org.rocksdb.MemoryUsageType\ + org.rocksdb.MemoryUtil\ org.rocksdb.MergeOperator\ + org.rocksdb.NativeComparatorWrapper\ + org.rocksdb.OptimisticTransactionDB\ + org.rocksdb.OptimisticTransactionOptions\ org.rocksdb.Options\ + org.rocksdb.OptionsUtil\ + org.rocksdb.PersistentCache\ org.rocksdb.PlainTableConfig\ org.rocksdb.RateLimiter\ org.rocksdb.ReadOptions\ org.rocksdb.RemoveEmptyValueCompactionFilter\ org.rocksdb.RestoreOptions\ + org.rocksdb.RocksCallbackObject\ org.rocksdb.RocksDB\ org.rocksdb.RocksEnv\ org.rocksdb.RocksIterator\ org.rocksdb.RocksMemEnv\ org.rocksdb.SkipListMemTableConfig\ org.rocksdb.Slice\ + org.rocksdb.SstFileManager\ org.rocksdb.SstFileWriter\ org.rocksdb.Statistics\ + org.rocksdb.ThreadStatus\ + org.rocksdb.TimedEnv\ + org.rocksdb.Transaction\ + org.rocksdb.TransactionDB\ + org.rocksdb.TransactionDBOptions\ + org.rocksdb.TransactionOptions\ org.rocksdb.TransactionLogIterator\ org.rocksdb.TtlDB\ org.rocksdb.VectorMemTableConfig\ org.rocksdb.Snapshot\ org.rocksdb.StringAppendOperator\ + org.rocksdb.UInt64AddOperator\ org.rocksdb.WriteBatch\ org.rocksdb.WriteBatch.Handler\ org.rocksdb.WriteOptions\ org.rocksdb.WriteBatchWithIndex\ + org.rocksdb.WriteBufferManager\ org.rocksdb.WBWIRocksIterator NATIVE_JAVA_TEST_CLASSES = org.rocksdb.RocksDBExceptionTest\ + org.rocksdb.NativeComparatorWrapperTest.NativeStringComparatorWrapper\ org.rocksdb.WriteBatchTest\ org.rocksdb.WriteBatchTestInternalHelper @@ -77,6 +104,10 @@ JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\ org.rocksdb.ClockCacheTest\ org.rocksdb.ColumnFamilyOptionsTest\ org.rocksdb.ColumnFamilyTest\ + org.rocksdb.CompactionFilterFactoryTest\ + org.rocksdb.CompactionJobInfoTest\ + org.rocksdb.CompactionJobStatsTest\ + org.rocksdb.CompactionOptionsTest\ org.rocksdb.CompactionOptionsFIFOTest\ org.rocksdb.CompactionOptionsUniversalTest\ org.rocksdb.CompactionPriorityTest\ @@ -89,6 +120,7 @@ JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\ org.rocksdb.DirectComparatorTest\ org.rocksdb.DirectSliceTest\ org.rocksdb.EnvOptionsTest\ + org.rocksdb.HdfsEnvTest\ org.rocksdb.IngestExternalFileOptionsTest\ org.rocksdb.util.EnvironmentTest\ org.rocksdb.FilterTest\ @@ -96,12 +128,19 @@ JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\ org.rocksdb.InfoLogLevelTest\ org.rocksdb.KeyMayExistTest\ org.rocksdb.LoggerTest\ - org.rocksdb.LRUCacheTest\ + org.rocksdb.LRUCacheTest\ + org.rocksdb.MemoryUtilTest\ org.rocksdb.MemTableTest\ org.rocksdb.MergeTest\ org.rocksdb.MixedOptionsTest\ org.rocksdb.MutableColumnFamilyOptionsTest\ + org.rocksdb.MutableDBOptionsTest\ + org.rocksdb.NativeComparatorWrapperTest\ org.rocksdb.NativeLibraryLoaderTest\ + org.rocksdb.OptimisticTransactionTest\ + org.rocksdb.OptimisticTransactionDBTest\ + org.rocksdb.OptimisticTransactionOptionsTest\ + org.rocksdb.OptionsUtilTest\ org.rocksdb.OptionsTest\ org.rocksdb.PlainTableConfigTest\ org.rocksdb.RateLimiterTest\ @@ -109,17 +148,25 @@ JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\ org.rocksdb.ReadOptionsTest\ org.rocksdb.RocksDBTest\ org.rocksdb.RocksDBExceptionTest\ - org.rocksdb.RocksEnvTest\ + org.rocksdb.DefaultEnvTest\ org.rocksdb.RocksIteratorTest\ org.rocksdb.RocksMemEnvTest\ org.rocksdb.util.SizeUnitTest\ org.rocksdb.SliceTest\ org.rocksdb.SnapshotTest\ + org.rocksdb.SstFileManagerTest\ org.rocksdb.SstFileWriterTest\ + org.rocksdb.TableFilterTest\ + org.rocksdb.TimedEnvTest\ + org.rocksdb.TransactionTest\ + org.rocksdb.TransactionDBTest\ + org.rocksdb.TransactionOptionsTest\ + org.rocksdb.TransactionDBOptionsTest\ org.rocksdb.TransactionLogIteratorTest\ org.rocksdb.TtlDBTest\ org.rocksdb.StatisticsTest\ org.rocksdb.StatisticsCollectorTest\ + org.rocksdb.WalFilterTest\ org.rocksdb.WALRecoveryModeTest\ org.rocksdb.WriteBatchHandlerTest\ org.rocksdb.WriteBatchTest\ @@ -205,6 +252,20 @@ column_family_sample: java java $(JAVA_ARGS) -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) RocksDBColumnFamilySample /tmp/rocksdbjni $(AM_V_at)@rm -rf /tmp/rocksdbjni +transaction_sample: java + $(AM_V_GEN)mkdir -p $(SAMPLES_MAIN_CLASSES) + $(AM_V_at)javac -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/TransactionSample.java + $(AM_V_at)@rm -rf /tmp/rocksdbjni + java -ea -Xcheck:jni -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) TransactionSample /tmp/rocksdbjni + $(AM_V_at)@rm -rf /tmp/rocksdbjni + +optimistic_transaction_sample: java + $(AM_V_GEN)mkdir -p $(SAMPLES_MAIN_CLASSES) + $(AM_V_at)javac -cp $(MAIN_CLASSES) -d $(SAMPLES_MAIN_CLASSES) $(SAMPLES_MAIN_SRC)/OptimisticTransactionSample.java + $(AM_V_at)@rm -rf /tmp/rocksdbjni + java -ea -Xcheck:jni -Djava.library.path=target -cp $(MAIN_CLASSES):$(SAMPLES_MAIN_CLASSES) OptimisticTransactionSample /tmp/rocksdbjni + $(AM_V_at)@rm -rf /tmp/rocksdbjni + resolve_test_deps: test -d "$(JAVA_TEST_LIBDIR)" || mkdir -p "$(JAVA_TEST_LIBDIR)" test -s "$(JAVA_JUNIT_JAR)" || cp $(MVN_LOCAL)/junit/junit/4.12/junit-4.12.jar $(JAVA_TEST_LIBDIR) || curl -k -L -o $(JAVA_JUNIT_JAR) $(SEARCH_REPO_URL)junit/junit/4.12/junit-4.12.jar diff --git a/thirdparty/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java b/thirdparty/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java index 8af6d2edfb..67f6a5cc05 100644 --- a/thirdparty/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java +++ b/thirdparty/rocksdb/java/benchmark/src/main/java/org/rocksdb/benchmark/DbBenchmark.java @@ -493,7 +493,7 @@ private void prepareOptions(Options options) throws RocksDBException { options.setCreateIfMissing(false); } if (useMemenv_) { - options.setEnv(new RocksMemEnv()); + options.setEnv(new RocksMemEnv(Env.getDefault())); } switch (memtable_) { case "skip_list": @@ -543,6 +543,7 @@ private void prepareOptions(Options options) throws RocksDBException { (Integer)flags_.get(Flag.max_background_compactions)); options.setMaxBackgroundFlushes( (Integer)flags_.get(Flag.max_background_flushes)); + options.setMaxBackgroundJobs((Integer) flags_.get(Flag.max_background_jobs)); options.setMaxOpenFiles( (Integer)flags_.get(Flag.open_files)); options.setUseFsync( @@ -1116,6 +1117,14 @@ private enum Flag { return Integer.parseInt(value); } }, + max_background_jobs(defaultOptions_.maxBackgroundJobs(), + "The maximum number of concurrent background jobs\n" + + "\tthat can occur in parallel.") { + @Override + public Object parseValue(String value) { + return Integer.parseInt(value); + } + }, /* TODO(yhchiang): enable the following compaction_style((int32_t) defaultOptions_.compactionStyle(), "style of compaction: level-based vs universal.") { diff --git a/thirdparty/rocksdb/java/crossbuild/build-linux-centos.sh b/thirdparty/rocksdb/java/crossbuild/build-linux-centos.sh index 2832eed8b8..c532398f66 100755 --- a/thirdparty/rocksdb/java/crossbuild/build-linux-centos.sh +++ b/thirdparty/rocksdb/java/crossbuild/build-linux-centos.sh @@ -9,7 +9,7 @@ sudo rm -f /etc/yum/vars/releasever sudo yum -y install epel-release # install all required packages for rocksdb that are available through yum -sudo yum -y install openssl java-1.7.0-openjdk-devel zlib-devel bzip2-devel lz4-devel snappy-devel libzstd-devel +sudo yum -y install openssl java-1.7.0-openjdk-devel zlib-devel bzip2-devel lz4-devel snappy-devel libzstd-devel jemalloc-devel # install gcc/g++ 4.8.2 from tru/devtools-2 sudo wget -O /etc/yum.repos.d/devtools-2.repo https://people.centos.org/tru/devtools-2/devtools-2.repo @@ -26,7 +26,6 @@ export JAVA_HOME=/usr/lib/jvm/java-1.7.0 # build rocksdb cd /rocksdb scl enable devtoolset-2 'make jclean clean' -scl enable devtoolset-2 'PORTABLE=1 make rocksdbjavastatic' +scl enable devtoolset-2 'PORTABLE=1 make -j8 rocksdbjavastatic' cp /rocksdb/java/target/librocksdbjni-* /rocksdb-build cp /rocksdb/java/target/rocksdbjni-* /rocksdb-build - diff --git a/thirdparty/rocksdb/java/crossbuild/docker-build-linux-centos.sh b/thirdparty/rocksdb/java/crossbuild/docker-build-linux-centos.sh index 44a8bfe06d..d894b14a2e 100755 --- a/thirdparty/rocksdb/java/crossbuild/docker-build-linux-centos.sh +++ b/thirdparty/rocksdb/java/crossbuild/docker-build-linux-centos.sh @@ -1,11 +1,28 @@ #!/usr/bin/env bash set -e +#set -x rm -rf /rocksdb-local cp -r /rocksdb-host /rocksdb-local cd /rocksdb-local -scl enable devtoolset-2 'make jclean clean' -scl enable devtoolset-2 'PORTABLE=1 make rocksdbjavastatic' + +# Use scl devtoolset if available (i.e. CentOS <7) +if hash scl 2>/dev/null; then + if scl --list | grep -q 'devtoolset-7'; then + scl enable devtoolset-7 'make jclean clean' + scl enable devtoolset-7 'PORTABLE=1 make -j6 rocksdbjavastatic' + elif scl --list | grep -q 'devtoolset-2'; then + scl enable devtoolset-2 'make jclean clean' + scl enable devtoolset-2 'PORTABLE=1 make -j6 rocksdbjavastatic' + else + echo "Could not find devtoolset" + exit 1; + fi +else + make jclean clean + PORTABLE=1 make -j6 rocksdbjavastatic +fi + cp java/target/librocksdbjni-linux*.so java/target/rocksdbjni-*-linux*.jar /rocksdb-host/java/target diff --git a/thirdparty/rocksdb/java/jdb_bench.sh b/thirdparty/rocksdb/java/jdb_bench.sh index 9665de785e..0a07fa8e2f 100755 --- a/thirdparty/rocksdb/java/jdb_bench.sh +++ b/thirdparty/rocksdb/java/jdb_bench.sh @@ -1,3 +1,4 @@ +# shellcheck disable=SC2148 PLATFORM=64 if [ `getconf LONG_BIT` != "64" ] then @@ -7,4 +8,5 @@ fi ROCKS_JAR=`find target -name rocksdbjni*.jar` echo "Running benchmark in $PLATFORM-Bit mode." +# shellcheck disable=SC2068 java -server -d$PLATFORM -XX:NewSize=4m -XX:+AggressiveOpts -Djava.library.path=target -cp "${ROCKS_JAR}:benchmark/target/classes" org.rocksdb.benchmark.DbBenchmark $@ diff --git a/thirdparty/rocksdb/java/rocksjni/backupablejni.cc b/thirdparty/rocksdb/java/rocksjni/backupablejni.cc index 28db2b0210..c5ac30377c 100644 --- a/thirdparty/rocksdb/java/rocksjni/backupablejni.cc +++ b/thirdparty/rocksdb/java/rocksjni/backupablejni.cc @@ -7,15 +7,15 @@ // calling c++ rocksdb::BackupEnginge and rocksdb::BackupableDBOptions methods // from Java side. +#include #include #include -#include #include #include #include "include/org_rocksdb_BackupableDBOptions.h" -#include "rocksjni/portal.h" #include "rocksdb/utilities/backupable_db.h" +#include "rocksjni/portal.h" /////////////////////////////////////////////////////////////////////////// // BackupDBOptions @@ -26,9 +26,9 @@ * Signature: (Ljava/lang/String;)J */ jlong Java_org_rocksdb_BackupableDBOptions_newBackupableDBOptions( - JNIEnv* env, jclass jcls, jstring jpath) { + JNIEnv* env, jclass /*jcls*/, jstring jpath) { const char* cpath = env->GetStringUTFChars(jpath, nullptr); - if(cpath == nullptr) { + if (cpath == nullptr) { // exception thrown: OutOfMemoryError return 0; } @@ -42,8 +42,9 @@ jlong Java_org_rocksdb_BackupableDBOptions_newBackupableDBOptions( * Method: backupDir * Signature: (J)Ljava/lang/String; */ -jstring Java_org_rocksdb_BackupableDBOptions_backupDir( - JNIEnv* env, jobject jopt, jlong jhandle) { +jstring Java_org_rocksdb_BackupableDBOptions_backupDir(JNIEnv* env, + jobject /*jopt*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return env->NewStringUTF(bopt->backup_dir.c_str()); } @@ -54,7 +55,7 @@ jstring Java_org_rocksdb_BackupableDBOptions_backupDir( * Signature: (JJ)V */ void Java_org_rocksdb_BackupableDBOptions_setBackupEnv( - JNIEnv* env, jobject jopt, jlong jhandle, jlong jrocks_env_handle) { + JNIEnv* /*env*/, jobject /*jopt*/, jlong jhandle, jlong jrocks_env_handle) { auto* bopt = reinterpret_cast(jhandle); auto* rocks_env = reinterpret_cast(jrocks_env_handle); bopt->backup_env = rocks_env; @@ -65,8 +66,10 @@ void Java_org_rocksdb_BackupableDBOptions_setBackupEnv( * Method: setShareTableFiles * Signature: (JZ)V */ -void Java_org_rocksdb_BackupableDBOptions_setShareTableFiles( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { +void Java_org_rocksdb_BackupableDBOptions_setShareTableFiles(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jboolean flag) { auto* bopt = reinterpret_cast(jhandle); bopt->share_table_files = flag; } @@ -76,8 +79,9 @@ void Java_org_rocksdb_BackupableDBOptions_setShareTableFiles( * Method: shareTableFiles * Signature: (J)Z */ -jboolean Java_org_rocksdb_BackupableDBOptions_shareTableFiles( - JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_BackupableDBOptions_shareTableFiles(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return bopt->share_table_files; } @@ -87,11 +91,13 @@ jboolean Java_org_rocksdb_BackupableDBOptions_shareTableFiles( * Method: setInfoLog * Signature: (JJ)V */ -void Java_org_rocksdb_BackupableDBOptions_setInfoLog( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jlogger_handle) { +void Java_org_rocksdb_BackupableDBOptions_setInfoLog(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jlong /*jlogger_handle*/) { auto* bopt = reinterpret_cast(jhandle); auto* sptr_logger = - reinterpret_cast *>(jhandle); + reinterpret_cast*>(jhandle); bopt->info_log = sptr_logger->get(); } @@ -100,8 +106,10 @@ void Java_org_rocksdb_BackupableDBOptions_setInfoLog( * Method: setSync * Signature: (JZ)V */ -void Java_org_rocksdb_BackupableDBOptions_setSync( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { +void Java_org_rocksdb_BackupableDBOptions_setSync(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jboolean flag) { auto* bopt = reinterpret_cast(jhandle); bopt->sync = flag; } @@ -111,8 +119,9 @@ void Java_org_rocksdb_BackupableDBOptions_setSync( * Method: sync * Signature: (J)Z */ -jboolean Java_org_rocksdb_BackupableDBOptions_sync( - JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_BackupableDBOptions_sync(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return bopt->sync; } @@ -122,8 +131,10 @@ jboolean Java_org_rocksdb_BackupableDBOptions_sync( * Method: setDestroyOldData * Signature: (JZ)V */ -void Java_org_rocksdb_BackupableDBOptions_setDestroyOldData( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { +void Java_org_rocksdb_BackupableDBOptions_setDestroyOldData(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jboolean flag) { auto* bopt = reinterpret_cast(jhandle); bopt->destroy_old_data = flag; } @@ -133,8 +144,9 @@ void Java_org_rocksdb_BackupableDBOptions_setDestroyOldData( * Method: destroyOldData * Signature: (J)Z */ -jboolean Java_org_rocksdb_BackupableDBOptions_destroyOldData( - JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_BackupableDBOptions_destroyOldData(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return bopt->destroy_old_data; } @@ -144,8 +156,10 @@ jboolean Java_org_rocksdb_BackupableDBOptions_destroyOldData( * Method: setBackupLogFiles * Signature: (JZ)V */ -void Java_org_rocksdb_BackupableDBOptions_setBackupLogFiles( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { +void Java_org_rocksdb_BackupableDBOptions_setBackupLogFiles(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jboolean flag) { auto* bopt = reinterpret_cast(jhandle); bopt->backup_log_files = flag; } @@ -155,8 +169,9 @@ void Java_org_rocksdb_BackupableDBOptions_setBackupLogFiles( * Method: backupLogFiles * Signature: (J)Z */ -jboolean Java_org_rocksdb_BackupableDBOptions_backupLogFiles( - JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_BackupableDBOptions_backupLogFiles(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return bopt->backup_log_files; } @@ -167,7 +182,8 @@ jboolean Java_org_rocksdb_BackupableDBOptions_backupLogFiles( * Signature: (JJ)V */ void Java_org_rocksdb_BackupableDBOptions_setBackupRateLimit( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jbackup_rate_limit) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jbackup_rate_limit) { auto* bopt = reinterpret_cast(jhandle); bopt->backup_rate_limit = jbackup_rate_limit; } @@ -177,8 +193,9 @@ void Java_org_rocksdb_BackupableDBOptions_setBackupRateLimit( * Method: backupRateLimit * Signature: (J)J */ -jlong Java_org_rocksdb_BackupableDBOptions_backupRateLimit( - JNIEnv* env, jobject jobj, jlong jhandle) { +jlong Java_org_rocksdb_BackupableDBOptions_backupRateLimit(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return bopt->backup_rate_limit; } @@ -189,10 +206,12 @@ jlong Java_org_rocksdb_BackupableDBOptions_backupRateLimit( * Signature: (JJ)V */ void Java_org_rocksdb_BackupableDBOptions_setBackupRateLimiter( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jrate_limiter_handle) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jrate_limiter_handle) { auto* bopt = reinterpret_cast(jhandle); auto* sptr_rate_limiter = - reinterpret_cast *>(jrate_limiter_handle); + reinterpret_cast*>( + jrate_limiter_handle); bopt->backup_rate_limiter = *sptr_rate_limiter; } @@ -202,7 +221,8 @@ void Java_org_rocksdb_BackupableDBOptions_setBackupRateLimiter( * Signature: (JJ)V */ void Java_org_rocksdb_BackupableDBOptions_setRestoreRateLimit( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jrestore_rate_limit) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jrestore_rate_limit) { auto* bopt = reinterpret_cast(jhandle); bopt->restore_rate_limit = jrestore_rate_limit; } @@ -212,8 +232,9 @@ void Java_org_rocksdb_BackupableDBOptions_setRestoreRateLimit( * Method: restoreRateLimit * Signature: (J)J */ -jlong Java_org_rocksdb_BackupableDBOptions_restoreRateLimit( - JNIEnv* env, jobject jobj, jlong jhandle) { +jlong Java_org_rocksdb_BackupableDBOptions_restoreRateLimit(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return bopt->restore_rate_limit; } @@ -224,10 +245,12 @@ jlong Java_org_rocksdb_BackupableDBOptions_restoreRateLimit( * Signature: (JJ)V */ void Java_org_rocksdb_BackupableDBOptions_setRestoreRateLimiter( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jrate_limiter_handle) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jrate_limiter_handle) { auto* bopt = reinterpret_cast(jhandle); auto* sptr_rate_limiter = - reinterpret_cast *>(jrate_limiter_handle); + reinterpret_cast*>( + jrate_limiter_handle); bopt->restore_rate_limiter = *sptr_rate_limiter; } @@ -237,7 +260,7 @@ void Java_org_rocksdb_BackupableDBOptions_setRestoreRateLimiter( * Signature: (JZ)V */ void Java_org_rocksdb_BackupableDBOptions_setShareFilesWithChecksum( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean flag) { auto* bopt = reinterpret_cast(jhandle); bopt->share_files_with_checksum = flag; } @@ -248,7 +271,7 @@ void Java_org_rocksdb_BackupableDBOptions_setShareFilesWithChecksum( * Signature: (J)Z */ jboolean Java_org_rocksdb_BackupableDBOptions_shareFilesWithChecksum( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return bopt->share_files_with_checksum; } @@ -259,10 +282,10 @@ jboolean Java_org_rocksdb_BackupableDBOptions_shareFilesWithChecksum( * Signature: (JI)V */ void Java_org_rocksdb_BackupableDBOptions_setMaxBackgroundOperations( - JNIEnv* env, jobject jobj, jlong jhandle, jint max_background_operations) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jint max_background_operations) { auto* bopt = reinterpret_cast(jhandle); - bopt->max_background_operations = - static_cast(max_background_operations); + bopt->max_background_operations = static_cast(max_background_operations); } /* @@ -271,7 +294,7 @@ void Java_org_rocksdb_BackupableDBOptions_setMaxBackgroundOperations( * Signature: (J)I */ jint Java_org_rocksdb_BackupableDBOptions_maxBackgroundOperations( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return static_cast(bopt->max_background_operations); } @@ -282,7 +305,7 @@ jint Java_org_rocksdb_BackupableDBOptions_maxBackgroundOperations( * Signature: (JJ)V */ void Java_org_rocksdb_BackupableDBOptions_setCallbackTriggerIntervalSize( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jlong jcallback_trigger_interval_size) { auto* bopt = reinterpret_cast(jhandle); bopt->callback_trigger_interval_size = @@ -295,7 +318,7 @@ void Java_org_rocksdb_BackupableDBOptions_setCallbackTriggerIntervalSize( * Signature: (J)J */ jlong Java_org_rocksdb_BackupableDBOptions_callbackTriggerIntervalSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); return static_cast(bopt->callback_trigger_interval_size); } @@ -305,8 +328,9 @@ jlong Java_org_rocksdb_BackupableDBOptions_callbackTriggerIntervalSize( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_BackupableDBOptions_disposeInternal( - JNIEnv* env, jobject jopt, jlong jhandle) { +void Java_org_rocksdb_BackupableDBOptions_disposeInternal(JNIEnv* /*env*/, + jobject /*jopt*/, + jlong jhandle) { auto* bopt = reinterpret_cast(jhandle); assert(bopt != nullptr); delete bopt; diff --git a/thirdparty/rocksdb/java/rocksjni/backupenginejni.cc b/thirdparty/rocksdb/java/rocksjni/backupenginejni.cc index 004de976cb..e62b0b4f0f 100644 --- a/thirdparty/rocksdb/java/rocksjni/backupenginejni.cc +++ b/thirdparty/rocksdb/java/rocksjni/backupenginejni.cc @@ -18,16 +18,15 @@ * Method: open * Signature: (JJ)J */ -jlong Java_org_rocksdb_BackupEngine_open( - JNIEnv* env, jclass jcls, jlong env_handle, - jlong backupable_db_options_handle) { +jlong Java_org_rocksdb_BackupEngine_open(JNIEnv* env, jclass /*jcls*/, + jlong env_handle, + jlong backupable_db_options_handle) { auto* rocks_env = reinterpret_cast(env_handle); - auto* backupable_db_options = - reinterpret_cast( + auto* backupable_db_options = reinterpret_cast( backupable_db_options_handle); rocksdb::BackupEngine* backup_engine; - auto status = rocksdb::BackupEngine::Open(rocks_env, - *backupable_db_options, &backup_engine); + auto status = rocksdb::BackupEngine::Open(rocks_env, *backupable_db_options, + &backup_engine); if (status.ok()) { return reinterpret_cast(backup_engine); @@ -43,12 +42,42 @@ jlong Java_org_rocksdb_BackupEngine_open( * Signature: (JJZ)V */ void Java_org_rocksdb_BackupEngine_createNewBackup( - JNIEnv* env, jobject jbe, jlong jbe_handle, jlong db_handle, + JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jlong db_handle, jboolean jflush_before_backup) { auto* db = reinterpret_cast(db_handle); auto* backup_engine = reinterpret_cast(jbe_handle); - auto status = backup_engine->CreateNewBackup(db, - static_cast(jflush_before_backup)); + auto status = backup_engine->CreateNewBackup( + db, static_cast(jflush_before_backup)); + + if (status.ok()) { + return; + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); +} + +/* + * Class: org_rocksdb_BackupEngine + * Method: createNewBackupWithMetadata + * Signature: (JJLjava/lang/String;Z)V + */ +void Java_org_rocksdb_BackupEngine_createNewBackupWithMetadata( + JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jlong db_handle, + jstring japp_metadata, jboolean jflush_before_backup) { + auto* db = reinterpret_cast(db_handle); + auto* backup_engine = reinterpret_cast(jbe_handle); + + jboolean has_exception = JNI_FALSE; + std::string app_metadata = + rocksdb::JniUtil::copyStdString(env, japp_metadata, &has_exception); + if (has_exception == JNI_TRUE) { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, "Could not copy jstring to std::string"); + return; + } + + auto status = backup_engine->CreateNewBackupWithMetadata( + db, app_metadata, static_cast(jflush_before_backup)); if (status.ok()) { return; @@ -62,8 +91,9 @@ void Java_org_rocksdb_BackupEngine_createNewBackup( * Method: getBackupInfo * Signature: (J)Ljava/util/List; */ -jobject Java_org_rocksdb_BackupEngine_getBackupInfo( - JNIEnv* env, jobject jbe, jlong jbe_handle) { +jobject Java_org_rocksdb_BackupEngine_getBackupInfo(JNIEnv* env, + jobject /*jbe*/, + jlong jbe_handle) { auto* backup_engine = reinterpret_cast(jbe_handle); std::vector backup_infos; backup_engine->GetBackupInfo(&backup_infos); @@ -75,24 +105,25 @@ jobject Java_org_rocksdb_BackupEngine_getBackupInfo( * Method: getCorruptedBackups * Signature: (J)[I */ -jintArray Java_org_rocksdb_BackupEngine_getCorruptedBackups( - JNIEnv* env, jobject jbe, jlong jbe_handle) { +jintArray Java_org_rocksdb_BackupEngine_getCorruptedBackups(JNIEnv* env, + jobject /*jbe*/, + jlong jbe_handle) { auto* backup_engine = reinterpret_cast(jbe_handle); std::vector backup_ids; backup_engine->GetCorruptedBackups(&backup_ids); // store backupids in int array std::vector int_backup_ids(backup_ids.begin(), backup_ids.end()); - + // Store ints in java array // Its ok to loose precision here (64->32) jsize ret_backup_ids_size = static_cast(backup_ids.size()); jintArray ret_backup_ids = env->NewIntArray(ret_backup_ids_size); - if(ret_backup_ids == nullptr) { + if (ret_backup_ids == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } env->SetIntArrayRegion(ret_backup_ids, 0, ret_backup_ids_size, - int_backup_ids.data()); + int_backup_ids.data()); return ret_backup_ids; } @@ -101,8 +132,8 @@ jintArray Java_org_rocksdb_BackupEngine_getCorruptedBackups( * Method: garbageCollect * Signature: (J)V */ -void Java_org_rocksdb_BackupEngine_garbageCollect( - JNIEnv* env, jobject jbe, jlong jbe_handle) { +void Java_org_rocksdb_BackupEngine_garbageCollect(JNIEnv* env, jobject /*jbe*/, + jlong jbe_handle) { auto* backup_engine = reinterpret_cast(jbe_handle); auto status = backup_engine->GarbageCollect(); @@ -118,12 +149,12 @@ void Java_org_rocksdb_BackupEngine_garbageCollect( * Method: purgeOldBackups * Signature: (JI)V */ -void Java_org_rocksdb_BackupEngine_purgeOldBackups( - JNIEnv* env, jobject jbe, jlong jbe_handle, jint jnum_backups_to_keep) { +void Java_org_rocksdb_BackupEngine_purgeOldBackups(JNIEnv* env, jobject /*jbe*/, + jlong jbe_handle, + jint jnum_backups_to_keep) { auto* backup_engine = reinterpret_cast(jbe_handle); - auto status = - backup_engine-> - PurgeOldBackups(static_cast(jnum_backups_to_keep)); + auto status = backup_engine->PurgeOldBackups( + static_cast(jnum_backups_to_keep)); if (status.ok()) { return; @@ -137,8 +168,9 @@ void Java_org_rocksdb_BackupEngine_purgeOldBackups( * Method: deleteBackup * Signature: (JI)V */ -void Java_org_rocksdb_BackupEngine_deleteBackup( - JNIEnv* env, jobject jbe, jlong jbe_handle, jint jbackup_id) { +void Java_org_rocksdb_BackupEngine_deleteBackup(JNIEnv* env, jobject /*jbe*/, + jlong jbe_handle, + jint jbackup_id) { auto* backup_engine = reinterpret_cast(jbe_handle); auto status = backup_engine->DeleteBackup(static_cast(jbackup_id)); @@ -156,26 +188,25 @@ void Java_org_rocksdb_BackupEngine_deleteBackup( * Signature: (JILjava/lang/String;Ljava/lang/String;J)V */ void Java_org_rocksdb_BackupEngine_restoreDbFromBackup( - JNIEnv* env, jobject jbe, jlong jbe_handle, jint jbackup_id, + JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jint jbackup_id, jstring jdb_dir, jstring jwal_dir, jlong jrestore_options_handle) { auto* backup_engine = reinterpret_cast(jbe_handle); const char* db_dir = env->GetStringUTFChars(jdb_dir, nullptr); - if(db_dir == nullptr) { + if (db_dir == nullptr) { // exception thrown: OutOfMemoryError return; } const char* wal_dir = env->GetStringUTFChars(jwal_dir, nullptr); - if(wal_dir == nullptr) { + if (wal_dir == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseStringUTFChars(jdb_dir, db_dir); return; } auto* restore_options = reinterpret_cast(jrestore_options_handle); - auto status = - backup_engine->RestoreDBFromBackup( - static_cast(jbackup_id), db_dir, wal_dir, - *restore_options); + auto status = backup_engine->RestoreDBFromBackup( + static_cast(jbackup_id), db_dir, wal_dir, + *restore_options); env->ReleaseStringUTFChars(jwal_dir, wal_dir); env->ReleaseStringUTFChars(jdb_dir, db_dir); @@ -193,25 +224,24 @@ void Java_org_rocksdb_BackupEngine_restoreDbFromBackup( * Signature: (JLjava/lang/String;Ljava/lang/String;J)V */ void Java_org_rocksdb_BackupEngine_restoreDbFromLatestBackup( - JNIEnv* env, jobject jbe, jlong jbe_handle, jstring jdb_dir, + JNIEnv* env, jobject /*jbe*/, jlong jbe_handle, jstring jdb_dir, jstring jwal_dir, jlong jrestore_options_handle) { auto* backup_engine = reinterpret_cast(jbe_handle); const char* db_dir = env->GetStringUTFChars(jdb_dir, nullptr); - if(db_dir == nullptr) { + if (db_dir == nullptr) { // exception thrown: OutOfMemoryError return; } const char* wal_dir = env->GetStringUTFChars(jwal_dir, nullptr); - if(wal_dir == nullptr) { + if (wal_dir == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseStringUTFChars(jdb_dir, db_dir); return; } auto* restore_options = reinterpret_cast(jrestore_options_handle); - auto status = - backup_engine->RestoreDBFromLatestBackup(db_dir, wal_dir, - *restore_options); + auto status = backup_engine->RestoreDBFromLatestBackup(db_dir, wal_dir, + *restore_options); env->ReleaseStringUTFChars(jwal_dir, wal_dir); env->ReleaseStringUTFChars(jdb_dir, db_dir); @@ -228,8 +258,9 @@ void Java_org_rocksdb_BackupEngine_restoreDbFromLatestBackup( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_BackupEngine_disposeInternal( - JNIEnv* env, jobject jbe, jlong jbe_handle) { +void Java_org_rocksdb_BackupEngine_disposeInternal(JNIEnv* /*env*/, + jobject /*jbe*/, + jlong jbe_handle) { auto* be = reinterpret_cast(jbe_handle); assert(be != nullptr); delete be; diff --git a/thirdparty/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc b/thirdparty/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc index 9d77559ab5..799e25e3f4 100644 --- a/thirdparty/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc +++ b/thirdparty/rocksdb/java/rocksjni/cassandra_compactionfilterjni.cc @@ -11,12 +11,13 @@ /* * Class: org_rocksdb_CassandraCompactionFilter * Method: createNewCassandraCompactionFilter0 - * Signature: ()J + * Signature: (ZI)J */ jlong Java_org_rocksdb_CassandraCompactionFilter_createNewCassandraCompactionFilter0( - JNIEnv* env, jclass jcls, jboolean purge_ttl_on_expiration) { - auto* compaction_filter = - new rocksdb::cassandra::CassandraCompactionFilter(purge_ttl_on_expiration); + JNIEnv* /*env*/, jclass /*jcls*/, jboolean purge_ttl_on_expiration, + jint gc_grace_period_in_seconds) { + auto* compaction_filter = new rocksdb::cassandra::CassandraCompactionFilter( + purge_ttl_on_expiration, gc_grace_period_in_seconds); // set the native handle to our native compaction filter return reinterpret_cast(compaction_filter); } diff --git a/thirdparty/rocksdb/java/rocksjni/cassandra_value_operator.cc b/thirdparty/rocksdb/java/rocksjni/cassandra_value_operator.cc index aa58eccc24..73b3dcc637 100644 --- a/thirdparty/rocksdb/java/rocksjni/cassandra_value_operator.cc +++ b/thirdparty/rocksdb/java/rocksjni/cassandra_value_operator.cc @@ -3,33 +3,35 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). +#include #include #include -#include -#include #include +#include #include "include/org_rocksdb_CassandraValueMergeOperator.h" -#include "rocksjni/portal.h" #include "rocksdb/db.h" +#include "rocksdb/memtablerep.h" +#include "rocksdb/merge_operator.h" #include "rocksdb/options.h" +#include "rocksdb/slice_transform.h" #include "rocksdb/statistics.h" -#include "rocksdb/memtablerep.h" #include "rocksdb/table.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/merge_operator.h" +#include "rocksjni/portal.h" #include "utilities/cassandra/merge_operator.h" /* * Class: org_rocksdb_CassandraValueMergeOperator * Method: newSharedCassandraValueMergeOperator - * Signature: ()J + * Signature: (II)J */ -jlong Java_org_rocksdb_CassandraValueMergeOperator_newSharedCassandraValueMergeOperator -(JNIEnv* env, jclass jclazz) { - auto* sptr_string_append_op = new std::shared_ptr( - rocksdb::CassandraValueMergeOperator::CreateSharedInstance()); - return reinterpret_cast(sptr_string_append_op); +jlong Java_org_rocksdb_CassandraValueMergeOperator_newSharedCassandraValueMergeOperator( + JNIEnv* /*env*/, jclass /*jclazz*/, jint gcGracePeriodInSeconds, + jint operands_limit) { + auto* op = new std::shared_ptr( + new rocksdb::cassandra::CassandraValueMergeOperator( + gcGracePeriodInSeconds, operands_limit)); + return reinterpret_cast(op); } /* @@ -38,8 +40,8 @@ jlong Java_org_rocksdb_CassandraValueMergeOperator_newSharedCassandraValueMergeO * Signature: (J)V */ void Java_org_rocksdb_CassandraValueMergeOperator_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { - auto* sptr_string_append_op = - reinterpret_cast* >(jhandle); - delete sptr_string_append_op; // delete std::shared_ptr + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* op = + reinterpret_cast*>(jhandle); + delete op; } diff --git a/thirdparty/rocksdb/java/rocksjni/checkpoint.cc b/thirdparty/rocksdb/java/rocksjni/checkpoint.cc index 426f5d029e..f67f016296 100644 --- a/thirdparty/rocksdb/java/rocksjni/checkpoint.cc +++ b/thirdparty/rocksdb/java/rocksjni/checkpoint.cc @@ -6,22 +6,23 @@ // This file implements the "bridge" between Java and C++ and enables // calling c++ rocksdb::Checkpoint methods from Java side. +#include #include #include -#include #include #include "include/org_rocksdb_Checkpoint.h" -#include "rocksjni/portal.h" #include "rocksdb/db.h" #include "rocksdb/utilities/checkpoint.h" +#include "rocksjni/portal.h" /* * Class: org_rocksdb_Checkpoint * Method: newCheckpoint * Signature: (J)J */ -jlong Java_org_rocksdb_Checkpoint_newCheckpoint(JNIEnv* env, - jclass jclazz, jlong jdb_handle) { +jlong Java_org_rocksdb_Checkpoint_newCheckpoint(JNIEnv* /*env*/, + jclass /*jclazz*/, + jlong jdb_handle) { auto* db = reinterpret_cast(jdb_handle); rocksdb::Checkpoint* checkpoint; rocksdb::Checkpoint::Create(db, &checkpoint); @@ -33,8 +34,9 @@ jlong Java_org_rocksdb_Checkpoint_newCheckpoint(JNIEnv* env, * Method: dispose * Signature: (J)V */ -void Java_org_rocksdb_Checkpoint_disposeInternal(JNIEnv* env, jobject jobj, - jlong jhandle) { +void Java_org_rocksdb_Checkpoint_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* checkpoint = reinterpret_cast(jhandle); assert(checkpoint != nullptr); delete checkpoint; @@ -45,24 +47,21 @@ void Java_org_rocksdb_Checkpoint_disposeInternal(JNIEnv* env, jobject jobj, * Method: createCheckpoint * Signature: (JLjava/lang/String;)V */ -void Java_org_rocksdb_Checkpoint_createCheckpoint( - JNIEnv* env, jobject jobj, jlong jcheckpoint_handle, - jstring jcheckpoint_path) { - const char* checkpoint_path = env->GetStringUTFChars( - jcheckpoint_path, 0); - if(checkpoint_path == nullptr) { +void Java_org_rocksdb_Checkpoint_createCheckpoint(JNIEnv* env, jobject /*jobj*/, + jlong jcheckpoint_handle, + jstring jcheckpoint_path) { + const char* checkpoint_path = env->GetStringUTFChars(jcheckpoint_path, 0); + if (checkpoint_path == nullptr) { // exception thrown: OutOfMemoryError return; } - auto* checkpoint = reinterpret_cast( - jcheckpoint_handle); - rocksdb::Status s = checkpoint->CreateCheckpoint( - checkpoint_path); - + auto* checkpoint = reinterpret_cast(jcheckpoint_handle); + rocksdb::Status s = checkpoint->CreateCheckpoint(checkpoint_path); + env->ReleaseStringUTFChars(jcheckpoint_path, checkpoint_path); - + if (!s.ok()) { - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } } diff --git a/thirdparty/rocksdb/java/rocksjni/clock_cache.cc b/thirdparty/rocksdb/java/rocksjni/clock_cache.cc index 0a4d7b28d6..b1cf0855ed 100644 --- a/thirdparty/rocksdb/java/rocksjni/clock_cache.cc +++ b/thirdparty/rocksdb/java/rocksjni/clock_cache.cc @@ -17,12 +17,11 @@ * Signature: (JIZ)J */ jlong Java_org_rocksdb_ClockCache_newClockCache( - JNIEnv* env, jclass jcls, jlong jcapacity, jint jnum_shard_bits, + JNIEnv* /*env*/, jclass /*jcls*/, jlong jcapacity, jint jnum_shard_bits, jboolean jstrict_capacity_limit) { auto* sptr_clock_cache = new std::shared_ptr(rocksdb::NewClockCache( - static_cast(jcapacity), - static_cast(jnum_shard_bits), + static_cast(jcapacity), static_cast(jnum_shard_bits), static_cast(jstrict_capacity_limit))); return reinterpret_cast(sptr_clock_cache); } @@ -32,9 +31,10 @@ jlong Java_org_rocksdb_ClockCache_newClockCache( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_ClockCache_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { +void Java_org_rocksdb_ClockCache_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* sptr_clock_cache = - reinterpret_cast *>(jhandle); + reinterpret_cast*>(jhandle); delete sptr_clock_cache; // delete std::shared_ptr } diff --git a/thirdparty/rocksdb/java/rocksjni/columnfamilyhandle.cc b/thirdparty/rocksdb/java/rocksjni/columnfamilyhandle.cc index 6e40a7e010..ed28057386 100644 --- a/thirdparty/rocksdb/java/rocksjni/columnfamilyhandle.cc +++ b/thirdparty/rocksdb/java/rocksjni/columnfamilyhandle.cc @@ -3,24 +3,70 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). // -// This file implements the "bridge" between Java and C++ and enables -// calling c++ rocksdb::Iterator methods from Java side. +// This file implements the "bridge" between Java and C++ for +// rocksdb::ColumnFamilyHandle. +#include #include #include -#include #include "include/org_rocksdb_ColumnFamilyHandle.h" #include "rocksjni/portal.h" +/* + * Class: org_rocksdb_ColumnFamilyHandle + * Method: getName + * Signature: (J)[B + */ +jbyteArray Java_org_rocksdb_ColumnFamilyHandle_getName(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle) { + auto* cfh = reinterpret_cast(jhandle); + std::string cf_name = cfh->GetName(); + return rocksdb::JniUtil::copyBytes(env, cf_name); +} + +/* + * Class: org_rocksdb_ColumnFamilyHandle + * Method: getID + * Signature: (J)I + */ +jint Java_org_rocksdb_ColumnFamilyHandle_getID(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* cfh = reinterpret_cast(jhandle); + const int32_t id = cfh->GetID(); + return static_cast(id); +} + +/* + * Class: org_rocksdb_ColumnFamilyHandle + * Method: getDescriptor + * Signature: (J)Lorg/rocksdb/ColumnFamilyDescriptor; + */ +jobject Java_org_rocksdb_ColumnFamilyHandle_getDescriptor(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle) { + auto* cfh = reinterpret_cast(jhandle); + rocksdb::ColumnFamilyDescriptor desc; + rocksdb::Status s = cfh->GetDescriptor(&desc); + if (s.ok()) { + return rocksdb::ColumnFamilyDescriptorJni::construct(env, &desc); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; + } +} + /* * Class: org_rocksdb_ColumnFamilyHandle * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_ColumnFamilyHandle_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { - auto* cfh = reinterpret_cast(handle); +void Java_org_rocksdb_ColumnFamilyHandle_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* cfh = reinterpret_cast(jhandle); assert(cfh != nullptr); delete cfh; } diff --git a/thirdparty/rocksdb/java/rocksjni/compact_range_options.cc b/thirdparty/rocksdb/java/rocksjni/compact_range_options.cc new file mode 100644 index 0000000000..cc9ac859e8 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/compact_range_options.cc @@ -0,0 +1,196 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::CompactRangeOptions. + +#include + +#include "include/org_rocksdb_CompactRangeOptions.h" +#include "rocksdb/options.h" +#include "rocksjni/portal.h" + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: newCompactRangeOptions + * Signature: ()J + */ +jlong Java_org_rocksdb_CompactRangeOptions_newCompactRangeOptions( + JNIEnv* /*env*/, jclass /*jclazz*/) { + auto* options = new rocksdb::CompactRangeOptions(); + return reinterpret_cast(options); +} + + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: exclusiveManualCompaction + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_CompactRangeOptions_exclusiveManualCompaction( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return static_cast(options->exclusive_manual_compaction); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: setExclusiveManualCompaction + * Signature: (JZ)V + */ +void Java_org_rocksdb_CompactRangeOptions_setExclusiveManualCompaction( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean exclusive_manual_compaction) { + auto* options = + reinterpret_cast(jhandle); + options->exclusive_manual_compaction = static_cast(exclusive_manual_compaction); +} + + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: bottommostLevelCompaction + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactRangeOptions_bottommostLevelCompaction( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return rocksdb::BottommostLevelCompactionJni::toJavaBottommostLevelCompaction( + options->bottommost_level_compaction); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: setBottommostLevelCompaction + * Signature: (JI)V + */ +void Java_org_rocksdb_CompactRangeOptions_setBottommostLevelCompaction( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jint bottommost_level_compaction) { + auto* options = reinterpret_cast(jhandle); + options->bottommost_level_compaction = + rocksdb::BottommostLevelCompactionJni::toCppBottommostLevelCompaction(bottommost_level_compaction); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: changeLevel + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_CompactRangeOptions_changeLevel + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return static_cast(options->change_level); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: setChangeLevel + * Signature: (JZ)V + */ +void Java_org_rocksdb_CompactRangeOptions_setChangeLevel + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean change_level) { + auto* options = reinterpret_cast(jhandle); + options->change_level = static_cast(change_level); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: targetLevel + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactRangeOptions_targetLevel + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return static_cast(options->target_level); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: setTargetLevel + * Signature: (JI)V + */ +void Java_org_rocksdb_CompactRangeOptions_setTargetLevel + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jint target_level) { + auto* options = reinterpret_cast(jhandle); + options->target_level = static_cast(target_level); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: targetPathId + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactRangeOptions_targetPathId + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return static_cast(options->target_path_id); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: setTargetPathId + * Signature: (JI)V + */ +void Java_org_rocksdb_CompactRangeOptions_setTargetPathId + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jint target_path_id) { + auto* options = reinterpret_cast(jhandle); + options->target_path_id = static_cast(target_path_id); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: allowWriteStall + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_CompactRangeOptions_allowWriteStall + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return static_cast(options->allow_write_stall); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: setAllowWriteStall + * Signature: (JZ)V + */ +void Java_org_rocksdb_CompactRangeOptions_setAllowWriteStall + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean allow_write_stall) { + auto* options = reinterpret_cast(jhandle); + options->allow_write_stall = static_cast(allow_write_stall); +} + + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: maxSubcompactions + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactRangeOptions_maxSubcompactions + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return static_cast(options->max_subcompactions); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: setMaxSubcompactions + * Signature: (JI)V + */ +void Java_org_rocksdb_CompactRangeOptions_setMaxSubcompactions + (JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jint max_subcompactions) { + auto* options = reinterpret_cast(jhandle); + options->max_subcompactions = static_cast(max_subcompactions); +} + +/* + * Class: org_rocksdb_CompactRangeOptions + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_CompactRangeOptions_disposeInternal( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + delete options; +} diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_filter.cc b/thirdparty/rocksdb/java/rocksjni/compaction_filter.cc index 72de46b3fb..263bae05ee 100644 --- a/thirdparty/rocksdb/java/rocksjni/compaction_filter.cc +++ b/thirdparty/rocksdb/java/rocksjni/compaction_filter.cc @@ -18,8 +18,9 @@ * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_AbstractCompactionFilter_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_AbstractCompactionFilter_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { auto* cf = reinterpret_cast(handle); assert(cf != nullptr); delete cf; diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory.cc b/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory.cc new file mode 100644 index 0000000000..2ef0a77462 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::CompactionFilterFactory. + +#include +#include + +#include "include/org_rocksdb_AbstractCompactionFilterFactory.h" +#include "rocksjni/compaction_filter_factory_jnicallback.h" + +/* + * Class: org_rocksdb_AbstractCompactionFilterFactory + * Method: createNewCompactionFilterFactory0 + * Signature: ()J + */ +jlong Java_org_rocksdb_AbstractCompactionFilterFactory_createNewCompactionFilterFactory0( + JNIEnv* env, jobject jobj) { + auto* cff = new rocksdb::CompactionFilterFactoryJniCallback(env, jobj); + auto* ptr_sptr_cff = + new std::shared_ptr(cff); + return reinterpret_cast(ptr_sptr_cff); +} + +/* + * Class: org_rocksdb_AbstractCompactionFilterFactory + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_AbstractCompactionFilterFactory_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* ptr_sptr_cff = reinterpret_cast< + std::shared_ptr*>(jhandle); + delete ptr_sptr_cff; +} diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.cc b/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.cc new file mode 100644 index 0000000000..c727a3e02f --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::CompactionFilterFactory. + +#include "rocksjni/compaction_filter_factory_jnicallback.h" +#include "rocksjni/portal.h" + +namespace rocksdb { +CompactionFilterFactoryJniCallback::CompactionFilterFactoryJniCallback( + JNIEnv* env, jobject jcompaction_filter_factory) + : JniCallback(env, jcompaction_filter_factory) { + + // Note: The name of a CompactionFilterFactory will not change during + // it's lifetime, so we cache it in a global var + jmethodID jname_method_id = + AbstractCompactionFilterFactoryJni::getNameMethodId(env); + if(jname_method_id == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } + + jstring jname = + (jstring)env->CallObjectMethod(m_jcallback_obj, jname_method_id); + if(env->ExceptionCheck()) { + // exception thrown + return; + } + jboolean has_exception = JNI_FALSE; + m_name = JniUtil::copyString(env, jname, &has_exception); // also releases jname + if (has_exception == JNI_TRUE) { + // exception thrown + return; + } + + m_jcreate_compaction_filter_methodid = + AbstractCompactionFilterFactoryJni::getCreateCompactionFilterMethodId(env); + if(m_jcreate_compaction_filter_methodid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } +} + +const char* CompactionFilterFactoryJniCallback::Name() const { + return m_name.get(); +} + +std::unique_ptr CompactionFilterFactoryJniCallback::CreateCompactionFilter( + const CompactionFilter::Context& context) { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + assert(env != nullptr); + + jlong addr_compaction_filter = env->CallLongMethod(m_jcallback_obj, + m_jcreate_compaction_filter_methodid, + static_cast(context.is_full_compaction), + static_cast(context.is_manual_compaction)); + + if(env->ExceptionCheck()) { + // exception thrown from CallLongMethod + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return nullptr; + } + + auto* cff = reinterpret_cast(addr_compaction_filter); + + releaseJniEnv(attached_thread); + + return std::unique_ptr(cff); +} + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.h b/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.h new file mode 100644 index 0000000000..10802edfd1 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/compaction_filter_factory_jnicallback.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::CompactionFilterFactory. + +#ifndef JAVA_ROCKSJNI_COMPACTION_FILTER_FACTORY_JNICALLBACK_H_ +#define JAVA_ROCKSJNI_COMPACTION_FILTER_FACTORY_JNICALLBACK_H_ + +#include +#include + +#include "rocksdb/compaction_filter.h" +#include "rocksjni/jnicallback.h" + +namespace rocksdb { + +class CompactionFilterFactoryJniCallback : public JniCallback, public CompactionFilterFactory { + public: + CompactionFilterFactoryJniCallback( + JNIEnv* env, jobject jcompaction_filter_factory); + virtual std::unique_ptr CreateCompactionFilter( + const CompactionFilter::Context& context); + virtual const char* Name() const; + + private: + std::unique_ptr m_name; + jmethodID m_jcreate_compaction_filter_methodid; +}; + +} //namespace rocksdb + +#endif // JAVA_ROCKSJNI_COMPACTION_FILTER_FACTORY_JNICALLBACK_H_ diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_job_info.cc b/thirdparty/rocksdb/java/rocksjni/compaction_job_info.cc new file mode 100644 index 0000000000..6af6efcb85 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/compaction_job_info.cc @@ -0,0 +1,222 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::CompactionJobInfo. + +#include + +#include "include/org_rocksdb_CompactionJobInfo.h" +#include "rocksdb/listener.h" +#include "rocksjni/portal.h" + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: newCompactionJobInfo + * Signature: ()J + */ +jlong Java_org_rocksdb_CompactionJobInfo_newCompactionJobInfo( + JNIEnv*, jclass) { + auto* compact_job_info = new rocksdb::CompactionJobInfo(); + return reinterpret_cast(compact_job_info); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_CompactionJobInfo_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + delete compact_job_info; +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: columnFamilyName + * Signature: (J)[B + */ +jbyteArray Java_org_rocksdb_CompactionJobInfo_columnFamilyName( + JNIEnv* env, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return rocksdb::JniUtil::copyBytes( + env, compact_job_info->cf_name); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: status + * Signature: (J)Lorg/rocksdb/Status; + */ +jobject Java_org_rocksdb_CompactionJobInfo_status( + JNIEnv* env, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return rocksdb::StatusJni::construct( + env, compact_job_info->status); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: threadId + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobInfo_threadId( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return static_cast(compact_job_info->thread_id); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: jobId + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactionJobInfo_jobId( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return static_cast(compact_job_info->job_id); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: baseInputLevel + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactionJobInfo_baseInputLevel( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return static_cast(compact_job_info->base_input_level); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: outputLevel + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactionJobInfo_outputLevel( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return static_cast(compact_job_info->output_level); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: inputFiles + * Signature: (J)[Ljava/lang/String; + */ +jobjectArray Java_org_rocksdb_CompactionJobInfo_inputFiles( + JNIEnv* env, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return rocksdb::JniUtil::toJavaStrings( + env, &compact_job_info->input_files); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: outputFiles + * Signature: (J)[Ljava/lang/String; + */ +jobjectArray Java_org_rocksdb_CompactionJobInfo_outputFiles( + JNIEnv* env, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return rocksdb::JniUtil::toJavaStrings( + env, &compact_job_info->output_files); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: tableProperties + * Signature: (J)Ljava/util/Map; + */ +jobject Java_org_rocksdb_CompactionJobInfo_tableProperties( + JNIEnv* env, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + auto* map = &compact_job_info->table_properties; + + jobject jhash_map = rocksdb::HashMapJni::construct( + env, static_cast(map->size())); + if (jhash_map == nullptr) { + // exception occurred + return nullptr; + } + + const rocksdb::HashMapJni::FnMapKV, jobject, jobject> fn_map_kv = + [env](const std::pair>& kv) { + jstring jkey = rocksdb::JniUtil::toJavaString(env, &(kv.first), false); + if (env->ExceptionCheck()) { + // an error occurred + return std::unique_ptr>(nullptr); + } + + jobject jtable_properties = rocksdb::TablePropertiesJni::fromCppTableProperties( + env, *(kv.second.get())); + if (env->ExceptionCheck()) { + // an error occurred + env->DeleteLocalRef(jkey); + return std::unique_ptr>(nullptr); + } + + return std::unique_ptr>( + new std::pair(static_cast(jkey), jtable_properties)); + }; + + if (!rocksdb::HashMapJni::putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { + // exception occurred + return nullptr; + } + + return jhash_map; +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: compactionReason + * Signature: (J)B + */ +jbyte Java_org_rocksdb_CompactionJobInfo_compactionReason( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return rocksdb::CompactionReasonJni::toJavaCompactionReason( + compact_job_info->compaction_reason); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: compression + * Signature: (J)B + */ +jbyte Java_org_rocksdb_CompactionJobInfo_compression( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + return rocksdb::CompressionTypeJni::toJavaCompressionType( + compact_job_info->compression); +} + +/* + * Class: org_rocksdb_CompactionJobInfo + * Method: stats + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobInfo_stats( + JNIEnv *, jclass, jlong jhandle) { + auto* compact_job_info = + reinterpret_cast(jhandle); + auto* stats = new rocksdb::CompactionJobStats(); + stats->Add(compact_job_info->stats); + return reinterpret_cast(stats); +} diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_job_stats.cc b/thirdparty/rocksdb/java/rocksjni/compaction_job_stats.cc new file mode 100644 index 0000000000..7d13dd12f9 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/compaction_job_stats.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::CompactionJobStats. + +#include + +#include "include/org_rocksdb_CompactionJobStats.h" +#include "rocksdb/compaction_job_stats.h" +#include "rocksjni/portal.h" + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: newCompactionJobStats + * Signature: ()J + */ +jlong Java_org_rocksdb_CompactionJobStats_newCompactionJobStats( + JNIEnv*, jclass) { + auto* compact_job_stats = new rocksdb::CompactionJobStats(); + return reinterpret_cast(compact_job_stats); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_CompactionJobStats_disposeInternal( + JNIEnv *, jobject, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + delete compact_job_stats; +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: reset + * Signature: (J)V + */ +void Java_org_rocksdb_CompactionJobStats_reset( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + compact_job_stats->Reset(); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: add + * Signature: (JJ)V + */ +void Java_org_rocksdb_CompactionJobStats_add( + JNIEnv*, jclass, jlong jhandle, jlong jother_handle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + auto* other_compact_job_stats = + reinterpret_cast(jother_handle); + compact_job_stats->Add(*other_compact_job_stats); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: elapsedMicros + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_elapsedMicros( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast(compact_job_stats->elapsed_micros); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numInputRecords + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numInputRecords( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast(compact_job_stats->num_input_records); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numInputFiles + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numInputFiles( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast(compact_job_stats->num_input_files); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numInputFilesAtOutputLevel + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numInputFilesAtOutputLevel( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_input_files_at_output_level); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numOutputRecords + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numOutputRecords( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_output_records); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numOutputFiles + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numOutputFiles( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_output_files); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: isManualCompaction + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_CompactionJobStats_isManualCompaction( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + if (compact_job_stats->is_manual_compaction) { + return JNI_TRUE; + } else { + return JNI_FALSE; + } +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: totalInputBytes + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_totalInputBytes( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->total_input_bytes); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: totalOutputBytes + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_totalOutputBytes( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->total_output_bytes); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numRecordsReplaced + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numRecordsReplaced( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_records_replaced); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: totalInputRawKeyBytes + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_totalInputRawKeyBytes( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->total_input_raw_key_bytes); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: totalInputRawValueBytes + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_totalInputRawValueBytes( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->total_input_raw_value_bytes); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numInputDeletionRecords + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numInputDeletionRecords( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_input_deletion_records); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numExpiredDeletionRecords + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numExpiredDeletionRecords( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_expired_deletion_records); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numCorruptKeys + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numCorruptKeys( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_corrupt_keys); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: fileWriteNanos + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_fileWriteNanos( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->file_write_nanos); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: fileRangeSyncNanos + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_fileRangeSyncNanos( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->file_range_sync_nanos); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: fileFsyncNanos + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_fileFsyncNanos( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->file_fsync_nanos); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: filePrepareWriteNanos + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_filePrepareWriteNanos( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->file_prepare_write_nanos); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: smallestOutputKeyPrefix + * Signature: (J)[B + */ +jbyteArray Java_org_rocksdb_CompactionJobStats_smallestOutputKeyPrefix( + JNIEnv* env, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return rocksdb::JniUtil::copyBytes(env, + compact_job_stats->smallest_output_key_prefix); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: largestOutputKeyPrefix + * Signature: (J)[B + */ +jbyteArray Java_org_rocksdb_CompactionJobStats_largestOutputKeyPrefix( + JNIEnv* env, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return rocksdb::JniUtil::copyBytes(env, + compact_job_stats->largest_output_key_prefix); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numSingleDelFallthru + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numSingleDelFallthru( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_single_del_fallthru); +} + +/* + * Class: org_rocksdb_CompactionJobStats + * Method: numSingleDelMismatch + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionJobStats_numSingleDelMismatch( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_job_stats = + reinterpret_cast(jhandle); + return static_cast( + compact_job_stats->num_single_del_mismatch); +} \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_options.cc b/thirdparty/rocksdb/java/rocksjni/compaction_options.cc new file mode 100644 index 0000000000..6aaabea736 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/compaction_options.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::CompactionOptions. + +#include + +#include "include/org_rocksdb_CompactionOptions.h" +#include "rocksdb/options.h" +#include "rocksjni/portal.h" + + +/* + * Class: org_rocksdb_CompactionOptions + * Method: newCompactionOptions + * Signature: ()J + */ +jlong Java_org_rocksdb_CompactionOptions_newCompactionOptions( + JNIEnv*, jclass) { + auto* compact_opts = new rocksdb::CompactionOptions(); + return reinterpret_cast(compact_opts); +} + +/* + * Class: org_rocksdb_CompactionOptions + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_CompactionOptions_disposeInternal( + JNIEnv *, jobject, jlong jhandle) { + auto* compact_opts = + reinterpret_cast(jhandle); + delete compact_opts; +} + +/* + * Class: org_rocksdb_CompactionOptions + * Method: compression + * Signature: (J)B + */ +jbyte Java_org_rocksdb_CompactionOptions_compression( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_opts = + reinterpret_cast(jhandle); + return rocksdb::CompressionTypeJni::toJavaCompressionType( + compact_opts->compression); +} + +/* + * Class: org_rocksdb_CompactionOptions + * Method: setCompression + * Signature: (JB)V + */ +void Java_org_rocksdb_CompactionOptions_setCompression( + JNIEnv*, jclass, jlong jhandle, jbyte jcompression_type_value) { + auto* compact_opts = + reinterpret_cast(jhandle); + compact_opts->compression = + rocksdb::CompressionTypeJni::toCppCompressionType( + jcompression_type_value); +} + +/* + * Class: org_rocksdb_CompactionOptions + * Method: outputFileSizeLimit + * Signature: (J)J + */ +jlong Java_org_rocksdb_CompactionOptions_outputFileSizeLimit( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_opts = + reinterpret_cast(jhandle); + return static_cast( + compact_opts->output_file_size_limit); +} + +/* + * Class: org_rocksdb_CompactionOptions + * Method: setOutputFileSizeLimit + * Signature: (JJ)V + */ +void Java_org_rocksdb_CompactionOptions_setOutputFileSizeLimit( + JNIEnv*, jclass, jlong jhandle, jlong joutput_file_size_limit) { + auto* compact_opts = + reinterpret_cast(jhandle); + compact_opts->output_file_size_limit = + static_cast(joutput_file_size_limit); +} + +/* + * Class: org_rocksdb_CompactionOptions + * Method: maxSubcompactions + * Signature: (J)I + */ +jint Java_org_rocksdb_CompactionOptions_maxSubcompactions( + JNIEnv*, jclass, jlong jhandle) { + auto* compact_opts = + reinterpret_cast(jhandle); + return static_cast( + compact_opts->max_subcompactions); +} + +/* + * Class: org_rocksdb_CompactionOptions + * Method: setMaxSubcompactions + * Signature: (JI)V + */ +void Java_org_rocksdb_CompactionOptions_setMaxSubcompactions( + JNIEnv*, jclass, jlong jhandle, jint jmax_subcompactions) { + auto* compact_opts = + reinterpret_cast(jhandle); + compact_opts->max_subcompactions = + static_cast(jmax_subcompactions); +} \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_options_fifo.cc b/thirdparty/rocksdb/java/rocksjni/compaction_options_fifo.cc index ef04d81c64..b7c445fd6f 100644 --- a/thirdparty/rocksdb/java/rocksjni/compaction_options_fifo.cc +++ b/thirdparty/rocksdb/java/rocksjni/compaction_options_fifo.cc @@ -17,7 +17,7 @@ * Signature: ()J */ jlong Java_org_rocksdb_CompactionOptionsFIFO_newCompactionOptionsFIFO( - JNIEnv* env, jclass jcls) { + JNIEnv*, jclass) { const auto* opt = new rocksdb::CompactionOptionsFIFO(); return reinterpret_cast(opt); } @@ -28,7 +28,7 @@ jlong Java_org_rocksdb_CompactionOptionsFIFO_newCompactionOptionsFIFO( * Signature: (JJ)V */ void Java_org_rocksdb_CompactionOptionsFIFO_setMaxTableFilesSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jmax_table_files_size) { + JNIEnv*, jobject, jlong jhandle, jlong jmax_table_files_size) { auto* opt = reinterpret_cast(jhandle); opt->max_table_files_size = static_cast(jmax_table_files_size); } @@ -39,17 +39,39 @@ void Java_org_rocksdb_CompactionOptionsFIFO_setMaxTableFilesSize( * Signature: (J)J */ jlong Java_org_rocksdb_CompactionOptionsFIFO_maxTableFilesSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->max_table_files_size); } +/* + * Class: org_rocksdb_CompactionOptionsFIFO + * Method: setAllowCompaction + * Signature: (JZ)V + */ +void Java_org_rocksdb_CompactionOptionsFIFO_setAllowCompaction( + JNIEnv*, jobject, jlong jhandle, jboolean allow_compaction) { + auto* opt = reinterpret_cast(jhandle); + opt->allow_compaction = static_cast(allow_compaction); +} + +/* + * Class: org_rocksdb_CompactionOptionsFIFO + * Method: allowCompaction + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_CompactionOptionsFIFO_allowCompaction( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->allow_compaction); +} + /* * Class: org_rocksdb_CompactionOptionsFIFO * Method: disposeInternal * Signature: (J)V */ void Java_org_rocksdb_CompactionOptionsFIFO_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { delete reinterpret_cast(jhandle); } diff --git a/thirdparty/rocksdb/java/rocksjni/compaction_options_universal.cc b/thirdparty/rocksdb/java/rocksjni/compaction_options_universal.cc index d397db8e43..7ca519885d 100644 --- a/thirdparty/rocksdb/java/rocksjni/compaction_options_universal.cc +++ b/thirdparty/rocksdb/java/rocksjni/compaction_options_universal.cc @@ -18,7 +18,7 @@ * Signature: ()J */ jlong Java_org_rocksdb_CompactionOptionsUniversal_newCompactionOptionsUniversal( - JNIEnv* env, jclass jcls) { + JNIEnv*, jclass) { const auto* opt = new rocksdb::CompactionOptionsUniversal(); return reinterpret_cast(opt); } @@ -29,7 +29,7 @@ jlong Java_org_rocksdb_CompactionOptionsUniversal_newCompactionOptionsUniversal( * Signature: (JI)V */ void Java_org_rocksdb_CompactionOptionsUniversal_setSizeRatio( - JNIEnv* env, jobject jobj, jlong jhandle, jint jsize_ratio) { + JNIEnv*, jobject, jlong jhandle, jint jsize_ratio) { auto* opt = reinterpret_cast(jhandle); opt->size_ratio = static_cast(jsize_ratio); } @@ -40,7 +40,7 @@ void Java_org_rocksdb_CompactionOptionsUniversal_setSizeRatio( * Signature: (J)I */ jint Java_org_rocksdb_CompactionOptionsUniversal_sizeRatio( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->size_ratio); } @@ -51,7 +51,7 @@ jint Java_org_rocksdb_CompactionOptionsUniversal_sizeRatio( * Signature: (JI)V */ void Java_org_rocksdb_CompactionOptionsUniversal_setMinMergeWidth( - JNIEnv* env, jobject jobj, jlong jhandle, jint jmin_merge_width) { + JNIEnv*, jobject, jlong jhandle, jint jmin_merge_width) { auto* opt = reinterpret_cast(jhandle); opt->min_merge_width = static_cast(jmin_merge_width); } @@ -62,7 +62,7 @@ void Java_org_rocksdb_CompactionOptionsUniversal_setMinMergeWidth( * Signature: (J)I */ jint Java_org_rocksdb_CompactionOptionsUniversal_minMergeWidth( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->min_merge_width); } @@ -73,7 +73,7 @@ jint Java_org_rocksdb_CompactionOptionsUniversal_minMergeWidth( * Signature: (JI)V */ void Java_org_rocksdb_CompactionOptionsUniversal_setMaxMergeWidth( - JNIEnv* env, jobject jobj, jlong jhandle, jint jmax_merge_width) { + JNIEnv*, jobject, jlong jhandle, jint jmax_merge_width) { auto* opt = reinterpret_cast(jhandle); opt->max_merge_width = static_cast(jmax_merge_width); } @@ -84,7 +84,7 @@ void Java_org_rocksdb_CompactionOptionsUniversal_setMaxMergeWidth( * Signature: (J)I */ jint Java_org_rocksdb_CompactionOptionsUniversal_maxMergeWidth( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->max_merge_width); } @@ -95,8 +95,7 @@ jint Java_org_rocksdb_CompactionOptionsUniversal_maxMergeWidth( * Signature: (JI)V */ void Java_org_rocksdb_CompactionOptionsUniversal_setMaxSizeAmplificationPercent( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jmax_size_amplification_percent) { + JNIEnv*, jobject, jlong jhandle, jint jmax_size_amplification_percent) { auto* opt = reinterpret_cast(jhandle); opt->max_size_amplification_percent = static_cast(jmax_size_amplification_percent); @@ -108,7 +107,7 @@ void Java_org_rocksdb_CompactionOptionsUniversal_setMaxSizeAmplificationPercent( * Signature: (J)I */ jint Java_org_rocksdb_CompactionOptionsUniversal_maxSizeAmplificationPercent( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->max_size_amplification_percent); } @@ -119,7 +118,8 @@ jint Java_org_rocksdb_CompactionOptionsUniversal_maxSizeAmplificationPercent( * Signature: (JI)V */ void Java_org_rocksdb_CompactionOptionsUniversal_setCompressionSizePercent( - JNIEnv* env, jobject jobj, jlong jhandle, jint jcompression_size_percent) { + JNIEnv*, jobject, jlong jhandle, + jint jcompression_size_percent) { auto* opt = reinterpret_cast(jhandle); opt->compression_size_percent = static_cast(jcompression_size_percent); @@ -131,7 +131,7 @@ void Java_org_rocksdb_CompactionOptionsUniversal_setCompressionSizePercent( * Signature: (J)I */ jint Java_org_rocksdb_CompactionOptionsUniversal_compressionSizePercent( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->compression_size_percent); } @@ -142,11 +142,10 @@ jint Java_org_rocksdb_CompactionOptionsUniversal_compressionSizePercent( * Signature: (JB)V */ void Java_org_rocksdb_CompactionOptionsUniversal_setStopStyle( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jstop_style_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jstop_style_value) { auto* opt = reinterpret_cast(jhandle); - opt->stop_style = - rocksdb::CompactionStopStyleJni::toCppCompactionStopStyle( - jstop_style_value); + opt->stop_style = rocksdb::CompactionStopStyleJni::toCppCompactionStopStyle( + jstop_style_value); } /* @@ -155,7 +154,7 @@ void Java_org_rocksdb_CompactionOptionsUniversal_setStopStyle( * Signature: (J)B */ jbyte Java_org_rocksdb_CompactionOptionsUniversal_stopStyle( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return rocksdb::CompactionStopStyleJni::toJavaCompactionStopStyle( opt->stop_style); @@ -167,7 +166,7 @@ jbyte Java_org_rocksdb_CompactionOptionsUniversal_stopStyle( * Signature: (JZ)V */ void Java_org_rocksdb_CompactionOptionsUniversal_setAllowTrivialMove( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jallow_trivial_move) { + JNIEnv*, jobject, jlong jhandle, jboolean jallow_trivial_move) { auto* opt = reinterpret_cast(jhandle); opt->allow_trivial_move = static_cast(jallow_trivial_move); } @@ -178,7 +177,7 @@ void Java_org_rocksdb_CompactionOptionsUniversal_setAllowTrivialMove( * Signature: (J)Z */ jboolean Java_org_rocksdb_CompactionOptionsUniversal_allowTrivialMove( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return opt->allow_trivial_move; } @@ -189,6 +188,6 @@ jboolean Java_org_rocksdb_CompactionOptionsUniversal_allowTrivialMove( * Signature: (J)V */ void Java_org_rocksdb_CompactionOptionsUniversal_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { delete reinterpret_cast(jhandle); } diff --git a/thirdparty/rocksdb/java/rocksjni/comparator.cc b/thirdparty/rocksdb/java/rocksjni/comparator.cc index 5955d0bf75..13f8feb6f3 100644 --- a/thirdparty/rocksdb/java/rocksjni/comparator.cc +++ b/thirdparty/rocksdb/java/rocksjni/comparator.cc @@ -6,33 +6,18 @@ // This file implements the "bridge" between Java and C++ for // rocksdb::Comparator. +#include #include #include -#include -#include #include +#include -#include "include/org_rocksdb_AbstractComparator.h" #include "include/org_rocksdb_Comparator.h" #include "include/org_rocksdb_DirectComparator.h" +#include "include/org_rocksdb_NativeComparatorWrapper.h" #include "rocksjni/comparatorjnicallback.h" #include "rocksjni/portal.h" -// /* @@ -40,12 +25,12 @@ void Java_org_rocksdb_AbstractComparator_disposeInternal( * Method: createNewComparator0 * Signature: ()J */ -jlong Java_org_rocksdb_Comparator_createNewComparator0( - JNIEnv* env, jobject jobj, jlong copt_handle) { - const rocksdb::ComparatorJniCallbackOptions* copt = - reinterpret_cast(copt_handle); - const rocksdb::ComparatorJniCallback* c = - new rocksdb::ComparatorJniCallback(env, jobj, copt); +jlong Java_org_rocksdb_Comparator_createNewComparator0(JNIEnv* env, + jobject jobj, + jlong copt_handle) { + auto* copt = + reinterpret_cast(copt_handle); + auto* c = new rocksdb::ComparatorJniCallback(env, jobj, copt); return reinterpret_cast(c); } // @@ -59,10 +44,20 @@ jlong Java_org_rocksdb_Comparator_createNewComparator0( */ jlong Java_org_rocksdb_DirectComparator_createNewDirectComparator0( JNIEnv* env, jobject jobj, jlong copt_handle) { - const rocksdb::ComparatorJniCallbackOptions* copt = - reinterpret_cast(copt_handle); - const rocksdb::DirectComparatorJniCallback* c = - new rocksdb::DirectComparatorJniCallback(env, jobj, copt); + auto* copt = + reinterpret_cast(copt_handle); + auto* c = new rocksdb::DirectComparatorJniCallback(env, jobj, copt); return reinterpret_cast(c); } + +/* + * Class: org_rocksdb_NativeComparatorWrapper + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_NativeComparatorWrapper_disposeInternal( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jcomparator_handle) { + auto* comparator = reinterpret_cast(jcomparator_handle); + delete comparator; +} // diff --git a/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.cc b/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.cc index 73ab46ad21..5b4d11b020 100644 --- a/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.cc +++ b/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.cc @@ -13,24 +13,9 @@ namespace rocksdb { BaseComparatorJniCallback::BaseComparatorJniCallback( JNIEnv* env, jobject jComparator, const ComparatorJniCallbackOptions* copt) - : mtx_compare(new port::Mutex(copt->use_adaptive_mutex)), + : JniCallback(env, jComparator), + mtx_compare(new port::Mutex(copt->use_adaptive_mutex)), mtx_findShortestSeparator(new port::Mutex(copt->use_adaptive_mutex)) { - // Note: Comparator methods may be accessed by multiple threads, - // so we ref the jvm not the env - const jint rs = env->GetJavaVM(&m_jvm); - if(rs != JNI_OK) { - // exception thrown - return; - } - - // Note: we want to access the Java Comparator instance - // across multiple method calls, so we create a global ref - assert(jComparator != nullptr); - m_jComparator = env->NewGlobalRef(jComparator); - if(m_jComparator == nullptr) { - // exception thrown: OutOfMemoryError - return; - } // Note: The name of a Comparator will not change during it's lifetime, // so we cache it in a global var @@ -39,7 +24,7 @@ BaseComparatorJniCallback::BaseComparatorJniCallback( // exception thrown: NoSuchMethodException or OutOfMemoryError return; } - jstring jsName = (jstring)env->CallObjectMethod(m_jComparator, jNameMethodId); + jstring jsName = (jstring)env->CallObjectMethod(m_jcallback_obj, jNameMethodId); if(env->ExceptionCheck()) { // exception thrown return; @@ -74,18 +59,18 @@ BaseComparatorJniCallback::BaseComparatorJniCallback( } const char* BaseComparatorJniCallback::Name() const { - return m_name.c_str(); + return m_name.get(); } int BaseComparatorJniCallback::Compare(const Slice& a, const Slice& b) const { jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); + JNIEnv* env = getJniEnv(&attached_thread); assert(env != nullptr); // TODO(adamretter): slice objects can potentially be cached using thread // local variables to avoid locking. Could make this configurable depending on // performance. - mtx_compare->Lock(); + mtx_compare.get()->Lock(); bool pending_exception = AbstractSliceJni::setHandle(env, m_jSliceA, &a, JNI_FALSE); @@ -94,7 +79,7 @@ int BaseComparatorJniCallback::Compare(const Slice& a, const Slice& b) const { // exception thrown from setHandle or descendant env->ExceptionDescribe(); // print out exception to stderr } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return 0; } @@ -105,15 +90,15 @@ int BaseComparatorJniCallback::Compare(const Slice& a, const Slice& b) const { // exception thrown from setHandle or descendant env->ExceptionDescribe(); // print out exception to stderr } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return 0; } - + jint result = - env->CallIntMethod(m_jComparator, m_jCompareMethodId, m_jSliceA, + env->CallIntMethod(m_jcallback_obj, m_jCompareMethodId, m_jSliceA, m_jSliceB); - mtx_compare->Unlock(); + mtx_compare.get()->Unlock(); if(env->ExceptionCheck()) { // exception thrown from CallIntMethod @@ -121,19 +106,19 @@ int BaseComparatorJniCallback::Compare(const Slice& a, const Slice& b) const { result = 0; // we could not get a result from java callback so use 0 } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return result; } void BaseComparatorJniCallback::FindShortestSeparator( - std::string* start, const Slice& limit) const { + std::string* start, const Slice& limit) const { if (start == nullptr) { return; } jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); + JNIEnv* env = getJniEnv(&attached_thread); assert(env != nullptr); const char* startUtf = start->c_str(); @@ -143,21 +128,21 @@ void BaseComparatorJniCallback::FindShortestSeparator( if(env->ExceptionCheck()) { env->ExceptionDescribe(); // print out exception to stderr } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } if(env->ExceptionCheck()) { // exception thrown: OutOfMemoryError env->ExceptionDescribe(); // print out exception to stderr env->DeleteLocalRef(jsStart); - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } // TODO(adamretter): slice object can potentially be cached using thread local // variable to avoid locking. Could make this configurable depending on // performance. - mtx_findShortestSeparator->Lock(); + mtx_findShortestSeparator.get()->Lock(); bool pending_exception = AbstractSliceJni::setHandle(env, m_jSliceLimit, &limit, JNI_FALSE); @@ -169,21 +154,21 @@ void BaseComparatorJniCallback::FindShortestSeparator( if(jsStart != nullptr) { env->DeleteLocalRef(jsStart); } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } jstring jsResultStart = - (jstring)env->CallObjectMethod(m_jComparator, + (jstring)env->CallObjectMethod(m_jcallback_obj, m_jFindShortestSeparatorMethodId, jsStart, m_jSliceLimit); - mtx_findShortestSeparator->Unlock(); + mtx_findShortestSeparator.get()->Unlock(); if(env->ExceptionCheck()) { // exception thrown from CallObjectMethod env->ExceptionDescribe(); // print out exception to stderr env->DeleteLocalRef(jsStart); - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } @@ -192,29 +177,29 @@ void BaseComparatorJniCallback::FindShortestSeparator( if (jsResultStart != nullptr) { // update start with result jboolean has_exception = JNI_FALSE; - std::string result = JniUtil::copyString(env, jsResultStart, + std::unique_ptr result_start = JniUtil::copyString(env, jsResultStart, &has_exception); // also releases jsResultStart if (has_exception == JNI_TRUE) { if (env->ExceptionCheck()) { env->ExceptionDescribe(); // print out exception to stderr } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } - *start = result; + start->assign(result_start.get()); } - - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); } -void BaseComparatorJniCallback::FindShortSuccessor(std::string* key) const { +void BaseComparatorJniCallback::FindShortSuccessor( + std::string* key) const { if (key == nullptr) { return; } jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); + JNIEnv* env = getJniEnv(&attached_thread); assert(env != nullptr); const char* keyUtf = key->c_str(); @@ -224,25 +209,25 @@ void BaseComparatorJniCallback::FindShortSuccessor(std::string* key) const { if(env->ExceptionCheck()) { env->ExceptionDescribe(); // print out exception to stderr } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } else if(env->ExceptionCheck()) { // exception thrown: OutOfMemoryError env->ExceptionDescribe(); // print out exception to stderr env->DeleteLocalRef(jsKey); - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } jstring jsResultKey = - (jstring)env->CallObjectMethod(m_jComparator, + (jstring)env->CallObjectMethod(m_jcallback_obj, m_jFindShortSuccessorMethodId, jsKey); if(env->ExceptionCheck()) { // exception thrown from CallObjectMethod env->ExceptionDescribe(); // print out exception to stderr env->DeleteLocalRef(jsKey); - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } @@ -251,31 +236,20 @@ void BaseComparatorJniCallback::FindShortSuccessor(std::string* key) const { if (jsResultKey != nullptr) { // updates key with result, also releases jsResultKey. jboolean has_exception = JNI_FALSE; - std::string result = JniUtil::copyString(env, jsResultKey, &has_exception); + std::unique_ptr result_key = JniUtil::copyString(env, jsResultKey, + &has_exception); // also releases jsResultKey if (has_exception == JNI_TRUE) { if (env->ExceptionCheck()) { env->ExceptionDescribe(); // print out exception to stderr } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } - *key = result; - } - - JniUtil::releaseJniEnv(m_jvm, attached_thread); -} - -BaseComparatorJniCallback::~BaseComparatorJniCallback() { - jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); - assert(env != nullptr); - - if(m_jComparator != nullptr) { - env->DeleteGlobalRef(m_jComparator); + key->assign(result_key.get()); } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); } ComparatorJniCallback::ComparatorJniCallback( @@ -303,7 +277,7 @@ ComparatorJniCallback::ComparatorJniCallback( ComparatorJniCallback::~ComparatorJniCallback() { jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); + JNIEnv* env = getJniEnv(&attached_thread); assert(env != nullptr); if(m_jSliceA != nullptr) { @@ -318,7 +292,7 @@ ComparatorJniCallback::~ComparatorJniCallback() { env->DeleteGlobalRef(m_jSliceLimit); } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); } DirectComparatorJniCallback::DirectComparatorJniCallback( @@ -346,7 +320,7 @@ DirectComparatorJniCallback::DirectComparatorJniCallback( DirectComparatorJniCallback::~DirectComparatorJniCallback() { jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); + JNIEnv* env = getJniEnv(&attached_thread); assert(env != nullptr); if(m_jSliceA != nullptr) { @@ -361,6 +335,6 @@ DirectComparatorJniCallback::~DirectComparatorJniCallback() { env->DeleteGlobalRef(m_jSliceLimit); } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.h b/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.h index a753008b33..0aa9cc0af8 100644 --- a/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.h +++ b/thirdparty/rocksdb/java/rocksjni/comparatorjnicallback.h @@ -10,7 +10,9 @@ #define JAVA_ROCKSJNI_COMPARATORJNICALLBACK_H_ #include +#include #include +#include "rocksjni/jnicallback.h" #include "rocksdb/comparator.h" #include "rocksdb/slice.h" #include "port/port.h" @@ -44,12 +46,11 @@ struct ComparatorJniCallbackOptions { * introduce independent locking in regions of each of those methods * via the mutexs mtx_compare and mtx_findShortestSeparator respectively */ -class BaseComparatorJniCallback : public Comparator { +class BaseComparatorJniCallback : public JniCallback, public Comparator { public: BaseComparatorJniCallback( JNIEnv* env, jobject jComparator, const ComparatorJniCallbackOptions* copt); - virtual ~BaseComparatorJniCallback(); virtual const char* Name() const; virtual int Compare(const Slice& a, const Slice& b) const; virtual void FindShortestSeparator( @@ -58,17 +59,15 @@ class BaseComparatorJniCallback : public Comparator { private: // used for synchronisation in compare method - port::Mutex* mtx_compare; + std::unique_ptr mtx_compare; // used for synchronisation in findShortestSeparator method - port::Mutex* mtx_findShortestSeparator; - jobject m_jComparator; - std::string m_name; + std::unique_ptr mtx_findShortestSeparator; + std::unique_ptr m_name; jmethodID m_jCompareMethodId; jmethodID m_jFindShortestSeparatorMethodId; jmethodID m_jFindShortSuccessorMethodId; protected: - JavaVM* m_jvm; jobject m_jSliceA; jobject m_jSliceB; jobject m_jSliceLimit; diff --git a/thirdparty/rocksdb/java/rocksjni/compression_options.cc b/thirdparty/rocksdb/java/rocksjni/compression_options.cc index 7d5af645ae..f0155eb335 100644 --- a/thirdparty/rocksdb/java/rocksjni/compression_options.cc +++ b/thirdparty/rocksdb/java/rocksjni/compression_options.cc @@ -17,7 +17,7 @@ * Signature: ()J */ jlong Java_org_rocksdb_CompressionOptions_newCompressionOptions( - JNIEnv* env, jclass jcls) { + JNIEnv*, jclass) { const auto* opt = new rocksdb::CompressionOptions(); return reinterpret_cast(opt); } @@ -28,7 +28,7 @@ jlong Java_org_rocksdb_CompressionOptions_newCompressionOptions( * Signature: (JI)V */ void Java_org_rocksdb_CompressionOptions_setWindowBits( - JNIEnv* env, jobject jobj, jlong jhandle, jint jwindow_bits) { + JNIEnv*, jobject, jlong jhandle, jint jwindow_bits) { auto* opt = reinterpret_cast(jhandle); opt->window_bits = static_cast(jwindow_bits); } @@ -39,7 +39,7 @@ void Java_org_rocksdb_CompressionOptions_setWindowBits( * Signature: (J)I */ jint Java_org_rocksdb_CompressionOptions_windowBits( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->window_bits); } @@ -50,7 +50,7 @@ jint Java_org_rocksdb_CompressionOptions_windowBits( * Signature: (JI)V */ void Java_org_rocksdb_CompressionOptions_setLevel( - JNIEnv* env, jobject jobj, jlong jhandle, jint jlevel) { + JNIEnv*, jobject, jlong jhandle, jint jlevel) { auto* opt = reinterpret_cast(jhandle); opt->level = static_cast(jlevel); } @@ -61,7 +61,7 @@ void Java_org_rocksdb_CompressionOptions_setLevel( * Signature: (J)I */ jint Java_org_rocksdb_CompressionOptions_level( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->level); } @@ -72,7 +72,7 @@ jint Java_org_rocksdb_CompressionOptions_level( * Signature: (JI)V */ void Java_org_rocksdb_CompressionOptions_setStrategy( - JNIEnv* env, jobject jobj, jlong jhandle, jint jstrategy) { + JNIEnv*, jobject, jlong jhandle, jint jstrategy) { auto* opt = reinterpret_cast(jhandle); opt->strategy = static_cast(jstrategy); } @@ -83,7 +83,7 @@ void Java_org_rocksdb_CompressionOptions_setStrategy( * Signature: (J)I */ jint Java_org_rocksdb_CompressionOptions_strategy( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->strategy); } @@ -94,9 +94,9 @@ jint Java_org_rocksdb_CompressionOptions_strategy( * Signature: (JI)V */ void Java_org_rocksdb_CompressionOptions_setMaxDictBytes( - JNIEnv* env, jobject jobj, jlong jhandle, jint jmax_dict_bytes) { + JNIEnv*, jobject, jlong jhandle, jint jmax_dict_bytes) { auto* opt = reinterpret_cast(jhandle); - opt->max_dict_bytes = static_cast(jmax_dict_bytes); + opt->max_dict_bytes = static_cast(jmax_dict_bytes); } /* @@ -105,17 +105,60 @@ void Java_org_rocksdb_CompressionOptions_setMaxDictBytes( * Signature: (J)I */ jint Java_org_rocksdb_CompressionOptions_maxDictBytes( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->max_dict_bytes); } +/* + * Class: org_rocksdb_CompressionOptions + * Method: setZstdMaxTrainBytes + * Signature: (JI)V + */ +void Java_org_rocksdb_CompressionOptions_setZstdMaxTrainBytes( + JNIEnv*, jobject, jlong jhandle, jint jzstd_max_train_bytes) { + auto* opt = reinterpret_cast(jhandle); + opt->zstd_max_train_bytes = static_cast(jzstd_max_train_bytes); +} + +/* + * Class: org_rocksdb_CompressionOptions + * Method: zstdMaxTrainBytes + * Signature: (J)I + */ +jint Java_org_rocksdb_CompressionOptions_zstdMaxTrainBytes( + JNIEnv *, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->zstd_max_train_bytes); +} + +/* + * Class: org_rocksdb_CompressionOptions + * Method: setEnabled + * Signature: (JZ)V + */ +void Java_org_rocksdb_CompressionOptions_setEnabled( + JNIEnv*, jobject, jlong jhandle, jboolean jenabled) { + auto* opt = reinterpret_cast(jhandle); + opt->enabled = jenabled == JNI_TRUE; +} + +/* + * Class: org_rocksdb_CompressionOptions + * Method: enabled + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_CompressionOptions_enabled( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->enabled); +} /* * Class: org_rocksdb_CompressionOptions * Method: disposeInternal * Signature: (J)V */ void Java_org_rocksdb_CompressionOptions_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { delete reinterpret_cast(jhandle); } diff --git a/thirdparty/rocksdb/java/rocksjni/env.cc b/thirdparty/rocksdb/java/rocksjni/env.cc index dc949a07fa..ed54bd36a0 100644 --- a/thirdparty/rocksdb/java/rocksjni/env.cc +++ b/thirdparty/rocksdb/java/rocksjni/env.cc @@ -6,10 +6,16 @@ // This file implements the "bridge" between Java and C++ and enables // calling c++ rocksdb::Env methods from Java side. +#include +#include + +#include "portal.h" +#include "rocksdb/env.h" #include "include/org_rocksdb_Env.h" +#include "include/org_rocksdb_HdfsEnv.h" #include "include/org_rocksdb_RocksEnv.h" #include "include/org_rocksdb_RocksMemEnv.h" -#include "rocksdb/env.h" +#include "include/org_rocksdb_TimedEnv.h" /* * Class: org_rocksdb_Env @@ -17,55 +23,143 @@ * Signature: ()J */ jlong Java_org_rocksdb_Env_getDefaultEnvInternal( - JNIEnv* env, jclass jclazz) { + JNIEnv*, jclass) { return reinterpret_cast(rocksdb::Env::Default()); } +/* + * Class: org_rocksdb_RocksEnv + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_RocksEnv_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* e = reinterpret_cast(jhandle); + assert(e != nullptr); + delete e; +} + /* * Class: org_rocksdb_Env * Method: setBackgroundThreads - * Signature: (JII)V + * Signature: (JIB)V */ void Java_org_rocksdb_Env_setBackgroundThreads( - JNIEnv* env, jobject jobj, jlong jhandle, - jint num, jint priority) { + JNIEnv*, jobject, jlong jhandle, jint jnum, jbyte jpriority_value) { auto* rocks_env = reinterpret_cast(jhandle); - switch (priority) { - case org_rocksdb_Env_FLUSH_POOL: - rocks_env->SetBackgroundThreads(num, rocksdb::Env::Priority::LOW); - break; - case org_rocksdb_Env_COMPACTION_POOL: - rocks_env->SetBackgroundThreads(num, rocksdb::Env::Priority::HIGH); - break; - } + rocks_env->SetBackgroundThreads(static_cast(jnum), + rocksdb::PriorityJni::toCppPriority(jpriority_value)); +} + +/* + * Class: org_rocksdb_Env + * Method: getBackgroundThreads + * Signature: (JB)I + */ +jint Java_org_rocksdb_Env_getBackgroundThreads( + JNIEnv*, jobject, jlong jhandle, jbyte jpriority_value) { + auto* rocks_env = reinterpret_cast(jhandle); + const int num = rocks_env->GetBackgroundThreads( + rocksdb::PriorityJni::toCppPriority(jpriority_value)); + return static_cast(num); } /* - * Class: org_rocksdb_sEnv + * Class: org_rocksdb_Env * Method: getThreadPoolQueueLen - * Signature: (JI)I + * Signature: (JB)I */ jint Java_org_rocksdb_Env_getThreadPoolQueueLen( - JNIEnv* env, jobject jobj, jlong jhandle, jint pool_id) { + JNIEnv*, jobject, jlong jhandle, jbyte jpriority_value) { + auto* rocks_env = reinterpret_cast(jhandle); + const int queue_len = rocks_env->GetThreadPoolQueueLen( + rocksdb::PriorityJni::toCppPriority(jpriority_value)); + return static_cast(queue_len); +} + +/* + * Class: org_rocksdb_Env + * Method: incBackgroundThreadsIfNeeded + * Signature: (JIB)V + */ +void Java_org_rocksdb_Env_incBackgroundThreadsIfNeeded( + JNIEnv*, jobject, jlong jhandle, jint jnum, jbyte jpriority_value) { + auto* rocks_env = reinterpret_cast(jhandle); + rocks_env->IncBackgroundThreadsIfNeeded(static_cast(jnum), + rocksdb::PriorityJni::toCppPriority(jpriority_value)); +} + +/* + * Class: org_rocksdb_Env + * Method: lowerThreadPoolIOPriority + * Signature: (JB)V + */ +void Java_org_rocksdb_Env_lowerThreadPoolIOPriority( + JNIEnv*, jobject, jlong jhandle, jbyte jpriority_value) { + auto* rocks_env = reinterpret_cast(jhandle); + rocks_env->LowerThreadPoolIOPriority( + rocksdb::PriorityJni::toCppPriority(jpriority_value)); +} + +/* + * Class: org_rocksdb_Env + * Method: lowerThreadPoolCPUPriority + * Signature: (JB)V + */ +void Java_org_rocksdb_Env_lowerThreadPoolCPUPriority( + JNIEnv*, jobject, jlong jhandle, jbyte jpriority_value) { + auto* rocks_env = reinterpret_cast(jhandle); + rocks_env->LowerThreadPoolCPUPriority( + rocksdb::PriorityJni::toCppPriority(jpriority_value)); +} + +/* + * Class: org_rocksdb_Env + * Method: getThreadList + * Signature: (J)[Lorg/rocksdb/ThreadStatus; + */ +jobjectArray Java_org_rocksdb_Env_getThreadList( + JNIEnv* env, jobject, jlong jhandle) { auto* rocks_env = reinterpret_cast(jhandle); - switch (pool_id) { - case org_rocksdb_RocksEnv_FLUSH_POOL: - return rocks_env->GetThreadPoolQueueLen(rocksdb::Env::Priority::LOW); - case org_rocksdb_RocksEnv_COMPACTION_POOL: - return rocks_env->GetThreadPoolQueueLen(rocksdb::Env::Priority::HIGH); + std::vector thread_status; + rocksdb::Status s = rocks_env->GetThreadList(&thread_status); + if (!s.ok()) { + // error, throw exception + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; } - return 0; + + // object[] + const jsize len = static_cast(thread_status.size()); + jobjectArray jthread_status = + env->NewObjectArray(len, rocksdb::ThreadStatusJni::getJClass(env), nullptr); + if (jthread_status == nullptr) { + // an exception occurred + return nullptr; + } + for (jsize i = 0; i < len; ++i) { + jobject jts = + rocksdb::ThreadStatusJni::construct(env, &(thread_status[i])); + env->SetObjectArrayElement(jthread_status, i, jts); + if (env->ExceptionCheck()) { + // exception occurred + env->DeleteLocalRef(jthread_status); + return nullptr; + } + } + + return jthread_status; } /* * Class: org_rocksdb_RocksMemEnv * Method: createMemEnv - * Signature: ()J + * Signature: (J)J */ jlong Java_org_rocksdb_RocksMemEnv_createMemEnv( - JNIEnv* env, jclass jclazz) { - return reinterpret_cast(rocksdb::NewMemEnv( - rocksdb::Env::Default())); + JNIEnv*, jclass, jlong jbase_env_handle) { + auto* base_env = reinterpret_cast(jbase_env_handle); + return reinterpret_cast(rocksdb::NewMemEnv(base_env)); } /* @@ -74,8 +168,67 @@ jlong Java_org_rocksdb_RocksMemEnv_createMemEnv( * Signature: (J)V */ void Java_org_rocksdb_RocksMemEnv_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* e = reinterpret_cast(jhandle); assert(e != nullptr); delete e; } + +/* + * Class: org_rocksdb_HdfsEnv + * Method: createHdfsEnv + * Signature: (Ljava/lang/String;)J + */ +jlong Java_org_rocksdb_HdfsEnv_createHdfsEnv( + JNIEnv* env, jclass, jstring jfsname) { + jboolean has_exception = JNI_FALSE; + auto fsname = rocksdb::JniUtil::copyStdString(env, jfsname, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return 0; + } + rocksdb::Env* hdfs_env; + rocksdb::Status s = rocksdb::NewHdfsEnv(&hdfs_env, fsname); + if (!s.ok()) { + // error occurred + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return 0; + } + return reinterpret_cast(hdfs_env); +} + +/* + * Class: org_rocksdb_HdfsEnv + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_HdfsEnv_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* e = reinterpret_cast(jhandle); + assert(e != nullptr); + delete e; +} + +/* + * Class: org_rocksdb_TimedEnv + * Method: createTimedEnv + * Signature: (J)J + */ +jlong Java_org_rocksdb_TimedEnv_createTimedEnv( + JNIEnv*, jclass, jlong jbase_env_handle) { + auto* base_env = reinterpret_cast(jbase_env_handle); + return reinterpret_cast(rocksdb::NewTimedEnv(base_env)); +} + +/* + * Class: org_rocksdb_TimedEnv + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_TimedEnv_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* e = reinterpret_cast(jhandle); + assert(e != nullptr); + delete e; +} + diff --git a/thirdparty/rocksdb/java/rocksjni/env_options.cc b/thirdparty/rocksdb/java/rocksjni/env_options.cc index 538b0b69f7..9ed330183c 100644 --- a/thirdparty/rocksdb/java/rocksjni/env_options.cc +++ b/thirdparty/rocksdb/java/rocksjni/env_options.cc @@ -32,104 +32,115 @@ * Method: newEnvOptions * Signature: ()J */ -jlong Java_org_rocksdb_EnvOptions_newEnvOptions(JNIEnv *env, jclass jcls) { +jlong Java_org_rocksdb_EnvOptions_newEnvOptions__( + JNIEnv*, jclass) { auto *env_opt = new rocksdb::EnvOptions(); return reinterpret_cast(env_opt); } +/* + * Class: org_rocksdb_EnvOptions + * Method: newEnvOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_EnvOptions_newEnvOptions__J( + JNIEnv*, jclass, jlong jdboptions_handle) { + auto* db_options = + reinterpret_cast(jdboptions_handle); + auto* env_opt = new rocksdb::EnvOptions(*db_options); + return reinterpret_cast(env_opt); +} + /* * Class: org_rocksdb_EnvOptions * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_EnvOptions_disposeInternal(JNIEnv *env, jobject jobj, - jlong jhandle) { - auto* eo = reinterpret_cast(jhandle); +void Java_org_rocksdb_EnvOptions_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto *eo = reinterpret_cast(jhandle); assert(eo != nullptr); delete eo; } /* * Class: org_rocksdb_EnvOptions - * Method: setUseDirectReads + * Method: setUseMmapReads * Signature: (JZ)V */ -void Java_org_rocksdb_EnvOptions_setUseDirectReads(JNIEnv *env, jobject jobj, - jlong jhandle, - jboolean use_direct_reads) { - ENV_OPTIONS_SET_BOOL(jhandle, use_direct_reads); +void Java_org_rocksdb_EnvOptions_setUseMmapReads( + JNIEnv*, jobject, jlong jhandle, jboolean use_mmap_reads) { + ENV_OPTIONS_SET_BOOL(jhandle, use_mmap_reads); } /* * Class: org_rocksdb_EnvOptions - * Method: useDirectReads + * Method: useMmapReads * Signature: (J)Z */ -jboolean Java_org_rocksdb_EnvOptions_useDirectReads(JNIEnv *env, jobject jobj, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_direct_reads); +jboolean Java_org_rocksdb_EnvOptions_useMmapReads( + JNIEnv*, jobject, jlong jhandle) { + return ENV_OPTIONS_GET(jhandle, use_mmap_reads); } /* * Class: org_rocksdb_EnvOptions - * Method: setUseDirectWrites + * Method: setUseMmapWrites * Signature: (JZ)V */ -void Java_org_rocksdb_EnvOptions_setUseDirectWrites( - JNIEnv *env, jobject jobj, jlong jhandle, jboolean use_direct_writes) { - ENV_OPTIONS_SET_BOOL(jhandle, use_direct_writes); +void Java_org_rocksdb_EnvOptions_setUseMmapWrites( + JNIEnv*, jobject, jlong jhandle, jboolean use_mmap_writes) { + ENV_OPTIONS_SET_BOOL(jhandle, use_mmap_writes); } /* * Class: org_rocksdb_EnvOptions - * Method: useDirectWrites + * Method: useMmapWrites * Signature: (J)Z */ -jboolean Java_org_rocksdb_EnvOptions_useDirectWrites(JNIEnv *env, jobject jobj, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_direct_writes); +jboolean Java_org_rocksdb_EnvOptions_useMmapWrites( + JNIEnv*, jobject, jlong jhandle) { + return ENV_OPTIONS_GET(jhandle, use_mmap_writes); } /* * Class: org_rocksdb_EnvOptions - * Method: setUseMmapReads + * Method: setUseDirectReads * Signature: (JZ)V */ -void Java_org_rocksdb_EnvOptions_setUseMmapReads(JNIEnv *env, jobject jobj, - jlong jhandle, - jboolean use_mmap_reads) { - ENV_OPTIONS_SET_BOOL(jhandle, use_mmap_reads); +void Java_org_rocksdb_EnvOptions_setUseDirectReads( + JNIEnv*, jobject, jlong jhandle, jboolean use_direct_reads) { + ENV_OPTIONS_SET_BOOL(jhandle, use_direct_reads); } /* * Class: org_rocksdb_EnvOptions - * Method: useMmapReads + * Method: useDirectReads * Signature: (J)Z */ -jboolean Java_org_rocksdb_EnvOptions_useMmapReads(JNIEnv *env, jobject jobj, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_mmap_reads); +jboolean Java_org_rocksdb_EnvOptions_useDirectReads( + JNIEnv*, jobject, jlong jhandle) { + return ENV_OPTIONS_GET(jhandle, use_direct_reads); } /* * Class: org_rocksdb_EnvOptions - * Method: setUseMmapWrites + * Method: setUseDirectWrites * Signature: (JZ)V */ -void Java_org_rocksdb_EnvOptions_setUseMmapWrites(JNIEnv *env, jobject jobj, - jlong jhandle, - jboolean use_mmap_writes) { - ENV_OPTIONS_SET_BOOL(jhandle, use_mmap_writes); +void Java_org_rocksdb_EnvOptions_setUseDirectWrites( + JNIEnv*, jobject, jlong jhandle, jboolean use_direct_writes) { + ENV_OPTIONS_SET_BOOL(jhandle, use_direct_writes); } /* * Class: org_rocksdb_EnvOptions - * Method: useMmapWrites + * Method: useDirectWrites * Signature: (J)Z */ -jboolean Java_org_rocksdb_EnvOptions_useMmapWrites(JNIEnv *env, jobject jobj, - jlong jhandle) { - return ENV_OPTIONS_GET(jhandle, use_mmap_writes); +jboolean Java_org_rocksdb_EnvOptions_useDirectWrites( + JNIEnv*, jobject, jlong jhandle) { + return ENV_OPTIONS_GET(jhandle, use_direct_writes); } /* @@ -137,9 +148,8 @@ jboolean Java_org_rocksdb_EnvOptions_useMmapWrites(JNIEnv *env, jobject jobj, * Method: setAllowFallocate * Signature: (JZ)V */ -void Java_org_rocksdb_EnvOptions_setAllowFallocate(JNIEnv *env, jobject jobj, - jlong jhandle, - jboolean allow_fallocate) { +void Java_org_rocksdb_EnvOptions_setAllowFallocate( + JNIEnv*, jobject, jlong jhandle, jboolean allow_fallocate) { ENV_OPTIONS_SET_BOOL(jhandle, allow_fallocate); } @@ -148,8 +158,8 @@ void Java_org_rocksdb_EnvOptions_setAllowFallocate(JNIEnv *env, jobject jobj, * Method: allowFallocate * Signature: (J)Z */ -jboolean Java_org_rocksdb_EnvOptions_allowFallocate(JNIEnv *env, jobject jobj, - jlong jhandle) { +jboolean Java_org_rocksdb_EnvOptions_allowFallocate( + JNIEnv*, jobject, jlong jhandle) { return ENV_OPTIONS_GET(jhandle, allow_fallocate); } @@ -158,9 +168,8 @@ jboolean Java_org_rocksdb_EnvOptions_allowFallocate(JNIEnv *env, jobject jobj, * Method: setSetFdCloexec * Signature: (JZ)V */ -void Java_org_rocksdb_EnvOptions_setSetFdCloexec(JNIEnv *env, jobject jobj, - jlong jhandle, - jboolean set_fd_cloexec) { +void Java_org_rocksdb_EnvOptions_setSetFdCloexec( + JNIEnv*, jobject, jlong jhandle, jboolean set_fd_cloexec) { ENV_OPTIONS_SET_BOOL(jhandle, set_fd_cloexec); } @@ -169,8 +178,8 @@ void Java_org_rocksdb_EnvOptions_setSetFdCloexec(JNIEnv *env, jobject jobj, * Method: setFdCloexec * Signature: (J)Z */ -jboolean Java_org_rocksdb_EnvOptions_setFdCloexec(JNIEnv *env, jobject jobj, - jlong jhandle) { +jboolean Java_org_rocksdb_EnvOptions_setFdCloexec( + JNIEnv*, jobject, jlong jhandle) { return ENV_OPTIONS_GET(jhandle, set_fd_cloexec); } @@ -179,9 +188,8 @@ jboolean Java_org_rocksdb_EnvOptions_setFdCloexec(JNIEnv *env, jobject jobj, * Method: setBytesPerSync * Signature: (JJ)V */ -void Java_org_rocksdb_EnvOptions_setBytesPerSync(JNIEnv *env, jobject jobj, - jlong jhandle, - jlong bytes_per_sync) { +void Java_org_rocksdb_EnvOptions_setBytesPerSync( + JNIEnv*, jobject, jlong jhandle, jlong bytes_per_sync) { ENV_OPTIONS_SET_UINT64_T(jhandle, bytes_per_sync); } @@ -190,8 +198,8 @@ void Java_org_rocksdb_EnvOptions_setBytesPerSync(JNIEnv *env, jobject jobj, * Method: bytesPerSync * Signature: (J)J */ -jlong Java_org_rocksdb_EnvOptions_bytesPerSync(JNIEnv *env, jobject jobj, - jlong jhandle) { +jlong Java_org_rocksdb_EnvOptions_bytesPerSync( + JNIEnv*, jobject, jlong jhandle) { return ENV_OPTIONS_GET(jhandle, bytes_per_sync); } @@ -201,8 +209,7 @@ jlong Java_org_rocksdb_EnvOptions_bytesPerSync(JNIEnv *env, jobject jobj, * Signature: (JZ)V */ void Java_org_rocksdb_EnvOptions_setFallocateWithKeepSize( - JNIEnv *env, jobject jobj, jlong jhandle, - jboolean fallocate_with_keep_size) { + JNIEnv*, jobject, jlong jhandle, jboolean fallocate_with_keep_size) { ENV_OPTIONS_SET_BOOL(jhandle, fallocate_with_keep_size); } @@ -211,9 +218,8 @@ void Java_org_rocksdb_EnvOptions_setFallocateWithKeepSize( * Method: fallocateWithKeepSize * Signature: (J)Z */ -jboolean Java_org_rocksdb_EnvOptions_fallocateWithKeepSize(JNIEnv *env, - jobject jobj, - jlong jhandle) { +jboolean Java_org_rocksdb_EnvOptions_fallocateWithKeepSize( + JNIEnv*, jobject, jlong jhandle) { return ENV_OPTIONS_GET(jhandle, fallocate_with_keep_size); } @@ -223,7 +229,7 @@ jboolean Java_org_rocksdb_EnvOptions_fallocateWithKeepSize(JNIEnv *env, * Signature: (JJ)V */ void Java_org_rocksdb_EnvOptions_setCompactionReadaheadSize( - JNIEnv *env, jobject jobj, jlong jhandle, jlong compaction_readahead_size) { + JNIEnv*, jobject, jlong jhandle, jlong compaction_readahead_size) { ENV_OPTIONS_SET_SIZE_T(jhandle, compaction_readahead_size); } @@ -232,9 +238,8 @@ void Java_org_rocksdb_EnvOptions_setCompactionReadaheadSize( * Method: compactionReadaheadSize * Signature: (J)J */ -jlong Java_org_rocksdb_EnvOptions_compactionReadaheadSize(JNIEnv *env, - jobject jobj, - jlong jhandle) { +jlong Java_org_rocksdb_EnvOptions_compactionReadaheadSize( + JNIEnv*, jobject, jlong jhandle) { return ENV_OPTIONS_GET(jhandle, compaction_readahead_size); } @@ -244,8 +249,7 @@ jlong Java_org_rocksdb_EnvOptions_compactionReadaheadSize(JNIEnv *env, * Signature: (JJ)V */ void Java_org_rocksdb_EnvOptions_setRandomAccessMaxBufferSize( - JNIEnv *env, jobject jobj, jlong jhandle, - jlong random_access_max_buffer_size) { + JNIEnv*, jobject, jlong jhandle, jlong random_access_max_buffer_size) { ENV_OPTIONS_SET_SIZE_T(jhandle, random_access_max_buffer_size); } @@ -254,9 +258,8 @@ void Java_org_rocksdb_EnvOptions_setRandomAccessMaxBufferSize( * Method: randomAccessMaxBufferSize * Signature: (J)J */ -jlong Java_org_rocksdb_EnvOptions_randomAccessMaxBufferSize(JNIEnv *env, - jobject jobj, - jlong jhandle) { +jlong Java_org_rocksdb_EnvOptions_randomAccessMaxBufferSize( + JNIEnv*, jobject, jlong jhandle) { return ENV_OPTIONS_GET(jhandle, random_access_max_buffer_size); } @@ -266,8 +269,7 @@ jlong Java_org_rocksdb_EnvOptions_randomAccessMaxBufferSize(JNIEnv *env, * Signature: (JJ)V */ void Java_org_rocksdb_EnvOptions_setWritableFileMaxBufferSize( - JNIEnv *env, jobject jobj, jlong jhandle, - jlong writable_file_max_buffer_size) { + JNIEnv*, jobject, jlong jhandle, jlong writable_file_max_buffer_size) { ENV_OPTIONS_SET_SIZE_T(jhandle, writable_file_max_buffer_size); } @@ -276,9 +278,8 @@ void Java_org_rocksdb_EnvOptions_setWritableFileMaxBufferSize( * Method: writableFileMaxBufferSize * Signature: (J)J */ -jlong Java_org_rocksdb_EnvOptions_writableFileMaxBufferSize(JNIEnv *env, - jobject jobj, - jlong jhandle) { +jlong Java_org_rocksdb_EnvOptions_writableFileMaxBufferSize( + JNIEnv*, jobject, jlong jhandle) { return ENV_OPTIONS_GET(jhandle, writable_file_max_buffer_size); } @@ -287,11 +288,10 @@ jlong Java_org_rocksdb_EnvOptions_writableFileMaxBufferSize(JNIEnv *env, * Method: setRateLimiter * Signature: (JJ)V */ -void Java_org_rocksdb_EnvOptions_setRateLimiter(JNIEnv *env, jobject jobj, - jlong jhandle, - jlong rl_handle) { - auto* sptr_rate_limiter = +void Java_org_rocksdb_EnvOptions_setRateLimiter( + JNIEnv*, jobject, jlong jhandle, jlong rl_handle) { + auto *sptr_rate_limiter = reinterpret_cast *>(rl_handle); - auto* env_opt = reinterpret_cast(jhandle); + auto *env_opt = reinterpret_cast(jhandle); env_opt->rate_limiter = sptr_rate_limiter->get(); } diff --git a/thirdparty/rocksdb/java/rocksjni/filter.cc b/thirdparty/rocksdb/java/rocksjni/filter.cc index 7b186b8943..5e9c63643d 100644 --- a/thirdparty/rocksdb/java/rocksjni/filter.cc +++ b/thirdparty/rocksdb/java/rocksjni/filter.cc @@ -6,15 +6,15 @@ // This file implements the "bridge" between Java and C++ for // rocksdb::FilterPolicy. +#include #include #include -#include #include -#include "include/org_rocksdb_Filter.h" #include "include/org_rocksdb_BloomFilter.h" -#include "rocksjni/portal.h" +#include "include/org_rocksdb_Filter.h" #include "rocksdb/filter_policy.h" +#include "rocksjni/portal.h" /* * Class: org_rocksdb_BloomFilter @@ -22,11 +22,10 @@ * Signature: (IZ)J */ jlong Java_org_rocksdb_BloomFilter_createNewBloomFilter( - JNIEnv* env, jclass jcls, jint bits_per_key, + JNIEnv* /*env*/, jclass /*jcls*/, jint bits_per_key, jboolean use_block_base_builder) { - auto* sptr_filter = - new std::shared_ptr( - rocksdb::NewBloomFilterPolicy(bits_per_key, use_block_base_builder)); + auto* sptr_filter = new std::shared_ptr( + rocksdb::NewBloomFilterPolicy(bits_per_key, use_block_base_builder)); return reinterpret_cast(sptr_filter); } @@ -35,9 +34,9 @@ jlong Java_org_rocksdb_BloomFilter_createNewBloomFilter( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_Filter_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { +void Java_org_rocksdb_Filter_disposeInternal(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { auto* handle = - reinterpret_cast *>(jhandle); + reinterpret_cast*>(jhandle); delete handle; // delete std::shared_ptr } diff --git a/thirdparty/rocksdb/java/rocksjni/ingest_external_file_options.cc b/thirdparty/rocksdb/java/rocksjni/ingest_external_file_options.cc index 251a6e3c62..e0871ff8ed 100644 --- a/thirdparty/rocksdb/java/rocksjni/ingest_external_file_options.cc +++ b/thirdparty/rocksdb/java/rocksjni/ingest_external_file_options.cc @@ -17,7 +17,7 @@ * Signature: ()J */ jlong Java_org_rocksdb_IngestExternalFileOptions_newIngestExternalFileOptions__( - JNIEnv* env, jclass jclazz) { + JNIEnv*, jclass) { auto* options = new rocksdb::IngestExternalFileOptions(); return reinterpret_cast(options); } @@ -28,7 +28,7 @@ jlong Java_org_rocksdb_IngestExternalFileOptions_newIngestExternalFileOptions__( * Signature: (ZZZZ)J */ jlong Java_org_rocksdb_IngestExternalFileOptions_newIngestExternalFileOptions__ZZZZ( - JNIEnv* env, jclass jcls, jboolean jmove_files, + JNIEnv*, jclass, jboolean jmove_files, jboolean jsnapshot_consistency, jboolean jallow_global_seqno, jboolean jallow_blocking_flush) { auto* options = new rocksdb::IngestExternalFileOptions(); @@ -45,7 +45,7 @@ jlong Java_org_rocksdb_IngestExternalFileOptions_newIngestExternalFileOptions__Z * Signature: (J)Z */ jboolean Java_org_rocksdb_IngestExternalFileOptions_moveFiles( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* options = reinterpret_cast(jhandle); return static_cast(options->move_files); @@ -57,7 +57,7 @@ jboolean Java_org_rocksdb_IngestExternalFileOptions_moveFiles( * Signature: (JZ)V */ void Java_org_rocksdb_IngestExternalFileOptions_setMoveFiles( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jmove_files) { + JNIEnv*, jobject, jlong jhandle, jboolean jmove_files) { auto* options = reinterpret_cast(jhandle); options->move_files = static_cast(jmove_files); @@ -69,7 +69,7 @@ void Java_org_rocksdb_IngestExternalFileOptions_setMoveFiles( * Signature: (J)Z */ jboolean Java_org_rocksdb_IngestExternalFileOptions_snapshotConsistency( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* options = reinterpret_cast(jhandle); return static_cast(options->snapshot_consistency); @@ -81,8 +81,7 @@ jboolean Java_org_rocksdb_IngestExternalFileOptions_snapshotConsistency( * Signature: (JZ)V */ void Java_org_rocksdb_IngestExternalFileOptions_setSnapshotConsistency( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jsnapshot_consistency) { + JNIEnv*, jobject, jlong jhandle, jboolean jsnapshot_consistency) { auto* options = reinterpret_cast(jhandle); options->snapshot_consistency = static_cast(jsnapshot_consistency); @@ -94,7 +93,7 @@ void Java_org_rocksdb_IngestExternalFileOptions_setSnapshotConsistency( * Signature: (J)Z */ jboolean Java_org_rocksdb_IngestExternalFileOptions_allowGlobalSeqNo( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* options = reinterpret_cast(jhandle); return static_cast(options->allow_global_seqno); @@ -106,7 +105,7 @@ jboolean Java_org_rocksdb_IngestExternalFileOptions_allowGlobalSeqNo( * Signature: (JZ)V */ void Java_org_rocksdb_IngestExternalFileOptions_setAllowGlobalSeqNo( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jallow_global_seqno) { + JNIEnv*, jobject, jlong jhandle, jboolean jallow_global_seqno) { auto* options = reinterpret_cast(jhandle); options->allow_global_seqno = static_cast(jallow_global_seqno); @@ -118,7 +117,7 @@ void Java_org_rocksdb_IngestExternalFileOptions_setAllowGlobalSeqNo( * Signature: (J)Z */ jboolean Java_org_rocksdb_IngestExternalFileOptions_allowBlockingFlush( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* options = reinterpret_cast(jhandle); return static_cast(options->allow_blocking_flush); @@ -130,19 +129,67 @@ jboolean Java_org_rocksdb_IngestExternalFileOptions_allowBlockingFlush( * Signature: (JZ)V */ void Java_org_rocksdb_IngestExternalFileOptions_setAllowBlockingFlush( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jallow_blocking_flush) { + JNIEnv*, jobject, jlong jhandle, jboolean jallow_blocking_flush) { auto* options = reinterpret_cast(jhandle); options->allow_blocking_flush = static_cast(jallow_blocking_flush); } +/* + * Class: org_rocksdb_IngestExternalFileOptions + * Method: ingestBehind + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_IngestExternalFileOptions_ingestBehind( + JNIEnv*, jobject, jlong jhandle) { + auto* options = + reinterpret_cast(jhandle); + return options->ingest_behind == JNI_TRUE; +} + +/* + * Class: org_rocksdb_IngestExternalFileOptions + * Method: setIngestBehind + * Signature: (JZ)V + */ +void Java_org_rocksdb_IngestExternalFileOptions_setIngestBehind( + JNIEnv*, jobject, jlong jhandle, jboolean jingest_behind) { + auto* options = + reinterpret_cast(jhandle); + options->ingest_behind = jingest_behind == JNI_TRUE; +} + +/* + * Class: org_rocksdb_IngestExternalFileOptions + * Method: writeGlobalSeqno + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_IngestExternalFileOptions_writeGlobalSeqno( + JNIEnv*, jobject, jlong jhandle) { + auto* options = + reinterpret_cast(jhandle); + return options->write_global_seqno == JNI_TRUE; +} + +/* + * Class: org_rocksdb_IngestExternalFileOptions + * Method: setWriteGlobalSeqno + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_org_rocksdb_IngestExternalFileOptions_setWriteGlobalSeqno( + JNIEnv*, jobject, jlong jhandle, jboolean jwrite_global_seqno) { + auto* options = + reinterpret_cast(jhandle); + options->write_global_seqno = jwrite_global_seqno == JNI_TRUE; +} + /* * Class: org_rocksdb_IngestExternalFileOptions * Method: disposeInternal * Signature: (J)V */ void Java_org_rocksdb_IngestExternalFileOptions_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* options = reinterpret_cast(jhandle); delete options; diff --git a/thirdparty/rocksdb/java/rocksjni/iterator.cc b/thirdparty/rocksdb/java/rocksjni/iterator.cc index 3ac9d5033f..18daeb8161 100644 --- a/thirdparty/rocksdb/java/rocksjni/iterator.cc +++ b/thirdparty/rocksdb/java/rocksjni/iterator.cc @@ -6,21 +6,22 @@ // This file implements the "bridge" between Java and C++ and enables // calling c++ rocksdb::Iterator methods from Java side. +#include #include #include -#include #include "include/org_rocksdb_RocksIterator.h" -#include "rocksjni/portal.h" #include "rocksdb/iterator.h" +#include "rocksjni/portal.h" /* * Class: org_rocksdb_RocksIterator * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_RocksIterator_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_RocksIterator_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { auto* it = reinterpret_cast(handle); assert(it != nullptr); delete it; @@ -31,8 +32,9 @@ void Java_org_rocksdb_RocksIterator_disposeInternal( * Method: isValid0 * Signature: (J)Z */ -jboolean Java_org_rocksdb_RocksIterator_isValid0( - JNIEnv* env, jobject jobj, jlong handle) { +jboolean Java_org_rocksdb_RocksIterator_isValid0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { return reinterpret_cast(handle)->Valid(); } @@ -41,8 +43,9 @@ jboolean Java_org_rocksdb_RocksIterator_isValid0( * Method: seekToFirst0 * Signature: (J)V */ -void Java_org_rocksdb_RocksIterator_seekToFirst0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_RocksIterator_seekToFirst0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->SeekToFirst(); } @@ -51,8 +54,9 @@ void Java_org_rocksdb_RocksIterator_seekToFirst0( * Method: seekToLast0 * Signature: (J)V */ -void Java_org_rocksdb_RocksIterator_seekToLast0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_RocksIterator_seekToLast0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->SeekToLast(); } @@ -61,8 +65,8 @@ void Java_org_rocksdb_RocksIterator_seekToLast0( * Method: next0 * Signature: (J)V */ -void Java_org_rocksdb_RocksIterator_next0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_RocksIterator_next0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->Next(); } @@ -71,8 +75,8 @@ void Java_org_rocksdb_RocksIterator_next0( * Method: prev0 * Signature: (J)V */ -void Java_org_rocksdb_RocksIterator_prev0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_RocksIterator_prev0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->Prev(); } @@ -81,17 +85,16 @@ void Java_org_rocksdb_RocksIterator_prev0( * Method: seek0 * Signature: (J[BI)V */ -void Java_org_rocksdb_RocksIterator_seek0( - JNIEnv* env, jobject jobj, jlong handle, - jbyteArray jtarget, jint jtarget_len) { +void Java_org_rocksdb_RocksIterator_seek0(JNIEnv* env, jobject /*jobj*/, + jlong handle, jbyteArray jtarget, + jint jtarget_len) { jbyte* target = env->GetByteArrayElements(jtarget, nullptr); - if(target == nullptr) { + if (target == nullptr) { // exception thrown: OutOfMemoryError return; } - rocksdb::Slice target_slice( - reinterpret_cast(target), jtarget_len); + rocksdb::Slice target_slice(reinterpret_cast(target), jtarget_len); auto* it = reinterpret_cast(handle); it->Seek(target_slice); @@ -99,13 +102,36 @@ void Java_org_rocksdb_RocksIterator_seek0( env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT); } +/* + * Class: org_rocksdb_RocksIterator + * Method: seekForPrev0 + * Signature: (J[BI)V + */ +void Java_org_rocksdb_RocksIterator_seekForPrev0(JNIEnv* env, jobject /*jobj*/, + jlong handle, + jbyteArray jtarget, + jint jtarget_len) { + jbyte* target = env->GetByteArrayElements(jtarget, nullptr); + if (target == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + + rocksdb::Slice target_slice(reinterpret_cast(target), jtarget_len); + + auto* it = reinterpret_cast(handle); + it->SeekForPrev(target_slice); + + env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT); +} + /* * Class: org_rocksdb_RocksIterator * Method: status0 * Signature: (J)V */ -void Java_org_rocksdb_RocksIterator_status0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_RocksIterator_status0(JNIEnv* env, jobject /*jobj*/, + jlong handle) { auto* it = reinterpret_cast(handle); rocksdb::Status s = it->status(); @@ -121,18 +147,19 @@ void Java_org_rocksdb_RocksIterator_status0( * Method: key0 * Signature: (J)[B */ -jbyteArray Java_org_rocksdb_RocksIterator_key0( - JNIEnv* env, jobject jobj, jlong handle) { +jbyteArray Java_org_rocksdb_RocksIterator_key0(JNIEnv* env, jobject /*jobj*/, + jlong handle) { auto* it = reinterpret_cast(handle); rocksdb::Slice key_slice = it->key(); jbyteArray jkey = env->NewByteArray(static_cast(key_slice.size())); - if(jkey == nullptr) { + if (jkey == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } - env->SetByteArrayRegion(jkey, 0, static_cast(key_slice.size()), - const_cast(reinterpret_cast(key_slice.data()))); + env->SetByteArrayRegion( + jkey, 0, static_cast(key_slice.size()), + const_cast(reinterpret_cast(key_slice.data()))); return jkey; } @@ -141,18 +168,19 @@ jbyteArray Java_org_rocksdb_RocksIterator_key0( * Method: value0 * Signature: (J)[B */ -jbyteArray Java_org_rocksdb_RocksIterator_value0( - JNIEnv* env, jobject jobj, jlong handle) { +jbyteArray Java_org_rocksdb_RocksIterator_value0(JNIEnv* env, jobject /*jobj*/, + jlong handle) { auto* it = reinterpret_cast(handle); rocksdb::Slice value_slice = it->value(); jbyteArray jkeyValue = env->NewByteArray(static_cast(value_slice.size())); - if(jkeyValue == nullptr) { + if (jkeyValue == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } - env->SetByteArrayRegion(jkeyValue, 0, static_cast(value_slice.size()), - const_cast(reinterpret_cast(value_slice.data()))); + env->SetByteArrayRegion( + jkeyValue, 0, static_cast(value_slice.size()), + const_cast(reinterpret_cast(value_slice.data()))); return jkeyValue; } diff --git a/thirdparty/rocksdb/java/rocksjni/jnicallback.cc b/thirdparty/rocksdb/java/rocksjni/jnicallback.cc new file mode 100644 index 0000000000..f72eecd4c2 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/jnicallback.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// JNI Callbacks from C++ to sub-classes or org.rocksdb.RocksCallbackObject + +#include +#include "rocksjni/jnicallback.h" +#include "rocksjni/portal.h" + +namespace rocksdb { +JniCallback::JniCallback(JNIEnv* env, jobject jcallback_obj) { + // Note: jcallback_obj may be accessed by multiple threads, + // so we ref the jvm not the env + const jint rs = env->GetJavaVM(&m_jvm); + if(rs != JNI_OK) { + // exception thrown + return; + } + + // Note: we may want to access the Java callback object instance + // across multiple method calls, so we create a global ref + assert(jcallback_obj != nullptr); + m_jcallback_obj = env->NewGlobalRef(jcallback_obj); + if(jcallback_obj == nullptr) { + // exception thrown: OutOfMemoryError + return; + } +} + +JNIEnv* JniCallback::getJniEnv(jboolean* attached) const { + return JniUtil::getJniEnv(m_jvm, attached); +} + +void JniCallback::releaseJniEnv(jboolean& attached) const { + JniUtil::releaseJniEnv(m_jvm, attached); +} + +JniCallback::~JniCallback() { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + assert(env != nullptr); + + if(m_jcallback_obj != nullptr) { + env->DeleteGlobalRef(m_jcallback_obj); + } + + releaseJniEnv(attached_thread); +} +// @lint-ignore TXT4 T25377293 Grandfathered in +} // namespace rocksdb \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/jnicallback.h b/thirdparty/rocksdb/java/rocksjni/jnicallback.h new file mode 100644 index 0000000000..940ecf064d --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/jnicallback.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// JNI Callbacks from C++ to sub-classes or org.rocksdb.RocksCallbackObject + +#ifndef JAVA_ROCKSJNI_JNICALLBACK_H_ +#define JAVA_ROCKSJNI_JNICALLBACK_H_ + +#include + +namespace rocksdb { + class JniCallback { + public: + JniCallback(JNIEnv* env, jobject jcallback_obj); + virtual ~JniCallback(); + + protected: + JavaVM* m_jvm; + jobject m_jcallback_obj; + JNIEnv* getJniEnv(jboolean* attached) const; + void releaseJniEnv(jboolean& attached) const; + }; +} + +// @lint-ignore TXT4 T25377293 Grandfathered in +#endif // JAVA_ROCKSJNI_JNICALLBACK_H_ \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.cc b/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.cc index 09140ed709..61571e9871 100644 --- a/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.cc +++ b/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.cc @@ -8,119 +8,102 @@ #include "include/org_rocksdb_Logger.h" -#include "rocksjni/loggerjnicallback.h" -#include "rocksjni/portal.h" #include #include +#include "rocksjni/loggerjnicallback.h" +#include "rocksjni/portal.h" namespace rocksdb { -LoggerJniCallback::LoggerJniCallback( - JNIEnv* env, jobject jlogger) { - // Note: Logger methods may be accessed by multiple threads, - // so we ref the jvm not the env - const jint rs = env->GetJavaVM(&m_jvm); - if(rs != JNI_OK) { - // exception thrown - return; - } - - // Note: we want to access the Java Logger instance - // across multiple method calls, so we create a global ref - assert(jlogger != nullptr); - m_jLogger = env->NewGlobalRef(jlogger); - if(m_jLogger == nullptr) { - // exception thrown: OutOfMemoryError - return; - } +LoggerJniCallback::LoggerJniCallback(JNIEnv* env, jobject jlogger) + : JniCallback(env, jlogger) { m_jLogMethodId = LoggerJni::getLogMethodId(env); - if(m_jLogMethodId == nullptr) { + if (m_jLogMethodId == nullptr) { // exception thrown: NoSuchMethodException or OutOfMemoryError return; } jobject jdebug_level = InfoLogLevelJni::DEBUG_LEVEL(env); - if(jdebug_level == nullptr) { + if (jdebug_level == nullptr) { // exception thrown: NoSuchFieldError, ExceptionInInitializerError // or OutOfMemoryError return; } m_jdebug_level = env->NewGlobalRef(jdebug_level); - if(m_jdebug_level == nullptr) { + if (m_jdebug_level == nullptr) { // exception thrown: OutOfMemoryError return; } jobject jinfo_level = InfoLogLevelJni::INFO_LEVEL(env); - if(jinfo_level == nullptr) { + if (jinfo_level == nullptr) { // exception thrown: NoSuchFieldError, ExceptionInInitializerError // or OutOfMemoryError return; } m_jinfo_level = env->NewGlobalRef(jinfo_level); - if(m_jinfo_level == nullptr) { + if (m_jinfo_level == nullptr) { // exception thrown: OutOfMemoryError return; } jobject jwarn_level = InfoLogLevelJni::WARN_LEVEL(env); - if(jwarn_level == nullptr) { + if (jwarn_level == nullptr) { // exception thrown: NoSuchFieldError, ExceptionInInitializerError // or OutOfMemoryError return; } m_jwarn_level = env->NewGlobalRef(jwarn_level); - if(m_jwarn_level == nullptr) { + if (m_jwarn_level == nullptr) { // exception thrown: OutOfMemoryError return; } jobject jerror_level = InfoLogLevelJni::ERROR_LEVEL(env); - if(jerror_level == nullptr) { + if (jerror_level == nullptr) { // exception thrown: NoSuchFieldError, ExceptionInInitializerError // or OutOfMemoryError return; } m_jerror_level = env->NewGlobalRef(jerror_level); - if(m_jerror_level == nullptr) { + if (m_jerror_level == nullptr) { // exception thrown: OutOfMemoryError return; } jobject jfatal_level = InfoLogLevelJni::FATAL_LEVEL(env); - if(jfatal_level == nullptr) { + if (jfatal_level == nullptr) { // exception thrown: NoSuchFieldError, ExceptionInInitializerError // or OutOfMemoryError return; } m_jfatal_level = env->NewGlobalRef(jfatal_level); - if(m_jfatal_level == nullptr) { + if (m_jfatal_level == nullptr) { // exception thrown: OutOfMemoryError return; } jobject jheader_level = InfoLogLevelJni::HEADER_LEVEL(env); - if(jheader_level == nullptr) { + if (jheader_level == nullptr) { // exception thrown: NoSuchFieldError, ExceptionInInitializerError // or OutOfMemoryError return; } m_jheader_level = env->NewGlobalRef(jheader_level); - if(m_jheader_level == nullptr) { + if (m_jheader_level == nullptr) { // exception thrown: OutOfMemoryError return; } } -void LoggerJniCallback::Logv(const char* format, va_list ap) { +void LoggerJniCallback::Logv(const char* /*format*/, va_list /*ap*/) { // We implement this method because it is virtual but we don't // use it because we need to know about the log level. } -void LoggerJniCallback::Logv(const InfoLogLevel log_level, - const char* format, va_list ap) { +void LoggerJniCallback::Logv(const InfoLogLevel log_level, const char* format, + va_list ap) { if (GetInfoLogLevel() <= log_level) { - // determine InfoLogLevel java enum instance jobject jlog_level; switch (log_level) { @@ -153,45 +136,47 @@ void LoggerJniCallback::Logv(const InfoLogLevel log_level, // pass msg to java callback handler jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); + JNIEnv* env = getJniEnv(&attached_thread); assert(env != nullptr); jstring jmsg = env->NewStringUTF(msg.get()); - if(jmsg == nullptr) { + if (jmsg == nullptr) { // unable to construct string - if(env->ExceptionCheck()) { - env->ExceptionDescribe(); // print out exception to stderr + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); // print out exception to stderr } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: OutOfMemoryError - env->ExceptionDescribe(); // print out exception to stderr + env->ExceptionDescribe(); // print out exception to stderr env->DeleteLocalRef(jmsg); - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } - env->CallVoidMethod(m_jLogger, m_jLogMethodId, jlog_level, jmsg); - if(env->ExceptionCheck()) { + env->CallVoidMethod(m_jcallback_obj, m_jLogMethodId, jlog_level, jmsg); + if (env->ExceptionCheck()) { // exception thrown - env->ExceptionDescribe(); // print out exception to stderr + env->ExceptionDescribe(); // print out exception to stderr env->DeleteLocalRef(jmsg); - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); return; } env->DeleteLocalRef(jmsg); - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); } } -std::unique_ptr LoggerJniCallback::format_str(const char* format, va_list ap) const { +std::unique_ptr LoggerJniCallback::format_str(const char* format, + va_list ap) const { va_list ap_copy; va_copy(ap_copy, ap); - const size_t required = vsnprintf(nullptr, 0, format, ap_copy) + 1; // Extra space for '\0' + const size_t required = + vsnprintf(nullptr, 0, format, ap_copy) + 1; // Extra space for '\0' va_end(ap_copy); std::unique_ptr buf(new char[required]); @@ -202,41 +187,36 @@ std::unique_ptr LoggerJniCallback::format_str(const char* format, va_lis return buf; } - LoggerJniCallback::~LoggerJniCallback() { jboolean attached_thread = JNI_FALSE; - JNIEnv* env = JniUtil::getJniEnv(m_jvm, &attached_thread); + JNIEnv* env = getJniEnv(&attached_thread); assert(env != nullptr); - if(m_jLogger != nullptr) { - env->DeleteGlobalRef(m_jLogger); - } - - if(m_jdebug_level != nullptr) { + if (m_jdebug_level != nullptr) { env->DeleteGlobalRef(m_jdebug_level); } - if(m_jinfo_level != nullptr) { + if (m_jinfo_level != nullptr) { env->DeleteGlobalRef(m_jinfo_level); } - if(m_jwarn_level != nullptr) { + if (m_jwarn_level != nullptr) { env->DeleteGlobalRef(m_jwarn_level); } - if(m_jerror_level != nullptr) { + if (m_jerror_level != nullptr) { env->DeleteGlobalRef(m_jerror_level); } - if(m_jfatal_level != nullptr) { + if (m_jfatal_level != nullptr) { env->DeleteGlobalRef(m_jfatal_level); } - if(m_jheader_level != nullptr) { + if (m_jheader_level != nullptr) { env->DeleteGlobalRef(m_jheader_level); } - JniUtil::releaseJniEnv(m_jvm, attached_thread); + releaseJniEnv(attached_thread); } } // namespace rocksdb @@ -246,8 +226,8 @@ LoggerJniCallback::~LoggerJniCallback() { * Method: createNewLoggerOptions * Signature: (J)J */ -jlong Java_org_rocksdb_Logger_createNewLoggerOptions( - JNIEnv* env, jobject jobj, jlong joptions) { +jlong Java_org_rocksdb_Logger_createNewLoggerOptions(JNIEnv* env, jobject jobj, + jlong joptions) { auto* sptr_logger = new std::shared_ptr( new rocksdb::LoggerJniCallback(env, jobj)); @@ -263,10 +243,11 @@ jlong Java_org_rocksdb_Logger_createNewLoggerOptions( * Method: createNewLoggerDbOptions * Signature: (J)J */ -jlong Java_org_rocksdb_Logger_createNewLoggerDbOptions( - JNIEnv* env, jobject jobj, jlong jdb_options) { +jlong Java_org_rocksdb_Logger_createNewLoggerDbOptions(JNIEnv* env, + jobject jobj, + jlong jdb_options) { auto* sptr_logger = new std::shared_ptr( - new rocksdb::LoggerJniCallback(env, jobj)); + new rocksdb::LoggerJniCallback(env, jobj)); // set log level auto* db_options = reinterpret_cast(jdb_options); @@ -280,12 +261,12 @@ jlong Java_org_rocksdb_Logger_createNewLoggerDbOptions( * Method: setInfoLogLevel * Signature: (JB)V */ -void Java_org_rocksdb_Logger_setInfoLogLevel( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jlog_level) { +void Java_org_rocksdb_Logger_setInfoLogLevel(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle, jbyte jlog_level) { auto* handle = - reinterpret_cast *>(jhandle); - handle->get()-> - SetInfoLogLevel(static_cast(jlog_level)); + reinterpret_cast*>(jhandle); + handle->get()->SetInfoLogLevel( + static_cast(jlog_level)); } /* @@ -293,10 +274,10 @@ void Java_org_rocksdb_Logger_setInfoLogLevel( * Method: infoLogLevel * Signature: (J)B */ -jbyte Java_org_rocksdb_Logger_infoLogLevel( - JNIEnv* env, jobject jobj, jlong jhandle) { +jbyte Java_org_rocksdb_Logger_infoLogLevel(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { auto* handle = - reinterpret_cast *>(jhandle); + reinterpret_cast*>(jhandle); return static_cast(handle->get()->GetInfoLogLevel()); } @@ -305,9 +286,9 @@ jbyte Java_org_rocksdb_Logger_infoLogLevel( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_Logger_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { +void Java_org_rocksdb_Logger_disposeInternal(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { auto* handle = - reinterpret_cast *>(jhandle); + reinterpret_cast*>(jhandle); delete handle; // delete std::shared_ptr } diff --git a/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.h b/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.h index 2db85975d6..80c5a19833 100644 --- a/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.h +++ b/thirdparty/rocksdb/java/rocksjni/loggerjnicallback.h @@ -12,15 +12,16 @@ #include #include #include +#include "rocksjni/jnicallback.h" #include "port/port.h" #include "rocksdb/env.h" namespace rocksdb { - class LoggerJniCallback : public Logger { + class LoggerJniCallback : public JniCallback, public Logger { public: LoggerJniCallback(JNIEnv* env, jobject jLogger); - virtual ~LoggerJniCallback(); + ~LoggerJniCallback(); using Logger::SetInfoLogLevel; using Logger::GetInfoLogLevel; @@ -34,8 +35,6 @@ namespace rocksdb { const char* format, va_list ap); private: - JavaVM* m_jvm; - jobject m_jLogger; jmethodID m_jLogMethodId; jobject m_jdebug_level; jobject m_jinfo_level; diff --git a/thirdparty/rocksdb/java/rocksjni/lru_cache.cc b/thirdparty/rocksdb/java/rocksjni/lru_cache.cc index 16582689e7..2424bc8e01 100644 --- a/thirdparty/rocksdb/java/rocksjni/lru_cache.cc +++ b/thirdparty/rocksdb/java/rocksjni/lru_cache.cc @@ -16,13 +16,14 @@ * Method: newLRUCache * Signature: (JIZD)J */ -jlong Java_org_rocksdb_LRUCache_newLRUCache( - JNIEnv* env, jclass jcls, jlong jcapacity, jint jnum_shard_bits, - jboolean jstrict_capacity_limit, jdouble jhigh_pri_pool_ratio) { +jlong Java_org_rocksdb_LRUCache_newLRUCache(JNIEnv* /*env*/, jclass /*jcls*/, + jlong jcapacity, + jint jnum_shard_bits, + jboolean jstrict_capacity_limit, + jdouble jhigh_pri_pool_ratio) { auto* sptr_lru_cache = new std::shared_ptr(rocksdb::NewLRUCache( - static_cast(jcapacity), - static_cast(jnum_shard_bits), + static_cast(jcapacity), static_cast(jnum_shard_bits), static_cast(jstrict_capacity_limit), static_cast(jhigh_pri_pool_ratio))); return reinterpret_cast(sptr_lru_cache); @@ -33,9 +34,10 @@ jlong Java_org_rocksdb_LRUCache_newLRUCache( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_LRUCache_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { +void Java_org_rocksdb_LRUCache_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* sptr_lru_cache = - reinterpret_cast *>(jhandle); + reinterpret_cast*>(jhandle); delete sptr_lru_cache; // delete std::shared_ptr } diff --git a/thirdparty/rocksdb/java/rocksjni/memory_util.cc b/thirdparty/rocksdb/java/rocksjni/memory_util.cc new file mode 100644 index 0000000000..0438502139 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/memory_util.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#include +#include +#include +#include +#include + +#include "include/org_rocksdb_MemoryUtil.h" + +#include "rocksjni/portal.h" + +#include "rocksdb/utilities/memory_util.h" + + +/* + * Class: org_rocksdb_MemoryUtil + * Method: getApproximateMemoryUsageByType + * Signature: ([J[J)Ljava/util/Map; + */ +jobject Java_org_rocksdb_MemoryUtil_getApproximateMemoryUsageByType( + JNIEnv *env, jclass /*jclazz*/, jlongArray jdb_handles, jlongArray jcache_handles) { + + std::vector dbs; + jsize db_handle_count = env->GetArrayLength(jdb_handles); + if(db_handle_count > 0) { + jlong *ptr_jdb_handles = env->GetLongArrayElements(jdb_handles, nullptr); + if (ptr_jdb_handles == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + for (jsize i = 0; i < db_handle_count; i++) { + dbs.push_back(reinterpret_cast(ptr_jdb_handles[i])); + } + env->ReleaseLongArrayElements(jdb_handles, ptr_jdb_handles, JNI_ABORT); + } + + std::unordered_set cache_set; + jsize cache_handle_count = env->GetArrayLength(jcache_handles); + if(cache_handle_count > 0) { + jlong *ptr_jcache_handles = env->GetLongArrayElements(jcache_handles, nullptr); + if (ptr_jcache_handles == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + for (jsize i = 0; i < cache_handle_count; i++) { + auto *cache_ptr = + reinterpret_cast *>(ptr_jcache_handles[i]); + cache_set.insert(cache_ptr->get()); + } + env->ReleaseLongArrayElements(jcache_handles, ptr_jcache_handles, JNI_ABORT); + } + + std::map usage_by_type; + if(rocksdb::MemoryUtil::GetApproximateMemoryUsageByType(dbs, cache_set, &usage_by_type) != rocksdb::Status::OK()) { + // Non-OK status + return nullptr; + } + + jobject jusage_by_type = rocksdb::HashMapJni::construct( + env, static_cast(usage_by_type.size())); + if (jusage_by_type == nullptr) { + // exception occurred + return nullptr; + } + const rocksdb::HashMapJni::FnMapKV + fn_map_kv = + [env](const std::pair& pair) { + // Construct key + const jobject jusage_type = + rocksdb::ByteJni::valueOf(env, rocksdb::MemoryUsageTypeJni::toJavaMemoryUsageType(pair.first)); + if (jusage_type == nullptr) { + // an error occurred + return std::unique_ptr>(nullptr); + } + // Construct value + const jobject jusage_value = + rocksdb::LongJni::valueOf(env, pair.second); + if (jusage_value == nullptr) { + // an error occurred + return std::unique_ptr>(nullptr); + } + // Construct and return pointer to pair of jobjects + return std::unique_ptr>( + new std::pair(jusage_type, + jusage_value)); + }; + + if (!rocksdb::HashMapJni::putAll(env, jusage_by_type, usage_by_type.begin(), + usage_by_type.end(), fn_map_kv)) { + // exception occcurred + jusage_by_type = nullptr; + } + + return jusage_by_type; + +} diff --git a/thirdparty/rocksdb/java/rocksjni/memtablejni.cc b/thirdparty/rocksdb/java/rocksjni/memtablejni.cc index 56a04f9f81..ad704c3b16 100644 --- a/thirdparty/rocksdb/java/rocksjni/memtablejni.cc +++ b/thirdparty/rocksdb/java/rocksjni/memtablejni.cc @@ -5,12 +5,12 @@ // // This file implements the "bridge" between Java and C++ for MemTables. -#include "rocksjni/portal.h" -#include "include/org_rocksdb_HashSkipListMemTableConfig.h" #include "include/org_rocksdb_HashLinkedListMemTableConfig.h" -#include "include/org_rocksdb_VectorMemTableConfig.h" +#include "include/org_rocksdb_HashSkipListMemTableConfig.h" #include "include/org_rocksdb_SkipListMemTableConfig.h" +#include "include/org_rocksdb_VectorMemTableConfig.h" #include "rocksdb/memtablerep.h" +#include "rocksjni/portal.h" /* * Class: org_rocksdb_HashSkipListMemTableConfig @@ -18,13 +18,12 @@ * Signature: (JII)J */ jlong Java_org_rocksdb_HashSkipListMemTableConfig_newMemTableFactoryHandle( - JNIEnv* env, jobject jobj, jlong jbucket_count, - jint jheight, jint jbranching_factor) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(jbucket_count); + JNIEnv* env, jobject /*jobj*/, jlong jbucket_count, jint jheight, + jint jbranching_factor) { + rocksdb::Status s = rocksdb::JniUtil::check_if_jlong_fits_size_t(jbucket_count); if (s.ok()) { return reinterpret_cast(rocksdb::NewHashSkipListRepFactory( - static_cast(jbucket_count), - static_cast(jheight), + static_cast(jbucket_count), static_cast(jheight), static_cast(jbranching_factor))); } rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); @@ -37,13 +36,13 @@ jlong Java_org_rocksdb_HashSkipListMemTableConfig_newMemTableFactoryHandle( * Signature: (JJIZI)J */ jlong Java_org_rocksdb_HashLinkedListMemTableConfig_newMemTableFactoryHandle( - JNIEnv* env, jobject jobj, jlong jbucket_count, jlong jhuge_page_tlb_size, - jint jbucket_entries_logging_threshold, + JNIEnv* env, jobject /*jobj*/, jlong jbucket_count, + jlong jhuge_page_tlb_size, jint jbucket_entries_logging_threshold, jboolean jif_log_bucket_dist_when_flash, jint jthreshold_use_skiplist) { rocksdb::Status statusBucketCount = - rocksdb::check_if_jlong_fits_size_t(jbucket_count); + rocksdb::JniUtil::check_if_jlong_fits_size_t(jbucket_count); rocksdb::Status statusHugePageTlb = - rocksdb::check_if_jlong_fits_size_t(jhuge_page_tlb_size); + rocksdb::JniUtil::check_if_jlong_fits_size_t(jhuge_page_tlb_size); if (statusBucketCount.ok() && statusHugePageTlb.ok()) { return reinterpret_cast(rocksdb::NewHashLinkListRepFactory( static_cast(jbucket_count), @@ -52,8 +51,8 @@ jlong Java_org_rocksdb_HashLinkedListMemTableConfig_newMemTableFactoryHandle( static_cast(jif_log_bucket_dist_when_flash), static_cast(jthreshold_use_skiplist))); } - rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, - !statusBucketCount.ok()?statusBucketCount:statusHugePageTlb); + rocksdb::IllegalArgumentExceptionJni::ThrowNew( + env, !statusBucketCount.ok() ? statusBucketCount : statusHugePageTlb); return 0; } @@ -63,11 +62,11 @@ jlong Java_org_rocksdb_HashLinkedListMemTableConfig_newMemTableFactoryHandle( * Signature: (J)J */ jlong Java_org_rocksdb_VectorMemTableConfig_newMemTableFactoryHandle( - JNIEnv* env, jobject jobj, jlong jreserved_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(jreserved_size); + JNIEnv* env, jobject /*jobj*/, jlong jreserved_size) { + rocksdb::Status s = rocksdb::JniUtil::check_if_jlong_fits_size_t(jreserved_size); if (s.ok()) { - return reinterpret_cast(new rocksdb::VectorRepFactory( - static_cast(jreserved_size))); + return reinterpret_cast( + new rocksdb::VectorRepFactory(static_cast(jreserved_size))); } rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); return 0; @@ -79,11 +78,11 @@ jlong Java_org_rocksdb_VectorMemTableConfig_newMemTableFactoryHandle( * Signature: (J)J */ jlong Java_org_rocksdb_SkipListMemTableConfig_newMemTableFactoryHandle0( - JNIEnv* env, jobject jobj, jlong jlookahead) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(jlookahead); + JNIEnv* env, jobject /*jobj*/, jlong jlookahead) { + rocksdb::Status s = rocksdb::JniUtil::check_if_jlong_fits_size_t(jlookahead); if (s.ok()) { - return reinterpret_cast(new rocksdb::SkipListFactory( - static_cast(jlookahead))); + return reinterpret_cast( + new rocksdb::SkipListFactory(static_cast(jlookahead))); } rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); return 0; diff --git a/thirdparty/rocksdb/java/rocksjni/merge_operator.cc b/thirdparty/rocksdb/java/rocksjni/merge_operator.cc index 1b94382ef0..e06a06f7e3 100644 --- a/thirdparty/rocksdb/java/rocksjni/merge_operator.cc +++ b/thirdparty/rocksdb/java/rocksjni/merge_operator.cc @@ -6,32 +6,33 @@ // This file implements the "bridge" between Java and C++ // for rocksdb::MergeOperator. +#include #include #include -#include -#include #include +#include #include "include/org_rocksdb_StringAppendOperator.h" -#include "rocksjni/portal.h" +#include "include/org_rocksdb_UInt64AddOperator.h" #include "rocksdb/db.h" +#include "rocksdb/memtablerep.h" +#include "rocksdb/merge_operator.h" #include "rocksdb/options.h" +#include "rocksdb/slice_transform.h" #include "rocksdb/statistics.h" -#include "rocksdb/memtablerep.h" #include "rocksdb/table.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/merge_operator.h" +#include "rocksjni/portal.h" #include "utilities/merge_operators.h" /* * Class: org_rocksdb_StringAppendOperator * Method: newSharedStringAppendOperator - * Signature: ()J + * Signature: (C)J */ -jlong Java_org_rocksdb_StringAppendOperator_newSharedStringAppendOperator -(JNIEnv* env, jclass jclazz) { +jlong Java_org_rocksdb_StringAppendOperator_newSharedStringAppendOperator( + JNIEnv* /*env*/, jclass /*jclazz*/, jchar jdelim) { auto* sptr_string_append_op = new std::shared_ptr( - rocksdb::MergeOperators::CreateFromStringId("stringappend")); + rocksdb::MergeOperators::CreateStringAppendOperator((char)jdelim)); return reinterpret_cast(sptr_string_append_op); } @@ -40,9 +41,35 @@ jlong Java_org_rocksdb_StringAppendOperator_newSharedStringAppendOperator * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_StringAppendOperator_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { +void Java_org_rocksdb_StringAppendOperator_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* sptr_string_append_op = - reinterpret_cast* >(jhandle); + reinterpret_cast*>(jhandle); delete sptr_string_append_op; // delete std::shared_ptr } + +/* + * Class: org_rocksdb_UInt64AddOperator + * Method: newSharedUInt64AddOperator + * Signature: ()J + */ +jlong Java_org_rocksdb_UInt64AddOperator_newSharedUInt64AddOperator( + JNIEnv* /*env*/, jclass /*jclazz*/) { + auto* sptr_uint64_add_op = new std::shared_ptr( + rocksdb::MergeOperators::CreateUInt64AddOperator()); + return reinterpret_cast(sptr_uint64_add_op); +} + +/* + * Class: org_rocksdb_UInt64AddOperator + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_UInt64AddOperator_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* sptr_uint64_add_op = + reinterpret_cast*>(jhandle); + delete sptr_uint64_add_op; // delete std::shared_ptr +} diff --git a/thirdparty/rocksdb/java/rocksjni/native_comparator_wrapper_test.cc b/thirdparty/rocksdb/java/rocksjni/native_comparator_wrapper_test.cc new file mode 100644 index 0000000000..829019f917 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/native_comparator_wrapper_test.cc @@ -0,0 +1,43 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#include +#include + +#include "rocksdb/comparator.h" +#include "rocksdb/slice.h" + +#include "include/org_rocksdb_NativeComparatorWrapperTest_NativeStringComparatorWrapper.h" + +namespace rocksdb { + +class NativeComparatorWrapperTestStringComparator : public Comparator { + const char* Name() const { + return "NativeComparatorWrapperTestStringComparator"; + } + + int Compare(const Slice& a, const Slice& b) const { + return a.ToString().compare(b.ToString()); + } + + void FindShortestSeparator(std::string* /*start*/, + const Slice& /*limit*/) const { + return; + } + + void FindShortSuccessor(std::string* /*key*/) const { return; } +}; +} // namespace rocksdb + +/* + * Class: org_rocksdb_NativeComparatorWrapperTest_NativeStringComparatorWrapper + * Method: newStringComparator + * Signature: ()J + */ +jlong Java_org_rocksdb_NativeComparatorWrapperTest_00024NativeStringComparatorWrapper_newStringComparator( + JNIEnv* /*env*/, jobject /*jobj*/) { + auto* comparator = new rocksdb::NativeComparatorWrapperTestStringComparator(); + return reinterpret_cast(comparator); +} diff --git a/thirdparty/rocksdb/java/rocksjni/optimistic_transaction_db.cc b/thirdparty/rocksdb/java/rocksjni/optimistic_transaction_db.cc new file mode 100644 index 0000000000..1505ff9895 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/optimistic_transaction_db.cc @@ -0,0 +1,278 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ +// for rocksdb::TransactionDB. + +#include + +#include "include/org_rocksdb_OptimisticTransactionDB.h" + +#include "rocksdb/options.h" +#include "rocksdb/utilities/optimistic_transaction_db.h" +#include "rocksdb/utilities/transaction.h" + +#include "rocksjni/portal.h" + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: open + * Signature: (JLjava/lang/String;)J + */ +jlong Java_org_rocksdb_OptimisticTransactionDB_open__JLjava_lang_String_2( + JNIEnv* env, jclass, jlong joptions_handle, jstring jdb_path) { + const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); + if (db_path == nullptr) { + // exception thrown: OutOfMemoryError + return 0; + } + + auto* options = reinterpret_cast(joptions_handle); + rocksdb::OptimisticTransactionDB* otdb = nullptr; + rocksdb::Status s = + rocksdb::OptimisticTransactionDB::Open(*options, db_path, &otdb); + env->ReleaseStringUTFChars(jdb_path, db_path); + + if (s.ok()) { + return reinterpret_cast(otdb); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return 0; + } +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: open + * Signature: (JLjava/lang/String;[[B[J)[J + */ +jlongArray +Java_org_rocksdb_OptimisticTransactionDB_open__JLjava_lang_String_2_3_3B_3J( + JNIEnv* env, jclass, jlong jdb_options_handle, jstring jdb_path, + jobjectArray jcolumn_names, jlongArray jcolumn_options_handles) { + const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); + if (db_path == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + std::vector column_families; + const jsize len_cols = env->GetArrayLength(jcolumn_names); + if (len_cols > 0) { + if (env->EnsureLocalCapacity(len_cols) != 0) { + // out of memory + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + + jlong* jco = env->GetLongArrayElements(jcolumn_options_handles, nullptr); + if (jco == nullptr) { + // exception thrown: OutOfMemoryError + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + + for (int i = 0; i < len_cols; i++) { + const jobject jcn = env->GetObjectArrayElement(jcolumn_names, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + + const jbyteArray jcn_ba = reinterpret_cast(jcn); + const jsize jcf_name_len = env->GetArrayLength(jcn_ba); + if (env->EnsureLocalCapacity(jcf_name_len) != 0) { + // out of memory + env->DeleteLocalRef(jcn); + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + + jbyte* jcf_name = env->GetByteArrayElements(jcn_ba, nullptr); + if (jcf_name == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jcn); + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + + const std::string cf_name(reinterpret_cast(jcf_name), + jcf_name_len); + const rocksdb::ColumnFamilyOptions* cf_options = + reinterpret_cast(jco[i]); + column_families.push_back( + rocksdb::ColumnFamilyDescriptor(cf_name, *cf_options)); + + env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT); + env->DeleteLocalRef(jcn); + } + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + } + + auto* db_options = reinterpret_cast(jdb_options_handle); + std::vector handles; + rocksdb::OptimisticTransactionDB* otdb = nullptr; + const rocksdb::Status s = rocksdb::OptimisticTransactionDB::Open( + *db_options, db_path, column_families, &handles, &otdb); + + env->ReleaseStringUTFChars(jdb_path, db_path); + + // check if open operation was successful + if (s.ok()) { + const jsize resultsLen = 1 + len_cols; // db handle + column family handles + std::unique_ptr results = + std::unique_ptr(new jlong[resultsLen]); + results[0] = reinterpret_cast(otdb); + for (int i = 1; i <= len_cols; i++) { + results[i] = reinterpret_cast(handles[i - 1]); + } + + jlongArray jresults = env->NewLongArray(resultsLen); + if (jresults == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + return nullptr; + } + return jresults; + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_OptimisticTransactionDB_disposeInternal( + JNIEnv *, jobject, jlong jhandle) { + auto* optimistic_txn_db = + reinterpret_cast(jhandle); + assert(optimistic_txn_db != nullptr); + delete optimistic_txn_db; +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: closeDatabase + * Signature: (J)V + */ +void Java_org_rocksdb_OptimisticTransactionDB_closeDatabase( + JNIEnv* env, jclass, jlong jhandle) { + auto* optimistic_txn_db = + reinterpret_cast(jhandle); + assert(optimistic_txn_db != nullptr); + rocksdb::Status s = optimistic_txn_db->Close(); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: beginTransaction + * Signature: (JJ)J + */ +jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction__JJ( + JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle) { + auto* optimistic_txn_db = + reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + rocksdb::Transaction* txn = + optimistic_txn_db->BeginTransaction(*write_options); + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: beginTransaction + * Signature: (JJJ)J + */ +jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction__JJJ( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jwrite_options_handle, jlong joptimistic_txn_options_handle) { + auto* optimistic_txn_db = + reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* optimistic_txn_options = + reinterpret_cast( + joptimistic_txn_options_handle); + rocksdb::Transaction* txn = optimistic_txn_db->BeginTransaction( + *write_options, *optimistic_txn_options); + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: beginTransaction_withOld + * Signature: (JJJ)J + */ +jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction_1withOld__JJJ( + JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, + jlong jold_txn_handle) { + auto* optimistic_txn_db = + reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* old_txn = reinterpret_cast(jold_txn_handle); + rocksdb::OptimisticTransactionOptions optimistic_txn_options; + rocksdb::Transaction* txn = optimistic_txn_db->BeginTransaction( + *write_options, optimistic_txn_options, old_txn); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_optimistic_txn + assert(txn == old_txn); + + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: beginTransaction_withOld + * Signature: (JJJJ)J + */ +jlong Java_org_rocksdb_OptimisticTransactionDB_beginTransaction_1withOld__JJJJ( + JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, + jlong joptimistic_txn_options_handle, jlong jold_txn_handle) { + auto* optimistic_txn_db = + reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* optimistic_txn_options = + reinterpret_cast( + joptimistic_txn_options_handle); + auto* old_txn = reinterpret_cast(jold_txn_handle); + rocksdb::Transaction* txn = optimistic_txn_db->BeginTransaction( + *write_options, *optimistic_txn_options, old_txn); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_optimisic_txn + assert(txn == old_txn); + + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_OptimisticTransactionDB + * Method: getBaseDB + * Signature: (J)J + */ +jlong Java_org_rocksdb_OptimisticTransactionDB_getBaseDB( + JNIEnv*, jobject, jlong jhandle) { + auto* optimistic_txn_db = + reinterpret_cast(jhandle); + return reinterpret_cast(optimistic_txn_db->GetBaseDB()); +} diff --git a/thirdparty/rocksdb/java/rocksjni/optimistic_transaction_options.cc b/thirdparty/rocksdb/java/rocksjni/optimistic_transaction_options.cc new file mode 100644 index 0000000000..3eee446673 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/optimistic_transaction_options.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ +// for rocksdb::OptimisticTransactionOptions. + +#include + +#include "include/org_rocksdb_OptimisticTransactionOptions.h" + +#include "rocksdb/comparator.h" +#include "rocksdb/utilities/optimistic_transaction_db.h" + +/* + * Class: org_rocksdb_OptimisticTransactionOptions + * Method: newOptimisticTransactionOptions + * Signature: ()J + */ +jlong Java_org_rocksdb_OptimisticTransactionOptions_newOptimisticTransactionOptions( + JNIEnv* /*env*/, jclass /*jcls*/) { + rocksdb::OptimisticTransactionOptions* opts = + new rocksdb::OptimisticTransactionOptions(); + return reinterpret_cast(opts); +} + +/* + * Class: org_rocksdb_OptimisticTransactionOptions + * Method: isSetSnapshot + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_OptimisticTransactionOptions_isSetSnapshot( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* opts = + reinterpret_cast(jhandle); + return opts->set_snapshot; +} + +/* + * Class: org_rocksdb_OptimisticTransactionOptions + * Method: setSetSnapshot + * Signature: (JZ)V + */ +void Java_org_rocksdb_OptimisticTransactionOptions_setSetSnapshot( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean jset_snapshot) { + auto* opts = + reinterpret_cast(jhandle); + opts->set_snapshot = jset_snapshot; +} + +/* + * Class: org_rocksdb_OptimisticTransactionOptions + * Method: setComparator + * Signature: (JJ)V + */ +void Java_org_rocksdb_OptimisticTransactionOptions_setComparator( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jcomparator_handle) { + auto* opts = + reinterpret_cast(jhandle); + opts->cmp = reinterpret_cast(jcomparator_handle); +} + +/* + * Class: org_rocksdb_OptimisticTransactionOptions + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_OptimisticTransactionOptions_disposeInternal( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + delete reinterpret_cast(jhandle); +} diff --git a/thirdparty/rocksdb/java/rocksjni/options.cc b/thirdparty/rocksdb/java/rocksjni/options.cc index 8194abaf6b..12f44b5eb0 100644 --- a/thirdparty/rocksdb/java/rocksjni/options.cc +++ b/thirdparty/rocksdb/java/rocksjni/options.cc @@ -5,34 +5,35 @@ // // This file implements the "bridge" between Java and C++ for rocksdb::Options. +#include #include #include -#include #include #include -#include "include/org_rocksdb_Options.h" -#include "include/org_rocksdb_DBOptions.h" #include "include/org_rocksdb_ColumnFamilyOptions.h" -#include "include/org_rocksdb_WriteOptions.h" -#include "include/org_rocksdb_ReadOptions.h" #include "include/org_rocksdb_ComparatorOptions.h" +#include "include/org_rocksdb_DBOptions.h" #include "include/org_rocksdb_FlushOptions.h" +#include "include/org_rocksdb_Options.h" +#include "include/org_rocksdb_ReadOptions.h" +#include "include/org_rocksdb_WriteOptions.h" #include "rocksjni/comparatorjnicallback.h" #include "rocksjni/portal.h" #include "rocksjni/statisticsjni.h" +#include "rocksjni/table_filter_jnicallback.h" +#include "rocksdb/comparator.h" +#include "rocksdb/convenience.h" #include "rocksdb/db.h" +#include "rocksdb/memtablerep.h" +#include "rocksdb/merge_operator.h" #include "rocksdb/options.h" +#include "rocksdb/rate_limiter.h" +#include "rocksdb/slice_transform.h" #include "rocksdb/statistics.h" -#include "rocksdb/memtablerep.h" #include "rocksdb/table.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/rate_limiter.h" -#include "rocksdb/comparator.h" -#include "rocksdb/convenience.h" -#include "rocksdb/merge_operator.h" #include "utilities/merge_operators.h" /* @@ -40,7 +41,8 @@ * Method: newOptions * Signature: ()J */ -jlong Java_org_rocksdb_Options_newOptions__(JNIEnv* env, jclass jcls) { +jlong Java_org_rocksdb_Options_newOptions__( + JNIEnv*, jclass) { auto* op = new rocksdb::Options(); return reinterpret_cast(op); } @@ -50,22 +52,34 @@ jlong Java_org_rocksdb_Options_newOptions__(JNIEnv* env, jclass jcls) { * Method: newOptions * Signature: (JJ)J */ -jlong Java_org_rocksdb_Options_newOptions__JJ(JNIEnv* env, jclass jcls, - jlong jdboptions, jlong jcfoptions) { +jlong Java_org_rocksdb_Options_newOptions__JJ( + JNIEnv*, jclass, jlong jdboptions, jlong jcfoptions) { auto* dbOpt = reinterpret_cast(jdboptions); - auto* cfOpt = reinterpret_cast( - jcfoptions); + auto* cfOpt = + reinterpret_cast(jcfoptions); auto* op = new rocksdb::Options(*dbOpt, *cfOpt); return reinterpret_cast(op); } +/* + * Class: org_rocksdb_Options + * Method: copyOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_Options_copyOptions( + JNIEnv*, jclass, jlong jhandle) { + auto new_opt = + new rocksdb::Options(*(reinterpret_cast(jhandle))); + return reinterpret_cast(new_opt); +} + /* * Class: org_rocksdb_Options * Method: disposeInternal * Signature: (J)V */ void Java_org_rocksdb_Options_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { + JNIEnv*, jobject, jlong handle) { auto* op = reinterpret_cast(handle); assert(op != nullptr); delete op; @@ -77,9 +91,9 @@ void Java_org_rocksdb_Options_disposeInternal( * Signature: (JI)V */ void Java_org_rocksdb_Options_setIncreaseParallelism( - JNIEnv * env, jobject jobj, jlong jhandle, jint totalThreads) { - reinterpret_cast - (jhandle)->IncreaseParallelism(static_cast(totalThreads)); + JNIEnv*, jobject, jlong jhandle, jint totalThreads) { + reinterpret_cast(jhandle)->IncreaseParallelism( + static_cast(totalThreads)); } /* @@ -88,7 +102,7 @@ void Java_org_rocksdb_Options_setIncreaseParallelism( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setCreateIfMissing( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { + JNIEnv*, jobject, jlong jhandle, jboolean flag) { reinterpret_cast(jhandle)->create_if_missing = flag; } @@ -98,7 +112,7 @@ void Java_org_rocksdb_Options_setCreateIfMissing( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_createIfMissing( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->create_if_missing; } @@ -108,9 +122,9 @@ jboolean Java_org_rocksdb_Options_createIfMissing( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setCreateMissingColumnFamilies( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { - reinterpret_cast - (jhandle)->create_missing_column_families = flag; + JNIEnv*, jobject, jlong jhandle, jboolean flag) { + reinterpret_cast(jhandle)->create_missing_column_families = + flag; } /* @@ -119,9 +133,9 @@ void Java_org_rocksdb_Options_setCreateMissingColumnFamilies( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_createMissingColumnFamilies( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast - (jhandle)->create_missing_column_families; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->create_missing_column_families; } /* @@ -130,7 +144,7 @@ jboolean Java_org_rocksdb_Options_createMissingColumnFamilies( * Signature: (JI)V */ void Java_org_rocksdb_Options_setComparatorHandle__JI( - JNIEnv* env, jobject jobj, jlong jhandle, jint builtinComparator) { + JNIEnv*, jobject, jlong jhandle, jint builtinComparator) { switch (builtinComparator) { case 1: reinterpret_cast(jhandle)->comparator = @@ -146,12 +160,32 @@ void Java_org_rocksdb_Options_setComparatorHandle__JI( /* * Class: org_rocksdb_Options * Method: setComparatorHandle - * Signature: (JJ)V - */ -void Java_org_rocksdb_Options_setComparatorHandle__JJ( - JNIEnv* env, jobject jobj, jlong jopt_handle, jlong jcomparator_handle) { - reinterpret_cast(jopt_handle)->comparator = - reinterpret_cast(jcomparator_handle); + * Signature: (JJB)V + */ +void Java_org_rocksdb_Options_setComparatorHandle__JJB( + JNIEnv*, jobject, jlong jopt_handle, jlong jcomparator_handle, + jbyte jcomparator_type) { + rocksdb::Comparator* comparator = nullptr; + switch (jcomparator_type) { + // JAVA_COMPARATOR + case 0x0: + comparator = + reinterpret_cast(jcomparator_handle); + break; + + // JAVA_DIRECT_COMPARATOR + case 0x1: + comparator = reinterpret_cast( + jcomparator_handle); + break; + + // JAVA_NATIVE_COMPARATOR_WRAPPER + case 0x2: + comparator = reinterpret_cast(jcomparator_handle); + break; + } + auto* opt = reinterpret_cast(jopt_handle); + opt->comparator = comparator; } /* @@ -160,16 +194,16 @@ void Java_org_rocksdb_Options_setComparatorHandle__JJ( * Signature: (JJjava/lang/String)V */ void Java_org_rocksdb_Options_setMergeOperatorName( - JNIEnv* env, jobject jobj, jlong jhandle, jstring jop_name) { + JNIEnv* env, jobject, jlong jhandle, jstring jop_name) { const char* op_name = env->GetStringUTFChars(jop_name, nullptr); - if(op_name == nullptr) { + if (op_name == nullptr) { // exception thrown: OutOfMemoryError return; } auto* options = reinterpret_cast(jhandle); - options->merge_operator = rocksdb::MergeOperators::CreateFromStringId( - op_name); + options->merge_operator = + rocksdb::MergeOperators::CreateFromStringId(op_name); env->ReleaseStringUTFChars(jop_name, op_name); } @@ -180,10 +214,38 @@ void Java_org_rocksdb_Options_setMergeOperatorName( * Signature: (JJjava/lang/String)V */ void Java_org_rocksdb_Options_setMergeOperator( - JNIEnv* env, jobject jobj, jlong jhandle, jlong mergeOperatorHandle) { + JNIEnv*, jobject, jlong jhandle, jlong mergeOperatorHandle) { reinterpret_cast(jhandle)->merge_operator = - *(reinterpret_cast*> - (mergeOperatorHandle)); + *(reinterpret_cast*>( + mergeOperatorHandle)); +} + +/* + * Class: org_rocksdb_Options + * Method: setCompactionFilterHandle + * Signature: (JJ)V + */ +void Java_org_rocksdb_Options_setCompactionFilterHandle( + JNIEnv*, jobject, jlong jopt_handle, + jlong jcompactionfilter_handle) { + reinterpret_cast(jopt_handle)-> + compaction_filter = reinterpret_cast + (jcompactionfilter_handle); +} + +/* + * Class: org_rocksdb_Options + * Method: setCompactionFilterFactoryHandle + * Signature: (JJ)V + */ +void JNICALL Java_org_rocksdb_Options_setCompactionFilterFactoryHandle( + JNIEnv*, jobject, jlong jopt_handle, + jlong jcompactionfilterfactory_handle) { + auto* cff_factory = + reinterpret_cast *>( + jcompactionfilterfactory_handle); + reinterpret_cast(jopt_handle)-> + compaction_filter_factory = *cff_factory; } /* @@ -192,8 +254,9 @@ void Java_org_rocksdb_Options_setMergeOperator( * Signature: (JJ)I */ void Java_org_rocksdb_Options_setWriteBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jwrite_buffer_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(jwrite_buffer_size); + JNIEnv* env, jobject, jlong jhandle, jlong jwrite_buffer_size) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(jwrite_buffer_size); if (s.ok()) { reinterpret_cast(jhandle)->write_buffer_size = jwrite_buffer_size; @@ -202,13 +265,27 @@ void Java_org_rocksdb_Options_setWriteBufferSize( } } +/* + * Class: org_rocksdb_Options + * Method: setWriteBufferManager + * Signature: (JJ)V + */ +void Java_org_rocksdb_Options_setWriteBufferManager( + JNIEnv*, jobject, jlong joptions_handle, + jlong jwrite_buffer_manager_handle) { + auto* write_buffer_manager = + reinterpret_cast *>(jwrite_buffer_manager_handle); + reinterpret_cast(joptions_handle)->write_buffer_manager = + *write_buffer_manager; +} + /* * Class: org_rocksdb_Options * Method: writeBufferSize * Signature: (J)J */ jlong Java_org_rocksdb_Options_writeBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->write_buffer_size; } @@ -218,9 +295,10 @@ jlong Java_org_rocksdb_Options_writeBufferSize( * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxWriteBufferNumber( - JNIEnv* env, jobject jobj, jlong jhandle, jint jmax_write_buffer_number) { + JNIEnv*, jobject, jlong jhandle, + jint jmax_write_buffer_number) { reinterpret_cast(jhandle)->max_write_buffer_number = - jmax_write_buffer_number; + jmax_write_buffer_number; } /* @@ -229,11 +307,10 @@ void Java_org_rocksdb_Options_setMaxWriteBufferNumber( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setStatistics( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jstatistics_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jstatistics_handle) { auto* opt = reinterpret_cast(jhandle); - auto* pSptr = - reinterpret_cast*>( - jstatistics_handle); + auto* pSptr = reinterpret_cast*>( + jstatistics_handle); opt->statistics = *pSptr; } @@ -243,7 +320,7 @@ void Java_org_rocksdb_Options_setStatistics( * Signature: (J)J */ jlong Java_org_rocksdb_Options_statistics( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); std::shared_ptr sptr = opt->statistics; if (sptr == nullptr) { @@ -261,7 +338,7 @@ jlong Java_org_rocksdb_Options_statistics( * Signature: (J)I */ jint Java_org_rocksdb_Options_maxWriteBufferNumber( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_write_buffer_number; } @@ -271,7 +348,7 @@ jint Java_org_rocksdb_Options_maxWriteBufferNumber( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_errorIfExists( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->error_if_exists; } @@ -281,7 +358,7 @@ jboolean Java_org_rocksdb_Options_errorIfExists( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setErrorIfExists( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean error_if_exists) { + JNIEnv*, jobject, jlong jhandle, jboolean error_if_exists) { reinterpret_cast(jhandle)->error_if_exists = static_cast(error_if_exists); } @@ -292,7 +369,7 @@ void Java_org_rocksdb_Options_setErrorIfExists( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_paranoidChecks( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->paranoid_checks; } @@ -302,7 +379,7 @@ jboolean Java_org_rocksdb_Options_paranoidChecks( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setParanoidChecks( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean paranoid_checks) { + JNIEnv*, jobject, jlong jhandle, jboolean paranoid_checks) { reinterpret_cast(jhandle)->paranoid_checks = static_cast(paranoid_checks); } @@ -313,7 +390,7 @@ void Java_org_rocksdb_Options_setParanoidChecks( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setEnv( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jenv) { + JNIEnv*, jobject, jlong jhandle, jlong jenv) { reinterpret_cast(jhandle)->env = reinterpret_cast(jenv); } @@ -324,8 +401,7 @@ void Java_org_rocksdb_Options_setEnv( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMaxTotalWalSize( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmax_total_wal_size) { + JNIEnv*, jobject, jlong jhandle, jlong jmax_total_wal_size) { reinterpret_cast(jhandle)->max_total_wal_size = static_cast(jmax_total_wal_size); } @@ -336,9 +412,8 @@ void Java_org_rocksdb_Options_setMaxTotalWalSize( * Signature: (J)J */ jlong Java_org_rocksdb_Options_maxTotalWalSize( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - max_total_wal_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_total_wal_size; } /* @@ -347,7 +422,7 @@ jlong Java_org_rocksdb_Options_maxTotalWalSize( * Signature: (J)I */ jint Java_org_rocksdb_Options_maxOpenFiles( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_open_files; } @@ -357,7 +432,7 @@ jint Java_org_rocksdb_Options_maxOpenFiles( * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxOpenFiles( - JNIEnv* env, jobject jobj, jlong jhandle, jint max_open_files) { + JNIEnv*, jobject, jlong jhandle, jint max_open_files) { reinterpret_cast(jhandle)->max_open_files = static_cast(max_open_files); } @@ -368,7 +443,7 @@ void Java_org_rocksdb_Options_setMaxOpenFiles( * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxFileOpeningThreads( - JNIEnv* env, jobject jobj, jlong jhandle, jint jmax_file_opening_threads) { + JNIEnv*, jobject, jlong jhandle, jint jmax_file_opening_threads) { reinterpret_cast(jhandle)->max_file_opening_threads = static_cast(jmax_file_opening_threads); } @@ -379,7 +454,7 @@ void Java_org_rocksdb_Options_setMaxFileOpeningThreads( * Signature: (J)I */ jint Java_org_rocksdb_Options_maxFileOpeningThreads( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->max_file_opening_threads); } @@ -390,7 +465,7 @@ jint Java_org_rocksdb_Options_maxFileOpeningThreads( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_useFsync( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->use_fsync; } @@ -400,7 +475,7 @@ jboolean Java_org_rocksdb_Options_useFsync( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setUseFsync( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean use_fsync) { + JNIEnv*, jobject, jlong jhandle, jboolean use_fsync) { reinterpret_cast(jhandle)->use_fsync = static_cast(use_fsync); } @@ -411,34 +486,32 @@ void Java_org_rocksdb_Options_setUseFsync( * Signature: (J[Ljava/lang/String;[J)V */ void Java_org_rocksdb_Options_setDbPaths( - JNIEnv* env, jobject jobj, jlong jhandle, jobjectArray jpaths, + JNIEnv* env, jobject, jlong jhandle, jobjectArray jpaths, jlongArray jtarget_sizes) { std::vector db_paths; jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr); - if(ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; + if (ptr_jtarget_size == nullptr) { + // exception thrown: OutOfMemoryError + return; } jboolean has_exception = JNI_FALSE; const jsize len = env->GetArrayLength(jpaths); - for(jsize i = 0; i < len; i++) { - jobject jpath = reinterpret_cast(env-> - GetObjectArrayElement(jpaths, i)); - if(env->ExceptionCheck()) { + for (jsize i = 0; i < len; i++) { + jobject jpath = + reinterpret_cast(env->GetObjectArrayElement(jpaths, i)); + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); return; } - std::string path = rocksdb::JniUtil::copyString( + std::string path = rocksdb::JniUtil::copyStdString( env, static_cast(jpath), &has_exception); env->DeleteLocalRef(jpath); - if(has_exception == JNI_TRUE) { - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; + if (has_exception == JNI_TRUE) { + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + return; } jlong jtarget_size = ptr_jtarget_size[i]; @@ -459,7 +532,7 @@ void Java_org_rocksdb_Options_setDbPaths( * Signature: (J)J */ jlong Java_org_rocksdb_Options_dbPathsLen( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->db_paths.size()); } @@ -470,32 +543,30 @@ jlong Java_org_rocksdb_Options_dbPathsLen( * Signature: (J[Ljava/lang/String;[J)V */ void Java_org_rocksdb_Options_dbPaths( - JNIEnv* env, jobject jobj, jlong jhandle, jobjectArray jpaths, + JNIEnv* env, jobject, jlong jhandle, jobjectArray jpaths, jlongArray jtarget_sizes) { jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr); - if(ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; + if (ptr_jtarget_size == nullptr) { + // exception thrown: OutOfMemoryError + return; } auto* opt = reinterpret_cast(jhandle); const jsize len = env->GetArrayLength(jpaths); - for(jsize i = 0; i < len; i++) { + for (jsize i = 0; i < len; i++) { rocksdb::DbPath db_path = opt->db_paths[i]; jstring jpath = env->NewStringUTF(db_path.path.c_str()); - if(jpath == nullptr) { + if (jpath == nullptr) { // exception thrown: OutOfMemoryError - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); return; } env->SetObjectArrayElement(jpaths, i, jpath); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(jpath); - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); return; } @@ -511,7 +582,7 @@ void Java_org_rocksdb_Options_dbPaths( * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_Options_dbLogDir( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { return env->NewStringUTF( reinterpret_cast(jhandle)->db_log_dir.c_str()); } @@ -522,9 +593,9 @@ jstring Java_org_rocksdb_Options_dbLogDir( * Signature: (JLjava/lang/String)V */ void Java_org_rocksdb_Options_setDbLogDir( - JNIEnv* env, jobject jobj, jlong jhandle, jstring jdb_log_dir) { + JNIEnv* env, jobject, jlong jhandle, jstring jdb_log_dir) { const char* log_dir = env->GetStringUTFChars(jdb_log_dir, nullptr); - if(log_dir == nullptr) { + if (log_dir == nullptr) { // exception thrown: OutOfMemoryError return; } @@ -538,7 +609,7 @@ void Java_org_rocksdb_Options_setDbLogDir( * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_Options_walDir( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { return env->NewStringUTF( reinterpret_cast(jhandle)->wal_dir.c_str()); } @@ -549,9 +620,9 @@ jstring Java_org_rocksdb_Options_walDir( * Signature: (JLjava/lang/String)V */ void Java_org_rocksdb_Options_setWalDir( - JNIEnv* env, jobject jobj, jlong jhandle, jstring jwal_dir) { + JNIEnv* env, jobject, jlong jhandle, jstring jwal_dir) { const char* wal_dir = env->GetStringUTFChars(jwal_dir, nullptr); - if(wal_dir == nullptr) { + if (wal_dir == nullptr) { // exception thrown: OutOfMemoryError return; } @@ -565,7 +636,7 @@ void Java_org_rocksdb_Options_setWalDir( * Signature: (J)J */ jlong Java_org_rocksdb_Options_deleteObsoleteFilesPeriodMicros( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->delete_obsolete_files_period_micros; } @@ -576,10 +647,9 @@ jlong Java_org_rocksdb_Options_deleteObsoleteFilesPeriodMicros( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setDeleteObsoleteFilesPeriodMicros( - JNIEnv* env, jobject jobj, jlong jhandle, jlong micros) { + JNIEnv*, jobject, jlong jhandle, jlong micros) { reinterpret_cast(jhandle) - ->delete_obsolete_files_period_micros = - static_cast(micros); + ->delete_obsolete_files_period_micros = static_cast(micros); } /* @@ -588,9 +658,9 @@ void Java_org_rocksdb_Options_setDeleteObsoleteFilesPeriodMicros( * Signature: (JI)V */ void Java_org_rocksdb_Options_setBaseBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle, jint max) { - reinterpret_cast(jhandle) - ->base_background_compactions = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jint max) { + reinterpret_cast(jhandle)->base_background_compactions = + static_cast(max); } /* @@ -599,7 +669,7 @@ void Java_org_rocksdb_Options_setBaseBackgroundCompactions( * Signature: (J)I */ jint Java_org_rocksdb_Options_baseBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->base_background_compactions; } @@ -610,9 +680,9 @@ jint Java_org_rocksdb_Options_baseBackgroundCompactions( * Signature: (J)I */ jint Java_org_rocksdb_Options_maxBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->max_background_compactions; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_background_compactions; } /* @@ -621,9 +691,9 @@ jint Java_org_rocksdb_Options_maxBackgroundCompactions( * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle, jint max) { - reinterpret_cast(jhandle) - ->max_background_compactions = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jint max) { + reinterpret_cast(jhandle)->max_background_compactions = + static_cast(max); } /* @@ -632,9 +702,9 @@ void Java_org_rocksdb_Options_setMaxBackgroundCompactions( * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxSubcompactions( - JNIEnv* env, jobject jobj, jlong jhandle, jint max) { - reinterpret_cast(jhandle) - ->max_subcompactions = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jint max) { + reinterpret_cast(jhandle)->max_subcompactions = + static_cast(max); } /* @@ -643,7 +713,7 @@ void Java_org_rocksdb_Options_setMaxSubcompactions( * Signature: (J)I */ jint Java_org_rocksdb_Options_maxSubcompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_subcompactions; } @@ -653,7 +723,7 @@ jint Java_org_rocksdb_Options_maxSubcompactions( * Signature: (J)I */ jint Java_org_rocksdb_Options_maxBackgroundFlushes( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_background_flushes; } @@ -663,18 +733,39 @@ jint Java_org_rocksdb_Options_maxBackgroundFlushes( * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxBackgroundFlushes( - JNIEnv* env, jobject jobj, jlong jhandle, jint max_background_flushes) { + JNIEnv*, jobject, jlong jhandle, jint max_background_flushes) { reinterpret_cast(jhandle)->max_background_flushes = static_cast(max_background_flushes); } +/* + * Class: org_rocksdb_Options + * Method: maxBackgroundJobs + * Signature: (J)I + */ +jint Java_org_rocksdb_Options_maxBackgroundJobs( + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_background_jobs; +} + +/* + * Class: org_rocksdb_Options + * Method: setMaxBackgroundJobs + * Signature: (JI)V + */ +void Java_org_rocksdb_Options_setMaxBackgroundJobs( + JNIEnv*, jobject, jlong jhandle, jint max_background_jobs) { + reinterpret_cast(jhandle)->max_background_jobs = + static_cast(max_background_jobs); +} + /* * Class: org_rocksdb_Options * Method: maxLogFileSize * Signature: (J)J */ jlong Java_org_rocksdb_Options_maxLogFileSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_log_file_size; } @@ -684,8 +775,8 @@ jlong Java_org_rocksdb_Options_maxLogFileSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMaxLogFileSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong max_log_file_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(max_log_file_size); + JNIEnv* env, jobject, jlong jhandle, jlong max_log_file_size) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(max_log_file_size); if (s.ok()) { reinterpret_cast(jhandle)->max_log_file_size = max_log_file_size; @@ -700,7 +791,7 @@ void Java_org_rocksdb_Options_setMaxLogFileSize( * Signature: (J)J */ jlong Java_org_rocksdb_Options_logFileTimeToRoll( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->log_file_time_to_roll; } @@ -710,9 +801,9 @@ jlong Java_org_rocksdb_Options_logFileTimeToRoll( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setLogFileTimeToRoll( - JNIEnv* env, jobject jobj, jlong jhandle, jlong log_file_time_to_roll) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - log_file_time_to_roll); + JNIEnv* env, jobject, jlong jhandle, jlong log_file_time_to_roll) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(log_file_time_to_roll); if (s.ok()) { reinterpret_cast(jhandle)->log_file_time_to_roll = log_file_time_to_roll; @@ -727,7 +818,7 @@ void Java_org_rocksdb_Options_setLogFileTimeToRoll( * Signature: (J)J */ jlong Java_org_rocksdb_Options_keepLogFileNum( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->keep_log_file_num; } @@ -737,8 +828,8 @@ jlong Java_org_rocksdb_Options_keepLogFileNum( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setKeepLogFileNum( - JNIEnv* env, jobject jobj, jlong jhandle, jlong keep_log_file_num) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(keep_log_file_num); + JNIEnv* env, jobject, jlong jhandle, jlong keep_log_file_num) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(keep_log_file_num); if (s.ok()) { reinterpret_cast(jhandle)->keep_log_file_num = keep_log_file_num; @@ -753,7 +844,7 @@ void Java_org_rocksdb_Options_setKeepLogFileNum( * Signature: (J)J */ jlong Java_org_rocksdb_Options_recycleLogFileNum( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->recycle_log_file_num; } @@ -763,9 +854,8 @@ jlong Java_org_rocksdb_Options_recycleLogFileNum( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setRecycleLogFileNum( - JNIEnv* env, jobject jobj, jlong jhandle, jlong recycle_log_file_num) { - rocksdb::Status s = - rocksdb::check_if_jlong_fits_size_t(recycle_log_file_num); + JNIEnv* env, jobject, jlong jhandle, jlong recycle_log_file_num) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(recycle_log_file_num); if (s.ok()) { reinterpret_cast(jhandle)->recycle_log_file_num = recycle_log_file_num; @@ -780,7 +870,7 @@ void Java_org_rocksdb_Options_setRecycleLogFileNum( * Signature: (J)J */ jlong Java_org_rocksdb_Options_maxManifestFileSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_manifest_file_size; } @@ -789,7 +879,7 @@ jlong Java_org_rocksdb_Options_maxManifestFileSize( * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_Options_memTableFactoryName( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); rocksdb::MemTableRepFactory* tf = opt->memtable_factory.get(); @@ -811,7 +901,7 @@ jstring Java_org_rocksdb_Options_memTableFactoryName( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMaxManifestFileSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong max_manifest_file_size) { + JNIEnv*, jobject, jlong jhandle, jlong max_manifest_file_size) { reinterpret_cast(jhandle)->max_manifest_file_size = static_cast(max_manifest_file_size); } @@ -821,7 +911,7 @@ void Java_org_rocksdb_Options_setMaxManifestFileSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMemTableFactory( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jfactory_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jfactory_handle) { reinterpret_cast(jhandle)->memtable_factory.reset( reinterpret_cast(jfactory_handle)); } @@ -832,12 +922,25 @@ void Java_org_rocksdb_Options_setMemTableFactory( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setRateLimiter( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jrate_limiter_handle) { - std::shared_ptr *pRateLimiter = - reinterpret_cast *>( + JNIEnv*, jobject, jlong jhandle, jlong jrate_limiter_handle) { + std::shared_ptr* pRateLimiter = + reinterpret_cast*>( jrate_limiter_handle); - reinterpret_cast(jhandle)-> - rate_limiter = *pRateLimiter; + reinterpret_cast(jhandle)->rate_limiter = *pRateLimiter; +} + +/* + * Class: org_rocksdb_Options + * Method: setSstFileManager + * Signature: (JJ)V + */ +void Java_org_rocksdb_Options_setSstFileManager( + JNIEnv*, jobject, jlong jhandle, jlong jsst_file_manager_handle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>( + jsst_file_manager_handle); + reinterpret_cast(jhandle)->sst_file_manager = + *sptr_sst_file_manager; } /* @@ -846,9 +949,9 @@ void Java_org_rocksdb_Options_setRateLimiter( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setLogger( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jlogger_handle) { -std::shared_ptr *pLogger = - reinterpret_cast *>( + JNIEnv*, jobject, jlong jhandle, jlong jlogger_handle) { + std::shared_ptr* pLogger = + reinterpret_cast*>( jlogger_handle); reinterpret_cast(jhandle)->info_log = *pLogger; } @@ -859,7 +962,7 @@ std::shared_ptr *pLogger = * Signature: (JB)V */ void Java_org_rocksdb_Options_setInfoLogLevel( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jlog_level) { + JNIEnv*, jobject, jlong jhandle, jbyte jlog_level) { reinterpret_cast(jhandle)->info_log_level = static_cast(jlog_level); } @@ -870,7 +973,7 @@ void Java_org_rocksdb_Options_setInfoLogLevel( * Signature: (J)B */ jbyte Java_org_rocksdb_Options_infoLogLevel( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return static_cast( reinterpret_cast(jhandle)->info_log_level); } @@ -881,7 +984,7 @@ jbyte Java_org_rocksdb_Options_infoLogLevel( * Signature: (J)I */ jint Java_org_rocksdb_Options_tableCacheNumshardbits( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->table_cache_numshardbits; } @@ -891,7 +994,7 @@ jint Java_org_rocksdb_Options_tableCacheNumshardbits( * Signature: (JI)V */ void Java_org_rocksdb_Options_setTableCacheNumshardbits( - JNIEnv* env, jobject jobj, jlong jhandle, jint table_cache_numshardbits) { + JNIEnv*, jobject, jlong jhandle, jint table_cache_numshardbits) { reinterpret_cast(jhandle)->table_cache_numshardbits = static_cast(table_cache_numshardbits); } @@ -901,10 +1004,9 @@ void Java_org_rocksdb_Options_setTableCacheNumshardbits( * Signature: (JI)V */ void Java_org_rocksdb_Options_useFixedLengthPrefixExtractor( - JNIEnv* env, jobject jobj, jlong jhandle, jint jprefix_length) { + JNIEnv*, jobject, jlong jhandle, jint jprefix_length) { reinterpret_cast(jhandle)->prefix_extractor.reset( - rocksdb::NewFixedPrefixTransform( - static_cast(jprefix_length))); + rocksdb::NewFixedPrefixTransform(static_cast(jprefix_length))); } /* @@ -912,10 +1014,9 @@ void Java_org_rocksdb_Options_useFixedLengthPrefixExtractor( * Signature: (JI)V */ void Java_org_rocksdb_Options_useCappedPrefixExtractor( - JNIEnv* env, jobject jobj, jlong jhandle, jint jprefix_length) { + JNIEnv*, jobject, jlong jhandle, jint jprefix_length) { reinterpret_cast(jhandle)->prefix_extractor.reset( - rocksdb::NewCappedPrefixTransform( - static_cast(jprefix_length))); + rocksdb::NewCappedPrefixTransform(static_cast(jprefix_length))); } /* @@ -924,7 +1025,7 @@ void Java_org_rocksdb_Options_useCappedPrefixExtractor( * Signature: (J)J */ jlong Java_org_rocksdb_Options_walTtlSeconds( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->WAL_ttl_seconds; } @@ -934,7 +1035,7 @@ jlong Java_org_rocksdb_Options_walTtlSeconds( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setWalTtlSeconds( - JNIEnv* env, jobject jobj, jlong jhandle, jlong WAL_ttl_seconds) { + JNIEnv*, jobject, jlong jhandle, jlong WAL_ttl_seconds) { reinterpret_cast(jhandle)->WAL_ttl_seconds = static_cast(WAL_ttl_seconds); } @@ -945,7 +1046,7 @@ void Java_org_rocksdb_Options_setWalTtlSeconds( * Signature: (J)J */ jlong Java_org_rocksdb_Options_walSizeLimitMB( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->WAL_size_limit_MB; } @@ -955,7 +1056,7 @@ jlong Java_org_rocksdb_Options_walSizeLimitMB( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setWalSizeLimitMB( - JNIEnv* env, jobject jobj, jlong jhandle, jlong WAL_size_limit_MB) { + JNIEnv*, jobject, jlong jhandle, jlong WAL_size_limit_MB) { reinterpret_cast(jhandle)->WAL_size_limit_MB = static_cast(WAL_size_limit_MB); } @@ -966,7 +1067,7 @@ void Java_org_rocksdb_Options_setWalSizeLimitMB( * Signature: (J)J */ jlong Java_org_rocksdb_Options_manifestPreallocationSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->manifest_preallocation_size; } @@ -977,8 +1078,8 @@ jlong Java_org_rocksdb_Options_manifestPreallocationSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setManifestPreallocationSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong preallocation_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(preallocation_size); + JNIEnv* env, jobject, jlong jhandle, jlong preallocation_size) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(preallocation_size); if (s.ok()) { reinterpret_cast(jhandle)->manifest_preallocation_size = preallocation_size; @@ -992,9 +1093,11 @@ void Java_org_rocksdb_Options_setManifestPreallocationSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setTableFactory( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jfactory_handle) { - reinterpret_cast(jhandle)->table_factory.reset( - reinterpret_cast(jfactory_handle)); + JNIEnv*, jobject, jlong jhandle, jlong jtable_factory_handle) { + auto* options = reinterpret_cast(jhandle); + auto* table_factory = + reinterpret_cast(jtable_factory_handle); + options->table_factory.reset(table_factory); } /* @@ -1003,7 +1106,7 @@ void Java_org_rocksdb_Options_setTableFactory( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_allowMmapReads( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->allow_mmap_reads; } @@ -1013,7 +1116,7 @@ jboolean Java_org_rocksdb_Options_allowMmapReads( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAllowMmapReads( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow_mmap_reads) { + JNIEnv*, jobject, jlong jhandle, jboolean allow_mmap_reads) { reinterpret_cast(jhandle)->allow_mmap_reads = static_cast(allow_mmap_reads); } @@ -1024,7 +1127,7 @@ void Java_org_rocksdb_Options_setAllowMmapReads( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_allowMmapWrites( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->allow_mmap_writes; } @@ -1034,7 +1137,7 @@ jboolean Java_org_rocksdb_Options_allowMmapWrites( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAllowMmapWrites( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow_mmap_writes) { + JNIEnv*, jobject, jlong jhandle, jboolean allow_mmap_writes) { reinterpret_cast(jhandle)->allow_mmap_writes = static_cast(allow_mmap_writes); } @@ -1044,8 +1147,8 @@ void Java_org_rocksdb_Options_setAllowMmapWrites( * Method: useDirectReads * Signature: (J)Z */ -jboolean Java_org_rocksdb_Options_useDirectReads(JNIEnv* env, jobject jobj, - jlong jhandle) { +jboolean Java_org_rocksdb_Options_useDirectReads( + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->use_direct_reads; } @@ -1054,9 +1157,8 @@ jboolean Java_org_rocksdb_Options_useDirectReads(JNIEnv* env, jobject jobj, * Method: setUseDirectReads * Signature: (JZ)V */ -void Java_org_rocksdb_Options_setUseDirectReads(JNIEnv* env, jobject jobj, - jlong jhandle, - jboolean use_direct_reads) { +void Java_org_rocksdb_Options_setUseDirectReads( + JNIEnv*, jobject, jlong jhandle, jboolean use_direct_reads) { reinterpret_cast(jhandle)->use_direct_reads = static_cast(use_direct_reads); } @@ -1067,7 +1169,7 @@ void Java_org_rocksdb_Options_setUseDirectReads(JNIEnv* env, jobject jobj, * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_useDirectIoForFlushAndCompaction( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->use_direct_io_for_flush_and_compaction; } @@ -1078,7 +1180,7 @@ jboolean Java_org_rocksdb_Options_useDirectIoForFlushAndCompaction( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setUseDirectIoForFlushAndCompaction( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jboolean use_direct_io_for_flush_and_compaction) { reinterpret_cast(jhandle) ->use_direct_io_for_flush_and_compaction = @@ -1091,7 +1193,7 @@ void Java_org_rocksdb_Options_setUseDirectIoForFlushAndCompaction( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAllowFAllocate( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jallow_fallocate) { + JNIEnv*, jobject, jlong jhandle, jboolean jallow_fallocate) { reinterpret_cast(jhandle)->allow_fallocate = static_cast(jallow_fallocate); } @@ -1102,7 +1204,7 @@ void Java_org_rocksdb_Options_setAllowFAllocate( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_allowFAllocate( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->allow_fallocate); } @@ -1113,7 +1215,7 @@ jboolean Java_org_rocksdb_Options_allowFAllocate( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_isFdCloseOnExec( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->is_fd_close_on_exec; } @@ -1123,7 +1225,7 @@ jboolean Java_org_rocksdb_Options_isFdCloseOnExec( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setIsFdCloseOnExec( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean is_fd_close_on_exec) { + JNIEnv*, jobject, jlong jhandle, jboolean is_fd_close_on_exec) { reinterpret_cast(jhandle)->is_fd_close_on_exec = static_cast(is_fd_close_on_exec); } @@ -1134,7 +1236,7 @@ void Java_org_rocksdb_Options_setIsFdCloseOnExec( * Signature: (J)I */ jint Java_org_rocksdb_Options_statsDumpPeriodSec( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->stats_dump_period_sec; } @@ -1144,7 +1246,8 @@ jint Java_org_rocksdb_Options_statsDumpPeriodSec( * Signature: (JI)V */ void Java_org_rocksdb_Options_setStatsDumpPeriodSec( - JNIEnv* env, jobject jobj, jlong jhandle, jint stats_dump_period_sec) { + JNIEnv*, jobject, jlong jhandle, + jint stats_dump_period_sec) { reinterpret_cast(jhandle)->stats_dump_period_sec = static_cast(stats_dump_period_sec); } @@ -1155,7 +1258,7 @@ void Java_org_rocksdb_Options_setStatsDumpPeriodSec( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_adviseRandomOnOpen( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->advise_random_on_open; } @@ -1165,7 +1268,8 @@ jboolean Java_org_rocksdb_Options_adviseRandomOnOpen( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAdviseRandomOnOpen( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean advise_random_on_open) { + JNIEnv*, jobject, jlong jhandle, + jboolean advise_random_on_open) { reinterpret_cast(jhandle)->advise_random_on_open = static_cast(advise_random_on_open); } @@ -1176,7 +1280,8 @@ void Java_org_rocksdb_Options_setAdviseRandomOnOpen( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setDbWriteBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jdb_write_buffer_size) { + JNIEnv*, jobject, jlong jhandle, + jlong jdb_write_buffer_size) { auto* opt = reinterpret_cast(jhandle); opt->db_write_buffer_size = static_cast(jdb_write_buffer_size); } @@ -1187,7 +1292,7 @@ void Java_org_rocksdb_Options_setDbWriteBufferSize( * Signature: (J)J */ jlong Java_org_rocksdb_Options_dbWriteBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->db_write_buffer_size); } @@ -1198,7 +1303,8 @@ jlong Java_org_rocksdb_Options_dbWriteBufferSize( * Signature: (JB)V */ void Java_org_rocksdb_Options_setAccessHintOnCompactionStart( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jaccess_hint_value) { + JNIEnv*, jobject, jlong jhandle, + jbyte jaccess_hint_value) { auto* opt = reinterpret_cast(jhandle); opt->access_hint_on_compaction_start = rocksdb::AccessHintJni::toCppAccessHint(jaccess_hint_value); @@ -1210,7 +1316,7 @@ void Java_org_rocksdb_Options_setAccessHintOnCompactionStart( * Signature: (J)B */ jbyte Java_org_rocksdb_Options_accessHintOnCompactionStart( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return rocksdb::AccessHintJni::toJavaAccessHint( opt->access_hint_on_compaction_start); @@ -1222,7 +1328,7 @@ jbyte Java_org_rocksdb_Options_accessHintOnCompactionStart( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setNewTableReaderForCompactionInputs( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jboolean jnew_table_reader_for_compaction_inputs) { auto* opt = reinterpret_cast(jhandle); opt->new_table_reader_for_compaction_inputs = @@ -1235,7 +1341,7 @@ void Java_org_rocksdb_Options_setNewTableReaderForCompactionInputs( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_newTableReaderForCompactionInputs( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->new_table_reader_for_compaction_inputs); } @@ -1246,7 +1352,8 @@ jboolean Java_org_rocksdb_Options_newTableReaderForCompactionInputs( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setCompactionReadaheadSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jcompaction_readahead_size) { + JNIEnv*, jobject, jlong jhandle, + jlong jcompaction_readahead_size) { auto* opt = reinterpret_cast(jhandle); opt->compaction_readahead_size = static_cast(jcompaction_readahead_size); @@ -1258,7 +1365,7 @@ void Java_org_rocksdb_Options_setCompactionReadaheadSize( * Signature: (J)J */ jlong Java_org_rocksdb_Options_compactionReadaheadSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->compaction_readahead_size); } @@ -1269,8 +1376,7 @@ jlong Java_org_rocksdb_Options_compactionReadaheadSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setRandomAccessMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jrandom_access_max_buffer_size) { + JNIEnv*, jobject, jlong jhandle, jlong jrandom_access_max_buffer_size) { auto* opt = reinterpret_cast(jhandle); opt->random_access_max_buffer_size = static_cast(jrandom_access_max_buffer_size); @@ -1282,7 +1388,7 @@ void Java_org_rocksdb_Options_setRandomAccessMaxBufferSize( * Signature: (J)J */ jlong Java_org_rocksdb_Options_randomAccessMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->random_access_max_buffer_size); } @@ -1293,7 +1399,7 @@ jlong Java_org_rocksdb_Options_randomAccessMaxBufferSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setWritableFileMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jlong jwritable_file_max_buffer_size) { auto* opt = reinterpret_cast(jhandle); opt->writable_file_max_buffer_size = @@ -1306,7 +1412,7 @@ void Java_org_rocksdb_Options_setWritableFileMaxBufferSize( * Signature: (J)J */ jlong Java_org_rocksdb_Options_writableFileMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->writable_file_max_buffer_size); } @@ -1317,7 +1423,7 @@ jlong Java_org_rocksdb_Options_writableFileMaxBufferSize( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_useAdaptiveMutex( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->use_adaptive_mutex; } @@ -1327,7 +1433,7 @@ jboolean Java_org_rocksdb_Options_useAdaptiveMutex( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setUseAdaptiveMutex( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean use_adaptive_mutex) { + JNIEnv*, jobject, jlong jhandle, jboolean use_adaptive_mutex) { reinterpret_cast(jhandle)->use_adaptive_mutex = static_cast(use_adaptive_mutex); } @@ -1338,7 +1444,7 @@ void Java_org_rocksdb_Options_setUseAdaptiveMutex( * Signature: (J)J */ jlong Java_org_rocksdb_Options_bytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->bytes_per_sync; } @@ -1348,7 +1454,7 @@ jlong Java_org_rocksdb_Options_bytesPerSync( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setBytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle, jlong bytes_per_sync) { + JNIEnv*, jobject, jlong jhandle, jlong bytes_per_sync) { reinterpret_cast(jhandle)->bytes_per_sync = static_cast(bytes_per_sync); } @@ -1359,7 +1465,7 @@ void Java_org_rocksdb_Options_setBytesPerSync( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setWalBytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jwal_bytes_per_sync) { + JNIEnv*, jobject, jlong jhandle, jlong jwal_bytes_per_sync) { reinterpret_cast(jhandle)->wal_bytes_per_sync = static_cast(jwal_bytes_per_sync); } @@ -1370,7 +1476,7 @@ void Java_org_rocksdb_Options_setWalBytesPerSync( * Signature: (J)J */ jlong Java_org_rocksdb_Options_walBytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->wal_bytes_per_sync); } @@ -1381,8 +1487,7 @@ jlong Java_org_rocksdb_Options_walBytesPerSync( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setEnableThreadTracking( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jenable_thread_tracking) { + JNIEnv*, jobject, jlong jhandle, jboolean jenable_thread_tracking) { auto* opt = reinterpret_cast(jhandle); opt->enable_thread_tracking = static_cast(jenable_thread_tracking); } @@ -1393,7 +1498,7 @@ void Java_org_rocksdb_Options_setEnableThreadTracking( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_enableThreadTracking( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->enable_thread_tracking); } @@ -1404,7 +1509,7 @@ jboolean Java_org_rocksdb_Options_enableThreadTracking( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setDelayedWriteRate( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jdelayed_write_rate) { + JNIEnv*, jobject, jlong jhandle, jlong jdelayed_write_rate) { auto* opt = reinterpret_cast(jhandle); opt->delayed_write_rate = static_cast(jdelayed_write_rate); } @@ -1415,20 +1520,42 @@ void Java_org_rocksdb_Options_setDelayedWriteRate( * Signature: (J)J */ jlong Java_org_rocksdb_Options_delayedWriteRate( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->delayed_write_rate); } +/* + * Class: org_rocksdb_Options + * Method: setEnablePipelinedWrite + * Signature: (JZ)V + */ +void Java_org_rocksdb_Options_setEnablePipelinedWrite( + JNIEnv*, jobject, jlong jhandle, jboolean jenable_pipelined_write) { + auto* opt = reinterpret_cast(jhandle); + opt->enable_pipelined_write = jenable_pipelined_write == JNI_TRUE; +} + +/* + * Class: org_rocksdb_Options + * Method: enablePipelinedWrite + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_Options_enablePipelinedWrite( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->enable_pipelined_write); +} + /* * Class: org_rocksdb_Options * Method: setAllowConcurrentMemtableWrite * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAllowConcurrentMemtableWrite( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow) { - reinterpret_cast(jhandle)-> - allow_concurrent_memtable_write = static_cast(allow); + JNIEnv*, jobject, jlong jhandle, jboolean allow) { + reinterpret_cast(jhandle) + ->allow_concurrent_memtable_write = static_cast(allow); } /* @@ -1437,9 +1564,9 @@ void Java_org_rocksdb_Options_setAllowConcurrentMemtableWrite( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_allowConcurrentMemtableWrite( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - allow_concurrent_memtable_write; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->allow_concurrent_memtable_write; } /* @@ -1448,9 +1575,9 @@ jboolean Java_org_rocksdb_Options_allowConcurrentMemtableWrite( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setEnableWriteThreadAdaptiveYield( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean yield) { - reinterpret_cast(jhandle)-> - enable_write_thread_adaptive_yield = static_cast(yield); + JNIEnv*, jobject, jlong jhandle, jboolean yield) { + reinterpret_cast(jhandle) + ->enable_write_thread_adaptive_yield = static_cast(yield); } /* @@ -1459,9 +1586,9 @@ void Java_org_rocksdb_Options_setEnableWriteThreadAdaptiveYield( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_enableWriteThreadAdaptiveYield( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - enable_write_thread_adaptive_yield; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->enable_write_thread_adaptive_yield; } /* @@ -1470,9 +1597,9 @@ jboolean Java_org_rocksdb_Options_enableWriteThreadAdaptiveYield( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setWriteThreadMaxYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle, jlong max) { - reinterpret_cast(jhandle)-> - write_thread_max_yield_usec = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jlong max) { + reinterpret_cast(jhandle)->write_thread_max_yield_usec = + static_cast(max); } /* @@ -1481,9 +1608,9 @@ void Java_org_rocksdb_Options_setWriteThreadMaxYieldUsec( * Signature: (J)J */ jlong Java_org_rocksdb_Options_writeThreadMaxYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - write_thread_max_yield_usec; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->write_thread_max_yield_usec; } /* @@ -1492,9 +1619,9 @@ jlong Java_org_rocksdb_Options_writeThreadMaxYieldUsec( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setWriteThreadSlowYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle, jlong slow) { - reinterpret_cast(jhandle)-> - write_thread_slow_yield_usec = static_cast(slow); + JNIEnv*, jobject, jlong jhandle, jlong slow) { + reinterpret_cast(jhandle)->write_thread_slow_yield_usec = + static_cast(slow); } /* @@ -1503,9 +1630,9 @@ void Java_org_rocksdb_Options_setWriteThreadSlowYieldUsec( * Signature: (J)J */ jlong Java_org_rocksdb_Options_writeThreadSlowYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - write_thread_slow_yield_usec; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->write_thread_slow_yield_usec; } /* @@ -1514,7 +1641,7 @@ jlong Java_org_rocksdb_Options_writeThreadSlowYieldUsec( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setSkipStatsUpdateOnDbOpen( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jboolean jskip_stats_update_on_db_open) { auto* opt = reinterpret_cast(jhandle); opt->skip_stats_update_on_db_open = @@ -1527,7 +1654,7 @@ void Java_org_rocksdb_Options_setSkipStatsUpdateOnDbOpen( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_skipStatsUpdateOnDbOpen( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->skip_stats_update_on_db_open); } @@ -1538,11 +1665,11 @@ jboolean Java_org_rocksdb_Options_skipStatsUpdateOnDbOpen( * Signature: (JB)V */ void Java_org_rocksdb_Options_setWalRecoveryMode( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jwal_recovery_mode_value) { + JNIEnv*, jobject, jlong jhandle, + jbyte jwal_recovery_mode_value) { auto* opt = reinterpret_cast(jhandle); - opt->wal_recovery_mode = - rocksdb::WALRecoveryModeJni::toCppWALRecoveryMode( - jwal_recovery_mode_value); + opt->wal_recovery_mode = rocksdb::WALRecoveryModeJni::toCppWALRecoveryMode( + jwal_recovery_mode_value); } /* @@ -1551,7 +1678,7 @@ void Java_org_rocksdb_Options_setWalRecoveryMode( * Signature: (J)B */ jbyte Java_org_rocksdb_Options_walRecoveryMode( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return rocksdb::WALRecoveryModeJni::toJavaWALRecoveryMode( opt->wal_recovery_mode); @@ -1563,7 +1690,7 @@ jbyte Java_org_rocksdb_Options_walRecoveryMode( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAllow2pc( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jallow_2pc) { + JNIEnv*, jobject, jlong jhandle, jboolean jallow_2pc) { auto* opt = reinterpret_cast(jhandle); opt->allow_2pc = static_cast(jallow_2pc); } @@ -1573,7 +1700,8 @@ void Java_org_rocksdb_Options_setAllow2pc( * Method: allow2pc * Signature: (J)Z */ -jboolean Java_org_rocksdb_Options_allow2pc(JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_Options_allow2pc( + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->allow_2pc); } @@ -1584,20 +1712,34 @@ jboolean Java_org_rocksdb_Options_allow2pc(JNIEnv* env, jobject jobj, jlong jhan * Signature: (JJ)V */ void Java_org_rocksdb_Options_setRowCache( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jrow_cache_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jrow_cache_handle) { auto* opt = reinterpret_cast(jhandle); - auto* row_cache = reinterpret_cast*>(jrow_cache_handle); + auto* row_cache = + reinterpret_cast*>(jrow_cache_handle); opt->row_cache = *row_cache; } + +/* + * Class: org_rocksdb_Options + * Method: setWalFilter + * Signature: (JJ)V + */ +void Java_org_rocksdb_Options_setWalFilter( + JNIEnv*, jobject, jlong jhandle, jlong jwal_filter_handle) { + auto* opt = reinterpret_cast(jhandle); + auto* wal_filter = + reinterpret_cast(jwal_filter_handle); + opt->wal_filter = wal_filter; +} + /* * Class: org_rocksdb_Options * Method: setFailIfOptionsFileError * Signature: (JZ)V */ void Java_org_rocksdb_Options_setFailIfOptionsFileError( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jfail_if_options_file_error) { + JNIEnv*, jobject, jlong jhandle, jboolean jfail_if_options_file_error) { auto* opt = reinterpret_cast(jhandle); opt->fail_if_options_file_error = static_cast(jfail_if_options_file_error); @@ -1609,7 +1751,7 @@ void Java_org_rocksdb_Options_setFailIfOptionsFileError( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_failIfOptionsFileError( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->fail_if_options_file_error); } @@ -1620,7 +1762,7 @@ jboolean Java_org_rocksdb_Options_failIfOptionsFileError( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setDumpMallocStats( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jdump_malloc_stats) { + JNIEnv*, jobject, jlong jhandle, jboolean jdump_malloc_stats) { auto* opt = reinterpret_cast(jhandle); opt->dump_malloc_stats = static_cast(jdump_malloc_stats); } @@ -1631,7 +1773,7 @@ void Java_org_rocksdb_Options_setDumpMallocStats( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_dumpMallocStats( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->dump_malloc_stats); } @@ -1642,10 +1784,10 @@ jboolean Java_org_rocksdb_Options_dumpMallocStats( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAvoidFlushDuringRecovery( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean javoid_flush_during_recovery) { + JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_recovery) { auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_recovery = static_cast(javoid_flush_during_recovery); + opt->avoid_flush_during_recovery = + static_cast(javoid_flush_during_recovery); } /* @@ -1654,7 +1796,7 @@ void Java_org_rocksdb_Options_setAvoidFlushDuringRecovery( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_avoidFlushDuringRecovery( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->avoid_flush_during_recovery); } @@ -1665,10 +1807,10 @@ jboolean Java_org_rocksdb_Options_avoidFlushDuringRecovery( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setAvoidFlushDuringShutdown( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean javoid_flush_during_shutdown) { + JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_shutdown) { auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_shutdown = static_cast(javoid_flush_during_shutdown); + opt->avoid_flush_during_shutdown = + static_cast(javoid_flush_during_shutdown); } /* @@ -1677,17 +1819,127 @@ void Java_org_rocksdb_Options_setAvoidFlushDuringShutdown( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_avoidFlushDuringShutdown( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->avoid_flush_during_shutdown); } +/* + * Class: org_rocksdb_Options + * Method: setAllowIngestBehind + * Signature: (JZ)V + */ +void Java_org_rocksdb_Options_setAllowIngestBehind( + JNIEnv*, jobject, jlong jhandle, jboolean jallow_ingest_behind) { + auto* opt = reinterpret_cast(jhandle); + opt->allow_ingest_behind = jallow_ingest_behind == JNI_TRUE; +} + +/* + * Class: org_rocksdb_Options + * Method: allowIngestBehind + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_Options_allowIngestBehind( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->allow_ingest_behind); +} + +/* + * Class: org_rocksdb_Options + * Method: setPreserveDeletes + * Signature: (JZ)V + */ +void Java_org_rocksdb_Options_setPreserveDeletes( + JNIEnv*, jobject, jlong jhandle, jboolean jpreserve_deletes) { + auto* opt = reinterpret_cast(jhandle); + opt->preserve_deletes = jpreserve_deletes == JNI_TRUE; +} + +/* + * Class: org_rocksdb_Options + * Method: preserveDeletes + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_Options_preserveDeletes( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->preserve_deletes); +} + +/* + * Class: org_rocksdb_Options + * Method: setTwoWriteQueues + * Signature: (JZ)V + */ +void Java_org_rocksdb_Options_setTwoWriteQueues( + JNIEnv*, jobject, jlong jhandle, jboolean jtwo_write_queues) { + auto* opt = reinterpret_cast(jhandle); + opt->two_write_queues = jtwo_write_queues == JNI_TRUE; +} + +/* + * Class: org_rocksdb_Options + * Method: twoWriteQueues + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_Options_twoWriteQueues( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->two_write_queues); +} + +/* + * Class: org_rocksdb_Options + * Method: setManualWalFlush + * Signature: (JZ)V + */ +void Java_org_rocksdb_Options_setManualWalFlush( + JNIEnv*, jobject, jlong jhandle, jboolean jmanual_wal_flush) { + auto* opt = reinterpret_cast(jhandle); + opt->manual_wal_flush = jmanual_wal_flush == JNI_TRUE; +} + +/* + * Class: org_rocksdb_Options + * Method: manualWalFlush + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_Options_manualWalFlush( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->manual_wal_flush); +} + +/* + * Class: org_rocksdb_Options + * Method: setAtomicFlush + * Signature: (JZ)V + */ +void Java_org_rocksdb_Options_setAtomicFlush( + JNIEnv*, jobject, jlong jhandle, jboolean jatomic_flush) { + auto* opt = reinterpret_cast(jhandle); + opt->atomic_flush = jatomic_flush == JNI_TRUE; +} + +/* + * Class: org_rocksdb_Options + * Method: atomicFlush + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_Options_atomicFlush( + JNIEnv *, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->atomic_flush); +} + /* * Method: tableFactoryName * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_Options_tableFactoryName( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); rocksdb::TableFactory* tf = opt->table_factory.get(); @@ -1698,16 +1950,15 @@ jstring Java_org_rocksdb_Options_tableFactoryName( return env->NewStringUTF(tf->Name()); } - /* * Class: org_rocksdb_Options * Method: minWriteBufferNumberToMerge * Signature: (J)I */ jint Java_org_rocksdb_Options_minWriteBufferNumberToMerge( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->min_write_buffer_number_to_merge; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->min_write_buffer_number_to_merge; } /* @@ -1716,20 +1967,18 @@ jint Java_org_rocksdb_Options_minWriteBufferNumberToMerge( * Signature: (JI)V */ void Java_org_rocksdb_Options_setMinWriteBufferNumberToMerge( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jmin_write_buffer_number_to_merge) { - reinterpret_cast( - jhandle)->min_write_buffer_number_to_merge = - static_cast(jmin_write_buffer_number_to_merge); + JNIEnv*, jobject, jlong jhandle, jint jmin_write_buffer_number_to_merge) { + reinterpret_cast(jhandle) + ->min_write_buffer_number_to_merge = + static_cast(jmin_write_buffer_number_to_merge); } /* * Class: org_rocksdb_Options * Method: maxWriteBufferNumberToMaintain * Signature: (J)I */ -jint Java_org_rocksdb_Options_maxWriteBufferNumberToMaintain(JNIEnv* env, - jobject jobj, - jlong jhandle) { +jint Java_org_rocksdb_Options_maxWriteBufferNumberToMaintain( + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->max_write_buffer_number_to_maintain; } @@ -1740,7 +1989,7 @@ jint Java_org_rocksdb_Options_maxWriteBufferNumberToMaintain(JNIEnv* env, * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxWriteBufferNumberToMaintain( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jint jmax_write_buffer_number_to_maintain) { reinterpret_cast(jhandle) ->max_write_buffer_number_to_maintain = @@ -1753,7 +2002,7 @@ void Java_org_rocksdb_Options_setMaxWriteBufferNumberToMaintain( * Signature: (JB)V */ void Java_org_rocksdb_Options_setCompressionType( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jcompression_type_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { auto* opts = reinterpret_cast(jhandle); opts->compression = rocksdb::CompressionTypeJni::toCppCompressionType( jcompression_type_value); @@ -1765,10 +2014,9 @@ void Java_org_rocksdb_Options_setCompressionType( * Signature: (J)B */ jbyte Java_org_rocksdb_Options_compressionType( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opts = reinterpret_cast(jhandle); - return rocksdb::CompressionTypeJni::toJavaCompressionType( - opts->compression); + return rocksdb::CompressionTypeJni::toJavaCompressionType(opts->compression); } /** @@ -1779,28 +2027,30 @@ jbyte Java_org_rocksdb_Options_compressionType( * @param jcompression_levels A reference to a java byte array * where each byte indicates a compression level * - * @return A unique_ptr to the vector, or unique_ptr(nullptr) if a JNI exception occurs + * @return A std::unique_ptr to the vector, or std::unique_ptr(nullptr) if a JNI + * exception occurs */ -std::unique_ptr> rocksdb_compression_vector_helper( +std::unique_ptr>rocksdb_compression_vector_helper( JNIEnv* env, jbyteArray jcompression_levels) { jsize len = env->GetArrayLength(jcompression_levels); jbyte* jcompression_level = env->GetByteArrayElements(jcompression_levels, nullptr); - if(jcompression_level == nullptr) { + if (jcompression_level == nullptr) { // exception thrown: OutOfMemoryError return std::unique_ptr>(); } auto* compression_levels = new std::vector(); - std::unique_ptr> uptr_compression_levels(compression_levels); + std::unique_ptr> + uptr_compression_levels(compression_levels); - for(jsize i = 0; i < len; i++) { + for (jsize i = 0; i < len; i++) { jbyte jcl = jcompression_level[i]; compression_levels->push_back(static_cast(jcl)); } env->ReleaseByteArrayElements(jcompression_levels, jcompression_level, - JNI_ABORT); + JNI_ABORT); return uptr_compression_levels; } @@ -1815,32 +2065,32 @@ std::unique_ptr> rocksdb_compression_vecto * * @return A jbytearray or nullptr if an exception occurs */ -jbyteArray rocksdb_compression_list_helper(JNIEnv* env, - std::vector compression_levels) { +jbyteArray rocksdb_compression_list_helper( + JNIEnv* env, std::vector compression_levels) { const size_t len = compression_levels.size(); jbyte* jbuf = new jbyte[len]; for (size_t i = 0; i < len; i++) { - jbuf[i] = compression_levels[i]; + jbuf[i] = compression_levels[i]; } // insert in java array jbyteArray jcompression_levels = env->NewByteArray(static_cast(len)); - if(jcompression_levels == nullptr) { - // exception thrown: OutOfMemoryError - delete [] jbuf; - return nullptr; + if (jcompression_levels == nullptr) { + // exception thrown: OutOfMemoryError + delete[] jbuf; + return nullptr; } env->SetByteArrayRegion(jcompression_levels, 0, static_cast(len), - jbuf); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jcompression_levels); - delete [] jbuf; - return nullptr; + jbuf); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jcompression_levels); + delete[] jbuf; + return nullptr; } - delete [] jbuf; + delete[] jbuf; return jcompression_levels; } @@ -1851,11 +2101,10 @@ jbyteArray rocksdb_compression_list_helper(JNIEnv* env, * Signature: (J[B)V */ void Java_org_rocksdb_Options_setCompressionPerLevel( - JNIEnv* env, jobject jobj, jlong jhandle, - jbyteArray jcompressionLevels) { + JNIEnv* env, jobject, jlong jhandle, jbyteArray jcompressionLevels) { auto uptr_compression_levels = rocksdb_compression_vector_helper(env, jcompressionLevels); - if(!uptr_compression_levels) { + if (!uptr_compression_levels) { // exception occurred return; } @@ -1869,10 +2118,9 @@ void Java_org_rocksdb_Options_setCompressionPerLevel( * Signature: (J)[B */ jbyteArray Java_org_rocksdb_Options_compressionPerLevel( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { auto* options = reinterpret_cast(jhandle); - return rocksdb_compression_list_helper(env, - options->compression_per_level); + return rocksdb_compression_list_helper(env, options->compression_per_level); } /* @@ -1881,7 +2129,7 @@ jbyteArray Java_org_rocksdb_Options_compressionPerLevel( * Signature: (JB)V */ void Java_org_rocksdb_Options_setBottommostCompressionType( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jcompression_type_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { auto* options = reinterpret_cast(jhandle); options->bottommost_compression = rocksdb::CompressionTypeJni::toCppCompressionType( @@ -1894,23 +2142,37 @@ void Java_org_rocksdb_Options_setBottommostCompressionType( * Signature: (J)B */ jbyte Java_org_rocksdb_Options_bottommostCompressionType( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* options = reinterpret_cast(jhandle); return rocksdb::CompressionTypeJni::toJavaCompressionType( options->bottommost_compression); } +/* + * Class: org_rocksdb_Options + * Method: setBottommostCompressionOptions + * Signature: (JJ)V + */ +void Java_org_rocksdb_Options_setBottommostCompressionOptions( + JNIEnv*, jobject, jlong jhandle, + jlong jbottommost_compression_options_handle) { + auto* options = reinterpret_cast(jhandle); + auto* bottommost_compression_options = + reinterpret_cast( + jbottommost_compression_options_handle); + options->bottommost_compression_opts = *bottommost_compression_options; +} + /* * Class: org_rocksdb_Options * Method: setCompressionOptions * Signature: (JJ)V */ void Java_org_rocksdb_Options_setCompressionOptions( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jcompression_options_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jcompression_options_handle) { auto* options = reinterpret_cast(jhandle); - auto* compression_options = - reinterpret_cast(jcompression_options_handle); + auto* compression_options = reinterpret_cast( + jcompression_options_handle); options->compression_opts = *compression_options; } @@ -1920,9 +2182,11 @@ void Java_org_rocksdb_Options_setCompressionOptions( * Signature: (JB)V */ void Java_org_rocksdb_Options_setCompactionStyle( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte compaction_style) { - reinterpret_cast(jhandle)->compaction_style = - static_cast(compaction_style); + JNIEnv*, jobject, jlong jhandle, jbyte jcompaction_style) { + auto* options = reinterpret_cast(jhandle); + options->compaction_style = + rocksdb::CompactionStyleJni::toCppCompactionStyle( + jcompaction_style); } /* @@ -1931,8 +2195,10 @@ void Java_org_rocksdb_Options_setCompactionStyle( * Signature: (J)B */ jbyte Java_org_rocksdb_Options_compactionStyle( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)->compaction_style; + JNIEnv*, jobject, jlong jhandle) { + auto* options = reinterpret_cast(jhandle); + return rocksdb::CompactionStyleJni::toJavaCompactionStyle( + options->compaction_style); } /* @@ -1941,9 +2207,10 @@ jbyte Java_org_rocksdb_Options_compactionStyle( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMaxTableFilesSizeFIFO( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jmax_table_files_size) { - reinterpret_cast(jhandle)->compaction_options_fifo.max_table_files_size = - static_cast(jmax_table_files_size); + JNIEnv*, jobject, jlong jhandle, jlong jmax_table_files_size) { + reinterpret_cast(jhandle) + ->compaction_options_fifo.max_table_files_size = + static_cast(jmax_table_files_size); } /* @@ -1952,8 +2219,9 @@ void Java_org_rocksdb_Options_setMaxTableFilesSizeFIFO( * Signature: (J)J */ jlong Java_org_rocksdb_Options_maxTableFilesSizeFIFO( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)->compaction_options_fifo.max_table_files_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->compaction_options_fifo.max_table_files_size; } /* @@ -1962,7 +2230,7 @@ jlong Java_org_rocksdb_Options_maxTableFilesSizeFIFO( * Signature: (J)I */ jint Java_org_rocksdb_Options_numLevels( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->num_levels; } @@ -1972,7 +2240,7 @@ jint Java_org_rocksdb_Options_numLevels( * Signature: (JI)V */ void Java_org_rocksdb_Options_setNumLevels( - JNIEnv* env, jobject jobj, jlong jhandle, jint jnum_levels) { + JNIEnv*, jobject, jlong jhandle, jint jnum_levels) { reinterpret_cast(jhandle)->num_levels = static_cast(jnum_levels); } @@ -1983,9 +2251,9 @@ void Java_org_rocksdb_Options_setNumLevels( * Signature: (J)I */ jint Java_org_rocksdb_Options_levelZeroFileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger; } /* @@ -1994,11 +2262,11 @@ jint Java_org_rocksdb_Options_levelZeroFileNumCompactionTrigger( * Signature: (JI)V */ void Java_org_rocksdb_Options_setLevelZeroFileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); + reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger = + static_cast(jlevel0_file_num_compaction_trigger); } /* @@ -2007,9 +2275,9 @@ void Java_org_rocksdb_Options_setLevelZeroFileNumCompactionTrigger( * Signature: (J)I */ jint Java_org_rocksdb_Options_levelZeroSlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_slowdown_writes_trigger; } /* @@ -2018,11 +2286,9 @@ jint Java_org_rocksdb_Options_levelZeroSlowdownWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_Options_setLevelZeroSlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); + JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { + reinterpret_cast(jhandle)->level0_slowdown_writes_trigger = + static_cast(jlevel0_slowdown_writes_trigger); } /* @@ -2031,9 +2297,9 @@ void Java_org_rocksdb_Options_setLevelZeroSlowdownWritesTrigger( * Signature: (J)I */ jint Java_org_rocksdb_Options_levelZeroStopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_stop_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_stop_writes_trigger; } /* @@ -2042,8 +2308,7 @@ jint Java_org_rocksdb_Options_levelZeroStopWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_Options_setLevelZeroStopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_stop_writes_trigger) { + JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { reinterpret_cast(jhandle)->level0_stop_writes_trigger = static_cast(jlevel0_stop_writes_trigger); } @@ -2054,7 +2319,7 @@ void Java_org_rocksdb_Options_setLevelZeroStopWritesTrigger( * Signature: (J)J */ jlong Java_org_rocksdb_Options_targetFileSizeBase( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->target_file_size_base; } @@ -2064,8 +2329,7 @@ jlong Java_org_rocksdb_Options_targetFileSizeBase( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setTargetFileSizeBase( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jtarget_file_size_base) { + JNIEnv*, jobject, jlong jhandle, jlong jtarget_file_size_base) { reinterpret_cast(jhandle)->target_file_size_base = static_cast(jtarget_file_size_base); } @@ -2076,9 +2340,9 @@ void Java_org_rocksdb_Options_setTargetFileSizeBase( * Signature: (J)I */ jint Java_org_rocksdb_Options_targetFileSizeMultiplier( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->target_file_size_multiplier; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->target_file_size_multiplier; } /* @@ -2087,11 +2351,9 @@ jint Java_org_rocksdb_Options_targetFileSizeMultiplier( * Signature: (JI)V */ void Java_org_rocksdb_Options_setTargetFileSizeMultiplier( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jtarget_file_size_multiplier) { - reinterpret_cast( - jhandle)->target_file_size_multiplier = - static_cast(jtarget_file_size_multiplier); + JNIEnv*, jobject, jlong jhandle, jint jtarget_file_size_multiplier) { + reinterpret_cast(jhandle)->target_file_size_multiplier = + static_cast(jtarget_file_size_multiplier); } /* @@ -2100,9 +2362,8 @@ void Java_org_rocksdb_Options_setTargetFileSizeMultiplier( * Signature: (J)J */ jlong Java_org_rocksdb_Options_maxBytesForLevelBase( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->max_bytes_for_level_base; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_bytes_for_level_base; } /* @@ -2111,11 +2372,9 @@ jlong Java_org_rocksdb_Options_maxBytesForLevelBase( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMaxBytesForLevelBase( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmax_bytes_for_level_base) { - reinterpret_cast( - jhandle)->max_bytes_for_level_base = - static_cast(jmax_bytes_for_level_base); + JNIEnv*, jobject, jlong jhandle, jlong jmax_bytes_for_level_base) { + reinterpret_cast(jhandle)->max_bytes_for_level_base = + static_cast(jmax_bytes_for_level_base); } /* @@ -2124,9 +2383,9 @@ void Java_org_rocksdb_Options_setMaxBytesForLevelBase( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_levelCompactionDynamicLevelBytes( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level_compaction_dynamic_level_bytes; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level_compaction_dynamic_level_bytes; } /* @@ -2135,11 +2394,9 @@ jboolean Java_org_rocksdb_Options_levelCompactionDynamicLevelBytes( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setLevelCompactionDynamicLevelBytes( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jenable_dynamic_level_bytes) { - reinterpret_cast( - jhandle)->level_compaction_dynamic_level_bytes = - (jenable_dynamic_level_bytes); + JNIEnv*, jobject, jlong jhandle, jboolean jenable_dynamic_level_bytes) { + reinterpret_cast(jhandle) + ->level_compaction_dynamic_level_bytes = (jenable_dynamic_level_bytes); } /* @@ -2147,11 +2404,10 @@ void Java_org_rocksdb_Options_setLevelCompactionDynamicLevelBytes( * Method: maxBytesForLevelMultiplier * Signature: (J)D */ -jdouble Java_org_rocksdb_Options_maxBytesForLevelMultiplier(JNIEnv* env, - jobject jobj, - jlong jhandle) { - return reinterpret_cast( - jhandle)->max_bytes_for_level_multiplier; +jdouble Java_org_rocksdb_Options_maxBytesForLevelMultiplier( + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_bytes_for_level_multiplier; } /* @@ -2160,8 +2416,7 @@ jdouble Java_org_rocksdb_Options_maxBytesForLevelMultiplier(JNIEnv* env, * Signature: (JD)V */ void Java_org_rocksdb_Options_setMaxBytesForLevelMultiplier( - JNIEnv* env, jobject jobj, jlong jhandle, - jdouble jmax_bytes_for_level_multiplier) { + JNIEnv*, jobject, jlong jhandle, jdouble jmax_bytes_for_level_multiplier) { reinterpret_cast(jhandle)->max_bytes_for_level_multiplier = static_cast(jmax_bytes_for_level_multiplier); } @@ -2171,8 +2426,8 @@ void Java_org_rocksdb_Options_setMaxBytesForLevelMultiplier( * Method: maxCompactionBytes * Signature: (J)I */ -jlong Java_org_rocksdb_Options_maxCompactionBytes(JNIEnv* env, jobject jobj, - jlong jhandle) { +jlong Java_org_rocksdb_Options_maxCompactionBytes( + JNIEnv*, jobject, jlong jhandle) { return static_cast( reinterpret_cast(jhandle)->max_compaction_bytes); } @@ -2183,7 +2438,7 @@ jlong Java_org_rocksdb_Options_maxCompactionBytes(JNIEnv* env, jobject jobj, * Signature: (JI)V */ void Java_org_rocksdb_Options_setMaxCompactionBytes( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jmax_compaction_bytes) { + JNIEnv*, jobject, jlong jhandle, jlong jmax_compaction_bytes) { reinterpret_cast(jhandle)->max_compaction_bytes = static_cast(jmax_compaction_bytes); } @@ -2194,7 +2449,7 @@ void Java_org_rocksdb_Options_setMaxCompactionBytes( * Signature: (J)J */ jlong Java_org_rocksdb_Options_arenaBlockSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->arena_block_size; } @@ -2204,8 +2459,8 @@ jlong Java_org_rocksdb_Options_arenaBlockSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setArenaBlockSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jarena_block_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(jarena_block_size); + JNIEnv* env, jobject, jlong jhandle, jlong jarena_block_size) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(jarena_block_size); if (s.ok()) { reinterpret_cast(jhandle)->arena_block_size = jarena_block_size; @@ -2220,9 +2475,8 @@ void Java_org_rocksdb_Options_setArenaBlockSize( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_disableAutoCompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->disable_auto_compactions; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->disable_auto_compactions; } /* @@ -2231,11 +2485,9 @@ jboolean Java_org_rocksdb_Options_disableAutoCompactions( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setDisableAutoCompactions( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jdisable_auto_compactions) { - reinterpret_cast( - jhandle)->disable_auto_compactions = - static_cast(jdisable_auto_compactions); + JNIEnv*, jobject, jlong jhandle, jboolean jdisable_auto_compactions) { + reinterpret_cast(jhandle)->disable_auto_compactions = + static_cast(jdisable_auto_compactions); } /* @@ -2244,9 +2496,9 @@ void Java_org_rocksdb_Options_setDisableAutoCompactions( * Signature: (J)J */ jlong Java_org_rocksdb_Options_maxSequentialSkipInIterations( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->max_sequential_skip_in_iterations; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_sequential_skip_in_iterations; } /* @@ -2255,11 +2507,11 @@ jlong Java_org_rocksdb_Options_maxSequentialSkipInIterations( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMaxSequentialSkipInIterations( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jlong jmax_sequential_skip_in_iterations) { - reinterpret_cast( - jhandle)->max_sequential_skip_in_iterations = - static_cast(jmax_sequential_skip_in_iterations); + reinterpret_cast(jhandle) + ->max_sequential_skip_in_iterations = + static_cast(jmax_sequential_skip_in_iterations); } /* @@ -2268,9 +2520,8 @@ void Java_org_rocksdb_Options_setMaxSequentialSkipInIterations( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_inplaceUpdateSupport( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->inplace_update_support; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->inplace_update_support; } /* @@ -2279,11 +2530,9 @@ jboolean Java_org_rocksdb_Options_inplaceUpdateSupport( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setInplaceUpdateSupport( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jinplace_update_support) { - reinterpret_cast( - jhandle)->inplace_update_support = - static_cast(jinplace_update_support); + JNIEnv*, jobject, jlong jhandle, jboolean jinplace_update_support) { + reinterpret_cast(jhandle)->inplace_update_support = + static_cast(jinplace_update_support); } /* @@ -2292,9 +2541,8 @@ void Java_org_rocksdb_Options_setInplaceUpdateSupport( * Signature: (J)J */ jlong Java_org_rocksdb_Options_inplaceUpdateNumLocks( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->inplace_update_num_locks; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->inplace_update_num_locks; } /* @@ -2303,10 +2551,9 @@ jlong Java_org_rocksdb_Options_inplaceUpdateNumLocks( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setInplaceUpdateNumLocks( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jinplace_update_num_locks) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - jinplace_update_num_locks); + JNIEnv* env, jobject, jlong jhandle, jlong jinplace_update_num_locks) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(jinplace_update_num_locks); if (s.ok()) { reinterpret_cast(jhandle)->inplace_update_num_locks = jinplace_update_num_locks; @@ -2320,9 +2567,8 @@ void Java_org_rocksdb_Options_setInplaceUpdateNumLocks( * Method: memtablePrefixBloomSizeRatio * Signature: (J)I */ -jdouble Java_org_rocksdb_Options_memtablePrefixBloomSizeRatio(JNIEnv* env, - jobject jobj, - jlong jhandle) { +jdouble Java_org_rocksdb_Options_memtablePrefixBloomSizeRatio( + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->memtable_prefix_bloom_size_ratio; } @@ -2333,7 +2579,7 @@ jdouble Java_org_rocksdb_Options_memtablePrefixBloomSizeRatio(JNIEnv* env, * Signature: (JI)V */ void Java_org_rocksdb_Options_setMemtablePrefixBloomSizeRatio( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jdouble jmemtable_prefix_bloom_size_ratio) { reinterpret_cast(jhandle) ->memtable_prefix_bloom_size_ratio = @@ -2346,7 +2592,7 @@ void Java_org_rocksdb_Options_setMemtablePrefixBloomSizeRatio( * Signature: (J)I */ jint Java_org_rocksdb_Options_bloomLocality( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->bloom_locality; } @@ -2356,7 +2602,7 @@ jint Java_org_rocksdb_Options_bloomLocality( * Signature: (JI)V */ void Java_org_rocksdb_Options_setBloomLocality( - JNIEnv* env, jobject jobj, jlong jhandle, jint jbloom_locality) { + JNIEnv*, jobject, jlong jhandle, jint jbloom_locality) { reinterpret_cast(jhandle)->bloom_locality = static_cast(jbloom_locality); } @@ -2367,7 +2613,7 @@ void Java_org_rocksdb_Options_setBloomLocality( * Signature: (J)J */ jlong Java_org_rocksdb_Options_maxSuccessiveMerges( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_successive_merges; } @@ -2377,10 +2623,9 @@ jlong Java_org_rocksdb_Options_maxSuccessiveMerges( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMaxSuccessiveMerges( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmax_successive_merges) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - jmax_successive_merges); + JNIEnv* env, jobject, jlong jhandle, jlong jmax_successive_merges) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(jmax_successive_merges); if (s.ok()) { reinterpret_cast(jhandle)->max_successive_merges = jmax_successive_merges; @@ -2395,9 +2640,9 @@ void Java_org_rocksdb_Options_setMaxSuccessiveMerges( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_optimizeFiltersForHits( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->optimize_filters_for_hits; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->optimize_filters_for_hits; } /* @@ -2406,11 +2651,9 @@ jboolean Java_org_rocksdb_Options_optimizeFiltersForHits( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setOptimizeFiltersForHits( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean joptimize_filters_for_hits) { - reinterpret_cast( - jhandle)->optimize_filters_for_hits = - static_cast(joptimize_filters_for_hits); + JNIEnv*, jobject, jlong jhandle, jboolean joptimize_filters_for_hits) { + reinterpret_cast(jhandle)->optimize_filters_for_hits = + static_cast(joptimize_filters_for_hits); } /* @@ -2419,7 +2662,7 @@ void Java_org_rocksdb_Options_setOptimizeFiltersForHits( * Signature: (J)V */ void Java_org_rocksdb_Options_optimizeForSmallDb( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { reinterpret_cast(jhandle)->OptimizeForSmallDb(); } @@ -2429,10 +2672,9 @@ void Java_org_rocksdb_Options_optimizeForSmallDb( * Signature: (JJ)V */ void Java_org_rocksdb_Options_optimizeForPointLookup( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong block_cache_size_mb) { - reinterpret_cast(jhandle)-> - OptimizeForPointLookup(block_cache_size_mb); + JNIEnv*, jobject, jlong jhandle, jlong block_cache_size_mb) { + reinterpret_cast(jhandle)->OptimizeForPointLookup( + block_cache_size_mb); } /* @@ -2441,10 +2683,9 @@ void Java_org_rocksdb_Options_optimizeForPointLookup( * Signature: (JJ)V */ void Java_org_rocksdb_Options_optimizeLevelStyleCompaction( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong memtable_memory_budget) { - reinterpret_cast(jhandle)-> - OptimizeLevelStyleCompaction(memtable_memory_budget); + JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { + reinterpret_cast(jhandle)->OptimizeLevelStyleCompaction( + memtable_memory_budget); } /* @@ -2453,10 +2694,9 @@ void Java_org_rocksdb_Options_optimizeLevelStyleCompaction( * Signature: (JJ)V */ void Java_org_rocksdb_Options_optimizeUniversalStyleCompaction( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong memtable_memory_budget) { - reinterpret_cast(jhandle)-> - OptimizeUniversalStyleCompaction(memtable_memory_budget); + JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { + reinterpret_cast(jhandle) + ->OptimizeUniversalStyleCompaction(memtable_memory_budget); } /* @@ -2465,9 +2705,8 @@ void Java_org_rocksdb_Options_optimizeUniversalStyleCompaction( * Signature: (J)V */ void Java_org_rocksdb_Options_prepareForBulkLoad( - JNIEnv* env, jobject jobj, jlong jhandle) { - reinterpret_cast(jhandle)-> - PrepareForBulkLoad(); + JNIEnv*, jobject, jlong jhandle) { + reinterpret_cast(jhandle)->PrepareForBulkLoad(); } /* @@ -2476,9 +2715,8 @@ void Java_org_rocksdb_Options_prepareForBulkLoad( * Signature: (J)J */ jlong Java_org_rocksdb_Options_memtableHugePageSize( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->memtable_huge_page_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->memtable_huge_page_size; } /* @@ -2487,14 +2725,12 @@ jlong Java_org_rocksdb_Options_memtableHugePageSize( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setMemtableHugePageSize( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmemtable_huge_page_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - jmemtable_huge_page_size); + JNIEnv* env, jobject, jlong jhandle, jlong jmemtable_huge_page_size) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(jmemtable_huge_page_size); if (s.ok()) { - reinterpret_cast( - jhandle)->memtable_huge_page_size = - jmemtable_huge_page_size; + reinterpret_cast(jhandle)->memtable_huge_page_size = + jmemtable_huge_page_size; } else { rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); } @@ -2506,9 +2742,9 @@ void Java_org_rocksdb_Options_setMemtableHugePageSize( * Signature: (J)J */ jlong Java_org_rocksdb_Options_softPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->soft_pending_compaction_bytes_limit; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->soft_pending_compaction_bytes_limit; } /* @@ -2517,10 +2753,11 @@ jlong Java_org_rocksdb_Options_softPendingCompactionBytesLimit( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setSoftPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jsoft_pending_compaction_bytes_limit) { - reinterpret_cast( - jhandle)->soft_pending_compaction_bytes_limit = - static_cast(jsoft_pending_compaction_bytes_limit); + JNIEnv*, jobject, jlong jhandle, + jlong jsoft_pending_compaction_bytes_limit) { + reinterpret_cast(jhandle) + ->soft_pending_compaction_bytes_limit = + static_cast(jsoft_pending_compaction_bytes_limit); } /* @@ -2529,9 +2766,9 @@ void Java_org_rocksdb_Options_setSoftPendingCompactionBytesLimit( * Signature: (J)J */ jlong Java_org_rocksdb_Options_hardPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->hard_pending_compaction_bytes_limit; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->hard_pending_compaction_bytes_limit; } /* @@ -2540,10 +2777,11 @@ jlong Java_org_rocksdb_Options_hardPendingCompactionBytesLimit( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setHardPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jhard_pending_compaction_bytes_limit) { - reinterpret_cast( - jhandle)->hard_pending_compaction_bytes_limit = - static_cast(jhard_pending_compaction_bytes_limit); + JNIEnv*, jobject, jlong jhandle, + jlong jhard_pending_compaction_bytes_limit) { + reinterpret_cast(jhandle) + ->hard_pending_compaction_bytes_limit = + static_cast(jhard_pending_compaction_bytes_limit); } /* @@ -2552,9 +2790,9 @@ void Java_org_rocksdb_Options_setHardPendingCompactionBytesLimit( * Signature: (J)I */ jint Java_org_rocksdb_Options_level0FileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger; } /* @@ -2563,11 +2801,11 @@ jint Java_org_rocksdb_Options_level0FileNumCompactionTrigger( * Signature: (JI)V */ void Java_org_rocksdb_Options_setLevel0FileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); + reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger = + static_cast(jlevel0_file_num_compaction_trigger); } /* @@ -2576,9 +2814,9 @@ void Java_org_rocksdb_Options_setLevel0FileNumCompactionTrigger( * Signature: (J)I */ jint Java_org_rocksdb_Options_level0SlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_slowdown_writes_trigger; } /* @@ -2587,11 +2825,9 @@ jint Java_org_rocksdb_Options_level0SlowdownWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_Options_setLevel0SlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); + JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { + reinterpret_cast(jhandle)->level0_slowdown_writes_trigger = + static_cast(jlevel0_slowdown_writes_trigger); } /* @@ -2600,9 +2836,9 @@ void Java_org_rocksdb_Options_setLevel0SlowdownWritesTrigger( * Signature: (J)I */ jint Java_org_rocksdb_Options_level0StopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_stop_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_stop_writes_trigger; } /* @@ -2611,11 +2847,9 @@ jint Java_org_rocksdb_Options_level0StopWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_Options_setLevel0StopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_stop_writes_trigger) { - reinterpret_cast( - jhandle)->level0_stop_writes_trigger = - static_cast(jlevel0_stop_writes_trigger); + JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { + reinterpret_cast(jhandle)->level0_stop_writes_trigger = + static_cast(jlevel0_stop_writes_trigger); } /* @@ -2624,10 +2858,9 @@ void Java_org_rocksdb_Options_setLevel0StopWritesTrigger( * Signature: (J)[I */ jintArray Java_org_rocksdb_Options_maxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject jobj, jlong jhandle) { - auto mbflma = - reinterpret_cast(jhandle)-> - max_bytes_for_level_multiplier_additional; + JNIEnv* env, jobject, jlong jhandle) { + auto mbflma = reinterpret_cast(jhandle) + ->max_bytes_for_level_multiplier_additional; const size_t size = mbflma.size(); @@ -2638,21 +2871,21 @@ jintArray Java_org_rocksdb_Options_maxBytesForLevelMultiplierAdditional( jsize jlen = static_cast(size); jintArray result = env->NewIntArray(jlen); - if(result == nullptr) { - // exception thrown: OutOfMemoryError - delete [] additionals; - return nullptr; + if (result == nullptr) { + // exception thrown: OutOfMemoryError + delete[] additionals; + return nullptr; } env->SetIntArrayRegion(result, 0, jlen, additionals); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(result); - delete [] additionals; - return nullptr; + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(result); + delete[] additionals; + return nullptr; } - delete [] additionals; + delete[] additionals; return result; } @@ -2663,12 +2896,12 @@ jintArray Java_org_rocksdb_Options_maxBytesForLevelMultiplierAdditional( * Signature: (J[I)V */ void Java_org_rocksdb_Options_setMaxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv* env, jobject, jlong jhandle, jintArray jmax_bytes_for_level_multiplier_additional) { jsize len = env->GetArrayLength(jmax_bytes_for_level_multiplier_additional); - jint *additionals = - env->GetIntArrayElements(jmax_bytes_for_level_multiplier_additional, nullptr); - if(additionals == nullptr) { + jint* additionals = env->GetIntArrayElements( + jmax_bytes_for_level_multiplier_additional, nullptr); + if (additionals == nullptr) { // exception thrown: OutOfMemoryError return; } @@ -2676,11 +2909,12 @@ void Java_org_rocksdb_Options_setMaxBytesForLevelMultiplierAdditional( auto* opt = reinterpret_cast(jhandle); opt->max_bytes_for_level_multiplier_additional.clear(); for (jsize i = 0; i < len; i++) { - opt->max_bytes_for_level_multiplier_additional.push_back(static_cast(additionals[i])); + opt->max_bytes_for_level_multiplier_additional.push_back( + static_cast(additionals[i])); } env->ReleaseIntArrayElements(jmax_bytes_for_level_multiplier_additional, - additionals, JNI_ABORT); + additionals, JNI_ABORT); } /* @@ -2689,9 +2923,8 @@ void Java_org_rocksdb_Options_setMaxBytesForLevelMultiplierAdditional( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_paranoidFileChecks( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->paranoid_file_checks; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->paranoid_file_checks; } /* @@ -2700,10 +2933,9 @@ jboolean Java_org_rocksdb_Options_paranoidFileChecks( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setParanoidFileChecks( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jparanoid_file_checks) { - reinterpret_cast( - jhandle)->paranoid_file_checks = - static_cast(jparanoid_file_checks); + JNIEnv*, jobject, jlong jhandle, jboolean jparanoid_file_checks) { + reinterpret_cast(jhandle)->paranoid_file_checks = + static_cast(jparanoid_file_checks); } /* @@ -2712,11 +2944,11 @@ void Java_org_rocksdb_Options_setParanoidFileChecks( * Signature: (JB)V */ void Java_org_rocksdb_Options_setCompactionPriority( - JNIEnv* env, jobject jobj, jlong jhandle, - jbyte jcompaction_priority_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jcompaction_priority_value) { auto* opts = reinterpret_cast(jhandle); opts->compaction_pri = - rocksdb::CompactionPriorityJni::toCppCompactionPriority(jcompaction_priority_value); + rocksdb::CompactionPriorityJni::toCppCompactionPriority( + jcompaction_priority_value); } /* @@ -2725,7 +2957,7 @@ void Java_org_rocksdb_Options_setCompactionPriority( * Signature: (J)B */ jbyte Java_org_rocksdb_Options_compactionPriority( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opts = reinterpret_cast(jhandle); return rocksdb::CompactionPriorityJni::toJavaCompactionPriority( opts->compaction_pri); @@ -2737,7 +2969,7 @@ jbyte Java_org_rocksdb_Options_compactionPriority( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setReportBgIoStats( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jreport_bg_io_stats) { + JNIEnv*, jobject, jlong jhandle, jboolean jreport_bg_io_stats) { auto* opts = reinterpret_cast(jhandle); opts->report_bg_io_stats = static_cast(jreport_bg_io_stats); } @@ -2748,23 +2980,44 @@ void Java_org_rocksdb_Options_setReportBgIoStats( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_reportBgIoStats( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opts = reinterpret_cast(jhandle); return static_cast(opts->report_bg_io_stats); } +/* + * Class: org_rocksdb_Options + * Method: setTtl + * Signature: (JJ)V + */ +void Java_org_rocksdb_Options_setTtl( + JNIEnv*, jobject, jlong jhandle, jlong jttl) { + auto* opts = reinterpret_cast(jhandle); + opts->ttl = static_cast(jttl); +} + +/* + * Class: org_rocksdb_Options + * Method: ttl + * Signature: (J)J + */ +jlong Java_org_rocksdb_Options_ttl( + JNIEnv*, jobject, jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return static_cast(opts->ttl); +} + /* * Class: org_rocksdb_Options * Method: setCompactionOptionsUniversal * Signature: (JJ)V */ void Java_org_rocksdb_Options_setCompactionOptionsUniversal( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jlong jcompaction_options_universal_handle) { auto* opts = reinterpret_cast(jhandle); - auto* opts_uni = - reinterpret_cast( - jcompaction_options_universal_handle); + auto* opts_uni = reinterpret_cast( + jcompaction_options_universal_handle); opts->compaction_options_universal = *opts_uni; } @@ -2774,11 +3027,10 @@ void Java_org_rocksdb_Options_setCompactionOptionsUniversal( * Signature: (JJ)V */ void Java_org_rocksdb_Options_setCompactionOptionsFIFO( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jcompaction_options_fifo_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jcompaction_options_fifo_handle) { auto* opts = reinterpret_cast(jhandle); - auto* opts_fifo = - reinterpret_cast( - jcompaction_options_fifo_handle); + auto* opts_fifo = reinterpret_cast( + jcompaction_options_fifo_handle); opts->compaction_options_fifo = *opts_fifo; } @@ -2788,8 +3040,7 @@ void Java_org_rocksdb_Options_setCompactionOptionsFIFO( * Signature: (JZ)V */ void Java_org_rocksdb_Options_setForceConsistencyChecks( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jforce_consistency_checks) { + JNIEnv*, jobject, jlong jhandle, jboolean jforce_consistency_checks) { auto* opts = reinterpret_cast(jhandle); opts->force_consistency_checks = static_cast(jforce_consistency_checks); } @@ -2800,7 +3051,7 @@ void Java_org_rocksdb_Options_setForceConsistencyChecks( * Signature: (J)Z */ jboolean Java_org_rocksdb_Options_forceConsistencyChecks( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opts = reinterpret_cast(jhandle); return static_cast(opts->force_consistency_checks); } @@ -2814,20 +3065,44 @@ jboolean Java_org_rocksdb_Options_forceConsistencyChecks( * Signature: ()J */ jlong Java_org_rocksdb_ColumnFamilyOptions_newColumnFamilyOptions( - JNIEnv* env, jclass jcls) { + JNIEnv*, jclass) { auto* op = new rocksdb::ColumnFamilyOptions(); return reinterpret_cast(op); } /* * Class: org_rocksdb_ColumnFamilyOptions - * Method: getColumnFamilyOptionsFromProps + * Method: copyColumnFamilyOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_ColumnFamilyOptions_copyColumnFamilyOptions( + JNIEnv*, jclass, jlong jhandle) { + auto new_opt = new rocksdb::ColumnFamilyOptions( + *(reinterpret_cast(jhandle))); + return reinterpret_cast(new_opt); +} + +/* + * Class: org_rocksdb_ColumnFamilyOptions + * Method: newColumnFamilyOptionsFromOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_ColumnFamilyOptions_newColumnFamilyOptionsFromOptions( + JNIEnv*, jclass, jlong joptions_handle) { + auto new_opt = new rocksdb::ColumnFamilyOptions( + *reinterpret_cast(joptions_handle)); + return reinterpret_cast(new_opt); +} + +/* + * Class: org_rocksdb_ColumnFamilyOptions + * Method: getColumnFamilyOptionsFromProps * Signature: (Ljava/util/String;)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_getColumnFamilyOptionsFromProps( - JNIEnv* env, jclass jclazz, jstring jopt_string) { + JNIEnv* env, jclass, jstring jopt_string) { const char* opt_string = env->GetStringUTFChars(jopt_string, nullptr); - if(opt_string == nullptr) { + if (opt_string == nullptr) { // exception thrown: OutOfMemoryError return 0; } @@ -2856,7 +3131,7 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_getColumnFamilyOptionsFromProps( * Signature: (J)V */ void Java_org_rocksdb_ColumnFamilyOptions_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { + JNIEnv*, jobject, jlong handle) { auto* cfo = reinterpret_cast(handle); assert(cfo != nullptr); delete cfo; @@ -2868,9 +3143,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_disposeInternal( * Signature: (J)V */ void Java_org_rocksdb_ColumnFamilyOptions_optimizeForSmallDb( - JNIEnv* env, jobject jobj, jlong jhandle) { - reinterpret_cast(jhandle)-> - OptimizeForSmallDb(); + JNIEnv*, jobject, jlong jhandle) { + reinterpret_cast(jhandle) + ->OptimizeForSmallDb(); } /* @@ -2879,10 +3154,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_optimizeForSmallDb( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_optimizeForPointLookup( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong block_cache_size_mb) { - reinterpret_cast(jhandle)-> - OptimizeForPointLookup(block_cache_size_mb); + JNIEnv*, jobject, jlong jhandle, jlong block_cache_size_mb) { + reinterpret_cast(jhandle) + ->OptimizeForPointLookup(block_cache_size_mb); } /* @@ -2891,10 +3165,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_optimizeForPointLookup( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_optimizeLevelStyleCompaction( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong memtable_memory_budget) { - reinterpret_cast(jhandle)-> - OptimizeLevelStyleCompaction(memtable_memory_budget); + JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { + reinterpret_cast(jhandle) + ->OptimizeLevelStyleCompaction(memtable_memory_budget); } /* @@ -2903,10 +3176,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_optimizeLevelStyleCompaction( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_optimizeUniversalStyleCompaction( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong memtable_memory_budget) { - reinterpret_cast(jhandle)-> - OptimizeUniversalStyleCompaction(memtable_memory_budget); + JNIEnv*, jobject, jlong jhandle, jlong memtable_memory_budget) { + reinterpret_cast(jhandle) + ->OptimizeUniversalStyleCompaction(memtable_memory_budget); } /* @@ -2915,7 +3187,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_optimizeUniversalStyleCompaction( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setComparatorHandle__JI( - JNIEnv* env, jobject jobj, jlong jhandle, jint builtinComparator) { + JNIEnv*, jobject, jlong jhandle, jint builtinComparator) { switch (builtinComparator) { case 1: reinterpret_cast(jhandle)->comparator = @@ -2931,12 +3203,32 @@ void Java_org_rocksdb_ColumnFamilyOptions_setComparatorHandle__JI( /* * Class: org_rocksdb_ColumnFamilyOptions * Method: setComparatorHandle - * Signature: (JJ)V - */ -void Java_org_rocksdb_ColumnFamilyOptions_setComparatorHandle__JJ( - JNIEnv* env, jobject jobj, jlong jopt_handle, jlong jcomparator_handle) { - reinterpret_cast(jopt_handle)->comparator = - reinterpret_cast(jcomparator_handle); + * Signature: (JJB)V + */ +void Java_org_rocksdb_ColumnFamilyOptions_setComparatorHandle__JJB( + JNIEnv*, jobject, jlong jopt_handle, jlong jcomparator_handle, + jbyte jcomparator_type) { + rocksdb::Comparator* comparator = nullptr; + switch (jcomparator_type) { + // JAVA_COMPARATOR + case 0x0: + comparator = + reinterpret_cast(jcomparator_handle); + break; + + // JAVA_DIRECT_COMPARATOR + case 0x1: + comparator = reinterpret_cast( + jcomparator_handle); + break; + + // JAVA_NATIVE_COMPARATOR_WRAPPER + case 0x2: + comparator = reinterpret_cast(jcomparator_handle); + break; + } + auto* opt = reinterpret_cast(jopt_handle); + opt->comparator = comparator; } /* @@ -2945,10 +3237,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_setComparatorHandle__JJ( * Signature: (JJjava/lang/String)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMergeOperatorName( - JNIEnv* env, jobject jobj, jlong jhandle, jstring jop_name) { + JNIEnv* env, jobject, jlong jhandle, jstring jop_name) { auto* options = reinterpret_cast(jhandle); const char* op_name = env->GetStringUTFChars(jop_name, nullptr); - if(op_name == nullptr) { + if (op_name == nullptr) { // exception thrown: OutOfMemoryError return; } @@ -2964,10 +3256,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMergeOperatorName( * Signature: (JJjava/lang/String)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMergeOperator( - JNIEnv* env, jobject jobj, jlong jhandle, jlong mergeOperatorHandle) { + JNIEnv*, jobject, jlong jhandle, jlong mergeOperatorHandle) { reinterpret_cast(jhandle)->merge_operator = - *(reinterpret_cast*> - (mergeOperatorHandle)); + *(reinterpret_cast*>( + mergeOperatorHandle)); } /* @@ -2976,11 +3268,25 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMergeOperator( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterHandle( - JNIEnv* env, jobject jobj, jlong jopt_handle, - jlong jcompactionfilter_handle) { - reinterpret_cast(jopt_handle)-> - compaction_filter = reinterpret_cast - (jcompactionfilter_handle); + JNIEnv*, jobject, jlong jopt_handle, jlong jcompactionfilter_handle) { + reinterpret_cast(jopt_handle) + ->compaction_filter = + reinterpret_cast(jcompactionfilter_handle); +} + +/* + * Class: org_rocksdb_ColumnFamilyOptions + * Method: setCompactionFilterFactoryHandle + * Signature: (JJ)V + */ +void Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterFactoryHandle( + JNIEnv*, jobject, jlong jopt_handle, + jlong jcompactionfilterfactory_handle) { + auto* cff_factory = + reinterpret_cast*>( + jcompactionfilterfactory_handle); + reinterpret_cast(jopt_handle) + ->compaction_filter_factory = *cff_factory; } /* @@ -2989,11 +3295,11 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionFilterHandle( * Signature: (JJ)I */ void Java_org_rocksdb_ColumnFamilyOptions_setWriteBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jwrite_buffer_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(jwrite_buffer_size); + JNIEnv* env, jobject, jlong jhandle, jlong jwrite_buffer_size) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(jwrite_buffer_size); if (s.ok()) { - reinterpret_cast(jhandle)-> - write_buffer_size = jwrite_buffer_size; + reinterpret_cast(jhandle) + ->write_buffer_size = jwrite_buffer_size; } else { rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); } @@ -3005,9 +3311,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setWriteBufferSize( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_writeBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - write_buffer_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->write_buffer_size; } /* @@ -3016,9 +3322,9 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_writeBufferSize( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxWriteBufferNumber( - JNIEnv* env, jobject jobj, jlong jhandle, jint jmax_write_buffer_number) { - reinterpret_cast(jhandle)-> - max_write_buffer_number = jmax_write_buffer_number; + JNIEnv*, jobject, jlong jhandle, jint jmax_write_buffer_number) { + reinterpret_cast(jhandle) + ->max_write_buffer_number = jmax_write_buffer_number; } /* @@ -3027,9 +3333,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxWriteBufferNumber( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_maxWriteBufferNumber( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - max_write_buffer_number; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_write_buffer_number; } /* @@ -3037,10 +3343,10 @@ jint Java_org_rocksdb_ColumnFamilyOptions_maxWriteBufferNumber( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMemTableFactory( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jfactory_handle) { - reinterpret_cast(jhandle)-> - memtable_factory.reset( - reinterpret_cast(jfactory_handle)); + JNIEnv*, jobject, jlong jhandle, jlong jfactory_handle) { + reinterpret_cast(jhandle) + ->memtable_factory.reset( + reinterpret_cast(jfactory_handle)); } /* @@ -3049,7 +3355,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMemTableFactory( * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_ColumnFamilyOptions_memTableFactoryName( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); rocksdb::MemTableRepFactory* tf = opt->memtable_factory.get(); @@ -3070,10 +3376,10 @@ jstring Java_org_rocksdb_ColumnFamilyOptions_memTableFactoryName( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_useFixedLengthPrefixExtractor( - JNIEnv* env, jobject jobj, jlong jhandle, jint jprefix_length) { - reinterpret_cast(jhandle)-> - prefix_extractor.reset(rocksdb::NewFixedPrefixTransform( - static_cast(jprefix_length))); + JNIEnv*, jobject, jlong jhandle, jint jprefix_length) { + reinterpret_cast(jhandle) + ->prefix_extractor.reset( + rocksdb::NewFixedPrefixTransform(static_cast(jprefix_length))); } /* @@ -3081,10 +3387,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_useFixedLengthPrefixExtractor( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_useCappedPrefixExtractor( - JNIEnv* env, jobject jobj, jlong jhandle, jint jprefix_length) { - reinterpret_cast(jhandle)-> - prefix_extractor.reset(rocksdb::NewCappedPrefixTransform( - static_cast(jprefix_length))); + JNIEnv*, jobject, jlong jhandle, jint jprefix_length) { + reinterpret_cast(jhandle) + ->prefix_extractor.reset( + rocksdb::NewCappedPrefixTransform(static_cast(jprefix_length))); } /* @@ -3092,10 +3398,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_useCappedPrefixExtractor( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setTableFactory( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jfactory_handle) { - reinterpret_cast(jhandle)-> - table_factory.reset(reinterpret_cast( - jfactory_handle)); + JNIEnv*, jobject, jlong jhandle, jlong jfactory_handle) { + reinterpret_cast(jhandle)->table_factory.reset( + reinterpret_cast(jfactory_handle)); } /* @@ -3103,7 +3408,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setTableFactory( * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_ColumnFamilyOptions_tableFactoryName( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); rocksdb::TableFactory* tf = opt->table_factory.get(); @@ -3114,16 +3419,15 @@ jstring Java_org_rocksdb_ColumnFamilyOptions_tableFactoryName( return env->NewStringUTF(tf->Name()); } - /* * Class: org_rocksdb_ColumnFamilyOptions * Method: minWriteBufferNumberToMerge * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_minWriteBufferNumberToMerge( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->min_write_buffer_number_to_merge; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->min_write_buffer_number_to_merge; } /* @@ -3132,11 +3436,10 @@ jint Java_org_rocksdb_ColumnFamilyOptions_minWriteBufferNumberToMerge( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMinWriteBufferNumberToMerge( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jmin_write_buffer_number_to_merge) { - reinterpret_cast( - jhandle)->min_write_buffer_number_to_merge = - static_cast(jmin_write_buffer_number_to_merge); + JNIEnv*, jobject, jlong jhandle, jint jmin_write_buffer_number_to_merge) { + reinterpret_cast(jhandle) + ->min_write_buffer_number_to_merge = + static_cast(jmin_write_buffer_number_to_merge); } /* @@ -3145,7 +3448,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMinWriteBufferNumberToMerge( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_maxWriteBufferNumberToMaintain( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->max_write_buffer_number_to_maintain; } @@ -3156,7 +3459,7 @@ jint Java_org_rocksdb_ColumnFamilyOptions_maxWriteBufferNumberToMaintain( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxWriteBufferNumberToMaintain( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jint jmax_write_buffer_number_to_maintain) { reinterpret_cast(jhandle) ->max_write_buffer_number_to_maintain = @@ -3169,7 +3472,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxWriteBufferNumberToMaintain( * Signature: (JB)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompressionType( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jcompression_type_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { auto* cf_opts = reinterpret_cast(jhandle); cf_opts->compression = rocksdb::CompressionTypeJni::toCppCompressionType( jcompression_type_value); @@ -3181,7 +3484,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompressionType( * Signature: (J)B */ jbyte Java_org_rocksdb_ColumnFamilyOptions_compressionType( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* cf_opts = reinterpret_cast(jhandle); return rocksdb::CompressionTypeJni::toJavaCompressionType( cf_opts->compression); @@ -3193,14 +3496,13 @@ jbyte Java_org_rocksdb_ColumnFamilyOptions_compressionType( * Signature: (J[B)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompressionPerLevel( - JNIEnv* env, jobject jobj, jlong jhandle, - jbyteArray jcompressionLevels) { + JNIEnv* env, jobject, jlong jhandle, jbyteArray jcompressionLevels) { auto* options = reinterpret_cast(jhandle); auto uptr_compression_levels = rocksdb_compression_vector_helper(env, jcompressionLevels); - if(!uptr_compression_levels) { - // exception occurred - return; + if (!uptr_compression_levels) { + // exception occurred + return; } options->compression_per_level = *(uptr_compression_levels.get()); } @@ -3211,10 +3513,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompressionPerLevel( * Signature: (J)[B */ jbyteArray Java_org_rocksdb_ColumnFamilyOptions_compressionPerLevel( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { auto* cf_options = reinterpret_cast(jhandle); return rocksdb_compression_list_helper(env, - cf_options->compression_per_level); + cf_options->compression_per_level); } /* @@ -3223,7 +3525,7 @@ jbyteArray Java_org_rocksdb_ColumnFamilyOptions_compressionPerLevel( * Signature: (JB)V */ void Java_org_rocksdb_ColumnFamilyOptions_setBottommostCompressionType( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jcompression_type_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jcompression_type_value) { auto* cf_options = reinterpret_cast(jhandle); cf_options->bottommost_compression = rocksdb::CompressionTypeJni::toCppCompressionType( @@ -3236,11 +3538,25 @@ void Java_org_rocksdb_ColumnFamilyOptions_setBottommostCompressionType( * Signature: (J)B */ jbyte Java_org_rocksdb_ColumnFamilyOptions_bottommostCompressionType( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* cf_options = reinterpret_cast(jhandle); return rocksdb::CompressionTypeJni::toJavaCompressionType( cf_options->bottommost_compression); } +/* + * Class: org_rocksdb_ColumnFamilyOptions + * Method: setBottommostCompressionOptions + * Signature: (JJ)V + */ +void Java_org_rocksdb_ColumnFamilyOptions_setBottommostCompressionOptions( + JNIEnv*, jobject, jlong jhandle, + jlong jbottommost_compression_options_handle) { + auto* cf_options = reinterpret_cast(jhandle); + auto* bottommost_compression_options = + reinterpret_cast( + jbottommost_compression_options_handle); + cf_options->bottommost_compression_opts = *bottommost_compression_options; +} /* * Class: org_rocksdb_ColumnFamilyOptions @@ -3248,11 +3564,10 @@ jbyte Java_org_rocksdb_ColumnFamilyOptions_bottommostCompressionType( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompressionOptions( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jcompression_options_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jcompression_options_handle) { auto* cf_options = reinterpret_cast(jhandle); - auto* compression_options = - reinterpret_cast(jcompression_options_handle); + auto* compression_options = reinterpret_cast( + jcompression_options_handle); cf_options->compression_opts = *compression_options; } @@ -3262,9 +3577,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompressionOptions( * Signature: (JB)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionStyle( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte compaction_style) { - reinterpret_cast(jhandle)->compaction_style = - static_cast(compaction_style); + JNIEnv*, jobject, jlong jhandle, jbyte jcompaction_style) { + auto* cf_options = reinterpret_cast(jhandle); + cf_options->compaction_style = + rocksdb::CompactionStyleJni::toCppCompactionStyle(jcompaction_style); } /* @@ -3273,9 +3589,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionStyle( * Signature: (J)B */ jbyte Java_org_rocksdb_ColumnFamilyOptions_compactionStyle( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast - (jhandle)->compaction_style; + JNIEnv*, jobject, jlong jhandle) { + auto* cf_options = reinterpret_cast(jhandle); + return rocksdb::CompactionStyleJni::toJavaCompactionStyle( + cf_options->compaction_style); } /* @@ -3284,9 +3601,10 @@ jbyte Java_org_rocksdb_ColumnFamilyOptions_compactionStyle( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxTableFilesSizeFIFO( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jmax_table_files_size) { - reinterpret_cast(jhandle)->compaction_options_fifo.max_table_files_size = - static_cast(jmax_table_files_size); + JNIEnv*, jobject, jlong jhandle, jlong jmax_table_files_size) { + reinterpret_cast(jhandle) + ->compaction_options_fifo.max_table_files_size = + static_cast(jmax_table_files_size); } /* @@ -3295,8 +3613,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxTableFilesSizeFIFO( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_maxTableFilesSizeFIFO( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)->compaction_options_fifo.max_table_files_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->compaction_options_fifo.max_table_files_size; } /* @@ -3305,7 +3624,7 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_maxTableFilesSizeFIFO( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_numLevels( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->num_levels; } @@ -3315,7 +3634,7 @@ jint Java_org_rocksdb_ColumnFamilyOptions_numLevels( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setNumLevels( - JNIEnv* env, jobject jobj, jlong jhandle, jint jnum_levels) { + JNIEnv*, jobject, jlong jhandle, jint jnum_levels) { reinterpret_cast(jhandle)->num_levels = static_cast(jnum_levels); } @@ -3326,9 +3645,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setNumLevels( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroFileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger; } /* @@ -3337,11 +3656,11 @@ jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroFileNumCompactionTrigger( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroFileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); + reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger = + static_cast(jlevel0_file_num_compaction_trigger); } /* @@ -3350,9 +3669,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroFileNumCompactionTrigger( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroSlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_slowdown_writes_trigger; } /* @@ -3361,11 +3680,10 @@ jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroSlowdownWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroSlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); + JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { + reinterpret_cast(jhandle) + ->level0_slowdown_writes_trigger = + static_cast(jlevel0_slowdown_writes_trigger); } /* @@ -3374,9 +3692,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroSlowdownWritesTrigger( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroStopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_stop_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_stop_writes_trigger; } /* @@ -3385,11 +3703,10 @@ jint Java_org_rocksdb_ColumnFamilyOptions_levelZeroStopWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroStopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_stop_writes_trigger) { - reinterpret_cast(jhandle)-> - level0_stop_writes_trigger = static_cast( - jlevel0_stop_writes_trigger); + JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { + reinterpret_cast(jhandle) + ->level0_stop_writes_trigger = + static_cast(jlevel0_stop_writes_trigger); } /* @@ -3398,9 +3715,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setLevelZeroStopWritesTrigger( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_targetFileSizeBase( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - target_file_size_base; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->target_file_size_base; } /* @@ -3409,10 +3726,9 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_targetFileSizeBase( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setTargetFileSizeBase( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jtarget_file_size_base) { - reinterpret_cast(jhandle)-> - target_file_size_base = static_cast(jtarget_file_size_base); + JNIEnv*, jobject, jlong jhandle, jlong jtarget_file_size_base) { + reinterpret_cast(jhandle) + ->target_file_size_base = static_cast(jtarget_file_size_base); } /* @@ -3421,9 +3737,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setTargetFileSizeBase( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_targetFileSizeMultiplier( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->target_file_size_multiplier; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->target_file_size_multiplier; } /* @@ -3432,11 +3748,10 @@ jint Java_org_rocksdb_ColumnFamilyOptions_targetFileSizeMultiplier( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setTargetFileSizeMultiplier( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jtarget_file_size_multiplier) { - reinterpret_cast( - jhandle)->target_file_size_multiplier = - static_cast(jtarget_file_size_multiplier); + JNIEnv*, jobject, jlong jhandle, jint jtarget_file_size_multiplier) { + reinterpret_cast(jhandle) + ->target_file_size_multiplier = + static_cast(jtarget_file_size_multiplier); } /* @@ -3445,9 +3760,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setTargetFileSizeMultiplier( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelBase( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->max_bytes_for_level_base; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_bytes_for_level_base; } /* @@ -3456,11 +3771,10 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelBase( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelBase( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmax_bytes_for_level_base) { - reinterpret_cast( - jhandle)->max_bytes_for_level_base = - static_cast(jmax_bytes_for_level_base); + JNIEnv*, jobject, jlong jhandle, jlong jmax_bytes_for_level_base) { + reinterpret_cast(jhandle) + ->max_bytes_for_level_base = + static_cast(jmax_bytes_for_level_base); } /* @@ -3469,9 +3783,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelBase( * Signature: (J)Z */ jboolean Java_org_rocksdb_ColumnFamilyOptions_levelCompactionDynamicLevelBytes( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level_compaction_dynamic_level_bytes; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level_compaction_dynamic_level_bytes; } /* @@ -3480,11 +3794,9 @@ jboolean Java_org_rocksdb_ColumnFamilyOptions_levelCompactionDynamicLevelBytes( * Signature: (JZ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setLevelCompactionDynamicLevelBytes( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jenable_dynamic_level_bytes) { - reinterpret_cast( - jhandle)->level_compaction_dynamic_level_bytes = - (jenable_dynamic_level_bytes); + JNIEnv*, jobject, jlong jhandle, jboolean jenable_dynamic_level_bytes) { + reinterpret_cast(jhandle) + ->level_compaction_dynamic_level_bytes = (jenable_dynamic_level_bytes); } /* @@ -3493,9 +3805,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setLevelCompactionDynamicLevelBytes( * Signature: (J)D */ jdouble Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelMultiplier( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->max_bytes_for_level_multiplier; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_bytes_for_level_multiplier; } /* @@ -3504,8 +3816,7 @@ jdouble Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelMultiplier( * Signature: (JD)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelMultiplier( - JNIEnv* env, jobject jobj, jlong jhandle, - jdouble jmax_bytes_for_level_multiplier) { + JNIEnv*, jobject, jlong jhandle, jdouble jmax_bytes_for_level_multiplier) { reinterpret_cast(jhandle) ->max_bytes_for_level_multiplier = static_cast(jmax_bytes_for_level_multiplier); @@ -3516,9 +3827,8 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelMultiplier( * Method: maxCompactionBytes * Signature: (J)I */ -jlong Java_org_rocksdb_ColumnFamilyOptions_maxCompactionBytes(JNIEnv* env, - jobject jobj, - jlong jhandle) { +jlong Java_org_rocksdb_ColumnFamilyOptions_maxCompactionBytes( + JNIEnv*, jobject, jlong jhandle) { return static_cast( reinterpret_cast(jhandle) ->max_compaction_bytes); @@ -3530,7 +3840,7 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_maxCompactionBytes(JNIEnv* env, * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxCompactionBytes( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jmax_compaction_bytes) { + JNIEnv*, jobject, jlong jhandle, jlong jmax_compaction_bytes) { reinterpret_cast(jhandle) ->max_compaction_bytes = static_cast(jmax_compaction_bytes); } @@ -3541,9 +3851,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxCompactionBytes( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_arenaBlockSize( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - arena_block_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->arena_block_size; } /* @@ -3552,11 +3862,11 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_arenaBlockSize( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setArenaBlockSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jarena_block_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(jarena_block_size); + JNIEnv* env, jobject, jlong jhandle, jlong jarena_block_size) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(jarena_block_size); if (s.ok()) { - reinterpret_cast(jhandle)-> - arena_block_size = jarena_block_size; + reinterpret_cast(jhandle)->arena_block_size = + jarena_block_size; } else { rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); } @@ -3568,9 +3878,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setArenaBlockSize( * Signature: (J)Z */ jboolean Java_org_rocksdb_ColumnFamilyOptions_disableAutoCompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->disable_auto_compactions; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->disable_auto_compactions; } /* @@ -3579,11 +3889,9 @@ jboolean Java_org_rocksdb_ColumnFamilyOptions_disableAutoCompactions( * Signature: (JZ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setDisableAutoCompactions( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jdisable_auto_compactions) { - reinterpret_cast( - jhandle)->disable_auto_compactions = - static_cast(jdisable_auto_compactions); + JNIEnv*, jobject, jlong jhandle, jboolean jdisable_auto_compactions) { + reinterpret_cast(jhandle) + ->disable_auto_compactions = static_cast(jdisable_auto_compactions); } /* @@ -3592,9 +3900,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setDisableAutoCompactions( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_maxSequentialSkipInIterations( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->max_sequential_skip_in_iterations; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_sequential_skip_in_iterations; } /* @@ -3603,11 +3911,11 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_maxSequentialSkipInIterations( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxSequentialSkipInIterations( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jlong jmax_sequential_skip_in_iterations) { - reinterpret_cast( - jhandle)->max_sequential_skip_in_iterations = - static_cast(jmax_sequential_skip_in_iterations); + reinterpret_cast(jhandle) + ->max_sequential_skip_in_iterations = + static_cast(jmax_sequential_skip_in_iterations); } /* @@ -3616,9 +3924,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxSequentialSkipInIterations( * Signature: (J)Z */ jboolean Java_org_rocksdb_ColumnFamilyOptions_inplaceUpdateSupport( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->inplace_update_support; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->inplace_update_support; } /* @@ -3627,11 +3935,9 @@ jboolean Java_org_rocksdb_ColumnFamilyOptions_inplaceUpdateSupport( * Signature: (JZ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setInplaceUpdateSupport( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jinplace_update_support) { - reinterpret_cast( - jhandle)->inplace_update_support = - static_cast(jinplace_update_support); + JNIEnv*, jobject, jlong jhandle, jboolean jinplace_update_support) { + reinterpret_cast(jhandle) + ->inplace_update_support = static_cast(jinplace_update_support); } /* @@ -3640,9 +3946,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setInplaceUpdateSupport( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_inplaceUpdateNumLocks( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->inplace_update_num_locks; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->inplace_update_num_locks; } /* @@ -3651,13 +3957,12 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_inplaceUpdateNumLocks( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setInplaceUpdateNumLocks( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jinplace_update_num_locks) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - jinplace_update_num_locks); + JNIEnv* env, jobject, jlong jhandle, jlong jinplace_update_num_locks) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(jinplace_update_num_locks); if (s.ok()) { - reinterpret_cast(jhandle)-> - inplace_update_num_locks = jinplace_update_num_locks; + reinterpret_cast(jhandle) + ->inplace_update_num_locks = jinplace_update_num_locks; } else { rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); } @@ -3669,7 +3974,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setInplaceUpdateNumLocks( * Signature: (J)I */ jdouble Java_org_rocksdb_ColumnFamilyOptions_memtablePrefixBloomSizeRatio( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->memtable_prefix_bloom_size_ratio; } @@ -3680,7 +3985,7 @@ jdouble Java_org_rocksdb_ColumnFamilyOptions_memtablePrefixBloomSizeRatio( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMemtablePrefixBloomSizeRatio( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jdouble jmemtable_prefix_bloom_size_ratio) { reinterpret_cast(jhandle) ->memtable_prefix_bloom_size_ratio = @@ -3693,9 +3998,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMemtablePrefixBloomSizeRatio( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_bloomLocality( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - bloom_locality; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->bloom_locality; } /* @@ -3704,7 +4009,7 @@ jint Java_org_rocksdb_ColumnFamilyOptions_bloomLocality( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setBloomLocality( - JNIEnv* env, jobject jobj, jlong jhandle, jint jbloom_locality) { + JNIEnv*, jobject, jlong jhandle, jint jbloom_locality) { reinterpret_cast(jhandle)->bloom_locality = static_cast(jbloom_locality); } @@ -3715,9 +4020,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setBloomLocality( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_maxSuccessiveMerges( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - max_successive_merges; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_successive_merges; } /* @@ -3726,13 +4031,12 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_maxSuccessiveMerges( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxSuccessiveMerges( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmax_successive_merges) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - jmax_successive_merges); + JNIEnv* env, jobject, jlong jhandle, jlong jmax_successive_merges) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(jmax_successive_merges); if (s.ok()) { - reinterpret_cast(jhandle)-> - max_successive_merges = jmax_successive_merges; + reinterpret_cast(jhandle) + ->max_successive_merges = jmax_successive_merges; } else { rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); } @@ -3744,9 +4048,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxSuccessiveMerges( * Signature: (J)Z */ jboolean Java_org_rocksdb_ColumnFamilyOptions_optimizeFiltersForHits( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->optimize_filters_for_hits; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->optimize_filters_for_hits; } /* @@ -3755,11 +4059,10 @@ jboolean Java_org_rocksdb_ColumnFamilyOptions_optimizeFiltersForHits( * Signature: (JZ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setOptimizeFiltersForHits( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean joptimize_filters_for_hits) { - reinterpret_cast( - jhandle)->optimize_filters_for_hits = - static_cast(joptimize_filters_for_hits); + JNIEnv*, jobject, jlong jhandle, jboolean joptimize_filters_for_hits) { + reinterpret_cast(jhandle) + ->optimize_filters_for_hits = + static_cast(joptimize_filters_for_hits); } /* @@ -3768,9 +4071,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setOptimizeFiltersForHits( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_memtableHugePageSize( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->memtable_huge_page_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->memtable_huge_page_size; } /* @@ -3779,15 +4082,12 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_memtableHugePageSize( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMemtableHugePageSize( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmemtable_huge_page_size) { - - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - jmemtable_huge_page_size); + JNIEnv* env, jobject, jlong jhandle, jlong jmemtable_huge_page_size) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(jmemtable_huge_page_size); if (s.ok()) { - reinterpret_cast( - jhandle)->memtable_huge_page_size = - jmemtable_huge_page_size; + reinterpret_cast(jhandle) + ->memtable_huge_page_size = jmemtable_huge_page_size; } else { rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); } @@ -3799,9 +4099,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMemtableHugePageSize( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_softPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->soft_pending_compaction_bytes_limit; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->soft_pending_compaction_bytes_limit; } /* @@ -3810,10 +4110,11 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_softPendingCompactionBytesLimit( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setSoftPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jsoft_pending_compaction_bytes_limit) { - reinterpret_cast( - jhandle)->soft_pending_compaction_bytes_limit = - static_cast(jsoft_pending_compaction_bytes_limit); + JNIEnv*, jobject, jlong jhandle, + jlong jsoft_pending_compaction_bytes_limit) { + reinterpret_cast(jhandle) + ->soft_pending_compaction_bytes_limit = + static_cast(jsoft_pending_compaction_bytes_limit); } /* @@ -3822,9 +4123,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setSoftPendingCompactionBytesLimit( * Signature: (J)J */ jlong Java_org_rocksdb_ColumnFamilyOptions_hardPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->hard_pending_compaction_bytes_limit; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->hard_pending_compaction_bytes_limit; } /* @@ -3833,10 +4134,11 @@ jlong Java_org_rocksdb_ColumnFamilyOptions_hardPendingCompactionBytesLimit( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setHardPendingCompactionBytesLimit( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jhard_pending_compaction_bytes_limit) { - reinterpret_cast( - jhandle)->hard_pending_compaction_bytes_limit = - static_cast(jhard_pending_compaction_bytes_limit); + JNIEnv*, jobject, jlong jhandle, + jlong jhard_pending_compaction_bytes_limit) { + reinterpret_cast(jhandle) + ->hard_pending_compaction_bytes_limit = + static_cast(jhard_pending_compaction_bytes_limit); } /* @@ -3845,9 +4147,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setHardPendingCompactionBytesLimit( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_level0FileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger; } /* @@ -3856,11 +4158,11 @@ jint Java_org_rocksdb_ColumnFamilyOptions_level0FileNumCompactionTrigger( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setLevel0FileNumCompactionTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jint jlevel0_file_num_compaction_trigger) { - reinterpret_cast( - jhandle)->level0_file_num_compaction_trigger = - static_cast(jlevel0_file_num_compaction_trigger); + reinterpret_cast(jhandle) + ->level0_file_num_compaction_trigger = + static_cast(jlevel0_file_num_compaction_trigger); } /* @@ -3869,9 +4171,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setLevel0FileNumCompactionTrigger( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_level0SlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_slowdown_writes_trigger; } /* @@ -3880,11 +4182,10 @@ jint Java_org_rocksdb_ColumnFamilyOptions_level0SlowdownWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setLevel0SlowdownWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_slowdown_writes_trigger) { - reinterpret_cast( - jhandle)->level0_slowdown_writes_trigger = - static_cast(jlevel0_slowdown_writes_trigger); + JNIEnv*, jobject, jlong jhandle, jint jlevel0_slowdown_writes_trigger) { + reinterpret_cast(jhandle) + ->level0_slowdown_writes_trigger = + static_cast(jlevel0_slowdown_writes_trigger); } /* @@ -3893,9 +4194,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setLevel0SlowdownWritesTrigger( * Signature: (J)I */ jint Java_org_rocksdb_ColumnFamilyOptions_level0StopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->level0_stop_writes_trigger; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->level0_stop_writes_trigger; } /* @@ -3904,11 +4205,10 @@ jint Java_org_rocksdb_ColumnFamilyOptions_level0StopWritesTrigger( * Signature: (JI)V */ void Java_org_rocksdb_ColumnFamilyOptions_setLevel0StopWritesTrigger( - JNIEnv* env, jobject jobj, jlong jhandle, - jint jlevel0_stop_writes_trigger) { - reinterpret_cast( - jhandle)->level0_stop_writes_trigger = - static_cast(jlevel0_stop_writes_trigger); + JNIEnv*, jobject, jlong jhandle, jint jlevel0_stop_writes_trigger) { + reinterpret_cast(jhandle) + ->level0_stop_writes_trigger = + static_cast(jlevel0_stop_writes_trigger); } /* @@ -3917,9 +4217,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setLevel0StopWritesTrigger( * Signature: (J)[I */ jintArray Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject jobj, jlong jhandle) { - auto mbflma = reinterpret_cast( - jhandle)->max_bytes_for_level_multiplier_additional; + JNIEnv* env, jobject, jlong jhandle) { + auto mbflma = reinterpret_cast(jhandle) + ->max_bytes_for_level_multiplier_additional; const size_t size = mbflma.size(); @@ -3930,20 +4230,20 @@ jintArray Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelMultiplierAdditio jsize jlen = static_cast(size); jintArray result = env->NewIntArray(jlen); - if(result == nullptr) { + if (result == nullptr) { // exception thrown: OutOfMemoryError - delete [] additionals; + delete[] additionals; return nullptr; } env->SetIntArrayRegion(result, 0, jlen, additionals); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(result); - delete [] additionals; - return nullptr; + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(result); + delete[] additionals; + return nullptr; } - delete [] additionals; + delete[] additionals; return result; } @@ -3954,12 +4254,12 @@ jintArray Java_org_rocksdb_ColumnFamilyOptions_maxBytesForLevelMultiplierAdditio * Signature: (J[I)V */ void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelMultiplierAdditional( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv* env, jobject, jlong jhandle, jintArray jmax_bytes_for_level_multiplier_additional) { jsize len = env->GetArrayLength(jmax_bytes_for_level_multiplier_additional); - jint *additionals = + jint* additionals = env->GetIntArrayElements(jmax_bytes_for_level_multiplier_additional, 0); - if(additionals == nullptr) { + if (additionals == nullptr) { // exception thrown: OutOfMemoryError return; } @@ -3967,11 +4267,12 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelMultiplierAdditiona auto* cf_opt = reinterpret_cast(jhandle); cf_opt->max_bytes_for_level_multiplier_additional.clear(); for (jsize i = 0; i < len; i++) { - cf_opt->max_bytes_for_level_multiplier_additional.push_back(static_cast(additionals[i])); + cf_opt->max_bytes_for_level_multiplier_additional.push_back( + static_cast(additionals[i])); } env->ReleaseIntArrayElements(jmax_bytes_for_level_multiplier_additional, - additionals, JNI_ABORT); + additionals, JNI_ABORT); } /* @@ -3980,9 +4281,9 @@ void Java_org_rocksdb_ColumnFamilyOptions_setMaxBytesForLevelMultiplierAdditiona * Signature: (J)Z */ jboolean Java_org_rocksdb_ColumnFamilyOptions_paranoidFileChecks( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->paranoid_file_checks; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->paranoid_file_checks; } /* @@ -3991,10 +4292,9 @@ jboolean Java_org_rocksdb_ColumnFamilyOptions_paranoidFileChecks( * Signature: (JZ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setParanoidFileChecks( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jparanoid_file_checks) { - reinterpret_cast( - jhandle)->paranoid_file_checks = - static_cast(jparanoid_file_checks); + JNIEnv*, jobject, jlong jhandle, jboolean jparanoid_file_checks) { + reinterpret_cast(jhandle) + ->paranoid_file_checks = static_cast(jparanoid_file_checks); } /* @@ -4003,11 +4303,11 @@ void Java_org_rocksdb_ColumnFamilyOptions_setParanoidFileChecks( * Signature: (JB)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionPriority( - JNIEnv* env, jobject jobj, jlong jhandle, - jbyte jcompaction_priority_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jcompaction_priority_value) { auto* cf_opts = reinterpret_cast(jhandle); cf_opts->compaction_pri = - rocksdb::CompactionPriorityJni::toCppCompactionPriority(jcompaction_priority_value); + rocksdb::CompactionPriorityJni::toCppCompactionPriority( + jcompaction_priority_value); } /* @@ -4016,7 +4316,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionPriority( * Signature: (J)B */ jbyte Java_org_rocksdb_ColumnFamilyOptions_compactionPriority( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* cf_opts = reinterpret_cast(jhandle); return rocksdb::CompactionPriorityJni::toJavaCompactionPriority( cf_opts->compaction_pri); @@ -4028,7 +4328,7 @@ jbyte Java_org_rocksdb_ColumnFamilyOptions_compactionPriority( * Signature: (JZ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setReportBgIoStats( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jreport_bg_io_stats) { + JNIEnv*, jobject, jlong jhandle, jboolean jreport_bg_io_stats) { auto* cf_opts = reinterpret_cast(jhandle); cf_opts->report_bg_io_stats = static_cast(jreport_bg_io_stats); } @@ -4039,23 +4339,44 @@ void Java_org_rocksdb_ColumnFamilyOptions_setReportBgIoStats( * Signature: (J)Z */ jboolean Java_org_rocksdb_ColumnFamilyOptions_reportBgIoStats( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* cf_opts = reinterpret_cast(jhandle); return static_cast(cf_opts->report_bg_io_stats); } +/* + * Class: org_rocksdb_ColumnFamilyOptions + * Method: setTtl + * Signature: (JJ)V + */ +void Java_org_rocksdb_ColumnFamilyOptions_setTtl( + JNIEnv*, jobject, jlong jhandle, jlong jttl) { + auto* cf_opts = reinterpret_cast(jhandle); + cf_opts->ttl = static_cast(jttl); +} + +/* + * Class: org_rocksdb_ColumnFamilyOptions + * Method: ttl + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_org_rocksdb_ColumnFamilyOptions_ttl( + JNIEnv*, jobject, jlong jhandle) { + auto* cf_opts = reinterpret_cast(jhandle); + return static_cast(cf_opts->ttl); +} + /* * Class: org_rocksdb_ColumnFamilyOptions * Method: setCompactionOptionsUniversal * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionOptionsUniversal( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jlong jcompaction_options_universal_handle) { auto* cf_opts = reinterpret_cast(jhandle); - auto* opts_uni = - reinterpret_cast( - jcompaction_options_universal_handle); + auto* opts_uni = reinterpret_cast( + jcompaction_options_universal_handle); cf_opts->compaction_options_universal = *opts_uni; } @@ -4065,11 +4386,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionOptionsUniversal( * Signature: (JJ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionOptionsFIFO( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jcompaction_options_fifo_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jcompaction_options_fifo_handle) { auto* cf_opts = reinterpret_cast(jhandle); - auto* opts_fifo = - reinterpret_cast( - jcompaction_options_fifo_handle); + auto* opts_fifo = reinterpret_cast( + jcompaction_options_fifo_handle); cf_opts->compaction_options_fifo = *opts_fifo; } @@ -4079,10 +4399,10 @@ void Java_org_rocksdb_ColumnFamilyOptions_setCompactionOptionsFIFO( * Signature: (JZ)V */ void Java_org_rocksdb_ColumnFamilyOptions_setForceConsistencyChecks( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jforce_consistency_checks) { + JNIEnv*, jobject, jlong jhandle, jboolean jforce_consistency_checks) { auto* cf_opts = reinterpret_cast(jhandle); - cf_opts->force_consistency_checks = static_cast(jforce_consistency_checks); + cf_opts->force_consistency_checks = + static_cast(jforce_consistency_checks); } /* @@ -4091,7 +4411,7 @@ void Java_org_rocksdb_ColumnFamilyOptions_setForceConsistencyChecks( * Signature: (J)Z */ jboolean Java_org_rocksdb_ColumnFamilyOptions_forceConsistencyChecks( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* cf_opts = reinterpret_cast(jhandle); return static_cast(cf_opts->force_consistency_checks); } @@ -4104,21 +4424,45 @@ jboolean Java_org_rocksdb_ColumnFamilyOptions_forceConsistencyChecks( * Method: newDBOptions * Signature: ()J */ -jlong Java_org_rocksdb_DBOptions_newDBOptions(JNIEnv* env, - jclass jcls) { +jlong Java_org_rocksdb_DBOptions_newDBOptions( + JNIEnv*, jclass) { auto* dbop = new rocksdb::DBOptions(); return reinterpret_cast(dbop); } +/* + * Class: org_rocksdb_DBOptions + * Method: copyDBOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_DBOptions_copyDBOptions( + JNIEnv*, jclass, jlong jhandle) { + auto new_opt = + new rocksdb::DBOptions(*(reinterpret_cast(jhandle))); + return reinterpret_cast(new_opt); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: newDBOptionsFromOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_DBOptions_newDBOptionsFromOptions( + JNIEnv*, jclass, jlong joptions_handle) { + auto new_opt = + new rocksdb::DBOptions(*reinterpret_cast(joptions_handle)); + return reinterpret_cast(new_opt); +} + /* * Class: org_rocksdb_DBOptions * Method: getDBOptionsFromProps * Signature: (Ljava/util/String;)J */ jlong Java_org_rocksdb_DBOptions_getDBOptionsFromProps( - JNIEnv* env, jclass jclazz, jstring jopt_string) { + JNIEnv* env, jclass, jstring jopt_string) { const char* opt_string = env->GetStringUTFChars(jopt_string, nullptr); - if(opt_string == nullptr) { + if (opt_string == nullptr) { // exception thrown: OutOfMemoryError return 0; } @@ -4147,7 +4491,7 @@ jlong Java_org_rocksdb_DBOptions_getDBOptionsFromProps( * Signature: (J)V */ void Java_org_rocksdb_DBOptions_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { + JNIEnv*, jobject, jlong handle) { auto* dbo = reinterpret_cast(handle); assert(dbo != nullptr); delete dbo; @@ -4159,7 +4503,7 @@ void Java_org_rocksdb_DBOptions_disposeInternal( * Signature: (J)V */ void Java_org_rocksdb_DBOptions_optimizeForSmallDb( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { reinterpret_cast(jhandle)->OptimizeForSmallDb(); } @@ -4169,7 +4513,7 @@ void Java_org_rocksdb_DBOptions_optimizeForSmallDb( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setEnv( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jenv_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jenv_handle) { reinterpret_cast(jhandle)->env = reinterpret_cast(jenv_handle); } @@ -4180,21 +4524,19 @@ void Java_org_rocksdb_DBOptions_setEnv( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setIncreaseParallelism( - JNIEnv * env, jobject jobj, jlong jhandle, jint totalThreads) { - reinterpret_cast - (jhandle)->IncreaseParallelism(static_cast(totalThreads)); + JNIEnv*, jobject, jlong jhandle, jint totalThreads) { + reinterpret_cast(jhandle)->IncreaseParallelism( + static_cast(totalThreads)); } - /* * Class: org_rocksdb_DBOptions * Method: setCreateIfMissing * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setCreateIfMissing( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { - reinterpret_cast(jhandle)-> - create_if_missing = flag; + JNIEnv*, jobject, jlong jhandle, jboolean flag) { + reinterpret_cast(jhandle)->create_if_missing = flag; } /* @@ -4203,7 +4545,7 @@ void Java_org_rocksdb_DBOptions_setCreateIfMissing( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_createIfMissing( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->create_if_missing; } @@ -4213,9 +4555,9 @@ jboolean Java_org_rocksdb_DBOptions_createIfMissing( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setCreateMissingColumnFamilies( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean flag) { - reinterpret_cast - (jhandle)->create_missing_column_families = flag; + JNIEnv*, jobject, jlong jhandle, jboolean flag) { + reinterpret_cast(jhandle) + ->create_missing_column_families = flag; } /* @@ -4224,9 +4566,9 @@ void Java_org_rocksdb_DBOptions_setCreateMissingColumnFamilies( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_createMissingColumnFamilies( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast - (jhandle)->create_missing_column_families; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->create_missing_column_families; } /* @@ -4235,7 +4577,7 @@ jboolean Java_org_rocksdb_DBOptions_createMissingColumnFamilies( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setErrorIfExists( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean error_if_exists) { + JNIEnv*, jobject, jlong jhandle, jboolean error_if_exists) { reinterpret_cast(jhandle)->error_if_exists = static_cast(error_if_exists); } @@ -4246,7 +4588,7 @@ void Java_org_rocksdb_DBOptions_setErrorIfExists( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_errorIfExists( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->error_if_exists; } @@ -4256,7 +4598,7 @@ jboolean Java_org_rocksdb_DBOptions_errorIfExists( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setParanoidChecks( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean paranoid_checks) { + JNIEnv*, jobject, jlong jhandle, jboolean paranoid_checks) { reinterpret_cast(jhandle)->paranoid_checks = static_cast(paranoid_checks); } @@ -4267,7 +4609,7 @@ void Java_org_rocksdb_DBOptions_setParanoidChecks( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_paranoidChecks( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->paranoid_checks; } @@ -4277,22 +4619,36 @@ jboolean Java_org_rocksdb_DBOptions_paranoidChecks( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setRateLimiter( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jrate_limiter_handle) { - std::shared_ptr *pRateLimiter = - reinterpret_cast *>( + JNIEnv*, jobject, jlong jhandle, jlong jrate_limiter_handle) { + std::shared_ptr* pRateLimiter = + reinterpret_cast*>( jrate_limiter_handle); reinterpret_cast(jhandle)->rate_limiter = *pRateLimiter; } +/* + * Class: org_rocksdb_DBOptions + * Method: setSstFileManager + * Signature: (JJ)V + */ +void Java_org_rocksdb_DBOptions_setSstFileManager( + JNIEnv*, jobject, jlong jhandle, jlong jsst_file_manager_handle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>( + jsst_file_manager_handle); + reinterpret_cast(jhandle)->sst_file_manager = + *sptr_sst_file_manager; +} + /* * Class: org_rocksdb_DBOptions * Method: setLogger * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setLogger( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jlogger_handle) { - std::shared_ptr *pLogger = - reinterpret_cast *>( + JNIEnv*, jobject, jlong jhandle, jlong jlogger_handle) { + std::shared_ptr* pLogger = + reinterpret_cast*>( jlogger_handle); reinterpret_cast(jhandle)->info_log = *pLogger; } @@ -4303,9 +4659,9 @@ void Java_org_rocksdb_DBOptions_setLogger( * Signature: (JB)V */ void Java_org_rocksdb_DBOptions_setInfoLogLevel( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jlog_level) { + JNIEnv*, jobject, jlong jhandle, jbyte jlog_level) { reinterpret_cast(jhandle)->info_log_level = - static_cast(jlog_level); + static_cast(jlog_level); } /* @@ -4314,7 +4670,7 @@ void Java_org_rocksdb_DBOptions_setInfoLogLevel( * Signature: (J)B */ jbyte Java_org_rocksdb_DBOptions_infoLogLevel( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return static_cast( reinterpret_cast(jhandle)->info_log_level); } @@ -4325,8 +4681,7 @@ jbyte Java_org_rocksdb_DBOptions_infoLogLevel( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setMaxTotalWalSize( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jmax_total_wal_size) { + JNIEnv*, jobject, jlong jhandle, jlong jmax_total_wal_size) { reinterpret_cast(jhandle)->max_total_wal_size = static_cast(jmax_total_wal_size); } @@ -4337,9 +4692,8 @@ void Java_org_rocksdb_DBOptions_setMaxTotalWalSize( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_maxTotalWalSize( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - max_total_wal_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_total_wal_size; } /* @@ -4348,7 +4702,7 @@ jlong Java_org_rocksdb_DBOptions_maxTotalWalSize( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setMaxOpenFiles( - JNIEnv* env, jobject jobj, jlong jhandle, jint max_open_files) { + JNIEnv*, jobject, jlong jhandle, jint max_open_files) { reinterpret_cast(jhandle)->max_open_files = static_cast(max_open_files); } @@ -4359,7 +4713,7 @@ void Java_org_rocksdb_DBOptions_setMaxOpenFiles( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_maxOpenFiles( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_open_files; } @@ -4369,7 +4723,7 @@ jint Java_org_rocksdb_DBOptions_maxOpenFiles( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setMaxFileOpeningThreads( - JNIEnv* env, jobject jobj, jlong jhandle, jint jmax_file_opening_threads) { + JNIEnv*, jobject, jlong jhandle, jint jmax_file_opening_threads) { reinterpret_cast(jhandle)->max_file_opening_threads = static_cast(jmax_file_opening_threads); } @@ -4380,7 +4734,7 @@ void Java_org_rocksdb_DBOptions_setMaxFileOpeningThreads( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_maxFileOpeningThreads( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->max_file_opening_threads); } @@ -4391,11 +4745,10 @@ jint Java_org_rocksdb_DBOptions_maxFileOpeningThreads( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setStatistics( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jstatistics_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jstatistics_handle) { auto* opt = reinterpret_cast(jhandle); - auto* pSptr = - reinterpret_cast*>( - jstatistics_handle); + auto* pSptr = reinterpret_cast*>( + jstatistics_handle); opt->statistics = *pSptr; } @@ -4405,7 +4758,7 @@ void Java_org_rocksdb_DBOptions_setStatistics( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_statistics( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); std::shared_ptr sptr = opt->statistics; if (sptr == nullptr) { @@ -4423,7 +4776,7 @@ jlong Java_org_rocksdb_DBOptions_statistics( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setUseFsync( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean use_fsync) { + JNIEnv*, jobject, jlong jhandle, jboolean use_fsync) { reinterpret_cast(jhandle)->use_fsync = static_cast(use_fsync); } @@ -4434,7 +4787,7 @@ void Java_org_rocksdb_DBOptions_setUseFsync( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_useFsync( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->use_fsync; } @@ -4444,34 +4797,32 @@ jboolean Java_org_rocksdb_DBOptions_useFsync( * Signature: (J[Ljava/lang/String;[J)V */ void Java_org_rocksdb_DBOptions_setDbPaths( - JNIEnv* env, jobject jobj, jlong jhandle, jobjectArray jpaths, + JNIEnv* env, jobject, jlong jhandle, jobjectArray jpaths, jlongArray jtarget_sizes) { std::vector db_paths; jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr); - if(ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; + if (ptr_jtarget_size == nullptr) { + // exception thrown: OutOfMemoryError + return; } jboolean has_exception = JNI_FALSE; const jsize len = env->GetArrayLength(jpaths); - for(jsize i = 0; i < len; i++) { - jobject jpath = reinterpret_cast(env-> - GetObjectArrayElement(jpaths, i)); - if(env->ExceptionCheck()) { + for (jsize i = 0; i < len; i++) { + jobject jpath = + reinterpret_cast(env->GetObjectArrayElement(jpaths, i)); + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); return; } - std::string path = rocksdb::JniUtil::copyString( + std::string path = rocksdb::JniUtil::copyStdString( env, static_cast(jpath), &has_exception); env->DeleteLocalRef(jpath); - if(has_exception == JNI_TRUE) { - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); - return; + if (has_exception == JNI_TRUE) { + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + return; } jlong jtarget_size = ptr_jtarget_size[i]; @@ -4492,7 +4843,7 @@ void Java_org_rocksdb_DBOptions_setDbPaths( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_dbPathsLen( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->db_paths.size()); } @@ -4503,32 +4854,30 @@ jlong Java_org_rocksdb_DBOptions_dbPathsLen( * Signature: (J[Ljava/lang/String;[J)V */ void Java_org_rocksdb_DBOptions_dbPaths( - JNIEnv* env, jobject jobj, jlong jhandle, jobjectArray jpaths, + JNIEnv* env, jobject, jlong jhandle, jobjectArray jpaths, jlongArray jtarget_sizes) { jlong* ptr_jtarget_size = env->GetLongArrayElements(jtarget_sizes, nullptr); - if(ptr_jtarget_size == nullptr) { - // exception thrown: OutOfMemoryError - return; + if (ptr_jtarget_size == nullptr) { + // exception thrown: OutOfMemoryError + return; } auto* opt = reinterpret_cast(jhandle); const jsize len = env->GetArrayLength(jpaths); - for(jsize i = 0; i < len; i++) { + for (jsize i = 0; i < len; i++) { rocksdb::DbPath db_path = opt->db_paths[i]; jstring jpath = env->NewStringUTF(db_path.path.c_str()); - if(jpath == nullptr) { + if (jpath == nullptr) { // exception thrown: OutOfMemoryError - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); return; } env->SetObjectArrayElement(jpaths, i, jpath); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(jpath); - env->ReleaseLongArrayElements( - jtarget_sizes, ptr_jtarget_size, JNI_ABORT); + env->ReleaseLongArrayElements(jtarget_sizes, ptr_jtarget_size, JNI_ABORT); return; } @@ -4544,9 +4893,9 @@ void Java_org_rocksdb_DBOptions_dbPaths( * Signature: (JLjava/lang/String)V */ void Java_org_rocksdb_DBOptions_setDbLogDir( - JNIEnv* env, jobject jobj, jlong jhandle, jstring jdb_log_dir) { + JNIEnv* env, jobject, jlong jhandle, jstring jdb_log_dir) { const char* log_dir = env->GetStringUTFChars(jdb_log_dir, nullptr); - if(log_dir == nullptr) { + if (log_dir == nullptr) { // exception thrown: OutOfMemoryError return; } @@ -4561,7 +4910,7 @@ void Java_org_rocksdb_DBOptions_setDbLogDir( * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_DBOptions_dbLogDir( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { return env->NewStringUTF( reinterpret_cast(jhandle)->db_log_dir.c_str()); } @@ -4572,7 +4921,7 @@ jstring Java_org_rocksdb_DBOptions_dbLogDir( * Signature: (JLjava/lang/String)V */ void Java_org_rocksdb_DBOptions_setWalDir( - JNIEnv* env, jobject jobj, jlong jhandle, jstring jwal_dir) { + JNIEnv* env, jobject, jlong jhandle, jstring jwal_dir) { const char* wal_dir = env->GetStringUTFChars(jwal_dir, 0); reinterpret_cast(jhandle)->wal_dir.assign(wal_dir); env->ReleaseStringUTFChars(jwal_dir, wal_dir); @@ -4584,7 +4933,7 @@ void Java_org_rocksdb_DBOptions_setWalDir( * Signature: (J)Ljava/lang/String */ jstring Java_org_rocksdb_DBOptions_walDir( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv* env, jobject, jlong jhandle) { return env->NewStringUTF( reinterpret_cast(jhandle)->wal_dir.c_str()); } @@ -4595,10 +4944,9 @@ jstring Java_org_rocksdb_DBOptions_walDir( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setDeleteObsoleteFilesPeriodMicros( - JNIEnv* env, jobject jobj, jlong jhandle, jlong micros) { + JNIEnv*, jobject, jlong jhandle, jlong micros) { reinterpret_cast(jhandle) - ->delete_obsolete_files_period_micros = - static_cast(micros); + ->delete_obsolete_files_period_micros = static_cast(micros); } /* @@ -4607,7 +4955,7 @@ void Java_org_rocksdb_DBOptions_setDeleteObsoleteFilesPeriodMicros( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_deleteObsoleteFilesPeriodMicros( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->delete_obsolete_files_period_micros; } @@ -4618,9 +4966,9 @@ jlong Java_org_rocksdb_DBOptions_deleteObsoleteFilesPeriodMicros( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setBaseBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle, jint max) { - reinterpret_cast(jhandle) - ->base_background_compactions = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jint max) { + reinterpret_cast(jhandle)->base_background_compactions = + static_cast(max); } /* @@ -4629,7 +4977,7 @@ void Java_org_rocksdb_DBOptions_setBaseBackgroundCompactions( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_baseBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->base_background_compactions; } @@ -4640,9 +4988,9 @@ jint Java_org_rocksdb_DBOptions_baseBackgroundCompactions( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setMaxBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle, jint max) { - reinterpret_cast(jhandle) - ->max_background_compactions = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jint max) { + reinterpret_cast(jhandle)->max_background_compactions = + static_cast(max); } /* @@ -4651,9 +4999,9 @@ void Java_org_rocksdb_DBOptions_setMaxBackgroundCompactions( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_maxBackgroundCompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->max_background_compactions; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->max_background_compactions; } /* @@ -4662,9 +5010,9 @@ jint Java_org_rocksdb_DBOptions_maxBackgroundCompactions( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setMaxSubcompactions( - JNIEnv* env, jobject jobj, jlong jhandle, jint max) { - reinterpret_cast(jhandle) - ->max_subcompactions = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jint max) { + reinterpret_cast(jhandle)->max_subcompactions = + static_cast(max); } /* @@ -4673,9 +5021,8 @@ void Java_org_rocksdb_DBOptions_setMaxSubcompactions( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_maxSubcompactions( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle) - ->max_subcompactions; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_subcompactions; } /* @@ -4684,7 +5031,7 @@ jint Java_org_rocksdb_DBOptions_maxSubcompactions( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setMaxBackgroundFlushes( - JNIEnv* env, jobject jobj, jlong jhandle, jint max_background_flushes) { + JNIEnv*, jobject, jlong jhandle, jint max_background_flushes) { reinterpret_cast(jhandle)->max_background_flushes = static_cast(max_background_flushes); } @@ -4695,9 +5042,29 @@ void Java_org_rocksdb_DBOptions_setMaxBackgroundFlushes( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_maxBackgroundFlushes( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - max_background_flushes; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_background_flushes; +} + +/* + * Class: org_rocksdb_DBOptions + * Method: setMaxBackgroundJobs + * Signature: (JI)V + */ +void Java_org_rocksdb_DBOptions_setMaxBackgroundJobs( + JNIEnv*, jobject, jlong jhandle, jint max_background_jobs) { + reinterpret_cast(jhandle)->max_background_jobs = + static_cast(max_background_jobs); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: maxBackgroundJobs + * Signature: (J)I + */ +jint Java_org_rocksdb_DBOptions_maxBackgroundJobs( + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_background_jobs; } /* @@ -4706,8 +5073,8 @@ jint Java_org_rocksdb_DBOptions_maxBackgroundFlushes( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setMaxLogFileSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong max_log_file_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(max_log_file_size); + JNIEnv* env, jobject, jlong jhandle, jlong max_log_file_size) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(max_log_file_size); if (s.ok()) { reinterpret_cast(jhandle)->max_log_file_size = max_log_file_size; @@ -4722,7 +5089,7 @@ void Java_org_rocksdb_DBOptions_setMaxLogFileSize( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_maxLogFileSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->max_log_file_size; } @@ -4732,9 +5099,9 @@ jlong Java_org_rocksdb_DBOptions_maxLogFileSize( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setLogFileTimeToRoll( - JNIEnv* env, jobject jobj, jlong jhandle, jlong log_file_time_to_roll) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t( - log_file_time_to_roll); + JNIEnv* env, jobject, jlong jhandle, jlong log_file_time_to_roll) { + auto s = + rocksdb::JniUtil::check_if_jlong_fits_size_t(log_file_time_to_roll); if (s.ok()) { reinterpret_cast(jhandle)->log_file_time_to_roll = log_file_time_to_roll; @@ -4749,7 +5116,7 @@ void Java_org_rocksdb_DBOptions_setLogFileTimeToRoll( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_logFileTimeToRoll( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->log_file_time_to_roll; } @@ -4759,8 +5126,8 @@ jlong Java_org_rocksdb_DBOptions_logFileTimeToRoll( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setKeepLogFileNum( - JNIEnv* env, jobject jobj, jlong jhandle, jlong keep_log_file_num) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(keep_log_file_num); + JNIEnv* env, jobject, jlong jhandle, jlong keep_log_file_num) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(keep_log_file_num); if (s.ok()) { reinterpret_cast(jhandle)->keep_log_file_num = keep_log_file_num; @@ -4775,7 +5142,7 @@ void Java_org_rocksdb_DBOptions_setKeepLogFileNum( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_keepLogFileNum( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->keep_log_file_num; } @@ -4785,8 +5152,8 @@ jlong Java_org_rocksdb_DBOptions_keepLogFileNum( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setRecycleLogFileNum( - JNIEnv* env, jobject jobj, jlong jhandle, jlong recycle_log_file_num) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(recycle_log_file_num); + JNIEnv* env, jobject, jlong jhandle, jlong recycle_log_file_num) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(recycle_log_file_num); if (s.ok()) { reinterpret_cast(jhandle)->recycle_log_file_num = recycle_log_file_num; @@ -4800,8 +5167,8 @@ void Java_org_rocksdb_DBOptions_setRecycleLogFileNum( * Method: recycleLogFileNum * Signature: (J)J */ -jlong Java_org_rocksdb_DBOptions_recycleLogFileNum(JNIEnv* env, jobject jobj, - jlong jhandle) { +jlong Java_org_rocksdb_DBOptions_recycleLogFileNum( + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->recycle_log_file_num; } @@ -4811,7 +5178,7 @@ jlong Java_org_rocksdb_DBOptions_recycleLogFileNum(JNIEnv* env, jobject jobj, * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setMaxManifestFileSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong max_manifest_file_size) { + JNIEnv*, jobject, jlong jhandle, jlong max_manifest_file_size) { reinterpret_cast(jhandle)->max_manifest_file_size = static_cast(max_manifest_file_size); } @@ -4822,9 +5189,8 @@ void Java_org_rocksdb_DBOptions_setMaxManifestFileSize( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_maxManifestFileSize( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - max_manifest_file_size; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->max_manifest_file_size; } /* @@ -4833,7 +5199,7 @@ jlong Java_org_rocksdb_DBOptions_maxManifestFileSize( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setTableCacheNumshardbits( - JNIEnv* env, jobject jobj, jlong jhandle, jint table_cache_numshardbits) { + JNIEnv*, jobject, jlong jhandle, jint table_cache_numshardbits) { reinterpret_cast(jhandle)->table_cache_numshardbits = static_cast(table_cache_numshardbits); } @@ -4844,9 +5210,9 @@ void Java_org_rocksdb_DBOptions_setTableCacheNumshardbits( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_tableCacheNumshardbits( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - table_cache_numshardbits; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->table_cache_numshardbits; } /* @@ -4855,7 +5221,7 @@ jint Java_org_rocksdb_DBOptions_tableCacheNumshardbits( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setWalTtlSeconds( - JNIEnv* env, jobject jobj, jlong jhandle, jlong WAL_ttl_seconds) { + JNIEnv*, jobject, jlong jhandle, jlong WAL_ttl_seconds) { reinterpret_cast(jhandle)->WAL_ttl_seconds = static_cast(WAL_ttl_seconds); } @@ -4866,7 +5232,7 @@ void Java_org_rocksdb_DBOptions_setWalTtlSeconds( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_walTtlSeconds( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->WAL_ttl_seconds; } @@ -4876,7 +5242,7 @@ jlong Java_org_rocksdb_DBOptions_walTtlSeconds( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setWalSizeLimitMB( - JNIEnv* env, jobject jobj, jlong jhandle, jlong WAL_size_limit_MB) { + JNIEnv*, jobject, jlong jhandle, jlong WAL_size_limit_MB) { reinterpret_cast(jhandle)->WAL_size_limit_MB = static_cast(WAL_size_limit_MB); } @@ -4887,7 +5253,7 @@ void Java_org_rocksdb_DBOptions_setWalSizeLimitMB( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_walSizeLimitMB( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->WAL_size_limit_MB; } @@ -4897,11 +5263,11 @@ jlong Java_org_rocksdb_DBOptions_walSizeLimitMB( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setManifestPreallocationSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong preallocation_size) { - rocksdb::Status s = rocksdb::check_if_jlong_fits_size_t(preallocation_size); + JNIEnv* env, jobject, jlong jhandle, jlong preallocation_size) { + auto s = rocksdb::JniUtil::check_if_jlong_fits_size_t(preallocation_size); if (s.ok()) { - reinterpret_cast(jhandle)-> - manifest_preallocation_size = preallocation_size; + reinterpret_cast(jhandle) + ->manifest_preallocation_size = preallocation_size; } else { rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, s); } @@ -4913,7 +5279,7 @@ void Java_org_rocksdb_DBOptions_setManifestPreallocationSize( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_manifestPreallocationSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->manifest_preallocation_size; } @@ -4923,8 +5289,8 @@ jlong Java_org_rocksdb_DBOptions_manifestPreallocationSize( * Method: useDirectReads * Signature: (J)Z */ -jboolean Java_org_rocksdb_DBOptions_useDirectReads(JNIEnv* env, jobject jobj, - jlong jhandle) { +jboolean Java_org_rocksdb_DBOptions_useDirectReads( + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->use_direct_reads; } @@ -4933,9 +5299,8 @@ jboolean Java_org_rocksdb_DBOptions_useDirectReads(JNIEnv* env, jobject jobj, * Method: setUseDirectReads * Signature: (JZ)V */ -void Java_org_rocksdb_DBOptions_setUseDirectReads(JNIEnv* env, jobject jobj, - jlong jhandle, - jboolean use_direct_reads) { +void Java_org_rocksdb_DBOptions_setUseDirectReads( + JNIEnv*, jobject, jlong jhandle, jboolean use_direct_reads) { reinterpret_cast(jhandle)->use_direct_reads = static_cast(use_direct_reads); } @@ -4946,7 +5311,7 @@ void Java_org_rocksdb_DBOptions_setUseDirectReads(JNIEnv* env, jobject jobj, * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_useDirectIoForFlushAndCompaction( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) ->use_direct_io_for_flush_and_compaction; } @@ -4957,7 +5322,7 @@ jboolean Java_org_rocksdb_DBOptions_useDirectIoForFlushAndCompaction( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setUseDirectIoForFlushAndCompaction( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jboolean use_direct_io_for_flush_and_compaction) { reinterpret_cast(jhandle) ->use_direct_io_for_flush_and_compaction = @@ -4970,7 +5335,7 @@ void Java_org_rocksdb_DBOptions_setUseDirectIoForFlushAndCompaction( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAllowFAllocate( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jallow_fallocate) { + JNIEnv*, jobject, jlong jhandle, jboolean jallow_fallocate) { reinterpret_cast(jhandle)->allow_fallocate = static_cast(jallow_fallocate); } @@ -4981,7 +5346,7 @@ void Java_org_rocksdb_DBOptions_setAllowFAllocate( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_allowFAllocate( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->allow_fallocate); } @@ -4992,7 +5357,7 @@ jboolean Java_org_rocksdb_DBOptions_allowFAllocate( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAllowMmapReads( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow_mmap_reads) { + JNIEnv*, jobject, jlong jhandle, jboolean allow_mmap_reads) { reinterpret_cast(jhandle)->allow_mmap_reads = static_cast(allow_mmap_reads); } @@ -5003,7 +5368,7 @@ void Java_org_rocksdb_DBOptions_setAllowMmapReads( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_allowMmapReads( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->allow_mmap_reads; } @@ -5013,7 +5378,7 @@ jboolean Java_org_rocksdb_DBOptions_allowMmapReads( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAllowMmapWrites( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow_mmap_writes) { + JNIEnv*, jobject, jlong jhandle, jboolean allow_mmap_writes) { reinterpret_cast(jhandle)->allow_mmap_writes = static_cast(allow_mmap_writes); } @@ -5024,7 +5389,7 @@ void Java_org_rocksdb_DBOptions_setAllowMmapWrites( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_allowMmapWrites( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->allow_mmap_writes; } @@ -5034,7 +5399,7 @@ jboolean Java_org_rocksdb_DBOptions_allowMmapWrites( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setIsFdCloseOnExec( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean is_fd_close_on_exec) { + JNIEnv*, jobject, jlong jhandle, jboolean is_fd_close_on_exec) { reinterpret_cast(jhandle)->is_fd_close_on_exec = static_cast(is_fd_close_on_exec); } @@ -5045,7 +5410,7 @@ void Java_org_rocksdb_DBOptions_setIsFdCloseOnExec( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_isFdCloseOnExec( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->is_fd_close_on_exec; } @@ -5055,7 +5420,7 @@ jboolean Java_org_rocksdb_DBOptions_isFdCloseOnExec( * Signature: (JI)V */ void Java_org_rocksdb_DBOptions_setStatsDumpPeriodSec( - JNIEnv* env, jobject jobj, jlong jhandle, jint stats_dump_period_sec) { + JNIEnv*, jobject, jlong jhandle, jint stats_dump_period_sec) { reinterpret_cast(jhandle)->stats_dump_period_sec = static_cast(stats_dump_period_sec); } @@ -5066,7 +5431,7 @@ void Java_org_rocksdb_DBOptions_setStatsDumpPeriodSec( * Signature: (J)I */ jint Java_org_rocksdb_DBOptions_statsDumpPeriodSec( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->stats_dump_period_sec; } @@ -5076,7 +5441,7 @@ jint Java_org_rocksdb_DBOptions_statsDumpPeriodSec( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAdviseRandomOnOpen( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean advise_random_on_open) { + JNIEnv*, jobject, jlong jhandle, jboolean advise_random_on_open) { reinterpret_cast(jhandle)->advise_random_on_open = static_cast(advise_random_on_open); } @@ -5087,7 +5452,7 @@ void Java_org_rocksdb_DBOptions_setAdviseRandomOnOpen( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_adviseRandomOnOpen( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->advise_random_on_open; } @@ -5097,18 +5462,32 @@ jboolean Java_org_rocksdb_DBOptions_adviseRandomOnOpen( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setDbWriteBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jdb_write_buffer_size) { + JNIEnv*, jobject, jlong jhandle, jlong jdb_write_buffer_size) { auto* opt = reinterpret_cast(jhandle); opt->db_write_buffer_size = static_cast(jdb_write_buffer_size); } +/* + * Class: org_rocksdb_DBOptions + * Method: setWriteBufferManager + * Signature: (JJ)V + */ +void Java_org_rocksdb_DBOptions_setWriteBufferManager( + JNIEnv*, jobject, jlong jdb_options_handle, + jlong jwrite_buffer_manager_handle) { + auto* write_buffer_manager = + reinterpret_cast *>(jwrite_buffer_manager_handle); + reinterpret_cast(jdb_options_handle)->write_buffer_manager = + *write_buffer_manager; +} + /* * Class: org_rocksdb_DBOptions * Method: dbWriteBufferSize * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_dbWriteBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->db_write_buffer_size); } @@ -5119,7 +5498,7 @@ jlong Java_org_rocksdb_DBOptions_dbWriteBufferSize( * Signature: (JB)V */ void Java_org_rocksdb_DBOptions_setAccessHintOnCompactionStart( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jaccess_hint_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jaccess_hint_value) { auto* opt = reinterpret_cast(jhandle); opt->access_hint_on_compaction_start = rocksdb::AccessHintJni::toCppAccessHint(jaccess_hint_value); @@ -5131,7 +5510,7 @@ void Java_org_rocksdb_DBOptions_setAccessHintOnCompactionStart( * Signature: (J)B */ jbyte Java_org_rocksdb_DBOptions_accessHintOnCompactionStart( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return rocksdb::AccessHintJni::toJavaAccessHint( opt->access_hint_on_compaction_start); @@ -5143,7 +5522,7 @@ jbyte Java_org_rocksdb_DBOptions_accessHintOnCompactionStart( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setNewTableReaderForCompactionInputs( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jboolean jnew_table_reader_for_compaction_inputs) { auto* opt = reinterpret_cast(jhandle); opt->new_table_reader_for_compaction_inputs = @@ -5156,7 +5535,7 @@ void Java_org_rocksdb_DBOptions_setNewTableReaderForCompactionInputs( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_newTableReaderForCompactionInputs( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->new_table_reader_for_compaction_inputs); } @@ -5167,7 +5546,7 @@ jboolean Java_org_rocksdb_DBOptions_newTableReaderForCompactionInputs( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setCompactionReadaheadSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jcompaction_readahead_size) { + JNIEnv*, jobject, jlong jhandle, jlong jcompaction_readahead_size) { auto* opt = reinterpret_cast(jhandle); opt->compaction_readahead_size = static_cast(jcompaction_readahead_size); @@ -5179,7 +5558,7 @@ void Java_org_rocksdb_DBOptions_setCompactionReadaheadSize( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_compactionReadaheadSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->compaction_readahead_size); } @@ -5190,8 +5569,7 @@ jlong Java_org_rocksdb_DBOptions_compactionReadaheadSize( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setRandomAccessMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jrandom_access_max_buffer_size) { + JNIEnv*, jobject, jlong jhandle, jlong jrandom_access_max_buffer_size) { auto* opt = reinterpret_cast(jhandle); opt->random_access_max_buffer_size = static_cast(jrandom_access_max_buffer_size); @@ -5203,7 +5581,7 @@ void Java_org_rocksdb_DBOptions_setRandomAccessMaxBufferSize( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_randomAccessMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->random_access_max_buffer_size); } @@ -5214,8 +5592,7 @@ jlong Java_org_rocksdb_DBOptions_randomAccessMaxBufferSize( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setWritableFileMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle, - jlong jwritable_file_max_buffer_size) { + JNIEnv*, jobject, jlong jhandle, jlong jwritable_file_max_buffer_size) { auto* opt = reinterpret_cast(jhandle); opt->writable_file_max_buffer_size = static_cast(jwritable_file_max_buffer_size); @@ -5227,7 +5604,7 @@ void Java_org_rocksdb_DBOptions_setWritableFileMaxBufferSize( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_writableFileMaxBufferSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->writable_file_max_buffer_size); } @@ -5238,7 +5615,7 @@ jlong Java_org_rocksdb_DBOptions_writableFileMaxBufferSize( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setUseAdaptiveMutex( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean use_adaptive_mutex) { + JNIEnv*, jobject, jlong jhandle, jboolean use_adaptive_mutex) { reinterpret_cast(jhandle)->use_adaptive_mutex = static_cast(use_adaptive_mutex); } @@ -5249,7 +5626,7 @@ void Java_org_rocksdb_DBOptions_setUseAdaptiveMutex( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_useAdaptiveMutex( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->use_adaptive_mutex; } @@ -5259,7 +5636,7 @@ jboolean Java_org_rocksdb_DBOptions_useAdaptiveMutex( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setBytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle, jlong bytes_per_sync) { + JNIEnv*, jobject, jlong jhandle, jlong bytes_per_sync) { reinterpret_cast(jhandle)->bytes_per_sync = static_cast(bytes_per_sync); } @@ -5270,7 +5647,7 @@ void Java_org_rocksdb_DBOptions_setBytesPerSync( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_bytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->bytes_per_sync; } @@ -5280,7 +5657,7 @@ jlong Java_org_rocksdb_DBOptions_bytesPerSync( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setWalBytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jwal_bytes_per_sync) { + JNIEnv*, jobject, jlong jhandle, jlong jwal_bytes_per_sync) { reinterpret_cast(jhandle)->wal_bytes_per_sync = static_cast(jwal_bytes_per_sync); } @@ -5291,54 +5668,75 @@ void Java_org_rocksdb_DBOptions_setWalBytesPerSync( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_walBytesPerSync( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->wal_bytes_per_sync); } /* * Class: org_rocksdb_DBOptions - * Method: setEnableThreadTracking + * Method: setDelayedWriteRate + * Signature: (JJ)V + */ +void Java_org_rocksdb_DBOptions_setDelayedWriteRate( + JNIEnv*, jobject, jlong jhandle, jlong jdelayed_write_rate) { + auto* opt = reinterpret_cast(jhandle); + opt->delayed_write_rate = static_cast(jdelayed_write_rate); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: delayedWriteRate + * Signature: (J)J + */ +jlong Java_org_rocksdb_DBOptions_delayedWriteRate( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->delayed_write_rate); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: setEnablePipelinedWrite * Signature: (JZ)V */ -void Java_org_rocksdb_DBOptions_setEnableThreadTracking( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jenable_thread_tracking) { +void Java_org_rocksdb_DBOptions_setEnablePipelinedWrite( + JNIEnv*, jobject, jlong jhandle, jboolean jenable_pipelined_write) { auto* opt = reinterpret_cast(jhandle); - opt->enable_thread_tracking = static_cast(jenable_thread_tracking); + opt->enable_pipelined_write = jenable_pipelined_write == JNI_TRUE; } /* * Class: org_rocksdb_DBOptions - * Method: enableThreadTracking + * Method: enablePipelinedWrite * Signature: (J)Z */ -jboolean Java_org_rocksdb_DBOptions_enableThreadTracking( - JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_DBOptions_enablePipelinedWrite( + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->enable_thread_tracking); + return static_cast(opt->enable_pipelined_write); } /* * Class: org_rocksdb_DBOptions - * Method: setDelayedWriteRate - * Signature: (JJ)V + * Method: setEnableThreadTracking + * Signature: (JZ)V */ -void Java_org_rocksdb_DBOptions_setDelayedWriteRate( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jdelayed_write_rate) { +void Java_org_rocksdb_DBOptions_setEnableThreadTracking( + JNIEnv*, jobject, jlong jhandle, jboolean jenable_thread_tracking) { auto* opt = reinterpret_cast(jhandle); - opt->delayed_write_rate = static_cast(jdelayed_write_rate); + opt->enable_thread_tracking = jenable_thread_tracking == JNI_TRUE; } /* * Class: org_rocksdb_DBOptions - * Method: delayedWriteRate - * Signature: (J)J + * Method: enableThreadTracking + * Signature: (J)Z */ -jlong Java_org_rocksdb_DBOptions_delayedWriteRate( - JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_DBOptions_enableThreadTracking( + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); - return static_cast(opt->delayed_write_rate); + return static_cast(opt->enable_thread_tracking); } /* @@ -5347,9 +5745,9 @@ jlong Java_org_rocksdb_DBOptions_delayedWriteRate( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAllowConcurrentMemtableWrite( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean allow) { - reinterpret_cast(jhandle)-> - allow_concurrent_memtable_write = static_cast(allow); + JNIEnv*, jobject, jlong jhandle, jboolean allow) { + reinterpret_cast(jhandle) + ->allow_concurrent_memtable_write = static_cast(allow); } /* @@ -5358,9 +5756,9 @@ void Java_org_rocksdb_DBOptions_setAllowConcurrentMemtableWrite( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_allowConcurrentMemtableWrite( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - allow_concurrent_memtable_write; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->allow_concurrent_memtable_write; } /* @@ -5369,9 +5767,9 @@ jboolean Java_org_rocksdb_DBOptions_allowConcurrentMemtableWrite( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setEnableWriteThreadAdaptiveYield( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean yield) { - reinterpret_cast(jhandle)-> - enable_write_thread_adaptive_yield = static_cast(yield); + JNIEnv*, jobject, jlong jhandle, jboolean yield) { + reinterpret_cast(jhandle) + ->enable_write_thread_adaptive_yield = static_cast(yield); } /* @@ -5380,9 +5778,9 @@ void Java_org_rocksdb_DBOptions_setEnableWriteThreadAdaptiveYield( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_enableWriteThreadAdaptiveYield( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - enable_write_thread_adaptive_yield; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->enable_write_thread_adaptive_yield; } /* @@ -5391,9 +5789,9 @@ jboolean Java_org_rocksdb_DBOptions_enableWriteThreadAdaptiveYield( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setWriteThreadMaxYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle, jlong max) { - reinterpret_cast(jhandle)-> - write_thread_max_yield_usec = static_cast(max); + JNIEnv*, jobject, jlong jhandle, jlong max) { + reinterpret_cast(jhandle)->write_thread_max_yield_usec = + static_cast(max); } /* @@ -5402,9 +5800,9 @@ void Java_org_rocksdb_DBOptions_setWriteThreadMaxYieldUsec( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_writeThreadMaxYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - write_thread_max_yield_usec; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->write_thread_max_yield_usec; } /* @@ -5413,9 +5811,9 @@ jlong Java_org_rocksdb_DBOptions_writeThreadMaxYieldUsec( * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setWriteThreadSlowYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle, jlong slow) { - reinterpret_cast(jhandle)-> - write_thread_slow_yield_usec = static_cast(slow); + JNIEnv*, jobject, jlong jhandle, jlong slow) { + reinterpret_cast(jhandle)->write_thread_slow_yield_usec = + static_cast(slow); } /* @@ -5424,9 +5822,9 @@ void Java_org_rocksdb_DBOptions_setWriteThreadSlowYieldUsec( * Signature: (J)J */ jlong Java_org_rocksdb_DBOptions_writeThreadSlowYieldUsec( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle)-> - write_thread_slow_yield_usec; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->write_thread_slow_yield_usec; } /* @@ -5435,8 +5833,7 @@ jlong Java_org_rocksdb_DBOptions_writeThreadSlowYieldUsec( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setSkipStatsUpdateOnDbOpen( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jskip_stats_update_on_db_open) { + JNIEnv*, jobject, jlong jhandle, jboolean jskip_stats_update_on_db_open) { auto* opt = reinterpret_cast(jhandle); opt->skip_stats_update_on_db_open = static_cast(jskip_stats_update_on_db_open); @@ -5448,7 +5845,7 @@ void Java_org_rocksdb_DBOptions_setSkipStatsUpdateOnDbOpen( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_skipStatsUpdateOnDbOpen( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->skip_stats_update_on_db_open); } @@ -5459,11 +5856,10 @@ jboolean Java_org_rocksdb_DBOptions_skipStatsUpdateOnDbOpen( * Signature: (JB)V */ void Java_org_rocksdb_DBOptions_setWalRecoveryMode( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jwal_recovery_mode_value) { + JNIEnv*, jobject, jlong jhandle, jbyte jwal_recovery_mode_value) { auto* opt = reinterpret_cast(jhandle); - opt->wal_recovery_mode = - rocksdb::WALRecoveryModeJni::toCppWALRecoveryMode( - jwal_recovery_mode_value); + opt->wal_recovery_mode = rocksdb::WALRecoveryModeJni::toCppWALRecoveryMode( + jwal_recovery_mode_value); } /* @@ -5472,7 +5868,7 @@ void Java_org_rocksdb_DBOptions_setWalRecoveryMode( * Signature: (J)B */ jbyte Java_org_rocksdb_DBOptions_walRecoveryMode( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return rocksdb::WALRecoveryModeJni::toJavaWALRecoveryMode( opt->wal_recovery_mode); @@ -5484,7 +5880,7 @@ jbyte Java_org_rocksdb_DBOptions_walRecoveryMode( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAllow2pc( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jallow_2pc) { + JNIEnv*, jobject, jlong jhandle, jboolean jallow_2pc) { auto* opt = reinterpret_cast(jhandle); opt->allow_2pc = static_cast(jallow_2pc); } @@ -5494,7 +5890,8 @@ void Java_org_rocksdb_DBOptions_setAllow2pc( * Method: allow2pc * Signature: (J)Z */ -jboolean Java_org_rocksdb_DBOptions_allow2pc(JNIEnv* env, jobject jobj, jlong jhandle) { +jboolean Java_org_rocksdb_DBOptions_allow2pc( + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->allow_2pc); } @@ -5505,20 +5902,33 @@ jboolean Java_org_rocksdb_DBOptions_allow2pc(JNIEnv* env, jobject jobj, jlong jh * Signature: (JJ)V */ void Java_org_rocksdb_DBOptions_setRowCache( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jrow_cache_handle) { + JNIEnv*, jobject, jlong jhandle, jlong jrow_cache_handle) { auto* opt = reinterpret_cast(jhandle); - auto* row_cache = reinterpret_cast*>(jrow_cache_handle); + auto* row_cache = + reinterpret_cast*>(jrow_cache_handle); opt->row_cache = *row_cache; } +/* + * Class: org_rocksdb_DBOptions + * Method: setWalFilter + * Signature: (JJ)V + */ +void Java_org_rocksdb_DBOptions_setWalFilter( + JNIEnv*, jobject, jlong jhandle, jlong jwal_filter_handle) { + auto* opt = reinterpret_cast(jhandle); + auto* wal_filter = + reinterpret_cast(jwal_filter_handle); + opt->wal_filter = wal_filter; +} + /* * Class: org_rocksdb_DBOptions * Method: setFailIfOptionsFileError * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setFailIfOptionsFileError( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jfail_if_options_file_error) { + JNIEnv*, jobject, jlong jhandle, jboolean jfail_if_options_file_error) { auto* opt = reinterpret_cast(jhandle); opt->fail_if_options_file_error = static_cast(jfail_if_options_file_error); @@ -5530,7 +5940,7 @@ void Java_org_rocksdb_DBOptions_setFailIfOptionsFileError( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_failIfOptionsFileError( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->fail_if_options_file_error); } @@ -5541,7 +5951,7 @@ jboolean Java_org_rocksdb_DBOptions_failIfOptionsFileError( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setDumpMallocStats( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jdump_malloc_stats) { + JNIEnv*, jobject, jlong jhandle, jboolean jdump_malloc_stats) { auto* opt = reinterpret_cast(jhandle); opt->dump_malloc_stats = static_cast(jdump_malloc_stats); } @@ -5552,7 +5962,7 @@ void Java_org_rocksdb_DBOptions_setDumpMallocStats( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_dumpMallocStats( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->dump_malloc_stats); } @@ -5563,10 +5973,10 @@ jboolean Java_org_rocksdb_DBOptions_dumpMallocStats( * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAvoidFlushDuringRecovery( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean javoid_flush_during_recovery) { + JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_recovery) { auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_recovery = static_cast(javoid_flush_during_recovery); + opt->avoid_flush_during_recovery = + static_cast(javoid_flush_during_recovery); } /* @@ -5575,21 +5985,131 @@ void Java_org_rocksdb_DBOptions_setAvoidFlushDuringRecovery( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_avoidFlushDuringRecovery( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->avoid_flush_during_recovery); } +/* + * Class: org_rocksdb_DBOptions + * Method: setAllowIngestBehind + * Signature: (JZ)V + */ +void Java_org_rocksdb_DBOptions_setAllowIngestBehind( + JNIEnv*, jobject, jlong jhandle, jboolean jallow_ingest_behind) { + auto* opt = reinterpret_cast(jhandle); + opt->allow_ingest_behind = jallow_ingest_behind == JNI_TRUE; +} + +/* + * Class: org_rocksdb_DBOptions + * Method: allowIngestBehind + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_DBOptions_allowIngestBehind( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->allow_ingest_behind); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: setPreserveDeletes + * Signature: (JZ)V + */ +void Java_org_rocksdb_DBOptions_setPreserveDeletes( + JNIEnv*, jobject, jlong jhandle, jboolean jpreserve_deletes) { + auto* opt = reinterpret_cast(jhandle); + opt->preserve_deletes = jpreserve_deletes == JNI_TRUE; +} + +/* + * Class: org_rocksdb_DBOptions + * Method: preserveDeletes + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_DBOptions_preserveDeletes( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->preserve_deletes); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: setTwoWriteQueues + * Signature: (JZ)V + */ +void Java_org_rocksdb_DBOptions_setTwoWriteQueues( + JNIEnv*, jobject, jlong jhandle, jboolean jtwo_write_queues) { + auto* opt = reinterpret_cast(jhandle); + opt->two_write_queues = jtwo_write_queues == JNI_TRUE; +} + +/* + * Class: org_rocksdb_DBOptions + * Method: twoWriteQueues + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_DBOptions_twoWriteQueues( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->two_write_queues); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: setManualWalFlush + * Signature: (JZ)V + */ +void Java_org_rocksdb_DBOptions_setManualWalFlush( + JNIEnv*, jobject, jlong jhandle, jboolean jmanual_wal_flush) { + auto* opt = reinterpret_cast(jhandle); + opt->manual_wal_flush = jmanual_wal_flush == JNI_TRUE; +} + +/* + * Class: org_rocksdb_DBOptions + * Method: manualWalFlush + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_DBOptions_manualWalFlush( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->manual_wal_flush); +} + +/* + * Class: org_rocksdb_DBOptions + * Method: setAtomicFlush + * Signature: (JZ)V + */ +void Java_org_rocksdb_DBOptions_setAtomicFlush( + JNIEnv*, jobject, jlong jhandle, jboolean jatomic_flush) { + auto* opt = reinterpret_cast(jhandle); + opt->atomic_flush = jatomic_flush == JNI_TRUE; +} + +/* + * Class: org_rocksdb_DBOptions + * Method: atomicFlush + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_DBOptions_atomicFlush( + JNIEnv *, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->atomic_flush); +} + /* * Class: org_rocksdb_DBOptions * Method: setAvoidFlushDuringShutdown * Signature: (JZ)V */ void Java_org_rocksdb_DBOptions_setAvoidFlushDuringShutdown( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean javoid_flush_during_shutdown) { + JNIEnv*, jobject, jlong jhandle, jboolean javoid_flush_during_shutdown) { auto* opt = reinterpret_cast(jhandle); - opt->avoid_flush_during_shutdown = static_cast(javoid_flush_during_shutdown); + opt->avoid_flush_during_shutdown = + static_cast(javoid_flush_during_shutdown); } /* @@ -5598,7 +6118,7 @@ void Java_org_rocksdb_DBOptions_setAvoidFlushDuringShutdown( * Signature: (J)Z */ jboolean Java_org_rocksdb_DBOptions_avoidFlushDuringShutdown( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->avoid_flush_during_shutdown); } @@ -5612,18 +6132,30 @@ jboolean Java_org_rocksdb_DBOptions_avoidFlushDuringShutdown( * Signature: ()J */ jlong Java_org_rocksdb_WriteOptions_newWriteOptions( - JNIEnv* env, jclass jcls) { + JNIEnv*, jclass) { auto* op = new rocksdb::WriteOptions(); return reinterpret_cast(op); } +/* + * Class: org_rocksdb_WriteOptions + * Method: copyWriteOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_WriteOptions_copyWriteOptions( + JNIEnv*, jclass, jlong jhandle) { + auto new_opt = new rocksdb::WriteOptions( + *(reinterpret_cast(jhandle))); + return reinterpret_cast(new_opt); +} + /* * Class: org_rocksdb_WriteOptions * Method: disposeInternal * Signature: ()V */ void Java_org_rocksdb_WriteOptions_disposeInternal( - JNIEnv* env, jobject jwrite_options, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* write_options = reinterpret_cast(jhandle); assert(write_options != nullptr); delete write_options; @@ -5635,7 +6167,7 @@ void Java_org_rocksdb_WriteOptions_disposeInternal( * Signature: (JZ)V */ void Java_org_rocksdb_WriteOptions_setSync( - JNIEnv* env, jobject jwrite_options, jlong jhandle, jboolean jflag) { + JNIEnv*, jobject, jlong jhandle, jboolean jflag) { reinterpret_cast(jhandle)->sync = jflag; } @@ -5645,7 +6177,7 @@ void Java_org_rocksdb_WriteOptions_setSync( * Signature: (J)Z */ jboolean Java_org_rocksdb_WriteOptions_sync( - JNIEnv* env, jobject jwrite_options, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->sync; } @@ -5655,7 +6187,7 @@ jboolean Java_org_rocksdb_WriteOptions_sync( * Signature: (JZ)V */ void Java_org_rocksdb_WriteOptions_setDisableWAL( - JNIEnv* env, jobject jwrite_options, jlong jhandle, jboolean jflag) { + JNIEnv*, jobject, jlong jhandle, jboolean jflag) { reinterpret_cast(jhandle)->disableWAL = jflag; } @@ -5665,7 +6197,7 @@ void Java_org_rocksdb_WriteOptions_setDisableWAL( * Signature: (J)Z */ jboolean Java_org_rocksdb_WriteOptions_disableWAL( - JNIEnv* env, jobject jwrite_options, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->disableWAL; } @@ -5675,11 +6207,11 @@ jboolean Java_org_rocksdb_WriteOptions_disableWAL( * Signature: (JZ)V */ void Java_org_rocksdb_WriteOptions_setIgnoreMissingColumnFamilies( - JNIEnv* env, jobject jwrite_options, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jboolean jignore_missing_column_families) { - reinterpret_cast(jhandle)-> - ignore_missing_column_families = - static_cast(jignore_missing_column_families); + reinterpret_cast(jhandle) + ->ignore_missing_column_families = + static_cast(jignore_missing_column_families); } /* @@ -5688,9 +6220,9 @@ void Java_org_rocksdb_WriteOptions_setIgnoreMissingColumnFamilies( * Signature: (J)Z */ jboolean Java_org_rocksdb_WriteOptions_ignoreMissingColumnFamilies( - JNIEnv* env, jobject jwrite_options, jlong jhandle) { - return reinterpret_cast(jhandle)-> - ignore_missing_column_families; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle) + ->ignore_missing_column_families; } /* @@ -5699,7 +6231,7 @@ jboolean Java_org_rocksdb_WriteOptions_ignoreMissingColumnFamilies( * Signature: (JZ)V */ void Java_org_rocksdb_WriteOptions_setNoSlowdown( - JNIEnv* env, jobject jwrite_options, jlong jhandle, jboolean jno_slowdown) { + JNIEnv*, jobject, jlong jhandle, jboolean jno_slowdown) { reinterpret_cast(jhandle)->no_slowdown = static_cast(jno_slowdown); } @@ -5710,10 +6242,31 @@ void Java_org_rocksdb_WriteOptions_setNoSlowdown( * Signature: (J)Z */ jboolean Java_org_rocksdb_WriteOptions_noSlowdown( - JNIEnv* env, jobject jwrite_options, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->no_slowdown; } +/* + * Class: org_rocksdb_WriteOptions + * Method: setLowPri + * Signature: (JZ)V + */ +void Java_org_rocksdb_WriteOptions_setLowPri( + JNIEnv*, jobject, jlong jhandle, jboolean jlow_pri) { + reinterpret_cast(jhandle)->low_pri = + static_cast(jlow_pri); +} + +/* + * Class: org_rocksdb_WriteOptions + * Method: lowPri + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_WriteOptions_lowPri( + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->low_pri; +} + ///////////////////////////////////////////////////////////////////// // rocksdb::ReadOptions @@ -5722,19 +6275,44 @@ jboolean Java_org_rocksdb_WriteOptions_noSlowdown( * Method: newReadOptions * Signature: ()J */ -jlong Java_org_rocksdb_ReadOptions_newReadOptions( - JNIEnv* env, jclass jcls) { +jlong Java_org_rocksdb_ReadOptions_newReadOptions__( + JNIEnv*, jclass) { auto* read_options = new rocksdb::ReadOptions(); return reinterpret_cast(read_options); } +/* + * Class: org_rocksdb_ReadOptions + * Method: newReadOptions + * Signature: (ZZ)J + */ +jlong Java_org_rocksdb_ReadOptions_newReadOptions__ZZ( + JNIEnv*, jclass, jboolean jverify_checksums, jboolean jfill_cache) { + auto* read_options = + new rocksdb::ReadOptions(static_cast(jverify_checksums), + static_cast(jfill_cache)); + return reinterpret_cast(read_options); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: copyReadOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_ReadOptions_copyReadOptions( + JNIEnv*, jclass, jlong jhandle) { + auto new_opt = new rocksdb::ReadOptions( + *(reinterpret_cast(jhandle))); + return reinterpret_cast(new_opt); +} + /* * Class: org_rocksdb_ReadOptions * Method: disposeInternal * Signature: (J)V */ void Java_org_rocksdb_ReadOptions_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* read_options = reinterpret_cast(jhandle); assert(read_options != nullptr); delete read_options; @@ -5746,8 +6324,7 @@ void Java_org_rocksdb_ReadOptions_disposeInternal( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setVerifyChecksums( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jverify_checksums) { + JNIEnv*, jobject, jlong jhandle, jboolean jverify_checksums) { reinterpret_cast(jhandle)->verify_checksums = static_cast(jverify_checksums); } @@ -5758,9 +6335,8 @@ void Java_org_rocksdb_ReadOptions_setVerifyChecksums( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_verifyChecksums( - JNIEnv* env, jobject jobj, jlong jhandle) { - return reinterpret_cast( - jhandle)->verify_checksums; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->verify_checksums; } /* @@ -5769,7 +6345,7 @@ jboolean Java_org_rocksdb_ReadOptions_verifyChecksums( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setFillCache( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jfill_cache) { + JNIEnv*, jobject, jlong jhandle, jboolean jfill_cache) { reinterpret_cast(jhandle)->fill_cache = static_cast(jfill_cache); } @@ -5780,7 +6356,7 @@ void Java_org_rocksdb_ReadOptions_setFillCache( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_fillCache( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->fill_cache; } @@ -5790,7 +6366,7 @@ jboolean Java_org_rocksdb_ReadOptions_fillCache( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setTailing( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jtailing) { + JNIEnv*, jobject, jlong jhandle, jboolean jtailing) { reinterpret_cast(jhandle)->tailing = static_cast(jtailing); } @@ -5801,7 +6377,7 @@ void Java_org_rocksdb_ReadOptions_setTailing( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_tailing( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->tailing; } @@ -5811,7 +6387,7 @@ jboolean Java_org_rocksdb_ReadOptions_tailing( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_managed( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->managed; } @@ -5821,7 +6397,7 @@ jboolean Java_org_rocksdb_ReadOptions_managed( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setManaged( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jmanaged) { + JNIEnv*, jobject, jlong jhandle, jboolean jmanaged) { reinterpret_cast(jhandle)->managed = static_cast(jmanaged); } @@ -5832,7 +6408,7 @@ void Java_org_rocksdb_ReadOptions_setManaged( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_totalOrderSeek( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->total_order_seek; } @@ -5842,7 +6418,7 @@ jboolean Java_org_rocksdb_ReadOptions_totalOrderSeek( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setTotalOrderSeek( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jtotal_order_seek) { + JNIEnv*, jobject, jlong jhandle, jboolean jtotal_order_seek) { reinterpret_cast(jhandle)->total_order_seek = static_cast(jtotal_order_seek); } @@ -5853,7 +6429,7 @@ void Java_org_rocksdb_ReadOptions_setTotalOrderSeek( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_prefixSameAsStart( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->prefix_same_as_start; } @@ -5863,7 +6439,7 @@ jboolean Java_org_rocksdb_ReadOptions_prefixSameAsStart( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setPrefixSameAsStart( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jprefix_same_as_start) { + JNIEnv*, jobject, jlong jhandle, jboolean jprefix_same_as_start) { reinterpret_cast(jhandle)->prefix_same_as_start = static_cast(jprefix_same_as_start); } @@ -5874,7 +6450,7 @@ void Java_org_rocksdb_ReadOptions_setPrefixSameAsStart( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_pinData( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle)->pin_data; } @@ -5884,7 +6460,7 @@ jboolean Java_org_rocksdb_ReadOptions_pinData( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setPinData( - JNIEnv* env, jobject jobj, jlong jhandle, jboolean jpin_data) { + JNIEnv*, jobject, jlong jhandle, jboolean jpin_data) { reinterpret_cast(jhandle)->pin_data = static_cast(jpin_data); } @@ -5895,7 +6471,7 @@ void Java_org_rocksdb_ReadOptions_setPinData( * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_backgroundPurgeOnIteratorCleanup( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->background_purge_on_iterator_cleanup); } @@ -5906,7 +6482,7 @@ jboolean Java_org_rocksdb_ReadOptions_backgroundPurgeOnIteratorCleanup( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setBackgroundPurgeOnIteratorCleanup( - JNIEnv* env, jobject jobj, jlong jhandle, + JNIEnv*, jobject, jlong jhandle, jboolean jbackground_purge_on_iterator_cleanup) { auto* opt = reinterpret_cast(jhandle); opt->background_purge_on_iterator_cleanup = @@ -5919,7 +6495,7 @@ void Java_org_rocksdb_ReadOptions_setBackgroundPurgeOnIteratorCleanup( * Signature: (J)J */ jlong Java_org_rocksdb_ReadOptions_readaheadSize( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->readahead_size); } @@ -5930,18 +6506,41 @@ jlong Java_org_rocksdb_ReadOptions_readaheadSize( * Signature: (JJ)V */ void Java_org_rocksdb_ReadOptions_setReadaheadSize( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jreadahead_size) { + JNIEnv*, jobject, jlong jhandle, jlong jreadahead_size) { auto* opt = reinterpret_cast(jhandle); opt->readahead_size = static_cast(jreadahead_size); } +/* + * Class: org_rocksdb_ReadOptions + * Method: maxSkippableInternalKeys + * Signature: (J)J + */ +jlong Java_org_rocksdb_ReadOptions_maxSkippableInternalKeys( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->max_skippable_internal_keys); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: setMaxSkippableInternalKeys + * Signature: (JJ)V + */ +void Java_org_rocksdb_ReadOptions_setMaxSkippableInternalKeys( + JNIEnv*, jobject, jlong jhandle, jlong jmax_skippable_internal_keys) { + auto* opt = reinterpret_cast(jhandle); + opt->max_skippable_internal_keys = + static_cast(jmax_skippable_internal_keys); +} + /* * Class: org_rocksdb_ReadOptions * Method: ignoreRangeDeletions * Signature: (J)Z */ jboolean Java_org_rocksdb_ReadOptions_ignoreRangeDeletions( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* opt = reinterpret_cast(jhandle); return static_cast(opt->ignore_range_deletions); } @@ -5952,8 +6551,7 @@ jboolean Java_org_rocksdb_ReadOptions_ignoreRangeDeletions( * Signature: (JZ)V */ void Java_org_rocksdb_ReadOptions_setIgnoreRangeDeletions( - JNIEnv* env, jobject jobj, jlong jhandle, - jboolean jignore_range_deletions) { + JNIEnv*, jobject, jlong jhandle, jboolean jignore_range_deletions) { auto* opt = reinterpret_cast(jhandle); opt->ignore_range_deletions = static_cast(jignore_range_deletions); } @@ -5964,7 +6562,7 @@ void Java_org_rocksdb_ReadOptions_setIgnoreRangeDeletions( * Signature: (JJ)V */ void Java_org_rocksdb_ReadOptions_setSnapshot( - JNIEnv* env, jobject jobj, jlong jhandle, jlong jsnapshot) { + JNIEnv*, jobject, jlong jhandle, jlong jsnapshot) { reinterpret_cast(jhandle)->snapshot = reinterpret_cast(jsnapshot); } @@ -5975,9 +6573,8 @@ void Java_org_rocksdb_ReadOptions_setSnapshot( * Signature: (J)J */ jlong Java_org_rocksdb_ReadOptions_snapshot( - JNIEnv* env, jobject jobj, jlong jhandle) { - auto& snapshot = - reinterpret_cast(jhandle)->snapshot; + JNIEnv*, jobject, jlong jhandle) { + auto& snapshot = reinterpret_cast(jhandle)->snapshot; return reinterpret_cast(snapshot); } @@ -5987,7 +6584,7 @@ jlong Java_org_rocksdb_ReadOptions_snapshot( * Signature: (J)B */ jbyte Java_org_rocksdb_ReadOptions_readTier( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return static_cast( reinterpret_cast(jhandle)->read_tier); } @@ -5998,11 +6595,92 @@ jbyte Java_org_rocksdb_ReadOptions_readTier( * Signature: (JB)V */ void Java_org_rocksdb_ReadOptions_setReadTier( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jread_tier) { + JNIEnv*, jobject, jlong jhandle, jbyte jread_tier) { reinterpret_cast(jhandle)->read_tier = static_cast(jread_tier); } +/* + * Class: org_rocksdb_ReadOptions + * Method: setIterateUpperBound + * Signature: (JJ)I + */ +void Java_org_rocksdb_ReadOptions_setIterateUpperBound( + JNIEnv*, jobject, jlong jhandle, jlong jupper_bound_slice_handle) { + reinterpret_cast(jhandle)->iterate_upper_bound = + reinterpret_cast(jupper_bound_slice_handle); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: iterateUpperBound + * Signature: (J)J + */ +jlong Java_org_rocksdb_ReadOptions_iterateUpperBound( + JNIEnv*, jobject, jlong jhandle) { + auto& upper_bound_slice_handle = + reinterpret_cast(jhandle)->iterate_upper_bound; + return reinterpret_cast(upper_bound_slice_handle); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: setIterateLowerBound + * Signature: (JJ)I + */ +void Java_org_rocksdb_ReadOptions_setIterateLowerBound( + JNIEnv*, jobject, jlong jhandle, jlong jlower_bound_slice_handle) { + reinterpret_cast(jhandle)->iterate_lower_bound = + reinterpret_cast(jlower_bound_slice_handle); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: iterateLowerBound + * Signature: (J)J + */ +jlong Java_org_rocksdb_ReadOptions_iterateLowerBound( + JNIEnv*, jobject, jlong jhandle) { + auto& lower_bound_slice_handle = + reinterpret_cast(jhandle)->iterate_lower_bound; + return reinterpret_cast(lower_bound_slice_handle); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: setTableFilter + * Signature: (JJ)V + */ +void Java_org_rocksdb_ReadOptions_setTableFilter( + JNIEnv*, jobject, jlong jhandle, jlong jjni_table_filter_handle) { + auto* opt = reinterpret_cast(jhandle); + auto* jni_table_filter = + reinterpret_cast(jjni_table_filter_handle); + opt->table_filter = jni_table_filter->GetTableFilterFunction(); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: setIterStartSeqnum + * Signature: (JJ)V + */ +void Java_org_rocksdb_ReadOptions_setIterStartSeqnum( + JNIEnv*, jobject, jlong jhandle, jlong jiter_start_seqnum) { + auto* opt = reinterpret_cast(jhandle); + opt->iter_start_seqnum = static_cast(jiter_start_seqnum); +} + +/* + * Class: org_rocksdb_ReadOptions + * Method: iterStartSeqnum + * Signature: (J)J + */ +jlong Java_org_rocksdb_ReadOptions_iterStartSeqnum( + JNIEnv*, jobject, jlong jhandle) { + auto* opt = reinterpret_cast(jhandle); + return static_cast(opt->iter_start_seqnum); +} + ///////////////////////////////////////////////////////////////////// // rocksdb::ComparatorOptions @@ -6012,7 +6690,7 @@ void Java_org_rocksdb_ReadOptions_setReadTier( * Signature: ()J */ jlong Java_org_rocksdb_ComparatorOptions_newComparatorOptions( - JNIEnv* env, jclass jcls) { + JNIEnv*, jclass) { auto* comparator_opt = new rocksdb::ComparatorJniCallbackOptions(); return reinterpret_cast(comparator_opt); } @@ -6023,9 +6701,9 @@ jlong Java_org_rocksdb_ComparatorOptions_newComparatorOptions( * Signature: (J)Z */ jboolean Java_org_rocksdb_ComparatorOptions_useAdaptiveMutex( - JNIEnv * env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { return reinterpret_cast(jhandle) - ->use_adaptive_mutex; + ->use_adaptive_mutex; } /* @@ -6034,9 +6712,9 @@ jboolean Java_org_rocksdb_ComparatorOptions_useAdaptiveMutex( * Signature: (JZ)V */ void Java_org_rocksdb_ComparatorOptions_setUseAdaptiveMutex( - JNIEnv * env, jobject jobj, jlong jhandle, jboolean juse_adaptive_mutex) { + JNIEnv*, jobject, jlong jhandle, jboolean juse_adaptive_mutex) { reinterpret_cast(jhandle) - ->use_adaptive_mutex = static_cast(juse_adaptive_mutex); + ->use_adaptive_mutex = static_cast(juse_adaptive_mutex); } /* @@ -6045,7 +6723,7 @@ void Java_org_rocksdb_ComparatorOptions_setUseAdaptiveMutex( * Signature: (J)V */ void Java_org_rocksdb_ComparatorOptions_disposeInternal( - JNIEnv * env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* comparator_opt = reinterpret_cast(jhandle); assert(comparator_opt != nullptr); @@ -6061,7 +6739,7 @@ void Java_org_rocksdb_ComparatorOptions_disposeInternal( * Signature: ()J */ jlong Java_org_rocksdb_FlushOptions_newFlushOptions( - JNIEnv* env, jclass jcls) { + JNIEnv*, jclass) { auto* flush_opt = new rocksdb::FlushOptions(); return reinterpret_cast(flush_opt); } @@ -6072,9 +6750,9 @@ jlong Java_org_rocksdb_FlushOptions_newFlushOptions( * Signature: (JZ)V */ void Java_org_rocksdb_FlushOptions_setWaitForFlush( - JNIEnv * env, jobject jobj, jlong jhandle, jboolean jwait) { - reinterpret_cast(jhandle) - ->wait = static_cast(jwait); + JNIEnv*, jobject, jlong jhandle, jboolean jwait) { + reinterpret_cast(jhandle)->wait = + static_cast(jwait); } /* @@ -6083,9 +6761,30 @@ void Java_org_rocksdb_FlushOptions_setWaitForFlush( * Signature: (J)Z */ jboolean Java_org_rocksdb_FlushOptions_waitForFlush( - JNIEnv * env, jobject jobj, jlong jhandle) { - return reinterpret_cast(jhandle) - ->wait; + JNIEnv*, jobject, jlong jhandle) { + return reinterpret_cast(jhandle)->wait; +} + +/* + * Class: org_rocksdb_FlushOptions + * Method: setAllowWriteStall + * Signature: (JZ)V + */ +void Java_org_rocksdb_FlushOptions_setAllowWriteStall( + JNIEnv*, jobject, jlong jhandle, jboolean jallow_write_stall) { + auto* flush_options = reinterpret_cast(jhandle); + flush_options->allow_write_stall = jallow_write_stall == JNI_TRUE; +} + +/* + * Class: org_rocksdb_FlushOptions + * Method: allowWriteStall + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_FlushOptions_allowWriteStall( + JNIEnv*, jobject, jlong jhandle) { + auto* flush_options = reinterpret_cast(jhandle); + return static_cast(flush_options->allow_write_stall); } /* @@ -6094,7 +6793,7 @@ jboolean Java_org_rocksdb_FlushOptions_waitForFlush( * Signature: (J)V */ void Java_org_rocksdb_FlushOptions_disposeInternal( - JNIEnv * env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* flush_opt = reinterpret_cast(jhandle); assert(flush_opt != nullptr); delete flush_opt; diff --git a/thirdparty/rocksdb/java/rocksjni/options_util.cc b/thirdparty/rocksdb/java/rocksjni/options_util.cc new file mode 100644 index 0000000000..7dd0078455 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/options_util.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ and enables +// calling C++ rocksdb::OptionsUtil methods from Java side. + +#include +#include + +#include "include/org_rocksdb_OptionsUtil.h" + +#include "rocksdb/db.h" +#include "rocksdb/env.h" +#include "rocksdb/utilities/options_util.h" +#include "rocksjni/portal.h" + +void build_column_family_descriptor_list( + JNIEnv* env, jobject jcfds, + std::vector& cf_descs) { + jmethodID add_mid = rocksdb::ListJni::getListAddMethodId(env); + if (add_mid == nullptr) { + // exception occurred accessing method + return; + } + + // Column family descriptor + for (rocksdb::ColumnFamilyDescriptor& cfd : cf_descs) { + // Construct a ColumnFamilyDescriptor java object + jobject jcfd = rocksdb::ColumnFamilyDescriptorJni::construct(env, &cfd); + if (env->ExceptionCheck()) { + // exception occurred constructing object + if (jcfd != nullptr) { + env->DeleteLocalRef(jcfd); + } + return; + } + + // Add the object to java list. + jboolean rs = env->CallBooleanMethod(jcfds, add_mid, jcfd); + if (env->ExceptionCheck() || rs == JNI_FALSE) { + // exception occurred calling method, or could not add + if (jcfd != nullptr) { + env->DeleteLocalRef(jcfd); + } + return; + } + } +} + +/* + * Class: org_rocksdb_OptionsUtil + * Method: loadLatestOptions + * Signature: (Ljava/lang/String;JLjava/util/List;Z)V + */ +void Java_org_rocksdb_OptionsUtil_loadLatestOptions( + JNIEnv* env, jclass /*jcls*/, jstring jdbpath, jlong jenv_handle, + jlong jdb_opts_handle, jobject jcfds, jboolean ignore_unknown_options) { + jboolean has_exception = JNI_FALSE; + auto db_path = rocksdb::JniUtil::copyStdString(env, jdbpath, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return; + } + std::vector cf_descs; + rocksdb::Status s = rocksdb::LoadLatestOptions( + db_path, reinterpret_cast(jenv_handle), + reinterpret_cast(jdb_opts_handle), &cf_descs, + ignore_unknown_options); + if (!s.ok()) { + // error, raise an exception + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } else { + build_column_family_descriptor_list(env, jcfds, cf_descs); + } +} + +/* + * Class: org_rocksdb_OptionsUtil + * Method: loadOptionsFromFile + * Signature: (Ljava/lang/String;JJLjava/util/List;Z)V + */ +void Java_org_rocksdb_OptionsUtil_loadOptionsFromFile( + JNIEnv* env, jclass /*jcls*/, jstring jopts_file_name, jlong jenv_handle, + jlong jdb_opts_handle, jobject jcfds, jboolean ignore_unknown_options) { + jboolean has_exception = JNI_FALSE; + auto opts_file_name = rocksdb::JniUtil::copyStdString(env, jopts_file_name, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return; + } + std::vector cf_descs; + rocksdb::Status s = rocksdb::LoadOptionsFromFile( + opts_file_name, reinterpret_cast(jenv_handle), + reinterpret_cast(jdb_opts_handle), &cf_descs, + ignore_unknown_options); + if (!s.ok()) { + // error, raise an exception + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } else { + build_column_family_descriptor_list(env, jcfds, cf_descs); + } +} + +/* + * Class: org_rocksdb_OptionsUtil + * Method: getLatestOptionsFileName + * Signature: (Ljava/lang/String;J)Ljava/lang/String; + */ +jstring Java_org_rocksdb_OptionsUtil_getLatestOptionsFileName( + JNIEnv* env, jclass /*jcls*/, jstring jdbpath, jlong jenv_handle) { + jboolean has_exception = JNI_FALSE; + auto db_path = rocksdb::JniUtil::copyStdString(env, jdbpath, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return nullptr; + } + std::string options_file_name; + rocksdb::Status s = rocksdb::GetLatestOptionsFileName( + db_path, reinterpret_cast(jenv_handle), + &options_file_name); + if (!s.ok()) { + // error, raise an exception + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; + } else { + return env->NewStringUTF(options_file_name.c_str()); + } +} diff --git a/thirdparty/rocksdb/java/rocksjni/persistent_cache.cc b/thirdparty/rocksdb/java/rocksjni/persistent_cache.cc new file mode 100644 index 0000000000..2b6fc60ba2 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/persistent_cache.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::PersistentCache. + +#include +#include + +#include "include/org_rocksdb_PersistentCache.h" +#include "rocksdb/persistent_cache.h" +#include "loggerjnicallback.h" +#include "portal.h" + +/* + * Class: org_rocksdb_PersistentCache + * Method: newPersistentCache + * Signature: (JLjava/lang/String;JJZ)J + */ +jlong Java_org_rocksdb_PersistentCache_newPersistentCache( + JNIEnv* env, jclass, jlong jenv_handle, jstring jpath, + jlong jsz, jlong jlogger_handle, jboolean joptimized_for_nvm) { + auto* rocks_env = reinterpret_cast(jenv_handle); + jboolean has_exception = JNI_FALSE; + std::string path = rocksdb::JniUtil::copyStdString(env, jpath, &has_exception); + if (has_exception == JNI_TRUE) { + return 0; + } + auto* logger = + reinterpret_cast*>(jlogger_handle); + auto* cache = new std::shared_ptr(nullptr); + rocksdb::Status s = rocksdb::NewPersistentCache( + rocks_env, path, static_cast(jsz), *logger, + static_cast(joptimized_for_nvm), cache); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } + return reinterpret_cast(cache); +} + +/* + * Class: org_rocksdb_PersistentCache + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_PersistentCache_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* cache = + reinterpret_cast*>(jhandle); + delete cache; // delete std::shared_ptr +} diff --git a/thirdparty/rocksdb/java/rocksjni/portal.h b/thirdparty/rocksdb/java/rocksjni/portal.h index ed671ce6e9..70e67653ec 100644 --- a/thirdparty/rocksdb/java/rocksjni/portal.h +++ b/thirdparty/rocksdb/java/rocksjni/portal.h @@ -10,20 +10,34 @@ #ifndef JAVA_ROCKSJNI_PORTAL_H_ #define JAVA_ROCKSJNI_PORTAL_H_ -#include +#include +#include #include #include +#include +#include #include +#include #include +#include #include #include "rocksdb/db.h" #include "rocksdb/filter_policy.h" +#include "rocksdb/rate_limiter.h" #include "rocksdb/status.h" +#include "rocksdb/table.h" #include "rocksdb/utilities/backupable_db.h" +#include "rocksdb/utilities/memory_util.h" +#include "rocksdb/utilities/transaction_db.h" #include "rocksdb/utilities/write_batch_with_index.h" +#include "rocksjni/compaction_filter_factory_jnicallback.h" #include "rocksjni/comparatorjnicallback.h" #include "rocksjni/loggerjnicallback.h" +#include "rocksjni/table_filter_jnicallback.h" +#include "rocksjni/trace_writer_jnicallback.h" +#include "rocksjni/transaction_notifier_jnicallback.h" +#include "rocksjni/wal_filter_jnicallback.h" #include "rocksjni/writebatchhandlerjnicallback.h" // Remove macro on windows @@ -33,15 +47,6 @@ namespace rocksdb { -// Detect if jlong overflows size_t -inline Status check_if_jlong_fits_size_t(const jlong& jvalue) { - Status s = Status::OK(); - if (static_cast(jvalue) > std::numeric_limits::max()) { - s = Status::InvalidArgument(Slice("jlong overflows 32 bit value.")); - } - return s; -} - class JavaClass { public: /** @@ -150,11 +155,12 @@ template class JavaException : public JavaClass { } }; -// The portal class for org.rocksdb.RocksDB -class RocksDBJni : public RocksDBNativeClass { +// The portal class for java.lang.IllegalArgumentException +class IllegalArgumentExceptionJni : + public JavaException { public: /** - * Get the Java Class org.rocksdb.RocksDB + * Get the Java Class java.lang.IllegalArgumentException * * @param env A pointer to the Java environment * @@ -163,7 +169,135 @@ class RocksDBJni : public RocksDBNativeClass { * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksDB"); + return JavaException::getJClass(env, "java/lang/IllegalArgumentException"); + } + + /** + * Create and throw a Java IllegalArgumentException with the provided status + * + * If s.ok() == true, then this function will not throw any exception. + * + * @param env A pointer to the Java environment + * @param s The status for the exception + * + * @return true if an exception was thrown, false otherwise + */ + static bool ThrowNew(JNIEnv* env, const Status& s) { + assert(!s.ok()); + if (s.ok()) { + return false; + } + + // get the IllegalArgumentException class + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + std::cerr << "IllegalArgumentExceptionJni::ThrowNew/class - Error: unexpected exception!" << std::endl; + return env->ExceptionCheck(); + } + + return JavaException::ThrowNew(env, s.ToString()); + } +}; + +// The portal class for org.rocksdb.Status.Code +class CodeJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.Status.Code + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/Status$Code"); + } + + /** + * Get the Java Method: Status.Code#getValue + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getValueMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "getValue", "()b"); + assert(mid != nullptr); + return mid; + } +}; + +// The portal class for org.rocksdb.Status.SubCode +class SubCodeJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.Status.SubCode + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/Status$SubCode"); + } + + /** + * Get the Java Method: Status.SubCode#getValue + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getValueMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "getValue", "()b"); + assert(mid != nullptr); + return mid; + } + + static rocksdb::Status::SubCode toCppSubCode(const jbyte jsub_code) { + switch (jsub_code) { + case 0x0: + return rocksdb::Status::SubCode::kNone; + case 0x1: + return rocksdb::Status::SubCode::kMutexTimeout; + case 0x2: + return rocksdb::Status::SubCode::kLockTimeout; + case 0x3: + return rocksdb::Status::SubCode::kLockLimit; + case 0x4: + return rocksdb::Status::SubCode::kNoSpace; + case 0x5: + return rocksdb::Status::SubCode::kDeadlock; + case 0x6: + return rocksdb::Status::SubCode::kStaleFile; + case 0x7: + return rocksdb::Status::SubCode::kMemoryLimit; + + case 0x7F: + default: + return rocksdb::Status::SubCode::kNone; + } } }; @@ -183,6 +317,69 @@ class StatusJni : public RocksDBNativeClass { return RocksDBNativeClass::getJClass(env, "org/rocksdb/Status"); } + /** + * Get the Java Method: Status#getCode + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getCodeMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "getCode", "()Lorg/rocksdb/Status$Code;"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: Status#getSubCode + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getSubCodeMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "getSubCode", "()Lorg/rocksdb/Status$SubCode;"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: Status#getState + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getStateMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "getState", "()Ljava/lang/String;"); + assert(mid != nullptr); + return mid; + } + /** * Create a new Java org.rocksdb.Status object with the same properties as * the provided C++ rocksdb::Status object @@ -287,12 +484,204 @@ class StatusJni : public RocksDBNativeClass { return 0x2; case rocksdb::Status::SubCode::kLockLimit: return 0x3; - case rocksdb::Status::SubCode::kMaxSubCode: - return 0x7E; + case rocksdb::Status::SubCode::kNoSpace: + return 0x4; + case rocksdb::Status::SubCode::kDeadlock: + return 0x5; + case rocksdb::Status::SubCode::kStaleFile: + return 0x6; + case rocksdb::Status::SubCode::kMemoryLimit: + return 0x7; default: return 0x7F; // undefined } } + + static std::unique_ptr toCppStatus( + const jbyte jcode_value, const jbyte jsub_code_value) { + std::unique_ptr status; + switch (jcode_value) { + case 0x0: + //Ok + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::OK())); + break; + case 0x1: + //NotFound + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::NotFound( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x2: + //Corruption + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::Corruption( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x3: + //NotSupported + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::NotSupported( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x4: + //InvalidArgument + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::InvalidArgument( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x5: + //IOError + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::IOError( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x6: + //MergeInProgress + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::MergeInProgress( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x7: + //Incomplete + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::Incomplete( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x8: + //ShutdownInProgress + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::ShutdownInProgress( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x9: + //TimedOut + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::TimedOut( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0xA: + //Aborted + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::Aborted( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0xB: + //Busy + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::Busy( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0xC: + //Expired + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::Expired( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0xD: + //TryAgain + status = std::unique_ptr( + new rocksdb::Status(rocksdb::Status::TryAgain( + rocksdb::SubCodeJni::toCppSubCode(jsub_code_value)))); + break; + case 0x7F: + default: + return nullptr; + } + return status; + } + + // Returns the equivalent rocksdb::Status for the Java org.rocksdb.Status + static std::unique_ptr toCppStatus(JNIEnv* env, const jobject jstatus) { + jmethodID mid_code = getCodeMethod(env); + if (mid_code == nullptr) { + // exception occurred + return nullptr; + } + jobject jcode = env->CallObjectMethod(jstatus, mid_code); + if (env->ExceptionCheck()) { + // exception occurred + return nullptr; + } + + jmethodID mid_code_value = rocksdb::CodeJni::getValueMethod(env); + if (mid_code_value == nullptr) { + // exception occurred + return nullptr; + } + jbyte jcode_value = env->CallByteMethod(jcode, mid_code_value); + if (env->ExceptionCheck()) { + // exception occurred + if (jcode != nullptr) { + env->DeleteLocalRef(jcode); + } + return nullptr; + } + + jmethodID mid_subCode = getSubCodeMethod(env); + if (mid_subCode == nullptr) { + // exception occurred + return nullptr; + } + jobject jsubCode = env->CallObjectMethod(jstatus, mid_subCode); + if (env->ExceptionCheck()) { + // exception occurred + if (jcode != nullptr) { + env->DeleteLocalRef(jcode); + } + return nullptr; + } + + jbyte jsub_code_value = 0x0; // None + if (jsubCode != nullptr) { + jmethodID mid_subCode_value = rocksdb::SubCodeJni::getValueMethod(env); + if (mid_subCode_value == nullptr) { + // exception occurred + return nullptr; + } + jsub_code_value = env->CallByteMethod(jsubCode, mid_subCode_value); + if (env->ExceptionCheck()) { + // exception occurred + if (jcode != nullptr) { + env->DeleteLocalRef(jcode); + } + return nullptr; + } + } + + jmethodID mid_state = getStateMethod(env); + if (mid_state == nullptr) { + // exception occurred + return nullptr; + } + jobject jstate = env->CallObjectMethod(jstatus, mid_state); + if (env->ExceptionCheck()) { + // exception occurred + if (jsubCode != nullptr) { + env->DeleteLocalRef(jsubCode); + } + if (jcode != nullptr) { + env->DeleteLocalRef(jcode); + } + return nullptr; + } + + std::unique_ptr status = + toCppStatus(jcode_value, jsub_code_value); + + // delete all local refs + if (jstate != nullptr) { + env->DeleteLocalRef(jstate); + } + if (jsubCode != nullptr) { + env->DeleteLocalRef(jsubCode); + } + if (jcode != nullptr) { + env->DeleteLocalRef(jcode); + } + + return status; + } }; // The portal class for org.rocksdb.RocksDBException @@ -324,6 +713,20 @@ class RocksDBExceptionJni : return JavaException::ThrowNew(env, msg); } + /** + * Create and throw a Java RocksDBException with the provided status + * + * If s->ok() == true, then this function will not throw any exception. + * + * @param env A pointer to the Java environment + * @param s The status for the exception + * + * @return true if an exception was thrown, false otherwise + */ + static bool ThrowNew(JNIEnv* env, std::unique_ptr& s) { + return rocksdb::RocksDBExceptionJni::ThrowNew(env, *(s.get())); + } + /** * Create and throw a Java RocksDBException with the provided status * @@ -335,7 +738,6 @@ class RocksDBExceptionJni : * @return true if an exception was thrown, false otherwise */ static bool ThrowNew(JNIEnv* env, const Status& s) { - assert(!s.ok()); if (s.ok()) { return false; } @@ -500,60 +902,61 @@ class RocksDBExceptionJni : return true; } -}; -// The portal class for java.lang.IllegalArgumentException -class IllegalArgumentExceptionJni : - public JavaException { - public: /** - * Get the Java Class java.lang.IllegalArgumentException + * Get the Java Method: RocksDBException#getStatus * * @param env A pointer to the Java environment * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved */ - static jclass getJClass(JNIEnv* env) { - return JavaException::getJClass(env, "java/lang/IllegalArgumentException"); + static jmethodID getStatusMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "getStatus", "()Lorg/rocksdb/Status;"); + assert(mid != nullptr); + return mid; } - /** - * Create and throw a Java IllegalArgumentException with the provided status - * - * If s.ok() == true, then this function will not throw any exception. - * - * @param env A pointer to the Java environment - * @param s The status for the exception - * - * @return true if an exception was thrown, false otherwise - */ - static bool ThrowNew(JNIEnv* env, const Status& s) { - assert(!s.ok()); - if (s.ok()) { - return false; + static std::unique_ptr toCppStatus( + JNIEnv* env, jthrowable jrocksdb_exception) { + if(!env->IsInstanceOf(jrocksdb_exception, getJClass(env))) { + // not an instance of RocksDBException + return nullptr; } - // get the IllegalArgumentException class - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class - std::cerr << "IllegalArgumentExceptionJni::ThrowNew/class - Error: unexpected exception!" << std::endl; - return env->ExceptionCheck(); + // get the java status object + jmethodID mid = getStatusMethod(env); + if(mid == nullptr) { + // exception occurred accessing class or method + return nullptr; } - return JavaException::ThrowNew(env, s.ToString()); + jobject jstatus = env->CallObjectMethod(jrocksdb_exception, mid); + if(env->ExceptionCheck()) { + // exception occurred + return nullptr; + } + + if(jstatus == nullptr) { + return nullptr; // no status available + } + + return rocksdb::StatusJni::toCppStatus(env, jstatus); } }; - -// The portal class for org.rocksdb.Options -class OptionsJni : public RocksDBNativeClass< - rocksdb::Options*, OptionsJni> { +// The portal class for java.util.List +class ListJni : public JavaClass { public: /** - * Get the Java Class org.rocksdb.Options + * Get the Java Class java.util.List * * @param env A pointer to the Java environment * @@ -561,17 +964,12 @@ class OptionsJni : public RocksDBNativeClass< * ClassFormatError, ClassCircularityError, NoClassDefFoundError, * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Options"); + static jclass getListClass(JNIEnv* env) { + return JavaClass::getJClass(env, "java/util/List"); } -}; -// The portal class for org.rocksdb.DBOptions -class DBOptionsJni : public RocksDBNativeClass< - rocksdb::DBOptions*, DBOptionsJni> { - public: /** - * Get the Java Class org.rocksdb.DBOptions + * Get the Java Class java.util.ArrayList * * @param env A pointer to the Java environment * @@ -579,15 +977,12 @@ class DBOptionsJni : public RocksDBNativeClass< * ClassFormatError, ClassCircularityError, NoClassDefFoundError, * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/DBOptions"); + static jclass getArrayListClass(JNIEnv* env) { + return JavaClass::getJClass(env, "java/util/ArrayList"); } -}; -class ColumnFamilyDescriptorJni : public JavaClass { - public: /** - * Get the Java Class org.rocksdb.ColumnFamilyDescriptor + * Get the Java Class java.util.Iterator * * @param env A pointer to the Java environment * @@ -595,115 +990,119 @@ class ColumnFamilyDescriptorJni : public JavaClass { * ClassFormatError, ClassCircularityError, NoClassDefFoundError, * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/ColumnFamilyDescriptor"); + static jclass getIteratorClass(JNIEnv* env) { + return JavaClass::getJClass(env, "java/util/Iterator"); } /** - * Get the Java Method: ColumnFamilyDescriptor#columnFamilyName + * Get the Java Method: List#iterator * * @param env A pointer to the Java environment * * @return The Java Method ID or nullptr if the class or method id could not * be retieved */ - static jmethodID getColumnFamilyNameMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + static jmethodID getIteratorMethod(JNIEnv* env) { + jclass jlist_clazz = getListClass(env); + if(jlist_clazz == nullptr) { // exception occurred accessing class return nullptr; } static jmethodID mid = - env->GetMethodID(jclazz, "columnFamilyName", "()[B"); + env->GetMethodID(jlist_clazz, "iterator", "()Ljava/util/Iterator;"); assert(mid != nullptr); return mid; } /** - * Get the Java Method: ColumnFamilyDescriptor#columnFamilyOptions + * Get the Java Method: Iterator#hasNext * * @param env A pointer to the Java environment * * @return The Java Method ID or nullptr if the class or method id could not * be retieved */ - static jmethodID getColumnFamilyOptionsMethod(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + static jmethodID getHasNextMethod(JNIEnv* env) { + jclass jiterator_clazz = getIteratorClass(env); + if(jiterator_clazz == nullptr) { // exception occurred accessing class return nullptr; } - static jmethodID mid = - env->GetMethodID(jclazz, "columnFamilyOptions", - "()Lorg/rocksdb/ColumnFamilyOptions;"); + static jmethodID mid = env->GetMethodID(jiterator_clazz, "hasNext", "()Z"); assert(mid != nullptr); return mid; } -}; -// The portal class for org.rocksdb.ColumnFamilyOptions -class ColumnFamilyOptionsJni : public RocksDBNativeClass< - rocksdb::ColumnFamilyOptions*, ColumnFamilyOptionsJni> { - public: /** - * Get the Java Class org.rocksdb.ColumnFamilyOptions + * Get the Java Method: Iterator#next * * @param env A pointer to the Java environment * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/ColumnFamilyOptions"); + static jmethodID getNextMethod(JNIEnv* env) { + jclass jiterator_clazz = getIteratorClass(env); + if(jiterator_clazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jiterator_clazz, "next", "()Ljava/lang/Object;"); + assert(mid != nullptr); + return mid; } -}; -// The portal class for org.rocksdb.WriteOptions -class WriteOptionsJni : public RocksDBNativeClass< - rocksdb::WriteOptions*, WriteOptionsJni> { - public: /** - * Get the Java Class org.rocksdb.WriteOptions + * Get the Java Method: ArrayList constructor * * @param env A pointer to the Java environment * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteOptions"); + static jmethodID getArrayListConstructorMethodId(JNIEnv* env) { + jclass jarray_list_clazz = getArrayListClass(env); + if(jarray_list_clazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + static jmethodID mid = + env->GetMethodID(jarray_list_clazz, "", "(I)V"); + assert(mid != nullptr); + return mid; } -}; -// The portal class for org.rocksdb.ReadOptions -class ReadOptionsJni : public RocksDBNativeClass< - rocksdb::ReadOptions*, ReadOptionsJni> { - public: /** - * Get the Java Class org.rocksdb.ReadOptions + * Get the Java Method: List#add * * @param env A pointer to the Java environment * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/ReadOptions"); + static jmethodID getListAddMethodId(JNIEnv* env) { + jclass jlist_clazz = getListClass(env); + if(jlist_clazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jlist_clazz, "add", "(Ljava/lang/Object;)Z"); + assert(mid != nullptr); + return mid; } }; -// The portal class for org.rocksdb.WriteBatch -class WriteBatchJni : public RocksDBNativeClass< - rocksdb::WriteBatch*, WriteBatchJni> { +// The portal class for java.lang.Byte +class ByteJni : public JavaClass { public: /** - * Get the Java Class org.rocksdb.WriteBatch + * Get the Java Class java.lang.Byte * * @param env A pointer to the Java environment * @@ -712,17 +1111,11 @@ class WriteBatchJni : public RocksDBNativeClass< * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteBatch"); + return JavaClass::getJClass(env, "java/lang/Byte"); } -}; -// The portal class for org.rocksdb.WriteBatch.Handler -class WriteBatchHandlerJni : public RocksDBNativeClass< - const rocksdb::WriteBatchHandlerJniCallback*, - WriteBatchHandlerJni> { - public: /** - * Get the Java Class org.rocksdb.WriteBatch.Handler + * Get the Java Class byte[] * * @param env A pointer to the Java environment * @@ -730,156 +1123,128 @@ class WriteBatchHandlerJni : public RocksDBNativeClass< * ClassFormatError, ClassCircularityError, NoClassDefFoundError, * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/WriteBatch$Handler"); + static jclass getArrayJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "[B"); } /** - * Get the Java Method: WriteBatch.Handler#put + * Creates a new 2-dimensional Java Byte Array byte[][] * * @param env A pointer to the Java environment + * @param len The size of the first dimension * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved + * @return A reference to the Java byte[][] or nullptr if an exception occurs */ - static jmethodID getPutMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + static jobjectArray new2dByteArray(JNIEnv* env, const jsize len) { + jclass clazz = getArrayJClass(env); + if(clazz == nullptr) { // exception occurred accessing class return nullptr; } - static jmethodID mid = env->GetMethodID(jclazz, "put", "([B[B)V"); - assert(mid != nullptr); - return mid; + return env->NewObjectArray(len, clazz, nullptr); } /** - * Get the Java Method: WriteBatch.Handler#merge + * Get the Java Method: Byte#byteValue * * @param env A pointer to the Java environment * * @return The Java Method ID or nullptr if the class or method id could not - * be retieved + * be retrieved */ - static jmethodID getMergeMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + static jmethodID getByteValueMethod(JNIEnv* env) { + jclass clazz = getJClass(env); + if(clazz == nullptr) { // exception occurred accessing class return nullptr; } - static jmethodID mid = env->GetMethodID(jclazz, "merge", "([B[B)V"); + static jmethodID mid = env->GetMethodID(clazz, "byteValue", "()B"); assert(mid != nullptr); return mid; } /** - * Get the Java Method: WriteBatch.Handler#delete + * Calls the Java Method: Byte#valueOf, returning a constructed Byte jobject * * @param env A pointer to the Java environment * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved + * @return A constructing Byte object or nullptr if the class or method id could not + * be retrieved, or an exception occurred */ - static jmethodID getDeleteMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + static jobject valueOf(JNIEnv* env, jbyte jprimitive_byte) { + jclass clazz = getJClass(env); + if (clazz == nullptr) { // exception occurred accessing class return nullptr; } - static jmethodID mid = env->GetMethodID(jclazz, "delete", "([B)V"); - assert(mid != nullptr); - return mid; + static jmethodID mid = + env->GetStaticMethodID(clazz, "valueOf", "(B)Ljava/lang/Byte;"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + const jobject jbyte_obj = + env->CallStaticObjectMethod(clazz, mid, jprimitive_byte); + if (env->ExceptionCheck()) { + // exception occurred + return nullptr; + } + + return jbyte_obj; } +}; + +// The portal class for java.lang.Integer +class IntegerJni : public JavaClass { + public: /** - * Get the Java Method: WriteBatch.Handler#deleteRange + * Get the Java Class java.lang.Integer * * @param env A pointer to the Java environment * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jmethodID getDeleteRangeMethodId(JNIEnv* env) { + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "java/lang/Integer"); + } + + static jobject valueOf(JNIEnv* env, jint jprimitive_int) { jclass jclazz = getJClass(env); if (jclazz == nullptr) { // exception occurred accessing class return nullptr; } - static jmethodID mid = env->GetMethodID(jclazz, "deleteRange", "([B[B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#logData - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getLogDataMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class + jmethodID mid = + env->GetStaticMethodID(jclazz, "valueOf", "(I)Ljava/lang/Integer;"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError return nullptr; } - static jmethodID mid = env->GetMethodID(jclazz, "logData", "([B)V"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: WriteBatch.Handler#shouldContinue - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getContinueMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class + const jobject jinteger_obj = + env->CallStaticObjectMethod(jclazz, mid, jprimitive_int); + if (env->ExceptionCheck()) { + // exception occurred return nullptr; } - static jmethodID mid = env->GetMethodID(jclazz, "shouldContinue", "()Z"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.WriteBatchWithIndex -class WriteBatchWithIndexJni : public RocksDBNativeClass< - rocksdb::WriteBatchWithIndex*, WriteBatchWithIndexJni> { - public: - /** - * Get the Java Class org.rocksdb.WriteBatchWithIndex - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/WriteBatchWithIndex"); + return jinteger_obj; } }; -// The portal class for org.rocksdb.HistogramData -class HistogramDataJni : public JavaClass { +// The portal class for java.lang.Long +class LongJni : public JavaClass { public: /** - * Get the Java Class org.rocksdb.HistogramData + * Get the Java Class java.lang.Long * * @param env A pointer to the Java environment * @@ -888,165 +1253,39 @@ class HistogramDataJni : public JavaClass { * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/HistogramData"); + return JavaClass::getJClass(env, "java/lang/Long"); } - /** - * Get the Java Method: HistogramData constructor - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getConstructorMethodId(JNIEnv* env) { + static jobject valueOf(JNIEnv* env, jlong jprimitive_long) { jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + if (jclazz == nullptr) { // exception occurred accessing class return nullptr; } - static jmethodID mid = env->GetMethodID(jclazz, "", "(DDDDD)V"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.BackupableDBOptions -class BackupableDBOptionsJni : public RocksDBNativeClass< - rocksdb::BackupableDBOptions*, BackupableDBOptionsJni> { - public: - /** - * Get the Java Class org.rocksdb.BackupableDBOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/BackupableDBOptions"); - } -}; - -// The portal class for org.rocksdb.BackupEngine -class BackupEngineJni : public RocksDBNativeClass< - rocksdb::BackupEngine*, BackupEngineJni> { - public: - /** - * Get the Java Class org.rocksdb.BackupableEngine - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/BackupEngine"); - } -}; - -// The portal class for org.rocksdb.RocksIterator -class IteratorJni : public RocksDBNativeClass< - rocksdb::Iterator*, IteratorJni> { - public: - /** - * Get the Java Class org.rocksdb.RocksIterator - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksIterator"); - } -}; - -// The portal class for org.rocksdb.Filter -class FilterJni : public RocksDBNativeClass< - std::shared_ptr*, FilterJni> { - public: - /** - * Get the Java Class org.rocksdb.Filter - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Filter"); - } -}; - -// The portal class for org.rocksdb.ColumnFamilyHandle -class ColumnFamilyHandleJni : public RocksDBNativeClass< - rocksdb::ColumnFamilyHandle*, ColumnFamilyHandleJni> { - public: - /** - * Get the Java Class org.rocksdb.ColumnFamilyHandle - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/ColumnFamilyHandle"); - } -}; + jmethodID mid = + env->GetStaticMethodID(jclazz, "valueOf", "(J)Ljava/lang/Long;"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } -// The portal class for org.rocksdb.FlushOptions -class FlushOptionsJni : public RocksDBNativeClass< - rocksdb::FlushOptions*, FlushOptionsJni> { - public: - /** - * Get the Java Class org.rocksdb.FlushOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/FlushOptions"); - } -}; + const jobject jlong_obj = + env->CallStaticObjectMethod(jclazz, mid, jprimitive_long); + if (env->ExceptionCheck()) { + // exception occurred + return nullptr; + } -// The portal class for org.rocksdb.ComparatorOptions -class ComparatorOptionsJni : public RocksDBNativeClass< - rocksdb::ComparatorJniCallbackOptions*, ComparatorOptionsJni> { - public: - /** - * Get the Java Class org.rocksdb.ComparatorOptions - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/ComparatorOptions"); + return jlong_obj; } }; -// The portal class for org.rocksdb.AbstractComparator -class AbstractComparatorJni : public RocksDBNativeClass< - const rocksdb::BaseComparatorJniCallback*, - AbstractComparatorJni> { - public: +// The portal class for java.lang.StringBuilder +class StringBuilderJni : public JavaClass { + public: /** - * Get the Java Class org.rocksdb.AbstractComparator + * Get the Java Class java.lang.StringBuilder * * @param env A pointer to the Java environment * @@ -1055,19 +1294,18 @@ class AbstractComparatorJni : public RocksDBNativeClass< * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, - "org/rocksdb/AbstractComparator"); + return JavaClass::getJClass(env, "java/lang/StringBuilder"); } /** - * Get the Java Method: Comparator#name + * Get the Java Method: StringBuilder#append * * @param env A pointer to the Java environment * * @return The Java Method ID or nullptr if the class or method id could not * be retieved */ - static jmethodID getNameMethodId(JNIEnv* env) { + static jmethodID getListAddMethodId(JNIEnv* env) { jclass jclazz = getJClass(env); if(jclazz == nullptr) { // exception occurred accessing class @@ -1075,614 +1313,773 @@ class AbstractComparatorJni : public RocksDBNativeClass< } static jmethodID mid = - env->GetMethodID(jclazz, "name", "()Ljava/lang/String;"); + env->GetMethodID(jclazz, "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); assert(mid != nullptr); return mid; } /** - * Get the Java Method: Comparator#compare + * Appends a C-style string to a StringBuilder * * @param env A pointer to the Java environment + * @param jstring_builder Reference to a java.lang.StringBuilder + * @param c_str A C-style string to append to the StringBuilder * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved + * @return A reference to the updated StringBuilder, or a nullptr if + * an exception occurs */ - static jmethodID getCompareMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class + static jobject append(JNIEnv* env, jobject jstring_builder, + const char* c_str) { + jmethodID mid = getListAddMethodId(env); + if(mid == nullptr) { + // exception occurred accessing class or method return nullptr; } - static jmethodID mid = - env->GetMethodID(jclazz, "compare", - "(Lorg/rocksdb/AbstractSlice;Lorg/rocksdb/AbstractSlice;)I"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Comparator#findShortestSeparator - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getFindShortestSeparatorMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class + jstring new_value_str = env->NewStringUTF(c_str); + if(new_value_str == nullptr) { + // exception thrown: OutOfMemoryError return nullptr; } - static jmethodID mid = - env->GetMethodID(jclazz, "findShortestSeparator", - "(Ljava/lang/String;Lorg/rocksdb/AbstractSlice;)Ljava/lang/String;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: Comparator#findShortSuccessor - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getFindShortSuccessorMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class + jobject jresult_string_builder = + env->CallObjectMethod(jstring_builder, mid, new_value_str); + if(env->ExceptionCheck()) { + // exception occurred + env->DeleteLocalRef(new_value_str); return nullptr; } - static jmethodID mid = - env->GetMethodID(jclazz, "findShortSuccessor", - "(Ljava/lang/String;)Ljava/lang/String;"); - assert(mid != nullptr); - return mid; - } -}; - -// The portal class for org.rocksdb.AbstractSlice -class AbstractSliceJni : public NativeRocksMutableObject< - const rocksdb::Slice*, AbstractSliceJni> { - public: - /** - * Get the Java Class org.rocksdb.AbstractSlice - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/AbstractSlice"); + return jresult_string_builder; } }; -// The portal class for org.rocksdb.Slice -class SliceJni : public NativeRocksMutableObject< - const rocksdb::Slice*, AbstractSliceJni> { +// various utility functions for working with RocksDB and JNI +class JniUtil { public: - /** - * Get the Java Class org.rocksdb.Slice - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Slice"); - } - - /** - * Constructs a Slice object - * - * @param env A pointer to the Java environment - * - * @return A reference to a Java Slice object, or a nullptr if an - * exception occurs - */ - static jobject construct0(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = env->GetMethodID(jclazz, "", "()V"); - if(mid == nullptr) { - // exception occurred accessing method - return nullptr; - } - - jobject jslice = env->NewObject(jclazz, mid); - if(env->ExceptionCheck()) { - return nullptr; + /** + * Detect if jlong overflows size_t + * + * @param jvalue the jlong value + * + * @return + */ + inline static Status check_if_jlong_fits_size_t(const jlong& jvalue) { + Status s = Status::OK(); + if (static_cast(jvalue) > std::numeric_limits::max()) { + s = Status::InvalidArgument(Slice("jlong overflows 32 bit value.")); + } + return s; } - return jslice; - } -}; + /** + * Obtains a reference to the JNIEnv from + * the JVM + * + * If the current thread is not attached to the JavaVM + * then it will be attached so as to retrieve the JNIEnv + * + * If a thread is attached, it must later be manually + * released by calling JavaVM::DetachCurrentThread. + * This can be handled by always matching calls to this + * function with calls to {@link JniUtil::releaseJniEnv(JavaVM*, jboolean)} + * + * @param jvm (IN) A pointer to the JavaVM instance + * @param attached (OUT) A pointer to a boolean which + * will be set to JNI_TRUE if we had to attach the thread + * + * @return A pointer to the JNIEnv or nullptr if a fatal error + * occurs and the JNIEnv cannot be retrieved + */ + static JNIEnv* getJniEnv(JavaVM* jvm, jboolean* attached) { + assert(jvm != nullptr); -// The portal class for org.rocksdb.DirectSlice -class DirectSliceJni : public NativeRocksMutableObject< - const rocksdb::Slice*, AbstractSliceJni> { - public: - /** - * Get the Java Class org.rocksdb.DirectSlice - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/DirectSlice"); - } + JNIEnv *env; + const jint env_rs = jvm->GetEnv(reinterpret_cast(&env), + JNI_VERSION_1_2); - /** - * Constructs a DirectSlice object - * - * @param env A pointer to the Java environment - * - * @return A reference to a Java DirectSlice object, or a nullptr if an - * exception occurs - */ - static jobject construct0(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class - return nullptr; + if(env_rs == JNI_OK) { + // current thread is already attached, return the JNIEnv + *attached = JNI_FALSE; + return env; + } else if(env_rs == JNI_EDETACHED) { + // current thread is not attached, attempt to attach + const jint rs_attach = jvm->AttachCurrentThread(reinterpret_cast(&env), NULL); + if(rs_attach == JNI_OK) { + *attached = JNI_TRUE; + return env; + } else { + // error, could not attach the thread + std::cerr << "JniUtil::getJniEnv - Fatal: could not attach current thread to JVM!" << std::endl; + return nullptr; + } + } else if(env_rs == JNI_EVERSION) { + // error, JDK does not support JNI_VERSION_1_2+ + std::cerr << "JniUtil::getJniEnv - Fatal: JDK does not support JNI_VERSION_1_2" << std::endl; + return nullptr; + } else { + std::cerr << "JniUtil::getJniEnv - Fatal: Unknown error: env_rs=" << env_rs << std::endl; + return nullptr; + } } - static jmethodID mid = env->GetMethodID(jclazz, "", "()V"); - if(mid == nullptr) { - // exception occurred accessing method - return nullptr; + /** + * Counterpart to {@link JniUtil::getJniEnv(JavaVM*, jboolean*)} + * + * Detachess the current thread from the JVM if it was previously + * attached + * + * @param jvm (IN) A pointer to the JavaVM instance + * @param attached (IN) JNI_TRUE if we previously had to attach the thread + * to the JavaVM to get the JNIEnv + */ + static void releaseJniEnv(JavaVM* jvm, jboolean& attached) { + assert(jvm != nullptr); + if(attached == JNI_TRUE) { + const jint rs_detach = jvm->DetachCurrentThread(); + assert(rs_detach == JNI_OK); + if(rs_detach != JNI_OK) { + std::cerr << "JniUtil::getJniEnv - Warn: Unable to detach current thread from JVM!" << std::endl; + } + } } - jobject jdirect_slice = env->NewObject(jclazz, mid); - if(env->ExceptionCheck()) { - return nullptr; + /** + * Copies a Java String[] to a C++ std::vector + * + * @param env (IN) A pointer to the java environment + * @param jss (IN) The Java String array to copy + * @param has_exception (OUT) will be set to JNI_TRUE + * if an OutOfMemoryError or ArrayIndexOutOfBoundsException + * exception occurs + * + * @return A std::vector containing copies of the Java strings + */ + static std::vector copyStrings(JNIEnv* env, + jobjectArray jss, jboolean* has_exception) { + return rocksdb::JniUtil::copyStrings(env, jss, + env->GetArrayLength(jss), has_exception); } - return jdirect_slice; - } -}; + /** + * Copies a Java String[] to a C++ std::vector + * + * @param env (IN) A pointer to the java environment + * @param jss (IN) The Java String array to copy + * @param jss_len (IN) The length of the Java String array to copy + * @param has_exception (OUT) will be set to JNI_TRUE + * if an OutOfMemoryError or ArrayIndexOutOfBoundsException + * exception occurs + * + * @return A std::vector containing copies of the Java strings + */ + static std::vector copyStrings(JNIEnv* env, + jobjectArray jss, const jsize jss_len, jboolean* has_exception) { + std::vector strs; + strs.reserve(jss_len); + for (jsize i = 0; i < jss_len; i++) { + jobject js = env->GetObjectArrayElement(jss, i); + if(env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + *has_exception = JNI_TRUE; + return strs; + } -// The portal class for java.util.List -class ListJni : public JavaClass { - public: - /** - * Get the Java Class java.util.List - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getListClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/List"); - } + jstring jstr = static_cast(js); + const char* str = env->GetStringUTFChars(jstr, nullptr); + if(str == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(js); + *has_exception = JNI_TRUE; + return strs; + } - /** - * Get the Java Class java.util.ArrayList - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getArrayListClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/ArrayList"); - } + strs.push_back(std::string(str)); - /** - * Get the Java Class java.util.Iterator - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getIteratorClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/util/Iterator"); - } + env->ReleaseStringUTFChars(jstr, str); + env->DeleteLocalRef(js); + } - /** - * Get the Java Method: List#iterator - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getIteratorMethod(JNIEnv* env) { - jclass jlist_clazz = getListClass(env); - if(jlist_clazz == nullptr) { - // exception occurred accessing class - return nullptr; + *has_exception = JNI_FALSE; + return strs; } - static jmethodID mid = - env->GetMethodID(jlist_clazz, "iterator", "()Ljava/util/Iterator;"); - assert(mid != nullptr); - return mid; - } + /** + * Copies a jstring to a C-style null-terminated byte string + * and releases the original jstring + * + * The jstring is copied as UTF-8 + * + * If an exception occurs, then JNIEnv::ExceptionCheck() + * will have been called + * + * @param env (IN) A pointer to the java environment + * @param js (IN) The java string to copy + * @param has_exception (OUT) will be set to JNI_TRUE + * if an OutOfMemoryError exception occurs + * + * @return A pointer to the copied string, or a + * nullptr if has_exception == JNI_TRUE + */ + static std::unique_ptr copyString(JNIEnv* env, jstring js, + jboolean* has_exception) { + const char *utf = env->GetStringUTFChars(js, nullptr); + if(utf == nullptr) { + // exception thrown: OutOfMemoryError + env->ExceptionCheck(); + *has_exception = JNI_TRUE; + return nullptr; + } else if(env->ExceptionCheck()) { + // exception thrown + env->ReleaseStringUTFChars(js, utf); + *has_exception = JNI_TRUE; + return nullptr; + } - /** - * Get the Java Method: Iterator#hasNext - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getHasNextMethod(JNIEnv* env) { - jclass jiterator_clazz = getIteratorClass(env); - if(jiterator_clazz == nullptr) { - // exception occurred accessing class - return nullptr; + const jsize utf_len = env->GetStringUTFLength(js); + std::unique_ptr str(new char[utf_len + 1]); // Note: + 1 is needed for the c_str null terminator + std::strcpy(str.get(), utf); + env->ReleaseStringUTFChars(js, utf); + *has_exception = JNI_FALSE; + return str; } - static jmethodID mid = env->GetMethodID(jiterator_clazz, "hasNext", "()Z"); - assert(mid != nullptr); - return mid; - } + /** + * Copies a jstring to a std::string + * and releases the original jstring + * + * If an exception occurs, then JNIEnv::ExceptionCheck() + * will have been called + * + * @param env (IN) A pointer to the java environment + * @param js (IN) The java string to copy + * @param has_exception (OUT) will be set to JNI_TRUE + * if an OutOfMemoryError exception occurs + * + * @return A std:string copy of the jstring, or an + * empty std::string if has_exception == JNI_TRUE + */ + static std::string copyStdString(JNIEnv* env, jstring js, + jboolean* has_exception) { + const char *utf = env->GetStringUTFChars(js, nullptr); + if(utf == nullptr) { + // exception thrown: OutOfMemoryError + env->ExceptionCheck(); + *has_exception = JNI_TRUE; + return std::string(); + } else if(env->ExceptionCheck()) { + // exception thrown + env->ReleaseStringUTFChars(js, utf); + *has_exception = JNI_TRUE; + return std::string(); + } - /** - * Get the Java Method: Iterator#next - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getNextMethod(JNIEnv* env) { - jclass jiterator_clazz = getIteratorClass(env); - if(jiterator_clazz == nullptr) { - // exception occurred accessing class - return nullptr; + std::string name(utf); + env->ReleaseStringUTFChars(js, utf); + *has_exception = JNI_FALSE; + return name; } - static jmethodID mid = - env->GetMethodID(jiterator_clazz, "next", "()Ljava/lang/Object;"); - assert(mid != nullptr); - return mid; - } - - /** - * Get the Java Method: ArrayList constructor - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getArrayListConstructorMethodId(JNIEnv* env) { - jclass jarray_list_clazz = getArrayListClass(env); - if(jarray_list_clazz == nullptr) { - // exception occurred accessing class - return nullptr; + /** + * Copies bytes from a std::string to a jByteArray + * + * @param env A pointer to the java environment + * @param bytes The bytes to copy + * + * @return the Java byte[], or nullptr if an exception occurs + * + * @throws RocksDBException thrown + * if memory size to copy exceeds general java specific array size limitation. + */ + static jbyteArray copyBytes(JNIEnv* env, std::string bytes) { + return createJavaByteArrayWithSizeCheck(env, bytes.c_str(), bytes.size()); } - static jmethodID mid = - env->GetMethodID(jarray_list_clazz, "", "(I)V"); - assert(mid != nullptr); - return mid; - } - /** - * Get the Java Method: List#add - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getListAddMethodId(JNIEnv* env) { - jclass jlist_clazz = getListClass(env); - if(jlist_clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } + /** + * Given a Java byte[][] which is an array of java.lang.Strings + * where each String is a byte[], the passed function `string_fn` + * will be called on each String, the result is the collected by + * calling the passed function `collector_fn` + * + * @param env (IN) A pointer to the java environment + * @param jbyte_strings (IN) A Java array of Strings expressed as bytes + * @param string_fn (IN) A transform function to call for each String + * @param collector_fn (IN) A collector which is called for the result + * of each `string_fn` + * @param has_exception (OUT) will be set to JNI_TRUE + * if an ArrayIndexOutOfBoundsException or OutOfMemoryError + * exception occurs + */ + template static void byteStrings(JNIEnv* env, + jobjectArray jbyte_strings, + std::function string_fn, + std::function collector_fn, + jboolean *has_exception) { + const jsize jlen = env->GetArrayLength(jbyte_strings); - static jmethodID mid = - env->GetMethodID(jlist_clazz, "add", "(Ljava/lang/Object;)Z"); - assert(mid != nullptr); - return mid; - } -}; + for(jsize i = 0; i < jlen; i++) { + jobject jbyte_string_obj = env->GetObjectArrayElement(jbyte_strings, i); + if(env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + *has_exception = JNI_TRUE; // signal error + return; + } -// The portal class for java.lang.Byte -class ByteJni : public JavaClass { - public: - /** - * Get the Java Class java.lang.Byte - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/lang/Byte"); - } + jbyteArray jbyte_string_ary = + reinterpret_cast(jbyte_string_obj); + T result = byteString(env, jbyte_string_ary, string_fn, has_exception); - /** - * Get the Java Class byte[] - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getArrayJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "[B"); - } + env->DeleteLocalRef(jbyte_string_obj); - /** - * Creates a new 2-dimensional Java Byte Array byte[][] - * - * @param env A pointer to the Java environment - * @param len The size of the first dimension - * - * @return A reference to the Java byte[][] or nullptr if an exception occurs - */ - static jobjectArray new2dByteArray(JNIEnv* env, const jsize len) { - jclass clazz = getArrayJClass(env); - if(clazz == nullptr) { - // exception occurred accessing class - return nullptr; - } + if(*has_exception == JNI_TRUE) { + // exception thrown: OutOfMemoryError + return; + } - return env->NewObjectArray(len, clazz, nullptr); - } + collector_fn(i, result); + } - /** - * Get the Java Method: Byte#byteValue - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getByteValueMethod(JNIEnv* env) { - jclass clazz = getJClass(env); - if(clazz == nullptr) { - // exception occurred accessing class - return nullptr; + *has_exception = JNI_FALSE; } - static jmethodID mid = env->GetMethodID(clazz, "byteValue", "()B"); - assert(mid != nullptr); - return mid; - } -}; + /** + * Given a Java String which is expressed as a Java Byte Array byte[], + * the passed function `string_fn` will be called on the String + * and the result returned + * + * @param env (IN) A pointer to the java environment + * @param jbyte_string_ary (IN) A Java String expressed in bytes + * @param string_fn (IN) A transform function to call on the String + * @param has_exception (OUT) will be set to JNI_TRUE + * if an OutOfMemoryError exception occurs + */ + template static T byteString(JNIEnv* env, + jbyteArray jbyte_string_ary, + std::function string_fn, + jboolean* has_exception) { + const jsize jbyte_string_len = env->GetArrayLength(jbyte_string_ary); + return byteString(env, jbyte_string_ary, jbyte_string_len, string_fn, + has_exception); + } -// The portal class for java.lang.StringBuilder -class StringBuilderJni : public JavaClass { - public: - /** - * Get the Java Class java.lang.StringBuilder - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "java/lang/StringBuilder"); - } + /** + * Given a Java String which is expressed as a Java Byte Array byte[], + * the passed function `string_fn` will be called on the String + * and the result returned + * + * @param env (IN) A pointer to the java environment + * @param jbyte_string_ary (IN) A Java String expressed in bytes + * @param jbyte_string_len (IN) The length of the Java String + * expressed in bytes + * @param string_fn (IN) A transform function to call on the String + * @param has_exception (OUT) will be set to JNI_TRUE + * if an OutOfMemoryError exception occurs + */ + template static T byteString(JNIEnv* env, + jbyteArray jbyte_string_ary, const jsize jbyte_string_len, + std::function string_fn, + jboolean* has_exception) { + jbyte* jbyte_string = + env->GetByteArrayElements(jbyte_string_ary, nullptr); + if(jbyte_string == nullptr) { + // exception thrown: OutOfMemoryError + *has_exception = JNI_TRUE; + return nullptr; // signal error + } - /** - * Get the Java Method: StringBuilder#append - * - * @param env A pointer to the Java environment - * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved - */ - static jmethodID getListAddMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } + T result = + string_fn(reinterpret_cast(jbyte_string), jbyte_string_len); - static jmethodID mid = - env->GetMethodID(jclazz, "append", - "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); - assert(mid != nullptr); - return mid; - } + env->ReleaseByteArrayElements(jbyte_string_ary, jbyte_string, JNI_ABORT); - /** - * Appends a C-style string to a StringBuilder - * - * @param env A pointer to the Java environment - * @param jstring_builder Reference to a java.lang.StringBuilder - * @param c_str A C-style string to append to the StringBuilder - * - * @return A reference to the updated StringBuilder, or a nullptr if - * an exception occurs - */ - static jobject append(JNIEnv* env, jobject jstring_builder, - const char* c_str) { - jmethodID mid = getListAddMethodId(env); - if(mid == nullptr) { - // exception occurred accessing class or method - return nullptr; + *has_exception = JNI_FALSE; + return result; } - jstring new_value_str = env->NewStringUTF(c_str); - if(new_value_str == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } + /** + * Converts a std::vector to a Java byte[][] where each Java String + * is expressed as a Java Byte Array byte[]. + * + * @param env A pointer to the java environment + * @param strings A vector of Strings + * + * @return A Java array of Strings expressed as bytes, + * or nullptr if an exception is thrown + */ + static jobjectArray stringsBytes(JNIEnv* env, std::vector strings) { + jclass jcls_ba = ByteJni::getArrayJClass(env); + if(jcls_ba == nullptr) { + // exception occurred + return nullptr; + } - jobject jresult_string_builder = - env->CallObjectMethod(jstring_builder, mid, new_value_str); - if(env->ExceptionCheck()) { - // exception occurred - env->DeleteLocalRef(new_value_str); - return nullptr; - } + const jsize len = static_cast(strings.size()); - return jresult_string_builder; - } -}; + jobjectArray jbyte_strings = env->NewObjectArray(len, jcls_ba, nullptr); + if(jbyte_strings == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } -// The portal class for org.rocksdb.BackupInfo -class BackupInfoJni : public JavaClass { - public: - /** - * Get the Java Class org.rocksdb.BackupInfo - * - * @param env A pointer to the Java environment - * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown - */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/BackupInfo"); - } + for (jsize i = 0; i < len; i++) { + std::string *str = &strings[i]; + const jsize str_len = static_cast(str->size()); - /** - * Constructs a BackupInfo object - * - * @param env A pointer to the Java environment - * @param backup_id id of the backup - * @param timestamp timestamp of the backup - * @param size size of the backup - * @param number_files number of files related to the backup - * - * @return A reference to a Java BackupInfo object, or a nullptr if an - * exception occurs - */ - static jobject construct0(JNIEnv* env, uint32_t backup_id, int64_t timestamp, - uint64_t size, uint32_t number_files) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } + jbyteArray jbyte_string_ary = env->NewByteArray(str_len); + if(jbyte_string_ary == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jbyte_strings); + return nullptr; + } - static jmethodID mid = env->GetMethodID(jclazz, "", "(IJJI)V"); - if(mid == nullptr) { - // exception occurred accessing method - return nullptr; - } + env->SetByteArrayRegion( + jbyte_string_ary, 0, str_len, + const_cast(reinterpret_cast(str->c_str()))); + if(env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jbyte_string_ary); + env->DeleteLocalRef(jbyte_strings); + return nullptr; + } - jobject jbackup_info = - env->NewObject(jclazz, mid, backup_id, timestamp, size, number_files); - if(env->ExceptionCheck()) { - return nullptr; - } + env->SetObjectArrayElement(jbyte_strings, i, jbyte_string_ary); + if(env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + // or ArrayStoreException + env->DeleteLocalRef(jbyte_string_ary); + env->DeleteLocalRef(jbyte_strings); + return nullptr; + } - return jbackup_info; - } -}; + env->DeleteLocalRef(jbyte_string_ary); + } -class BackupInfoListJni { - public: - /** - * Converts a C++ std::vector object to - * a Java ArrayList object - * - * @param env A pointer to the Java environment - * @param backup_infos A vector of BackupInfo - * - * @return Either a reference to a Java ArrayList object, or a nullptr - * if an exception occurs - */ - static jobject getBackupInfo(JNIEnv* env, - std::vector backup_infos) { - jclass jarray_list_clazz = rocksdb::ListJni::getArrayListClass(env); - if(jarray_list_clazz == nullptr) { - // exception occurred accessing class - return nullptr; + return jbyte_strings; } - jmethodID cstr_mid = rocksdb::ListJni::getArrayListConstructorMethodId(env); - if(cstr_mid == nullptr) { - // exception occurred accessing method - return nullptr; + /** + * Converts a std::vector to a Java String[]. + * + * @param env A pointer to the java environment + * @param strings A vector of Strings + * + * @return A Java array of Strings, + * or nullptr if an exception is thrown + */ + static jobjectArray toJavaStrings(JNIEnv* env, + const std::vector* strings) { + jclass jcls_str = env->FindClass("java/lang/String"); + if(jcls_str == nullptr) { + // exception occurred + return nullptr; + } + + const jsize len = static_cast(strings->size()); + + jobjectArray jstrings = env->NewObjectArray(len, jcls_str, nullptr); + if(jstrings == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + for (jsize i = 0; i < len; i++) { + const std::string *str = &((*strings)[i]); + jstring js = rocksdb::JniUtil::toJavaString(env, str); + if (js == nullptr) { + env->DeleteLocalRef(jstrings); + return nullptr; + } + + env->SetObjectArrayElement(jstrings, i, js); + if(env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + // or ArrayStoreException + env->DeleteLocalRef(js); + env->DeleteLocalRef(jstrings); + return nullptr; + } + } + + return jstrings; } - jmethodID add_mid = rocksdb::ListJni::getListAddMethodId(env); - if(add_mid == nullptr) { - // exception occurred accessing method - return nullptr; + /** + * Creates a Java UTF String from a C++ std::string + * + * @param env A pointer to the java environment + * @param string the C++ std::string + * @param treat_empty_as_null true if empty strings should be treated as null + * + * @return the Java UTF string, or nullptr if the provided string + * is null (or empty and treat_empty_as_null is set), or if an + * exception occurs allocating the Java String. + */ + static jstring toJavaString(JNIEnv* env, const std::string* string, + const bool treat_empty_as_null = false) { + if (string == nullptr) { + return nullptr; + } + + if (treat_empty_as_null && string->empty()) { + return nullptr; + } + + return env->NewStringUTF(string->c_str()); + } + + /** + * Copies bytes to a new jByteArray with the check of java array size limitation. + * + * @param bytes pointer to memory to copy to a new jByteArray + * @param size number of bytes to copy + * + * @return the Java byte[], or nullptr if an exception occurs + * + * @throws RocksDBException thrown + * if memory size to copy exceeds general java array size limitation to avoid overflow. + */ + static jbyteArray createJavaByteArrayWithSizeCheck(JNIEnv* env, const char* bytes, const size_t size) { + // Limitation for java array size is vm specific + // In general it cannot exceed Integer.MAX_VALUE (2^31 - 1) + // Current HotSpot VM limitation for array size is Integer.MAX_VALUE - 5 (2^31 - 1 - 5) + // It means that the next call to env->NewByteArray can still end with + // OutOfMemoryError("Requested array size exceeds VM limit") coming from VM + static const size_t MAX_JARRAY_SIZE = (static_cast(1)) << 31; + if(size > MAX_JARRAY_SIZE) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, "Requested array size exceeds VM limit"); + return nullptr; + } + + const jsize jlen = static_cast(size); + jbyteArray jbytes = env->NewByteArray(jlen); + if(jbytes == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + env->SetByteArrayRegion(jbytes, 0, jlen, + const_cast(reinterpret_cast(bytes))); + if(env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jbytes); + return nullptr; + } + + return jbytes; } - // create java list - jobject jbackup_info_handle_list = - env->NewObject(jarray_list_clazz, cstr_mid, backup_infos.size()); - if(env->ExceptionCheck()) { - // exception occurred constructing object - return nullptr; + /** + * Copies bytes from a rocksdb::Slice to a jByteArray + * + * @param env A pointer to the java environment + * @param bytes The bytes to copy + * + * @return the Java byte[] or nullptr if an exception occurs + * + * @throws RocksDBException thrown + * if memory size to copy exceeds general java specific array size limitation. + */ + static jbyteArray copyBytes(JNIEnv* env, const Slice& bytes) { + return createJavaByteArrayWithSizeCheck(env, bytes.data(), bytes.size()); } - // insert in java list - auto end = backup_infos.end(); - for (auto it = backup_infos.begin(); it != end; ++it) { - auto backup_info = *it; + /* + * Helper for operations on a key and value + * for example WriteBatch->Put + * + * TODO(AR) could be used for RocksDB->Put etc. + */ + static std::unique_ptr kv_op( + std::function op, + JNIEnv* env, jobject /*jobj*/, + jbyteArray jkey, jint jkey_len, + jbyteArray jvalue, jint jvalue_len) { + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if(env->ExceptionCheck()) { + // exception thrown: OutOfMemoryError + return nullptr; + } - jobject obj = rocksdb::BackupInfoJni::construct0(env, - backup_info.backup_id, - backup_info.timestamp, - backup_info.size, - backup_info.number_files); + jbyte* value = env->GetByteArrayElements(jvalue, nullptr); if(env->ExceptionCheck()) { - // exception occurred constructing object - if(obj != nullptr) { - env->DeleteLocalRef(obj); - } - if(jbackup_info_handle_list != nullptr) { - env->DeleteLocalRef(jbackup_info_handle_list); + // exception thrown: OutOfMemoryError + if(key != nullptr) { + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); } return nullptr; } - jboolean rs = - env->CallBooleanMethod(jbackup_info_handle_list, add_mid, obj); - if(env->ExceptionCheck() || rs == JNI_FALSE) { - // exception occurred calling method, or could not add - if(obj != nullptr) { - env->DeleteLocalRef(obj); + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + rocksdb::Slice value_slice(reinterpret_cast(value), + jvalue_len); + + auto status = op(key_slice, value_slice); + + if(value != nullptr) { + env->ReleaseByteArrayElements(jvalue, value, JNI_ABORT); + } + if(key != nullptr) { + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + } + + return std::unique_ptr(new rocksdb::Status(status)); + } + + /* + * Helper for operations on a key + * for example WriteBatch->Delete + * + * TODO(AR) could be used for RocksDB->Delete etc. + */ + static std::unique_ptr k_op( + std::function op, + JNIEnv* env, jobject /*jobj*/, + jbyteArray jkey, jint jkey_len) { + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if(env->ExceptionCheck()) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + + auto status = op(key_slice); + + if(key != nullptr) { + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + } + + return std::unique_ptr(new rocksdb::Status(status)); + } + + /* + * Helper for operations on a value + * for example WriteBatchWithIndex->GetFromBatch + */ + static jbyteArray v_op( + std::function op, + JNIEnv* env, jbyteArray jkey, jint jkey_len) { + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if(env->ExceptionCheck()) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + + std::string value; + rocksdb::Status s = op(key_slice, &value); + + if(key != nullptr) { + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + } + + if (s.IsNotFound()) { + return nullptr; + } + + if (s.ok()) { + jbyteArray jret_value = + env->NewByteArray(static_cast(value.size())); + if(jret_value == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; } - if(jbackup_info_handle_list != nullptr) { - env->DeleteLocalRef(jbackup_info_handle_list); + + env->SetByteArrayRegion(jret_value, 0, static_cast(value.size()), + const_cast(reinterpret_cast(value.c_str()))); + if(env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + if(jret_value != nullptr) { + env->DeleteLocalRef(jret_value); + } + return nullptr; } - return nullptr; + + return jret_value; } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; } - return jbackup_info_handle_list; - } + /** + * Creates a vector of C++ pointers from + * a Java array of C++ pointer addresses. + * + * @param env (IN) A pointer to the java environment + * @param pointers (IN) A Java array of C++ pointer addresses + * @param has_exception (OUT) will be set to JNI_TRUE + * if an ArrayIndexOutOfBoundsException or OutOfMemoryError + * exception occurs. + * + * @return A vector of C++ pointers. + */ + template static std::vector fromJPointers( + JNIEnv* env, jlongArray jptrs, jboolean *has_exception) { + const jsize jptrs_len = env->GetArrayLength(jptrs); + std::vector ptrs; + jlong* jptr = env->GetLongArrayElements(jptrs, nullptr); + if (jptr == nullptr) { + // exception thrown: OutOfMemoryError + *has_exception = JNI_TRUE; + return ptrs; + } + ptrs.reserve(jptrs_len); + for (jsize i = 0; i < jptrs_len; i++) { + ptrs.push_back(reinterpret_cast(jptr[i])); + } + env->ReleaseLongArrayElements(jptrs, jptr, JNI_ABORT); + return ptrs; + } + + /** + * Creates a Java array of C++ pointer addresses + * from a vector of C++ pointers. + * + * @param env (IN) A pointer to the java environment + * @param pointers (IN) A vector of C++ pointers + * @param has_exception (OUT) will be set to JNI_TRUE + * if an ArrayIndexOutOfBoundsException or OutOfMemoryError + * exception occurs + * + * @return Java array of C++ pointer addresses. + */ + template static jlongArray toJPointers(JNIEnv* env, + const std::vector &pointers, + jboolean *has_exception) { + const jsize len = static_cast(pointers.size()); + std::unique_ptr results(new jlong[len]); + std::transform(pointers.begin(), pointers.end(), results.get(), [](T* pointer) -> jlong { + return reinterpret_cast(pointer); + }); + + jlongArray jpointers = env->NewLongArray(len); + if (jpointers == nullptr) { + // exception thrown: OutOfMemoryError + *has_exception = JNI_TRUE; + return nullptr; + } + + env->SetLongArrayRegion(jpointers, 0, len, results.get()); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + *has_exception = JNI_TRUE; + env->DeleteLocalRef(jpointers); + return nullptr; + } + + *has_exception = JNI_FALSE; + + return jpointers; + } }; -// The portal class for org.rocksdb.WBWIRocksIterator -class WBWIRocksIteratorJni : public JavaClass { +class MapJni : public JavaClass { public: /** - * Get the Java Class org.rocksdb.WBWIRocksIterator + * Get the Java Class java.util.Map * * @param env A pointer to the Java environment * @@ -1691,247 +2088,308 @@ class WBWIRocksIteratorJni : public JavaClass { * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator"); + return JavaClass::getJClass(env, "java/util/Map"); } /** - * Get the Java Field: WBWIRocksIterator#entry + * Get the Java Method: Map#put * * @param env A pointer to the Java environment * - * @return The Java Field ID or nullptr if the class or field id could not + * @return The Java Method ID or nullptr if the class or method id could not * be retieved */ - static jfieldID getWriteEntryField(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + static jmethodID getMapPutMethodId(JNIEnv* env) { + jclass jlist_clazz = getJClass(env); + if(jlist_clazz == nullptr) { // exception occurred accessing class return nullptr; } - static jfieldID fid = - env->GetFieldID(jclazz, "entry", - "Lorg/rocksdb/WBWIRocksIterator$WriteEntry;"); - assert(fid != nullptr); - return fid; + static jmethodID mid = + env->GetMethodID(jlist_clazz, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + assert(mid != nullptr); + return mid; } +}; +class HashMapJni : public JavaClass { + public: /** - * Gets the value of the WBWIRocksIterator#entry + * Get the Java Class java.util.HashMap * - * @param env A pointer to the Java environment - * @param jwbwi_rocks_iterator A reference to a WBWIIterator + * @param env A pointer to the Java environment * - * @return A reference to a Java WBWIRocksIterator.WriteEntry object, or - * a nullptr if an exception occurs + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jobject getWriteEntry(JNIEnv* env, jobject jwbwi_rocks_iterator) { - assert(jwbwi_rocks_iterator != nullptr); + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "java/util/HashMap"); + } - jfieldID jwrite_entry_field = getWriteEntryField(env); - if(jwrite_entry_field == nullptr) { - // exception occurred accessing the field + /** + * Create a new Java java.util.HashMap object. + * + * @param env A pointer to the Java environment + * + * @return A reference to a Java java.util.HashMap object, or + * nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, const uint32_t initial_capacity = 16) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class return nullptr; } - jobject jwe = env->GetObjectField(jwbwi_rocks_iterator, jwrite_entry_field); - assert(jwe != nullptr); - return jwe; - } -}; - -// The portal class for org.rocksdb.WBWIRocksIterator.WriteType -class WriteTypeJni : public JavaClass { - public: - /** - * Get the PUT enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject PUT(JNIEnv* env) { - return getEnum(env, "PUT"); + jmethodID mid = env->GetMethodID(jclazz, "", "(I)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; } - /** - * Get the MERGE enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject MERGE(JNIEnv* env) { - return getEnum(env, "MERGE"); + jobject jhash_map = env->NewObject(jclazz, mid, static_cast(initial_capacity)); + if (env->ExceptionCheck()) { + return nullptr; } - /** - * Get the DELETE enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject DELETE(JNIEnv* env) { - return getEnum(env, "DELETE"); + return jhash_map; + } + + /** + * A function which maps a std::pair to a std::pair + * + * @return Either a pointer to a std::pair, or nullptr + * if an error occurs during the mapping + */ + template + using FnMapKV = std::function> (const std::pair&)>; + + // template ::value_type, std::pair>::value, int32_t>::type = 0> + // static void putAll(JNIEnv* env, const jobject jhash_map, I iterator, const FnMapKV &fn_map_kv) { + /** + * Returns true if it succeeds, false if an error occurs + */ + template + static bool putAll(JNIEnv* env, const jobject jhash_map, iterator_type iterator, iterator_type end, const FnMapKV &fn_map_kv) { + const jmethodID jmid_put = rocksdb::MapJni::getMapPutMethodId(env); + if (jmid_put == nullptr) { + return false; } - /** - * Get the LOG enum field value of WBWIRocksIterator.WriteType - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject LOG(JNIEnv* env) { - return getEnum(env, "LOG"); + for (auto it = iterator; it != end; ++it) { + const std::unique_ptr> result = fn_map_kv(*it); + if (result == nullptr) { + // an error occurred during fn_map_kv + return false; + } + env->CallObjectMethod(jhash_map, jmid_put, result->first, result->second); + if (env->ExceptionCheck()) { + // exception occurred + env->DeleteLocalRef(result->second); + env->DeleteLocalRef(result->first); + return false; + } + + // release local references + env->DeleteLocalRef(result->second); + env->DeleteLocalRef(result->first); } - private: + return true; + } + /** - * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteType + * Creates a java.util.Map from a std::map * * @param env A pointer to the Java environment + * @param map the Cpp map * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator$WriteType"); + static jobject fromCppMap(JNIEnv* env, const std::map* map) { + if (map == nullptr) { + return nullptr; + } + + jobject jhash_map = construct(env, static_cast(map->size())); + if (jhash_map == nullptr) { + // exception occurred + return nullptr; + } + + const rocksdb::HashMapJni::FnMapKV fn_map_kv = + [env](const std::pair& kv) { + jstring jkey = rocksdb::JniUtil::toJavaString(env, &(kv.first), false); + if (env->ExceptionCheck()) { + // an error occurred + return std::unique_ptr>(nullptr); + } + + jstring jvalue = rocksdb::JniUtil::toJavaString(env, &(kv.second), true); + if (env->ExceptionCheck()) { + // an error occurred + env->DeleteLocalRef(jkey); + return std::unique_ptr>(nullptr); + } + + return std::unique_ptr>(new std::pair(static_cast(jkey), static_cast(jvalue))); + }; + + if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { + // exception occurred + return nullptr; + } + + return jhash_map; } /** - * Get an enum field of org.rocksdb.WBWIRocksIterator.WriteType + * Creates a java.util.Map from a std::map * * @param env A pointer to the Java environment - * @param name The name of the enum field + * @param map the Cpp map * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved + * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred */ - static jobject getEnum(JNIEnv* env, const char name[]) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class + static jobject fromCppMap(JNIEnv* env, const std::map* map) { + if (map == nullptr) { return nullptr; } - jfieldID jfid = - env->GetStaticFieldID(jclazz, name, - "Lorg/rocksdb/WBWIRocksIterator$WriteType;"); - if(env->ExceptionCheck()) { - // exception occurred while getting field + if (map == nullptr) { return nullptr; - } else if(jfid == nullptr) { + } + + jobject jhash_map = construct(env, static_cast(map->size())); + if (jhash_map == nullptr) { + // exception occurred return nullptr; } - jobject jwrite_type = env->GetStaticObjectField(jclazz, jfid); - assert(jwrite_type != nullptr); - return jwrite_type; + const rocksdb::HashMapJni::FnMapKV fn_map_kv = + [env](const std::pair& kv) { + jstring jkey = rocksdb::JniUtil::toJavaString(env, &(kv.first), false); + if (env->ExceptionCheck()) { + // an error occurred + return std::unique_ptr>(nullptr); + } + + jobject jvalue = rocksdb::IntegerJni::valueOf(env, static_cast(kv.second)); + if (env->ExceptionCheck()) { + // an error occurred + env->DeleteLocalRef(jkey); + return std::unique_ptr>(nullptr); + } + + return std::unique_ptr>(new std::pair(static_cast(jkey), jvalue)); + }; + + if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { + // exception occurred + return nullptr; + } + + return jhash_map; } -}; -// The portal class for org.rocksdb.WBWIRocksIterator.WriteEntry -class WriteEntryJni : public JavaClass { - public: /** - * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteEntry + * Creates a java.util.Map from a std::map * * @param env A pointer to the Java environment + * @param map the Cpp map * - * @return The Java Class or nullptr if one of the - * ClassFormatError, ClassCircularityError, NoClassDefFoundError, - * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred */ - static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator$WriteEntry"); + static jobject fromCppMap(JNIEnv* env, const std::map* map) { + if (map == nullptr) { + return nullptr; } -}; -// The portal class for org.rocksdb.InfoLogLevel -class InfoLogLevelJni : public JavaClass { - public: - /** - * Get the DEBUG_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject DEBUG_LEVEL(JNIEnv* env) { - return getEnum(env, "DEBUG_LEVEL"); + jobject jhash_map = construct(env, static_cast(map->size())); + if (jhash_map == nullptr) { + // exception occurred + return nullptr; } - /** - * Get the INFO_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject INFO_LEVEL(JNIEnv* env) { - return getEnum(env, "INFO_LEVEL"); - } + const rocksdb::HashMapJni::FnMapKV fn_map_kv = + [env](const std::pair& kv) { + jstring jkey = rocksdb::JniUtil::toJavaString(env, &(kv.first), false); + if (env->ExceptionCheck()) { + // an error occurred + return std::unique_ptr>(nullptr); + } - /** - * Get the WARN_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject WARN_LEVEL(JNIEnv* env) { - return getEnum(env, "WARN_LEVEL"); + jobject jvalue = rocksdb::LongJni::valueOf(env, static_cast(kv.second)); + if (env->ExceptionCheck()) { + // an error occurred + env->DeleteLocalRef(jkey); + return std::unique_ptr>(nullptr); + } + + return std::unique_ptr>(new std::pair(static_cast(jkey), jvalue)); + }; + + if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { + // exception occurred + return nullptr; } + return jhash_map; + } + /** - * Get the ERROR_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject ERROR_LEVEL(JNIEnv* env) { - return getEnum(env, "ERROR_LEVEL"); + * Creates a java.util.Map from a std::map + * + * @param env A pointer to the Java environment + * @param map the Cpp map + * + * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred + */ + static jobject fromCppMap(JNIEnv* env, const std::map* map) { + if (map == nullptr) { + return nullptr; } - /** - * Get the FATAL_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject FATAL_LEVEL(JNIEnv* env) { - return getEnum(env, "FATAL_LEVEL"); + jobject jhash_map = construct(env, static_cast(map->size())); + if (jhash_map == nullptr) { + // exception occurred + return nullptr; } - /** - * Get the HEADER_LEVEL enum field value of InfoLogLevel - * - * @param env A pointer to the Java environment - * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved - */ - static jobject HEADER_LEVEL(JNIEnv* env) { - return getEnum(env, "HEADER_LEVEL"); + const rocksdb::HashMapJni::FnMapKV fn_map_kv = + [env](const std::pair& kv) { + jobject jkey = rocksdb::IntegerJni::valueOf(env, static_cast(kv.first)); + if (env->ExceptionCheck()) { + // an error occurred + return std::unique_ptr>(nullptr); + } + + jobject jvalue = rocksdb::LongJni::valueOf(env, static_cast(kv.second)); + if (env->ExceptionCheck()) { + // an error occurred + env->DeleteLocalRef(jkey); + return std::unique_ptr>(nullptr); + } + + return std::unique_ptr>(new std::pair(static_cast(jkey), jvalue)); + }; + + if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) { + // exception occurred + return nullptr; } - private: + return jhash_map; + } +}; + +// The portal class for org.rocksdb.RocksDB +class RocksDBJni : public RocksDBNativeClass { + public: /** - * Get the Java Class org.rocksdb.InfoLogLevel + * Get the Java Class org.rocksdb.RocksDB * * @param env A pointer to the Java environment * @@ -1940,46 +2398,104 @@ class InfoLogLevelJni : public JavaClass { * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, "org/rocksdb/InfoLogLevel"); + return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksDB"); } +}; +// The portal class for org.rocksdb.Options +class OptionsJni : public RocksDBNativeClass< + rocksdb::Options*, OptionsJni> { + public: /** - * Get an enum field of org.rocksdb.InfoLogLevel + * Get the Java Class org.rocksdb.Options * * @param env A pointer to the Java environment - * @param name The name of the enum field * - * @return A reference to the enum field value or a nullptr if - * the enum field value could not be retrieved + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jobject getEnum(JNIEnv* env, const char name[]) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/Options"); + } +}; + +// The portal class for org.rocksdb.DBOptions +class DBOptionsJni : public RocksDBNativeClass< + rocksdb::DBOptions*, DBOptionsJni> { + public: + /** + * Get the Java Class org.rocksdb.DBOptions + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/DBOptions"); + } +}; + +// The portal class for org.rocksdb.ColumnFamilyOptions +class ColumnFamilyOptionsJni + : public RocksDBNativeClass { + public: + /** + * Get the Java Class org.rocksdb.ColumnFamilyOptions + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/ColumnFamilyOptions"); + } + + /** + * Create a new Java org.rocksdb.ColumnFamilyOptions object with the same + * properties as the provided C++ rocksdb::ColumnFamilyOptions object + * + * @param env A pointer to the Java environment + * @param cfoptions A pointer to rocksdb::ColumnFamilyOptions object + * + * @return A reference to a Java org.rocksdb.ColumnFamilyOptions object, or + * nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, const ColumnFamilyOptions* cfoptions) { + auto* cfo = new rocksdb::ColumnFamilyOptions(*cfoptions); + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { // exception occurred accessing class return nullptr; } - jfieldID jfid = - env->GetStaticFieldID(jclazz, name, "Lorg/rocksdb/InfoLogLevel;"); - if(env->ExceptionCheck()) { - // exception occurred while getting field + jmethodID mid = env->GetMethodID(jclazz, "", "(J)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError return nullptr; - } else if(jfid == nullptr) { + } + + jobject jcfd = env->NewObject(jclazz, mid, reinterpret_cast(cfo)); + if (env->ExceptionCheck()) { return nullptr; } - jobject jinfo_log_level = env->GetStaticObjectField(jclazz, jfid); - assert(jinfo_log_level != nullptr); - return jinfo_log_level; + return jcfd; } }; -// The portal class for org.rocksdb.Logger -class LoggerJni : public RocksDBNativeClass< - std::shared_ptr*, LoggerJni> { +// The portal class for org.rocksdb.WriteOptions +class WriteOptionsJni : public RocksDBNativeClass< + rocksdb::WriteOptions*, WriteOptionsJni> { public: /** - * Get the Java Class org/rocksdb/Logger + * Get the Java Class org.rocksdb.WriteOptions * * @param env A pointer to the Java environment * @@ -1988,37 +2504,34 @@ class LoggerJni : public RocksDBNativeClass< * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return RocksDBNativeClass::getJClass(env, "org/rocksdb/Logger"); + return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteOptions"); } +}; +// The portal class for org.rocksdb.ReadOptions +class ReadOptionsJni : public RocksDBNativeClass< + rocksdb::ReadOptions*, ReadOptionsJni> { + public: /** - * Get the Java Method: Logger#log + * Get the Java Class org.rocksdb.ReadOptions * * @param env A pointer to the Java environment * - * @return The Java Method ID or nullptr if the class or method id could not - * be retieved + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ - static jmethodID getLogMethodId(JNIEnv* env) { - jclass jclazz = getJClass(env); - if(jclazz == nullptr) { - // exception occurred accessing class - return nullptr; - } - - static jmethodID mid = - env->GetMethodID(jclazz, "log", - "(Lorg/rocksdb/InfoLogLevel;Ljava/lang/String;)V"); - assert(mid != nullptr); - return mid; + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/ReadOptions"); } }; -// The portal class for org.rocksdb.TransactionLogIterator.BatchResult -class BatchResultJni : public JavaClass { - public: +// The portal class for org.rocksdb.WriteBatch +class WriteBatchJni : public RocksDBNativeClass< + rocksdb::WriteBatch*, WriteBatchJni> { + public: /** - * Get the Java Class org.rocksdb.TransactionLogIterator.BatchResult + * Get the Java Class org.rocksdb.WriteBatch * * @param env A pointer to the Java environment * @@ -2027,1315 +2540,4553 @@ class BatchResultJni : public JavaClass { * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown */ static jclass getJClass(JNIEnv* env) { - return JavaClass::getJClass(env, - "org/rocksdb/TransactionLogIterator$BatchResult"); + return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteBatch"); } /** - * Create a new Java org.rocksdb.TransactionLogIterator.BatchResult object - * with the same properties as the provided C++ rocksdb::BatchResult object + * Create a new Java org.rocksdb.WriteBatch object * * @param env A pointer to the Java environment - * @param batch_result The rocksdb::BatchResult object + * @param wb A pointer to rocksdb::WriteBatch object * - * @return A reference to a Java - * org.rocksdb.TransactionLogIterator.BatchResult object, - * or nullptr if an an exception occurs + * @return A reference to a Java org.rocksdb.WriteBatch object, or + * nullptr if an an exception occurs */ - static jobject construct(JNIEnv* env, - rocksdb::BatchResult& batch_result) { + static jobject construct(JNIEnv* env, const WriteBatch* wb) { jclass jclazz = getJClass(env); if(jclazz == nullptr) { // exception occurred accessing class return nullptr; } - jmethodID mid = env->GetMethodID( - jclazz, "", "(JJ)V"); - if(mid == nullptr) { + jmethodID mid = env->GetMethodID(jclazz, "", "(J)V"); + if (mid == nullptr) { // exception thrown: NoSuchMethodException or OutOfMemoryError return nullptr; } - jobject jbatch_result = env->NewObject(jclazz, mid, - batch_result.sequence, batch_result.writeBatchPtr.get()); - if(jbatch_result == nullptr) { - // exception thrown: InstantiationException or OutOfMemoryError + jobject jwb = env->NewObject(jclazz, mid, reinterpret_cast(wb)); + if (env->ExceptionCheck()) { return nullptr; } - batch_result.writeBatchPtr.release(); - return jbatch_result; + return jwb; } }; -// The portal class for org.rocksdb.CompactionStopStyle -class CompactionStopStyleJni { +// The portal class for org.rocksdb.WriteBatch.Handler +class WriteBatchHandlerJni : public RocksDBNativeClass< + const rocksdb::WriteBatchHandlerJniCallback*, + WriteBatchHandlerJni> { public: - // Returns the equivalent org.rocksdb.CompactionStopStyle for the provided - // C++ rocksdb::CompactionStopStyle enum - static jbyte toJavaCompactionStopStyle( - const rocksdb::CompactionStopStyle& compaction_stop_style) { - switch(compaction_stop_style) { - case rocksdb::CompactionStopStyle::kCompactionStopStyleSimilarSize: - return 0x0; - case rocksdb::CompactionStopStyle::kCompactionStopStyleTotalSize: - return 0x1; - default: - return 0x7F; // undefined - } + /** + * Get the Java Class org.rocksdb.WriteBatch.Handler + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/WriteBatch$Handler"); } - // Returns the equivalent C++ rocksdb::CompactionStopStyle enum for the - // provided Java org.rocksdb.CompactionStopStyle - static rocksdb::CompactionStopStyle toCppCompactionStopStyle( - jbyte jcompaction_stop_style) { - switch(jcompaction_stop_style) { - case 0x0: - return rocksdb::CompactionStopStyle::kCompactionStopStyleSimilarSize; - case 0x1: - return rocksdb::CompactionStopStyle::kCompactionStopStyleTotalSize; - default: - // undefined/default - return rocksdb::CompactionStopStyle::kCompactionStopStyleSimilarSize; + /** + * Get the Java Method: WriteBatch.Handler#put + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getPutCfMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } - } -}; -// The portal class for org.rocksdb.CompressionType -class CompressionTypeJni { - public: - // Returns the equivalent org.rocksdb.CompressionType for the provided - // C++ rocksdb::CompressionType enum - static jbyte toJavaCompressionType( - const rocksdb::CompressionType& compression_type) { - switch(compression_type) { - case rocksdb::CompressionType::kNoCompression: - return 0x0; - case rocksdb::CompressionType::kSnappyCompression: - return 0x1; - case rocksdb::CompressionType::kZlibCompression: - return 0x2; - case rocksdb::CompressionType::kBZip2Compression: - return 0x3; - case rocksdb::CompressionType::kLZ4Compression: - return 0x4; - case rocksdb::CompressionType::kLZ4HCCompression: - return 0x5; - case rocksdb::CompressionType::kXpressCompression: - return 0x6; - case rocksdb::CompressionType::kZSTD: - return 0x7; - case rocksdb::CompressionType::kDisableCompressionOption: - default: - return 0x7F; - } + static jmethodID mid = env->GetMethodID(jclazz, "put", "(I[B[B)V"); + assert(mid != nullptr); + return mid; } - // Returns the equivalent C++ rocksdb::CompressionType enum for the - // provided Java org.rocksdb.CompressionType - static rocksdb::CompressionType toCppCompressionType( - jbyte jcompression_type) { - switch(jcompression_type) { - case 0x0: - return rocksdb::CompressionType::kNoCompression; - case 0x1: - return rocksdb::CompressionType::kSnappyCompression; - case 0x2: - return rocksdb::CompressionType::kZlibCompression; - case 0x3: - return rocksdb::CompressionType::kBZip2Compression; - case 0x4: - return rocksdb::CompressionType::kLZ4Compression; - case 0x5: - return rocksdb::CompressionType::kLZ4HCCompression; - case 0x6: - return rocksdb::CompressionType::kXpressCompression; - case 0x7: - return rocksdb::CompressionType::kZSTD; - case 0x7F: - default: - return rocksdb::CompressionType::kDisableCompressionOption; + /** + * Get the Java Method: WriteBatch.Handler#put + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getPutMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID(jclazz, "put", "([B[B)V"); + assert(mid != nullptr); + return mid; } -}; -// The portal class for org.rocksdb.CompactionPriority -class CompactionPriorityJni { - public: - // Returns the equivalent org.rocksdb.CompactionPriority for the provided - // C++ rocksdb::CompactionPri enum - static jbyte toJavaCompactionPriority( - const rocksdb::CompactionPri& compaction_priority) { - switch(compaction_priority) { - case rocksdb::CompactionPri::kByCompensatedSize: - return 0x0; - case rocksdb::CompactionPri::kOldestLargestSeqFirst: - return 0x1; - case rocksdb::CompactionPri::kOldestSmallestSeqFirst: - return 0x2; - case rocksdb::CompactionPri::kMinOverlappingRatio: - return 0x3; - default: - return 0x0; // undefined + /** + * Get the Java Method: WriteBatch.Handler#merge + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getMergeCfMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID(jclazz, "merge", "(I[B[B)V"); + assert(mid != nullptr); + return mid; } - // Returns the equivalent C++ rocksdb::CompactionPri enum for the - // provided Java org.rocksdb.CompactionPriority - static rocksdb::CompactionPri toCppCompactionPriority( - jbyte jcompaction_priority) { - switch(jcompaction_priority) { - case 0x0: - return rocksdb::CompactionPri::kByCompensatedSize; - case 0x1: - return rocksdb::CompactionPri::kOldestLargestSeqFirst; - case 0x2: - return rocksdb::CompactionPri::kOldestSmallestSeqFirst; - case 0x3: - return rocksdb::CompactionPri::kMinOverlappingRatio; - default: - // undefined/default - return rocksdb::CompactionPri::kByCompensatedSize; + /** + * Get the Java Method: WriteBatch.Handler#merge + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getMergeMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID(jclazz, "merge", "([B[B)V"); + assert(mid != nullptr); + return mid; } -}; -// The portal class for org.rocksdb.AccessHint -class AccessHintJni { - public: - // Returns the equivalent org.rocksdb.AccessHint for the provided - // C++ rocksdb::DBOptions::AccessHint enum - static jbyte toJavaAccessHint( - const rocksdb::DBOptions::AccessHint& access_hint) { - switch(access_hint) { - case rocksdb::DBOptions::AccessHint::NONE: - return 0x0; - case rocksdb::DBOptions::AccessHint::NORMAL: - return 0x1; - case rocksdb::DBOptions::AccessHint::SEQUENTIAL: - return 0x2; - case rocksdb::DBOptions::AccessHint::WILLNEED: - return 0x3; - default: - // undefined/default - return 0x1; + /** + * Get the Java Method: WriteBatch.Handler#delete + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getDeleteCfMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID(jclazz, "delete", "(I[B)V"); + assert(mid != nullptr); + return mid; } - // Returns the equivalent C++ rocksdb::DBOptions::AccessHint enum for the - // provided Java org.rocksdb.AccessHint - static rocksdb::DBOptions::AccessHint toCppAccessHint(jbyte jaccess_hint) { - switch(jaccess_hint) { - case 0x0: - return rocksdb::DBOptions::AccessHint::NONE; - case 0x1: - return rocksdb::DBOptions::AccessHint::NORMAL; - case 0x2: - return rocksdb::DBOptions::AccessHint::SEQUENTIAL; - case 0x3: - return rocksdb::DBOptions::AccessHint::WILLNEED; - default: - // undefined/default - return rocksdb::DBOptions::AccessHint::NORMAL; + /** + * Get the Java Method: WriteBatch.Handler#delete + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getDeleteMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID(jclazz, "delete", "([B)V"); + assert(mid != nullptr); + return mid; } -}; -// The portal class for org.rocksdb.WALRecoveryMode -class WALRecoveryModeJni { - public: - // Returns the equivalent org.rocksdb.WALRecoveryMode for the provided - // C++ rocksdb::WALRecoveryMode enum - static jbyte toJavaWALRecoveryMode( - const rocksdb::WALRecoveryMode& wal_recovery_mode) { - switch(wal_recovery_mode) { - case rocksdb::WALRecoveryMode::kTolerateCorruptedTailRecords: - return 0x0; - case rocksdb::WALRecoveryMode::kAbsoluteConsistency: - return 0x1; - case rocksdb::WALRecoveryMode::kPointInTimeRecovery: - return 0x2; - case rocksdb::WALRecoveryMode::kSkipAnyCorruptedRecords: - return 0x3; - default: - // undefined/default - return 0x2; + /** + * Get the Java Method: WriteBatch.Handler#singleDelete + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getSingleDeleteCfMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID(jclazz, "singleDelete", "(I[B)V"); + assert(mid != nullptr); + return mid; } - // Returns the equivalent C++ rocksdb::WALRecoveryMode enum for the - // provided Java org.rocksdb.WALRecoveryMode - static rocksdb::WALRecoveryMode toCppWALRecoveryMode(jbyte jwal_recovery_mode) { - switch(jwal_recovery_mode) { - case 0x0: - return rocksdb::WALRecoveryMode::kTolerateCorruptedTailRecords; - case 0x1: - return rocksdb::WALRecoveryMode::kAbsoluteConsistency; - case 0x2: - return rocksdb::WALRecoveryMode::kPointInTimeRecovery; - case 0x3: - return rocksdb::WALRecoveryMode::kSkipAnyCorruptedRecords; - default: - // undefined/default - return rocksdb::WALRecoveryMode::kPointInTimeRecovery; + /** + * Get the Java Method: WriteBatch.Handler#singleDelete + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getSingleDeleteMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID(jclazz, "singleDelete", "([B)V"); + assert(mid != nullptr); + return mid; } -}; -// The portal class for org.rocksdb.TickerType -class TickerTypeJni { - public: - // Returns the equivalent org.rocksdb.TickerType for the provided - // C++ rocksdb::Tickers enum - static jbyte toJavaTickerType( - const rocksdb::Tickers& tickers) { - switch(tickers) { - case rocksdb::Tickers::BLOCK_CACHE_MISS: - return 0x0; - case rocksdb::Tickers::BLOCK_CACHE_HIT: - return 0x1; - case rocksdb::Tickers::BLOCK_CACHE_ADD: - return 0x2; - case rocksdb::Tickers::BLOCK_CACHE_ADD_FAILURES: - return 0x3; - case rocksdb::Tickers::BLOCK_CACHE_INDEX_MISS: - return 0x4; - case rocksdb::Tickers::BLOCK_CACHE_INDEX_HIT: - return 0x5; - case rocksdb::Tickers::BLOCK_CACHE_INDEX_ADD: - return 0x6; - case rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT: - return 0x7; - case rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_EVICT: - return 0x8; - case rocksdb::Tickers::BLOCK_CACHE_FILTER_MISS: - return 0x9; - case rocksdb::Tickers::BLOCK_CACHE_FILTER_HIT: - return 0xA; - case rocksdb::Tickers::BLOCK_CACHE_FILTER_ADD: - return 0xB; - case rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT: - return 0xC; - case rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_EVICT: - return 0xD; - case rocksdb::Tickers::BLOCK_CACHE_DATA_MISS: - return 0xE; - case rocksdb::Tickers::BLOCK_CACHE_DATA_HIT: - return 0xF; - case rocksdb::Tickers::BLOCK_CACHE_DATA_ADD: - return 0x10; - case rocksdb::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT: - return 0x11; - case rocksdb::Tickers::BLOCK_CACHE_BYTES_READ: - return 0x12; - case rocksdb::Tickers::BLOCK_CACHE_BYTES_WRITE: - return 0x13; - case rocksdb::Tickers::BLOOM_FILTER_USEFUL: - return 0x14; - case rocksdb::Tickers::PERSISTENT_CACHE_HIT: - return 0x15; - case rocksdb::Tickers::PERSISTENT_CACHE_MISS: - return 0x16; - case rocksdb::Tickers::SIM_BLOCK_CACHE_HIT: - return 0x17; - case rocksdb::Tickers::SIM_BLOCK_CACHE_MISS: - return 0x18; - case rocksdb::Tickers::MEMTABLE_HIT: - return 0x19; - case rocksdb::Tickers::MEMTABLE_MISS: - return 0x1A; - case rocksdb::Tickers::GET_HIT_L0: - return 0x1B; - case rocksdb::Tickers::GET_HIT_L1: - return 0x1C; + /** + * Get the Java Method: WriteBatch.Handler#deleteRange + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getDeleteRangeCfMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "deleteRange", "(I[B[B)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#deleteRange + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getDeleteRangeMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "deleteRange", "([B[B)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#logData + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getLogDataMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "logData", "([B)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#putBlobIndex + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getPutBlobIndexCfMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "putBlobIndex", "(I[B[B)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#markBeginPrepare + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getMarkBeginPrepareMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "markBeginPrepare", "()V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#markEndPrepare + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getMarkEndPrepareMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "markEndPrepare", "([B)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#markNoop + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getMarkNoopMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "markNoop", "(Z)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#markRollback + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getMarkRollbackMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "markRollback", "([B)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#markCommit + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getMarkCommitMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "markCommit", "([B)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: WriteBatch.Handler#shouldContinue + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getContinueMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "shouldContinue", "()Z"); + assert(mid != nullptr); + return mid; + } +}; + +class WriteBatchSavePointJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.WriteBatch.SavePoint + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/WriteBatch$SavePoint"); + } + + /** + * Get the Java Method: HistogramData constructor + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getConstructorMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "", "(JJJ)V"); + assert(mid != nullptr); + return mid; + } + + /** + * Create a new Java org.rocksdb.WriteBatch.SavePoint object + * + * @param env A pointer to the Java environment + * @param savePoint A pointer to rocksdb::WriteBatch::SavePoint object + * + * @return A reference to a Java org.rocksdb.WriteBatch.SavePoint object, or + * nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, const SavePoint &save_point) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = getConstructorMethodId(env); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + jobject jsave_point = env->NewObject(jclazz, mid, + static_cast(save_point.size), + static_cast(save_point.count), + static_cast(save_point.content_flags)); + if (env->ExceptionCheck()) { + return nullptr; + } + + return jsave_point; + } +}; + +// The portal class for org.rocksdb.WriteBatchWithIndex +class WriteBatchWithIndexJni : public RocksDBNativeClass< + rocksdb::WriteBatchWithIndex*, WriteBatchWithIndexJni> { + public: + /** + * Get the Java Class org.rocksdb.WriteBatchWithIndex + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/WriteBatchWithIndex"); + } +}; + +// The portal class for org.rocksdb.HistogramData +class HistogramDataJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.HistogramData + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/HistogramData"); + } + + /** + * Get the Java Method: HistogramData constructor + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getConstructorMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "", "(DDDDDDJJD)V"); + assert(mid != nullptr); + return mid; + } +}; + +// The portal class for org.rocksdb.BackupableDBOptions +class BackupableDBOptionsJni : public RocksDBNativeClass< + rocksdb::BackupableDBOptions*, BackupableDBOptionsJni> { + public: + /** + * Get the Java Class org.rocksdb.BackupableDBOptions + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/BackupableDBOptions"); + } +}; + +// The portal class for org.rocksdb.BackupEngine +class BackupEngineJni : public RocksDBNativeClass< + rocksdb::BackupEngine*, BackupEngineJni> { + public: + /** + * Get the Java Class org.rocksdb.BackupableEngine + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/BackupEngine"); + } +}; + +// The portal class for org.rocksdb.RocksIterator +class IteratorJni : public RocksDBNativeClass< + rocksdb::Iterator*, IteratorJni> { + public: + /** + * Get the Java Class org.rocksdb.RocksIterator + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksIterator"); + } +}; + +// The portal class for org.rocksdb.Filter +class FilterJni : public RocksDBNativeClass< + std::shared_ptr*, FilterJni> { + public: + /** + * Get the Java Class org.rocksdb.Filter + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/Filter"); + } +}; + +// The portal class for org.rocksdb.ColumnFamilyHandle +class ColumnFamilyHandleJni : public RocksDBNativeClass< + rocksdb::ColumnFamilyHandle*, ColumnFamilyHandleJni> { + public: + /** + * Get the Java Class org.rocksdb.ColumnFamilyHandle + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/ColumnFamilyHandle"); + } +}; + +// The portal class for org.rocksdb.FlushOptions +class FlushOptionsJni : public RocksDBNativeClass< + rocksdb::FlushOptions*, FlushOptionsJni> { + public: + /** + * Get the Java Class org.rocksdb.FlushOptions + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/FlushOptions"); + } +}; + +// The portal class for org.rocksdb.ComparatorOptions +class ComparatorOptionsJni : public RocksDBNativeClass< + rocksdb::ComparatorJniCallbackOptions*, ComparatorOptionsJni> { + public: + /** + * Get the Java Class org.rocksdb.ComparatorOptions + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/ComparatorOptions"); + } +}; + +// The portal class for org.rocksdb.AbstractCompactionFilterFactory +class AbstractCompactionFilterFactoryJni : public RocksDBNativeClass< + const rocksdb::CompactionFilterFactoryJniCallback*, + AbstractCompactionFilterFactoryJni> { + public: + /** + * Get the Java Class org.rocksdb.AbstractCompactionFilterFactory + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/AbstractCompactionFilterFactory"); + } + + /** + * Get the Java Method: AbstractCompactionFilterFactory#name + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getNameMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID( + jclazz, "name", "()Ljava/lang/String;"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: AbstractCompactionFilterFactory#createCompactionFilter + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getCreateCompactionFilterMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, + "createCompactionFilter", + "(ZZ)J"); + assert(mid != nullptr); + return mid; + } +}; + +// The portal class for org.rocksdb.AbstractTransactionNotifier +class AbstractTransactionNotifierJni : public RocksDBNativeClass< + const rocksdb::TransactionNotifierJniCallback*, + AbstractTransactionNotifierJni> { + public: + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/AbstractTransactionNotifier"); + } + + // Get the java method `snapshotCreated` + // of org.rocksdb.AbstractTransactionNotifier. + static jmethodID getSnapshotCreatedMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "snapshotCreated", "(J)V"); + assert(mid != nullptr); + return mid; + } +}; + +// The portal class for org.rocksdb.AbstractComparator +class AbstractComparatorJni : public RocksDBNativeClass< + const rocksdb::BaseComparatorJniCallback*, + AbstractComparatorJni> { + public: + /** + * Get the Java Class org.rocksdb.AbstractComparator + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/AbstractComparator"); + } + + /** + * Get the Java Method: Comparator#name + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getNameMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "name", "()Ljava/lang/String;"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: Comparator#compare + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getCompareMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "compare", + "(Lorg/rocksdb/AbstractSlice;Lorg/rocksdb/AbstractSlice;)I"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: Comparator#findShortestSeparator + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getFindShortestSeparatorMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "findShortestSeparator", + "(Ljava/lang/String;Lorg/rocksdb/AbstractSlice;)Ljava/lang/String;"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: Comparator#findShortSuccessor + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getFindShortSuccessorMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "findShortSuccessor", + "(Ljava/lang/String;)Ljava/lang/String;"); + assert(mid != nullptr); + return mid; + } +}; + +// The portal class for org.rocksdb.AbstractSlice +class AbstractSliceJni : public NativeRocksMutableObject< + const rocksdb::Slice*, AbstractSliceJni> { + public: + /** + * Get the Java Class org.rocksdb.AbstractSlice + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/AbstractSlice"); + } +}; + +// The portal class for org.rocksdb.Slice +class SliceJni : public NativeRocksMutableObject< + const rocksdb::Slice*, AbstractSliceJni> { + public: + /** + * Get the Java Class org.rocksdb.Slice + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/Slice"); + } + + /** + * Constructs a Slice object + * + * @param env A pointer to the Java environment + * + * @return A reference to a Java Slice object, or a nullptr if an + * exception occurs + */ + static jobject construct0(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "", "()V"); + if(mid == nullptr) { + // exception occurred accessing method + return nullptr; + } + + jobject jslice = env->NewObject(jclazz, mid); + if(env->ExceptionCheck()) { + return nullptr; + } + + return jslice; + } +}; + +// The portal class for org.rocksdb.DirectSlice +class DirectSliceJni : public NativeRocksMutableObject< + const rocksdb::Slice*, AbstractSliceJni> { + public: + /** + * Get the Java Class org.rocksdb.DirectSlice + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/DirectSlice"); + } + + /** + * Constructs a DirectSlice object + * + * @param env A pointer to the Java environment + * + * @return A reference to a Java DirectSlice object, or a nullptr if an + * exception occurs + */ + static jobject construct0(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID(jclazz, "", "()V"); + if(mid == nullptr) { + // exception occurred accessing method + return nullptr; + } + + jobject jdirect_slice = env->NewObject(jclazz, mid); + if(env->ExceptionCheck()) { + return nullptr; + } + + return jdirect_slice; + } +}; + +// The portal class for org.rocksdb.BackupInfo +class BackupInfoJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.BackupInfo + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/BackupInfo"); + } + + /** + * Constructs a BackupInfo object + * + * @param env A pointer to the Java environment + * @param backup_id id of the backup + * @param timestamp timestamp of the backup + * @param size size of the backup + * @param number_files number of files related to the backup + * @param app_metadata application specific metadata + * + * @return A reference to a Java BackupInfo object, or a nullptr if an + * exception occurs + */ + static jobject construct0(JNIEnv* env, uint32_t backup_id, int64_t timestamp, + uint64_t size, uint32_t number_files, + const std::string& app_metadata) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "", "(IJJILjava/lang/String;)V"); + if(mid == nullptr) { + // exception occurred accessing method + return nullptr; + } + + jstring japp_metadata = nullptr; + if (app_metadata != nullptr) { + japp_metadata = env->NewStringUTF(app_metadata.c_str()); + if (japp_metadata == nullptr) { + // exception occurred creating java string + return nullptr; + } + } + + jobject jbackup_info = env->NewObject(jclazz, mid, backup_id, timestamp, + size, number_files, japp_metadata); + if(env->ExceptionCheck()) { + env->DeleteLocalRef(japp_metadata); + return nullptr; + } + + return jbackup_info; + } +}; + +class BackupInfoListJni { + public: + /** + * Converts a C++ std::vector object to + * a Java ArrayList object + * + * @param env A pointer to the Java environment + * @param backup_infos A vector of BackupInfo + * + * @return Either a reference to a Java ArrayList object, or a nullptr + * if an exception occurs + */ + static jobject getBackupInfo(JNIEnv* env, + std::vector backup_infos) { + jclass jarray_list_clazz = rocksdb::ListJni::getArrayListClass(env); + if(jarray_list_clazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID cstr_mid = rocksdb::ListJni::getArrayListConstructorMethodId(env); + if(cstr_mid == nullptr) { + // exception occurred accessing method + return nullptr; + } + + jmethodID add_mid = rocksdb::ListJni::getListAddMethodId(env); + if(add_mid == nullptr) { + // exception occurred accessing method + return nullptr; + } + + // create java list + jobject jbackup_info_handle_list = + env->NewObject(jarray_list_clazz, cstr_mid, backup_infos.size()); + if(env->ExceptionCheck()) { + // exception occurred constructing object + return nullptr; + } + + // insert in java list + auto end = backup_infos.end(); + for (auto it = backup_infos.begin(); it != end; ++it) { + auto backup_info = *it; + + jobject obj = rocksdb::BackupInfoJni::construct0( + env, backup_info.backup_id, backup_info.timestamp, backup_info.size, + backup_info.number_files, backup_info.app_metadata); + if(env->ExceptionCheck()) { + // exception occurred constructing object + if(obj != nullptr) { + env->DeleteLocalRef(obj); + } + if(jbackup_info_handle_list != nullptr) { + env->DeleteLocalRef(jbackup_info_handle_list); + } + return nullptr; + } + + jboolean rs = + env->CallBooleanMethod(jbackup_info_handle_list, add_mid, obj); + if(env->ExceptionCheck() || rs == JNI_FALSE) { + // exception occurred calling method, or could not add + if(obj != nullptr) { + env->DeleteLocalRef(obj); + } + if(jbackup_info_handle_list != nullptr) { + env->DeleteLocalRef(jbackup_info_handle_list); + } + return nullptr; + } + } + + return jbackup_info_handle_list; + } +}; + +// The portal class for org.rocksdb.WBWIRocksIterator +class WBWIRocksIteratorJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.WBWIRocksIterator + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator"); + } + + /** + * Get the Java Field: WBWIRocksIterator#entry + * + * @param env A pointer to the Java environment + * + * @return The Java Field ID or nullptr if the class or field id could not + * be retieved + */ + static jfieldID getWriteEntryField(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jfieldID fid = + env->GetFieldID(jclazz, "entry", + "Lorg/rocksdb/WBWIRocksIterator$WriteEntry;"); + assert(fid != nullptr); + return fid; + } + + /** + * Gets the value of the WBWIRocksIterator#entry + * + * @param env A pointer to the Java environment + * @param jwbwi_rocks_iterator A reference to a WBWIIterator + * + * @return A reference to a Java WBWIRocksIterator.WriteEntry object, or + * a nullptr if an exception occurs + */ + static jobject getWriteEntry(JNIEnv* env, jobject jwbwi_rocks_iterator) { + assert(jwbwi_rocks_iterator != nullptr); + + jfieldID jwrite_entry_field = getWriteEntryField(env); + if(jwrite_entry_field == nullptr) { + // exception occurred accessing the field + return nullptr; + } + + jobject jwe = env->GetObjectField(jwbwi_rocks_iterator, jwrite_entry_field); + assert(jwe != nullptr); + return jwe; + } +}; + +// The portal class for org.rocksdb.WBWIRocksIterator.WriteType +class WriteTypeJni : public JavaClass { + public: + /** + * Get the PUT enum field value of WBWIRocksIterator.WriteType + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject PUT(JNIEnv* env) { + return getEnum(env, "PUT"); + } + + /** + * Get the MERGE enum field value of WBWIRocksIterator.WriteType + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject MERGE(JNIEnv* env) { + return getEnum(env, "MERGE"); + } + + /** + * Get the DELETE enum field value of WBWIRocksIterator.WriteType + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject DELETE(JNIEnv* env) { + return getEnum(env, "DELETE"); + } + + /** + * Get the LOG enum field value of WBWIRocksIterator.WriteType + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject LOG(JNIEnv* env) { + return getEnum(env, "LOG"); + } + + // Returns the equivalent org.rocksdb.WBWIRocksIterator.WriteType for the + // provided C++ rocksdb::WriteType enum + static jbyte toJavaWriteType(const rocksdb::WriteType& writeType) { + switch (writeType) { + case rocksdb::WriteType::kPutRecord: + return 0x0; + case rocksdb::WriteType::kMergeRecord: + return 0x1; + case rocksdb::WriteType::kDeleteRecord: + return 0x2; + case rocksdb::WriteType::kSingleDeleteRecord: + return 0x3; + case rocksdb::WriteType::kDeleteRangeRecord: + return 0x4; + case rocksdb::WriteType::kLogDataRecord: + return 0x5; + case rocksdb::WriteType::kXIDRecord: + return 0x6; + default: + return 0x7F; // undefined + } + } + + private: + /** + * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteType + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator$WriteType"); + } + + /** + * Get an enum field of org.rocksdb.WBWIRocksIterator.WriteType + * + * @param env A pointer to the Java environment + * @param name The name of the enum field + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject getEnum(JNIEnv* env, const char name[]) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jfieldID jfid = + env->GetStaticFieldID(jclazz, name, + "Lorg/rocksdb/WBWIRocksIterator$WriteType;"); + if(env->ExceptionCheck()) { + // exception occurred while getting field + return nullptr; + } else if(jfid == nullptr) { + return nullptr; + } + + jobject jwrite_type = env->GetStaticObjectField(jclazz, jfid); + assert(jwrite_type != nullptr); + return jwrite_type; + } +}; + +// The portal class for org.rocksdb.WBWIRocksIterator.WriteEntry +class WriteEntryJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteEntry + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator$WriteEntry"); + } +}; + +// The portal class for org.rocksdb.InfoLogLevel +class InfoLogLevelJni : public JavaClass { + public: + /** + * Get the DEBUG_LEVEL enum field value of InfoLogLevel + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject DEBUG_LEVEL(JNIEnv* env) { + return getEnum(env, "DEBUG_LEVEL"); + } + + /** + * Get the INFO_LEVEL enum field value of InfoLogLevel + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject INFO_LEVEL(JNIEnv* env) { + return getEnum(env, "INFO_LEVEL"); + } + + /** + * Get the WARN_LEVEL enum field value of InfoLogLevel + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject WARN_LEVEL(JNIEnv* env) { + return getEnum(env, "WARN_LEVEL"); + } + + /** + * Get the ERROR_LEVEL enum field value of InfoLogLevel + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject ERROR_LEVEL(JNIEnv* env) { + return getEnum(env, "ERROR_LEVEL"); + } + + /** + * Get the FATAL_LEVEL enum field value of InfoLogLevel + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject FATAL_LEVEL(JNIEnv* env) { + return getEnum(env, "FATAL_LEVEL"); + } + + /** + * Get the HEADER_LEVEL enum field value of InfoLogLevel + * + * @param env A pointer to the Java environment + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject HEADER_LEVEL(JNIEnv* env) { + return getEnum(env, "HEADER_LEVEL"); + } + + private: + /** + * Get the Java Class org.rocksdb.InfoLogLevel + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/InfoLogLevel"); + } + + /** + * Get an enum field of org.rocksdb.InfoLogLevel + * + * @param env A pointer to the Java environment + * @param name The name of the enum field + * + * @return A reference to the enum field value or a nullptr if + * the enum field value could not be retrieved + */ + static jobject getEnum(JNIEnv* env, const char name[]) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jfieldID jfid = + env->GetStaticFieldID(jclazz, name, "Lorg/rocksdb/InfoLogLevel;"); + if(env->ExceptionCheck()) { + // exception occurred while getting field + return nullptr; + } else if(jfid == nullptr) { + return nullptr; + } + + jobject jinfo_log_level = env->GetStaticObjectField(jclazz, jfid); + assert(jinfo_log_level != nullptr); + return jinfo_log_level; + } +}; + +// The portal class for org.rocksdb.Logger +class LoggerJni : public RocksDBNativeClass< + std::shared_ptr*, LoggerJni> { + public: + /** + * Get the Java Class org/rocksdb/Logger + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, "org/rocksdb/Logger"); + } + + /** + * Get the Java Method: Logger#log + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getLogMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = + env->GetMethodID(jclazz, "log", + "(Lorg/rocksdb/InfoLogLevel;Ljava/lang/String;)V"); + assert(mid != nullptr); + return mid; + } +}; + +// The portal class for org.rocksdb.TransactionLogIterator.BatchResult +class BatchResultJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.TransactionLogIterator.BatchResult + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, + "org/rocksdb/TransactionLogIterator$BatchResult"); + } + + /** + * Create a new Java org.rocksdb.TransactionLogIterator.BatchResult object + * with the same properties as the provided C++ rocksdb::BatchResult object + * + * @param env A pointer to the Java environment + * @param batch_result The rocksdb::BatchResult object + * + * @return A reference to a Java + * org.rocksdb.TransactionLogIterator.BatchResult object, + * or nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, + rocksdb::BatchResult& batch_result) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = env->GetMethodID( + jclazz, "", "(JJ)V"); + if(mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + jobject jbatch_result = env->NewObject(jclazz, mid, + batch_result.sequence, batch_result.writeBatchPtr.get()); + if(jbatch_result == nullptr) { + // exception thrown: InstantiationException or OutOfMemoryError + return nullptr; + } + + batch_result.writeBatchPtr.release(); + return jbatch_result; + } +}; + +// The portal class for org.rocksdb.BottommostLevelCompaction +class BottommostLevelCompactionJni { + public: + // Returns the equivalent org.rocksdb.BottommostLevelCompaction for the provided + // C++ rocksdb::BottommostLevelCompaction enum + static jint toJavaBottommostLevelCompaction( + const rocksdb::BottommostLevelCompaction& bottommost_level_compaction) { + switch(bottommost_level_compaction) { + case rocksdb::BottommostLevelCompaction::kSkip: + return 0x0; + case rocksdb::BottommostLevelCompaction::kIfHaveCompactionFilter: + return 0x1; + case rocksdb::BottommostLevelCompaction::kForce: + return 0x2; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::BottommostLevelCompaction enum for the + // provided Java org.rocksdb.BottommostLevelCompaction + static rocksdb::BottommostLevelCompaction toCppBottommostLevelCompaction( + jint bottommost_level_compaction) { + switch(bottommost_level_compaction) { + case 0x0: + return rocksdb::BottommostLevelCompaction::kSkip; + case 0x1: + return rocksdb::BottommostLevelCompaction::kIfHaveCompactionFilter; + case 0x2: + return rocksdb::BottommostLevelCompaction::kForce; + default: + // undefined/default + return rocksdb::BottommostLevelCompaction::kIfHaveCompactionFilter; + } + } +}; + +// The portal class for org.rocksdb.CompactionStopStyle +class CompactionStopStyleJni { + public: + // Returns the equivalent org.rocksdb.CompactionStopStyle for the provided + // C++ rocksdb::CompactionStopStyle enum + static jbyte toJavaCompactionStopStyle( + const rocksdb::CompactionStopStyle& compaction_stop_style) { + switch(compaction_stop_style) { + case rocksdb::CompactionStopStyle::kCompactionStopStyleSimilarSize: + return 0x0; + case rocksdb::CompactionStopStyle::kCompactionStopStyleTotalSize: + return 0x1; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::CompactionStopStyle enum for the + // provided Java org.rocksdb.CompactionStopStyle + static rocksdb::CompactionStopStyle toCppCompactionStopStyle( + jbyte jcompaction_stop_style) { + switch(jcompaction_stop_style) { + case 0x0: + return rocksdb::CompactionStopStyle::kCompactionStopStyleSimilarSize; + case 0x1: + return rocksdb::CompactionStopStyle::kCompactionStopStyleTotalSize; + default: + // undefined/default + return rocksdb::CompactionStopStyle::kCompactionStopStyleSimilarSize; + } + } +}; + +// The portal class for org.rocksdb.CompressionType +class CompressionTypeJni { + public: + // Returns the equivalent org.rocksdb.CompressionType for the provided + // C++ rocksdb::CompressionType enum + static jbyte toJavaCompressionType( + const rocksdb::CompressionType& compression_type) { + switch(compression_type) { + case rocksdb::CompressionType::kNoCompression: + return 0x0; + case rocksdb::CompressionType::kSnappyCompression: + return 0x1; + case rocksdb::CompressionType::kZlibCompression: + return 0x2; + case rocksdb::CompressionType::kBZip2Compression: + return 0x3; + case rocksdb::CompressionType::kLZ4Compression: + return 0x4; + case rocksdb::CompressionType::kLZ4HCCompression: + return 0x5; + case rocksdb::CompressionType::kXpressCompression: + return 0x6; + case rocksdb::CompressionType::kZSTD: + return 0x7; + case rocksdb::CompressionType::kDisableCompressionOption: + default: + return 0x7F; + } + } + + // Returns the equivalent C++ rocksdb::CompressionType enum for the + // provided Java org.rocksdb.CompressionType + static rocksdb::CompressionType toCppCompressionType( + jbyte jcompression_type) { + switch(jcompression_type) { + case 0x0: + return rocksdb::CompressionType::kNoCompression; + case 0x1: + return rocksdb::CompressionType::kSnappyCompression; + case 0x2: + return rocksdb::CompressionType::kZlibCompression; + case 0x3: + return rocksdb::CompressionType::kBZip2Compression; + case 0x4: + return rocksdb::CompressionType::kLZ4Compression; + case 0x5: + return rocksdb::CompressionType::kLZ4HCCompression; + case 0x6: + return rocksdb::CompressionType::kXpressCompression; + case 0x7: + return rocksdb::CompressionType::kZSTD; + case 0x7F: + default: + return rocksdb::CompressionType::kDisableCompressionOption; + } + } +}; + +// The portal class for org.rocksdb.CompactionPriority +class CompactionPriorityJni { + public: + // Returns the equivalent org.rocksdb.CompactionPriority for the provided + // C++ rocksdb::CompactionPri enum + static jbyte toJavaCompactionPriority( + const rocksdb::CompactionPri& compaction_priority) { + switch(compaction_priority) { + case rocksdb::CompactionPri::kByCompensatedSize: + return 0x0; + case rocksdb::CompactionPri::kOldestLargestSeqFirst: + return 0x1; + case rocksdb::CompactionPri::kOldestSmallestSeqFirst: + return 0x2; + case rocksdb::CompactionPri::kMinOverlappingRatio: + return 0x3; + default: + return 0x0; // undefined + } + } + + // Returns the equivalent C++ rocksdb::CompactionPri enum for the + // provided Java org.rocksdb.CompactionPriority + static rocksdb::CompactionPri toCppCompactionPriority( + jbyte jcompaction_priority) { + switch(jcompaction_priority) { + case 0x0: + return rocksdb::CompactionPri::kByCompensatedSize; + case 0x1: + return rocksdb::CompactionPri::kOldestLargestSeqFirst; + case 0x2: + return rocksdb::CompactionPri::kOldestSmallestSeqFirst; + case 0x3: + return rocksdb::CompactionPri::kMinOverlappingRatio; + default: + // undefined/default + return rocksdb::CompactionPri::kByCompensatedSize; + } + } +}; + +// The portal class for org.rocksdb.AccessHint +class AccessHintJni { + public: + // Returns the equivalent org.rocksdb.AccessHint for the provided + // C++ rocksdb::DBOptions::AccessHint enum + static jbyte toJavaAccessHint( + const rocksdb::DBOptions::AccessHint& access_hint) { + switch(access_hint) { + case rocksdb::DBOptions::AccessHint::NONE: + return 0x0; + case rocksdb::DBOptions::AccessHint::NORMAL: + return 0x1; + case rocksdb::DBOptions::AccessHint::SEQUENTIAL: + return 0x2; + case rocksdb::DBOptions::AccessHint::WILLNEED: + return 0x3; + default: + // undefined/default + return 0x1; + } + } + + // Returns the equivalent C++ rocksdb::DBOptions::AccessHint enum for the + // provided Java org.rocksdb.AccessHint + static rocksdb::DBOptions::AccessHint toCppAccessHint(jbyte jaccess_hint) { + switch(jaccess_hint) { + case 0x0: + return rocksdb::DBOptions::AccessHint::NONE; + case 0x1: + return rocksdb::DBOptions::AccessHint::NORMAL; + case 0x2: + return rocksdb::DBOptions::AccessHint::SEQUENTIAL; + case 0x3: + return rocksdb::DBOptions::AccessHint::WILLNEED; + default: + // undefined/default + return rocksdb::DBOptions::AccessHint::NORMAL; + } + } +}; + +// The portal class for org.rocksdb.WALRecoveryMode +class WALRecoveryModeJni { + public: + // Returns the equivalent org.rocksdb.WALRecoveryMode for the provided + // C++ rocksdb::WALRecoveryMode enum + static jbyte toJavaWALRecoveryMode( + const rocksdb::WALRecoveryMode& wal_recovery_mode) { + switch(wal_recovery_mode) { + case rocksdb::WALRecoveryMode::kTolerateCorruptedTailRecords: + return 0x0; + case rocksdb::WALRecoveryMode::kAbsoluteConsistency: + return 0x1; + case rocksdb::WALRecoveryMode::kPointInTimeRecovery: + return 0x2; + case rocksdb::WALRecoveryMode::kSkipAnyCorruptedRecords: + return 0x3; + default: + // undefined/default + return 0x2; + } + } + + // Returns the equivalent C++ rocksdb::WALRecoveryMode enum for the + // provided Java org.rocksdb.WALRecoveryMode + static rocksdb::WALRecoveryMode toCppWALRecoveryMode(jbyte jwal_recovery_mode) { + switch(jwal_recovery_mode) { + case 0x0: + return rocksdb::WALRecoveryMode::kTolerateCorruptedTailRecords; + case 0x1: + return rocksdb::WALRecoveryMode::kAbsoluteConsistency; + case 0x2: + return rocksdb::WALRecoveryMode::kPointInTimeRecovery; + case 0x3: + return rocksdb::WALRecoveryMode::kSkipAnyCorruptedRecords; + default: + // undefined/default + return rocksdb::WALRecoveryMode::kPointInTimeRecovery; + } + } +}; + +// The portal class for org.rocksdb.TickerType +class TickerTypeJni { + public: + // Returns the equivalent org.rocksdb.TickerType for the provided + // C++ rocksdb::Tickers enum + static jbyte toJavaTickerType( + const rocksdb::Tickers& tickers) { + switch(tickers) { + case rocksdb::Tickers::BLOCK_CACHE_MISS: + return 0x0; + case rocksdb::Tickers::BLOCK_CACHE_HIT: + return 0x1; + case rocksdb::Tickers::BLOCK_CACHE_ADD: + return 0x2; + case rocksdb::Tickers::BLOCK_CACHE_ADD_FAILURES: + return 0x3; + case rocksdb::Tickers::BLOCK_CACHE_INDEX_MISS: + return 0x4; + case rocksdb::Tickers::BLOCK_CACHE_INDEX_HIT: + return 0x5; + case rocksdb::Tickers::BLOCK_CACHE_INDEX_ADD: + return 0x6; + case rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT: + return 0x7; + case rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_EVICT: + return 0x8; + case rocksdb::Tickers::BLOCK_CACHE_FILTER_MISS: + return 0x9; + case rocksdb::Tickers::BLOCK_CACHE_FILTER_HIT: + return 0xA; + case rocksdb::Tickers::BLOCK_CACHE_FILTER_ADD: + return 0xB; + case rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT: + return 0xC; + case rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_EVICT: + return 0xD; + case rocksdb::Tickers::BLOCK_CACHE_DATA_MISS: + return 0xE; + case rocksdb::Tickers::BLOCK_CACHE_DATA_HIT: + return 0xF; + case rocksdb::Tickers::BLOCK_CACHE_DATA_ADD: + return 0x10; + case rocksdb::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT: + return 0x11; + case rocksdb::Tickers::BLOCK_CACHE_BYTES_READ: + return 0x12; + case rocksdb::Tickers::BLOCK_CACHE_BYTES_WRITE: + return 0x13; + case rocksdb::Tickers::BLOOM_FILTER_USEFUL: + return 0x14; + case rocksdb::Tickers::PERSISTENT_CACHE_HIT: + return 0x15; + case rocksdb::Tickers::PERSISTENT_CACHE_MISS: + return 0x16; + case rocksdb::Tickers::SIM_BLOCK_CACHE_HIT: + return 0x17; + case rocksdb::Tickers::SIM_BLOCK_CACHE_MISS: + return 0x18; + case rocksdb::Tickers::MEMTABLE_HIT: + return 0x19; + case rocksdb::Tickers::MEMTABLE_MISS: + return 0x1A; + case rocksdb::Tickers::GET_HIT_L0: + return 0x1B; + case rocksdb::Tickers::GET_HIT_L1: + return 0x1C; case rocksdb::Tickers::GET_HIT_L2_AND_UP: return 0x1D; - case rocksdb::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY: + case rocksdb::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY: + return 0x1E; + case rocksdb::Tickers::COMPACTION_KEY_DROP_OBSOLETE: + return 0x1F; + case rocksdb::Tickers::COMPACTION_KEY_DROP_RANGE_DEL: + return 0x20; + case rocksdb::Tickers::COMPACTION_KEY_DROP_USER: + return 0x21; + case rocksdb::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE: + return 0x22; + case rocksdb::Tickers::NUMBER_KEYS_WRITTEN: + return 0x23; + case rocksdb::Tickers::NUMBER_KEYS_READ: + return 0x24; + case rocksdb::Tickers::NUMBER_KEYS_UPDATED: + return 0x25; + case rocksdb::Tickers::BYTES_WRITTEN: + return 0x26; + case rocksdb::Tickers::BYTES_READ: + return 0x27; + case rocksdb::Tickers::NUMBER_DB_SEEK: + return 0x28; + case rocksdb::Tickers::NUMBER_DB_NEXT: + return 0x29; + case rocksdb::Tickers::NUMBER_DB_PREV: + return 0x2A; + case rocksdb::Tickers::NUMBER_DB_SEEK_FOUND: + return 0x2B; + case rocksdb::Tickers::NUMBER_DB_NEXT_FOUND: + return 0x2C; + case rocksdb::Tickers::NUMBER_DB_PREV_FOUND: + return 0x2D; + case rocksdb::Tickers::ITER_BYTES_READ: + return 0x2E; + case rocksdb::Tickers::NO_FILE_CLOSES: + return 0x2F; + case rocksdb::Tickers::NO_FILE_OPENS: + return 0x30; + case rocksdb::Tickers::NO_FILE_ERRORS: + return 0x31; + case rocksdb::Tickers::STALL_L0_SLOWDOWN_MICROS: + return 0x32; + case rocksdb::Tickers::STALL_MEMTABLE_COMPACTION_MICROS: + return 0x33; + case rocksdb::Tickers::STALL_L0_NUM_FILES_MICROS: + return 0x34; + case rocksdb::Tickers::STALL_MICROS: + return 0x35; + case rocksdb::Tickers::DB_MUTEX_WAIT_MICROS: + return 0x36; + case rocksdb::Tickers::RATE_LIMIT_DELAY_MILLIS: + return 0x37; + case rocksdb::Tickers::NO_ITERATORS: + return 0x38; + case rocksdb::Tickers::NUMBER_MULTIGET_CALLS: + return 0x39; + case rocksdb::Tickers::NUMBER_MULTIGET_KEYS_READ: + return 0x3A; + case rocksdb::Tickers::NUMBER_MULTIGET_BYTES_READ: + return 0x3B; + case rocksdb::Tickers::NUMBER_FILTERED_DELETES: + return 0x3C; + case rocksdb::Tickers::NUMBER_MERGE_FAILURES: + return 0x3D; + case rocksdb::Tickers::BLOOM_FILTER_PREFIX_CHECKED: + return 0x3E; + case rocksdb::Tickers::BLOOM_FILTER_PREFIX_USEFUL: + return 0x3F; + case rocksdb::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION: + return 0x40; + case rocksdb::Tickers::GET_UPDATES_SINCE_CALLS: + return 0x41; + case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_MISS: + return 0x42; + case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_HIT: + return 0x43; + case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD: + return 0x44; + case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES: + return 0x45; + case rocksdb::Tickers::WAL_FILE_SYNCED: + return 0x46; + case rocksdb::Tickers::WAL_FILE_BYTES: + return 0x47; + case rocksdb::Tickers::WRITE_DONE_BY_SELF: + return 0x48; + case rocksdb::Tickers::WRITE_DONE_BY_OTHER: + return 0x49; + case rocksdb::Tickers::WRITE_TIMEDOUT: + return 0x4A; + case rocksdb::Tickers::WRITE_WITH_WAL: + return 0x4B; + case rocksdb::Tickers::COMPACT_READ_BYTES: + return 0x4C; + case rocksdb::Tickers::COMPACT_WRITE_BYTES: + return 0x4D; + case rocksdb::Tickers::FLUSH_WRITE_BYTES: + return 0x4E; + case rocksdb::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES: + return 0x4F; + case rocksdb::Tickers::NUMBER_SUPERVERSION_ACQUIRES: + return 0x50; + case rocksdb::Tickers::NUMBER_SUPERVERSION_RELEASES: + return 0x51; + case rocksdb::Tickers::NUMBER_SUPERVERSION_CLEANUPS: + return 0x52; + case rocksdb::Tickers::NUMBER_BLOCK_COMPRESSED: + return 0x53; + case rocksdb::Tickers::NUMBER_BLOCK_DECOMPRESSED: + return 0x54; + case rocksdb::Tickers::NUMBER_BLOCK_NOT_COMPRESSED: + return 0x55; + case rocksdb::Tickers::MERGE_OPERATION_TOTAL_TIME: + return 0x56; + case rocksdb::Tickers::FILTER_OPERATION_TOTAL_TIME: + return 0x57; + case rocksdb::Tickers::ROW_CACHE_HIT: + return 0x58; + case rocksdb::Tickers::ROW_CACHE_MISS: + return 0x59; + case rocksdb::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES: + return 0x5A; + case rocksdb::Tickers::READ_AMP_TOTAL_READ_BYTES: + return 0x5B; + case rocksdb::Tickers::NUMBER_RATE_LIMITER_DRAINS: + return 0x5C; + case rocksdb::Tickers::NUMBER_ITER_SKIP: + return 0x5D; + case rocksdb::Tickers::NUMBER_MULTIGET_KEYS_FOUND: + return 0x5E; + case rocksdb::Tickers::NO_ITERATOR_CREATED: + // -0x01 to fixate the new value that incorrectly changed TICKER_ENUM_MAX. + return -0x01; + case rocksdb::Tickers::NO_ITERATOR_DELETED: + return 0x60; + case rocksdb::Tickers::COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE: + return 0x61; + case rocksdb::Tickers::COMPACTION_CANCELLED: + return 0x62; + case rocksdb::Tickers::BLOOM_FILTER_FULL_POSITIVE: + return 0x63; + case rocksdb::Tickers::BLOOM_FILTER_FULL_TRUE_POSITIVE: + return 0x64; + case rocksdb::Tickers::BLOB_DB_NUM_PUT: + return 0x65; + case rocksdb::Tickers::BLOB_DB_NUM_WRITE: + return 0x66; + case rocksdb::Tickers::BLOB_DB_NUM_GET: + return 0x67; + case rocksdb::Tickers::BLOB_DB_NUM_MULTIGET: + return 0x68; + case rocksdb::Tickers::BLOB_DB_NUM_SEEK: + return 0x69; + case rocksdb::Tickers::BLOB_DB_NUM_NEXT: + return 0x6A; + case rocksdb::Tickers::BLOB_DB_NUM_PREV: + return 0x6B; + case rocksdb::Tickers::BLOB_DB_NUM_KEYS_WRITTEN: + return 0x6C; + case rocksdb::Tickers::BLOB_DB_NUM_KEYS_READ: + return 0x6D; + case rocksdb::Tickers::BLOB_DB_BYTES_WRITTEN: + return 0x6E; + case rocksdb::Tickers::BLOB_DB_BYTES_READ: + return 0x6F; + case rocksdb::Tickers::BLOB_DB_WRITE_INLINED: + return 0x70; + case rocksdb::Tickers::BLOB_DB_WRITE_INLINED_TTL: + return 0x71; + case rocksdb::Tickers::BLOB_DB_WRITE_BLOB: + return 0x72; + case rocksdb::Tickers::BLOB_DB_WRITE_BLOB_TTL: + return 0x73; + case rocksdb::Tickers::BLOB_DB_BLOB_FILE_BYTES_WRITTEN: + return 0x74; + case rocksdb::Tickers::BLOB_DB_BLOB_FILE_BYTES_READ: + return 0x75; + case rocksdb::Tickers::BLOB_DB_BLOB_FILE_SYNCED: + return 0x76; + case rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_COUNT: + return 0x77; + case rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_SIZE: + return 0x78; + case rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_COUNT: + return 0x79; + case rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_SIZE: + return 0x7A; + case rocksdb::Tickers::BLOB_DB_GC_NUM_FILES: + return 0x7B; + case rocksdb::Tickers::BLOB_DB_GC_NUM_NEW_FILES: + return 0x7C; + case rocksdb::Tickers::BLOB_DB_GC_FAILURES: + return 0x7D; + case rocksdb::Tickers::BLOB_DB_GC_NUM_KEYS_OVERWRITTEN: + return 0x7E; + case rocksdb::Tickers::BLOB_DB_GC_NUM_KEYS_EXPIRED: + return 0x7F; + case rocksdb::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED: + return -0x02; + case rocksdb::Tickers::BLOB_DB_GC_BYTES_OVERWRITTEN: + return -0x03; + case rocksdb::Tickers::BLOB_DB_GC_BYTES_EXPIRED: + return -0x04; + case rocksdb::Tickers::BLOB_DB_GC_BYTES_RELOCATED: + return -0x05; + case rocksdb::Tickers::BLOB_DB_FIFO_NUM_FILES_EVICTED: + return -0x06; + case rocksdb::Tickers::BLOB_DB_FIFO_NUM_KEYS_EVICTED: + return -0x07; + case rocksdb::Tickers::BLOB_DB_FIFO_BYTES_EVICTED: + return -0x08; + case rocksdb::Tickers::TXN_PREPARE_MUTEX_OVERHEAD: + return -0x09; + case rocksdb::Tickers::TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD: + return -0x0A; + case rocksdb::Tickers::TXN_DUPLICATE_KEY_OVERHEAD: + return -0x0B; + case rocksdb::Tickers::TXN_SNAPSHOT_MUTEX_OVERHEAD: + return -0x0C; + case rocksdb::Tickers::TICKER_ENUM_MAX: + // 0x5F for backwards compatibility on current minor version. + return 0x5F; + default: + // undefined/default + return 0x0; + } + } + + // Returns the equivalent C++ rocksdb::Tickers enum for the + // provided Java org.rocksdb.TickerType + static rocksdb::Tickers toCppTickers(jbyte jticker_type) { + switch(jticker_type) { + case 0x0: + return rocksdb::Tickers::BLOCK_CACHE_MISS; + case 0x1: + return rocksdb::Tickers::BLOCK_CACHE_HIT; + case 0x2: + return rocksdb::Tickers::BLOCK_CACHE_ADD; + case 0x3: + return rocksdb::Tickers::BLOCK_CACHE_ADD_FAILURES; + case 0x4: + return rocksdb::Tickers::BLOCK_CACHE_INDEX_MISS; + case 0x5: + return rocksdb::Tickers::BLOCK_CACHE_INDEX_HIT; + case 0x6: + return rocksdb::Tickers::BLOCK_CACHE_INDEX_ADD; + case 0x7: + return rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT; + case 0x8: + return rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_EVICT; + case 0x9: + return rocksdb::Tickers::BLOCK_CACHE_FILTER_MISS; + case 0xA: + return rocksdb::Tickers::BLOCK_CACHE_FILTER_HIT; + case 0xB: + return rocksdb::Tickers::BLOCK_CACHE_FILTER_ADD; + case 0xC: + return rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT; + case 0xD: + return rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_EVICT; + case 0xE: + return rocksdb::Tickers::BLOCK_CACHE_DATA_MISS; + case 0xF: + return rocksdb::Tickers::BLOCK_CACHE_DATA_HIT; + case 0x10: + return rocksdb::Tickers::BLOCK_CACHE_DATA_ADD; + case 0x11: + return rocksdb::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT; + case 0x12: + return rocksdb::Tickers::BLOCK_CACHE_BYTES_READ; + case 0x13: + return rocksdb::Tickers::BLOCK_CACHE_BYTES_WRITE; + case 0x14: + return rocksdb::Tickers::BLOOM_FILTER_USEFUL; + case 0x15: + return rocksdb::Tickers::PERSISTENT_CACHE_HIT; + case 0x16: + return rocksdb::Tickers::PERSISTENT_CACHE_MISS; + case 0x17: + return rocksdb::Tickers::SIM_BLOCK_CACHE_HIT; + case 0x18: + return rocksdb::Tickers::SIM_BLOCK_CACHE_MISS; + case 0x19: + return rocksdb::Tickers::MEMTABLE_HIT; + case 0x1A: + return rocksdb::Tickers::MEMTABLE_MISS; + case 0x1B: + return rocksdb::Tickers::GET_HIT_L0; + case 0x1C: + return rocksdb::Tickers::GET_HIT_L1; + case 0x1D: + return rocksdb::Tickers::GET_HIT_L2_AND_UP; + case 0x1E: + return rocksdb::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY; + case 0x1F: + return rocksdb::Tickers::COMPACTION_KEY_DROP_OBSOLETE; + case 0x20: + return rocksdb::Tickers::COMPACTION_KEY_DROP_RANGE_DEL; + case 0x21: + return rocksdb::Tickers::COMPACTION_KEY_DROP_USER; + case 0x22: + return rocksdb::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE; + case 0x23: + return rocksdb::Tickers::NUMBER_KEYS_WRITTEN; + case 0x24: + return rocksdb::Tickers::NUMBER_KEYS_READ; + case 0x25: + return rocksdb::Tickers::NUMBER_KEYS_UPDATED; + case 0x26: + return rocksdb::Tickers::BYTES_WRITTEN; + case 0x27: + return rocksdb::Tickers::BYTES_READ; + case 0x28: + return rocksdb::Tickers::NUMBER_DB_SEEK; + case 0x29: + return rocksdb::Tickers::NUMBER_DB_NEXT; + case 0x2A: + return rocksdb::Tickers::NUMBER_DB_PREV; + case 0x2B: + return rocksdb::Tickers::NUMBER_DB_SEEK_FOUND; + case 0x2C: + return rocksdb::Tickers::NUMBER_DB_NEXT_FOUND; + case 0x2D: + return rocksdb::Tickers::NUMBER_DB_PREV_FOUND; + case 0x2E: + return rocksdb::Tickers::ITER_BYTES_READ; + case 0x2F: + return rocksdb::Tickers::NO_FILE_CLOSES; + case 0x30: + return rocksdb::Tickers::NO_FILE_OPENS; + case 0x31: + return rocksdb::Tickers::NO_FILE_ERRORS; + case 0x32: + return rocksdb::Tickers::STALL_L0_SLOWDOWN_MICROS; + case 0x33: + return rocksdb::Tickers::STALL_MEMTABLE_COMPACTION_MICROS; + case 0x34: + return rocksdb::Tickers::STALL_L0_NUM_FILES_MICROS; + case 0x35: + return rocksdb::Tickers::STALL_MICROS; + case 0x36: + return rocksdb::Tickers::DB_MUTEX_WAIT_MICROS; + case 0x37: + return rocksdb::Tickers::RATE_LIMIT_DELAY_MILLIS; + case 0x38: + return rocksdb::Tickers::NO_ITERATORS; + case 0x39: + return rocksdb::Tickers::NUMBER_MULTIGET_CALLS; + case 0x3A: + return rocksdb::Tickers::NUMBER_MULTIGET_KEYS_READ; + case 0x3B: + return rocksdb::Tickers::NUMBER_MULTIGET_BYTES_READ; + case 0x3C: + return rocksdb::Tickers::NUMBER_FILTERED_DELETES; + case 0x3D: + return rocksdb::Tickers::NUMBER_MERGE_FAILURES; + case 0x3E: + return rocksdb::Tickers::BLOOM_FILTER_PREFIX_CHECKED; + case 0x3F: + return rocksdb::Tickers::BLOOM_FILTER_PREFIX_USEFUL; + case 0x40: + return rocksdb::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION; + case 0x41: + return rocksdb::Tickers::GET_UPDATES_SINCE_CALLS; + case 0x42: + return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_MISS; + case 0x43: + return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_HIT; + case 0x44: + return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD; + case 0x45: + return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES; + case 0x46: + return rocksdb::Tickers::WAL_FILE_SYNCED; + case 0x47: + return rocksdb::Tickers::WAL_FILE_BYTES; + case 0x48: + return rocksdb::Tickers::WRITE_DONE_BY_SELF; + case 0x49: + return rocksdb::Tickers::WRITE_DONE_BY_OTHER; + case 0x4A: + return rocksdb::Tickers::WRITE_TIMEDOUT; + case 0x4B: + return rocksdb::Tickers::WRITE_WITH_WAL; + case 0x4C: + return rocksdb::Tickers::COMPACT_READ_BYTES; + case 0x4D: + return rocksdb::Tickers::COMPACT_WRITE_BYTES; + case 0x4E: + return rocksdb::Tickers::FLUSH_WRITE_BYTES; + case 0x4F: + return rocksdb::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES; + case 0x50: + return rocksdb::Tickers::NUMBER_SUPERVERSION_ACQUIRES; + case 0x51: + return rocksdb::Tickers::NUMBER_SUPERVERSION_RELEASES; + case 0x52: + return rocksdb::Tickers::NUMBER_SUPERVERSION_CLEANUPS; + case 0x53: + return rocksdb::Tickers::NUMBER_BLOCK_COMPRESSED; + case 0x54: + return rocksdb::Tickers::NUMBER_BLOCK_DECOMPRESSED; + case 0x55: + return rocksdb::Tickers::NUMBER_BLOCK_NOT_COMPRESSED; + case 0x56: + return rocksdb::Tickers::MERGE_OPERATION_TOTAL_TIME; + case 0x57: + return rocksdb::Tickers::FILTER_OPERATION_TOTAL_TIME; + case 0x58: + return rocksdb::Tickers::ROW_CACHE_HIT; + case 0x59: + return rocksdb::Tickers::ROW_CACHE_MISS; + case 0x5A: + return rocksdb::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES; + case 0x5B: + return rocksdb::Tickers::READ_AMP_TOTAL_READ_BYTES; + case 0x5C: + return rocksdb::Tickers::NUMBER_RATE_LIMITER_DRAINS; + case 0x5D: + return rocksdb::Tickers::NUMBER_ITER_SKIP; + case 0x5E: + return rocksdb::Tickers::NUMBER_MULTIGET_KEYS_FOUND; + case -0x01: + // -0x01 to fixate the new value that incorrectly changed TICKER_ENUM_MAX. + return rocksdb::Tickers::NO_ITERATOR_CREATED; + case 0x60: + return rocksdb::Tickers::NO_ITERATOR_DELETED; + case 0x61: + return rocksdb::Tickers::COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE; + case 0x62: + return rocksdb::Tickers::COMPACTION_CANCELLED; + case 0x63: + return rocksdb::Tickers::BLOOM_FILTER_FULL_POSITIVE; + case 0x64: + return rocksdb::Tickers::BLOOM_FILTER_FULL_TRUE_POSITIVE; + case 0x65: + return rocksdb::Tickers::BLOB_DB_NUM_PUT; + case 0x66: + return rocksdb::Tickers::BLOB_DB_NUM_WRITE; + case 0x67: + return rocksdb::Tickers::BLOB_DB_NUM_GET; + case 0x68: + return rocksdb::Tickers::BLOB_DB_NUM_MULTIGET; + case 0x69: + return rocksdb::Tickers::BLOB_DB_NUM_SEEK; + case 0x6A: + return rocksdb::Tickers::BLOB_DB_NUM_NEXT; + case 0x6B: + return rocksdb::Tickers::BLOB_DB_NUM_PREV; + case 0x6C: + return rocksdb::Tickers::BLOB_DB_NUM_KEYS_WRITTEN; + case 0x6D: + return rocksdb::Tickers::BLOB_DB_NUM_KEYS_READ; + case 0x6E: + return rocksdb::Tickers::BLOB_DB_BYTES_WRITTEN; + case 0x6F: + return rocksdb::Tickers::BLOB_DB_BYTES_READ; + case 0x70: + return rocksdb::Tickers::BLOB_DB_WRITE_INLINED; + case 0x71: + return rocksdb::Tickers::BLOB_DB_WRITE_INLINED_TTL; + case 0x72: + return rocksdb::Tickers::BLOB_DB_WRITE_BLOB; + case 0x73: + return rocksdb::Tickers::BLOB_DB_WRITE_BLOB_TTL; + case 0x74: + return rocksdb::Tickers::BLOB_DB_BLOB_FILE_BYTES_WRITTEN; + case 0x75: + return rocksdb::Tickers::BLOB_DB_BLOB_FILE_BYTES_READ; + case 0x76: + return rocksdb::Tickers::BLOB_DB_BLOB_FILE_SYNCED; + case 0x77: + return rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_COUNT; + case 0x78: + return rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_SIZE; + case 0x79: + return rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_COUNT; + case 0x7A: + return rocksdb::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_SIZE; + case 0x7B: + return rocksdb::Tickers::BLOB_DB_GC_NUM_FILES; + case 0x7C: + return rocksdb::Tickers::BLOB_DB_GC_NUM_NEW_FILES; + case 0x7D: + return rocksdb::Tickers::BLOB_DB_GC_FAILURES; + case 0x7E: + return rocksdb::Tickers::BLOB_DB_GC_NUM_KEYS_OVERWRITTEN; + case 0x7F: + return rocksdb::Tickers::BLOB_DB_GC_NUM_KEYS_EXPIRED; + case -0x02: + return rocksdb::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED; + case -0x03: + return rocksdb::Tickers::BLOB_DB_GC_BYTES_OVERWRITTEN; + case -0x04: + return rocksdb::Tickers::BLOB_DB_GC_BYTES_EXPIRED; + case -0x05: + return rocksdb::Tickers::BLOB_DB_GC_BYTES_RELOCATED; + case -0x06: + return rocksdb::Tickers::BLOB_DB_FIFO_NUM_FILES_EVICTED; + case -0x07: + return rocksdb::Tickers::BLOB_DB_FIFO_NUM_KEYS_EVICTED; + case -0x08: + return rocksdb::Tickers::BLOB_DB_FIFO_BYTES_EVICTED; + case -0x09: + return rocksdb::Tickers::TXN_PREPARE_MUTEX_OVERHEAD; + case -0x0A: + return rocksdb::Tickers::TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD; + case -0x0B: + return rocksdb::Tickers::TXN_DUPLICATE_KEY_OVERHEAD; + case -0x0C: + return rocksdb::Tickers::TXN_SNAPSHOT_MUTEX_OVERHEAD; + case 0x5F: + // 0x5F for backwards compatibility on current minor version. + return rocksdb::Tickers::TICKER_ENUM_MAX; + + default: + // undefined/default + return rocksdb::Tickers::BLOCK_CACHE_MISS; + } + } +}; + +// The portal class for org.rocksdb.HistogramType +class HistogramTypeJni { + public: + // Returns the equivalent org.rocksdb.HistogramType for the provided + // C++ rocksdb::Histograms enum + static jbyte toJavaHistogramsType( + const rocksdb::Histograms& histograms) { + switch(histograms) { + case rocksdb::Histograms::DB_GET: + return 0x0; + case rocksdb::Histograms::DB_WRITE: + return 0x1; + case rocksdb::Histograms::COMPACTION_TIME: + return 0x2; + case rocksdb::Histograms::SUBCOMPACTION_SETUP_TIME: + return 0x3; + case rocksdb::Histograms::TABLE_SYNC_MICROS: + return 0x4; + case rocksdb::Histograms::COMPACTION_OUTFILE_SYNC_MICROS: + return 0x5; + case rocksdb::Histograms::WAL_FILE_SYNC_MICROS: + return 0x6; + case rocksdb::Histograms::MANIFEST_FILE_SYNC_MICROS: + return 0x7; + case rocksdb::Histograms::TABLE_OPEN_IO_MICROS: + return 0x8; + case rocksdb::Histograms::DB_MULTIGET: + return 0x9; + case rocksdb::Histograms::READ_BLOCK_COMPACTION_MICROS: + return 0xA; + case rocksdb::Histograms::READ_BLOCK_GET_MICROS: + return 0xB; + case rocksdb::Histograms::WRITE_RAW_BLOCK_MICROS: + return 0xC; + case rocksdb::Histograms::STALL_L0_SLOWDOWN_COUNT: + return 0xD; + case rocksdb::Histograms::STALL_MEMTABLE_COMPACTION_COUNT: + return 0xE; + case rocksdb::Histograms::STALL_L0_NUM_FILES_COUNT: + return 0xF; + case rocksdb::Histograms::HARD_RATE_LIMIT_DELAY_COUNT: + return 0x10; + case rocksdb::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT: + return 0x11; + case rocksdb::Histograms::NUM_FILES_IN_SINGLE_COMPACTION: + return 0x12; + case rocksdb::Histograms::DB_SEEK: + return 0x13; + case rocksdb::Histograms::WRITE_STALL: + return 0x14; + case rocksdb::Histograms::SST_READ_MICROS: + return 0x15; + case rocksdb::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED: + return 0x16; + case rocksdb::Histograms::BYTES_PER_READ: + return 0x17; + case rocksdb::Histograms::BYTES_PER_WRITE: + return 0x18; + case rocksdb::Histograms::BYTES_PER_MULTIGET: + return 0x19; + case rocksdb::Histograms::BYTES_COMPRESSED: + return 0x1A; + case rocksdb::Histograms::BYTES_DECOMPRESSED: + return 0x1B; + case rocksdb::Histograms::COMPRESSION_TIMES_NANOS: + return 0x1C; + case rocksdb::Histograms::DECOMPRESSION_TIMES_NANOS: + return 0x1D; + case rocksdb::Histograms::READ_NUM_MERGE_OPERANDS: return 0x1E; - case rocksdb::Tickers::COMPACTION_KEY_DROP_OBSOLETE: - return 0x1F; - case rocksdb::Tickers::COMPACTION_KEY_DROP_RANGE_DEL: + // 0x20 to skip 0x1F so TICKER_ENUM_MAX remains unchanged for minor version compatibility. + case rocksdb::Histograms::FLUSH_TIME: return 0x20; - case rocksdb::Tickers::COMPACTION_KEY_DROP_USER: + case rocksdb::Histograms::BLOB_DB_KEY_SIZE: return 0x21; - case rocksdb::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE: + case rocksdb::Histograms::BLOB_DB_VALUE_SIZE: return 0x22; - case rocksdb::Tickers::NUMBER_KEYS_WRITTEN: + case rocksdb::Histograms::BLOB_DB_WRITE_MICROS: return 0x23; - case rocksdb::Tickers::NUMBER_KEYS_READ: + case rocksdb::Histograms::BLOB_DB_GET_MICROS: return 0x24; - case rocksdb::Tickers::NUMBER_KEYS_UPDATED: + case rocksdb::Histograms::BLOB_DB_MULTIGET_MICROS: return 0x25; - case rocksdb::Tickers::BYTES_WRITTEN: + case rocksdb::Histograms::BLOB_DB_SEEK_MICROS: return 0x26; - case rocksdb::Tickers::BYTES_READ: + case rocksdb::Histograms::BLOB_DB_NEXT_MICROS: return 0x27; - case rocksdb::Tickers::NUMBER_DB_SEEK: + case rocksdb::Histograms::BLOB_DB_PREV_MICROS: return 0x28; - case rocksdb::Tickers::NUMBER_DB_NEXT: + case rocksdb::Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS: return 0x29; - case rocksdb::Tickers::NUMBER_DB_PREV: + case rocksdb::Histograms::BLOB_DB_BLOB_FILE_READ_MICROS: return 0x2A; - case rocksdb::Tickers::NUMBER_DB_SEEK_FOUND: + case rocksdb::Histograms::BLOB_DB_BLOB_FILE_SYNC_MICROS: return 0x2B; - case rocksdb::Tickers::NUMBER_DB_NEXT_FOUND: + case rocksdb::Histograms::BLOB_DB_GC_MICROS: return 0x2C; - case rocksdb::Tickers::NUMBER_DB_PREV_FOUND: + case rocksdb::Histograms::BLOB_DB_COMPRESSION_MICROS: return 0x2D; - case rocksdb::Tickers::ITER_BYTES_READ: + case rocksdb::Histograms::BLOB_DB_DECOMPRESSION_MICROS: return 0x2E; - case rocksdb::Tickers::NO_FILE_CLOSES: - return 0x2F; - case rocksdb::Tickers::NO_FILE_OPENS: - return 0x30; - case rocksdb::Tickers::NO_FILE_ERRORS: - return 0x31; - case rocksdb::Tickers::STALL_L0_SLOWDOWN_MICROS: - return 0x32; - case rocksdb::Tickers::STALL_MEMTABLE_COMPACTION_MICROS: - return 0x33; - case rocksdb::Tickers::STALL_L0_NUM_FILES_MICROS: - return 0x34; - case rocksdb::Tickers::STALL_MICROS: - return 0x35; - case rocksdb::Tickers::DB_MUTEX_WAIT_MICROS: - return 0x36; - case rocksdb::Tickers::RATE_LIMIT_DELAY_MILLIS: - return 0x37; - case rocksdb::Tickers::NO_ITERATORS: - return 0x38; - case rocksdb::Tickers::NUMBER_MULTIGET_CALLS: - return 0x39; - case rocksdb::Tickers::NUMBER_MULTIGET_KEYS_READ: - return 0x3A; - case rocksdb::Tickers::NUMBER_MULTIGET_BYTES_READ: - return 0x3B; - case rocksdb::Tickers::NUMBER_FILTERED_DELETES: - return 0x3C; - case rocksdb::Tickers::NUMBER_MERGE_FAILURES: - return 0x3D; - case rocksdb::Tickers::BLOOM_FILTER_PREFIX_CHECKED: - return 0x3E; - case rocksdb::Tickers::BLOOM_FILTER_PREFIX_USEFUL: - return 0x3F; - case rocksdb::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION: - return 0x40; - case rocksdb::Tickers::GET_UPDATES_SINCE_CALLS: - return 0x41; - case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_MISS: - return 0x42; - case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_HIT: - return 0x43; - case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD: - return 0x44; - case rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES: - return 0x45; - case rocksdb::Tickers::WAL_FILE_SYNCED: - return 0x46; - case rocksdb::Tickers::WAL_FILE_BYTES: - return 0x47; - case rocksdb::Tickers::WRITE_DONE_BY_SELF: - return 0x48; - case rocksdb::Tickers::WRITE_DONE_BY_OTHER: - return 0x49; - case rocksdb::Tickers::WRITE_TIMEDOUT: - return 0x4A; - case rocksdb::Tickers::WRITE_WITH_WAL: - return 0x4B; - case rocksdb::Tickers::COMPACT_READ_BYTES: - return 0x4C; - case rocksdb::Tickers::COMPACT_WRITE_BYTES: - return 0x4D; - case rocksdb::Tickers::FLUSH_WRITE_BYTES: - return 0x4E; - case rocksdb::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES: - return 0x4F; - case rocksdb::Tickers::NUMBER_SUPERVERSION_ACQUIRES: - return 0x50; - case rocksdb::Tickers::NUMBER_SUPERVERSION_RELEASES: - return 0x51; - case rocksdb::Tickers::NUMBER_SUPERVERSION_CLEANUPS: - return 0x52; - case rocksdb::Tickers::NUMBER_BLOCK_COMPRESSED: - return 0x53; - case rocksdb::Tickers::NUMBER_BLOCK_DECOMPRESSED: - return 0x54; - case rocksdb::Tickers::NUMBER_BLOCK_NOT_COMPRESSED: - return 0x55; - case rocksdb::Tickers::MERGE_OPERATION_TOTAL_TIME: - return 0x56; - case rocksdb::Tickers::FILTER_OPERATION_TOTAL_TIME: - return 0x57; - case rocksdb::Tickers::ROW_CACHE_HIT: - return 0x58; - case rocksdb::Tickers::ROW_CACHE_MISS: - return 0x59; - case rocksdb::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES: - return 0x5A; - case rocksdb::Tickers::READ_AMP_TOTAL_READ_BYTES: - return 0x5B; - case rocksdb::Tickers::NUMBER_RATE_LIMITER_DRAINS: - return 0x5C; - case rocksdb::Tickers::TICKER_ENUM_MAX: - return 0x5D; - + case rocksdb::Histograms::HISTOGRAM_ENUM_MAX: + // 0x1F for backwards compatibility on current minor version. + return 0x1F; + + default: + // undefined/default + return 0x0; + } + } + + // Returns the equivalent C++ rocksdb::Histograms enum for the + // provided Java org.rocksdb.HistogramsType + static rocksdb::Histograms toCppHistograms(jbyte jhistograms_type) { + switch(jhistograms_type) { + case 0x0: + return rocksdb::Histograms::DB_GET; + case 0x1: + return rocksdb::Histograms::DB_WRITE; + case 0x2: + return rocksdb::Histograms::COMPACTION_TIME; + case 0x3: + return rocksdb::Histograms::SUBCOMPACTION_SETUP_TIME; + case 0x4: + return rocksdb::Histograms::TABLE_SYNC_MICROS; + case 0x5: + return rocksdb::Histograms::COMPACTION_OUTFILE_SYNC_MICROS; + case 0x6: + return rocksdb::Histograms::WAL_FILE_SYNC_MICROS; + case 0x7: + return rocksdb::Histograms::MANIFEST_FILE_SYNC_MICROS; + case 0x8: + return rocksdb::Histograms::TABLE_OPEN_IO_MICROS; + case 0x9: + return rocksdb::Histograms::DB_MULTIGET; + case 0xA: + return rocksdb::Histograms::READ_BLOCK_COMPACTION_MICROS; + case 0xB: + return rocksdb::Histograms::READ_BLOCK_GET_MICROS; + case 0xC: + return rocksdb::Histograms::WRITE_RAW_BLOCK_MICROS; + case 0xD: + return rocksdb::Histograms::STALL_L0_SLOWDOWN_COUNT; + case 0xE: + return rocksdb::Histograms::STALL_MEMTABLE_COMPACTION_COUNT; + case 0xF: + return rocksdb::Histograms::STALL_L0_NUM_FILES_COUNT; + case 0x10: + return rocksdb::Histograms::HARD_RATE_LIMIT_DELAY_COUNT; + case 0x11: + return rocksdb::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT; + case 0x12: + return rocksdb::Histograms::NUM_FILES_IN_SINGLE_COMPACTION; + case 0x13: + return rocksdb::Histograms::DB_SEEK; + case 0x14: + return rocksdb::Histograms::WRITE_STALL; + case 0x15: + return rocksdb::Histograms::SST_READ_MICROS; + case 0x16: + return rocksdb::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED; + case 0x17: + return rocksdb::Histograms::BYTES_PER_READ; + case 0x18: + return rocksdb::Histograms::BYTES_PER_WRITE; + case 0x19: + return rocksdb::Histograms::BYTES_PER_MULTIGET; + case 0x1A: + return rocksdb::Histograms::BYTES_COMPRESSED; + case 0x1B: + return rocksdb::Histograms::BYTES_DECOMPRESSED; + case 0x1C: + return rocksdb::Histograms::COMPRESSION_TIMES_NANOS; + case 0x1D: + return rocksdb::Histograms::DECOMPRESSION_TIMES_NANOS; + case 0x1E: + return rocksdb::Histograms::READ_NUM_MERGE_OPERANDS; + // 0x20 to skip 0x1F so TICKER_ENUM_MAX remains unchanged for minor version compatibility. + case 0x20: + return rocksdb::Histograms::FLUSH_TIME; + case 0x21: + return rocksdb::Histograms::BLOB_DB_KEY_SIZE; + case 0x22: + return rocksdb::Histograms::BLOB_DB_VALUE_SIZE; + case 0x23: + return rocksdb::Histograms::BLOB_DB_WRITE_MICROS; + case 0x24: + return rocksdb::Histograms::BLOB_DB_GET_MICROS; + case 0x25: + return rocksdb::Histograms::BLOB_DB_MULTIGET_MICROS; + case 0x26: + return rocksdb::Histograms::BLOB_DB_SEEK_MICROS; + case 0x27: + return rocksdb::Histograms::BLOB_DB_NEXT_MICROS; + case 0x28: + return rocksdb::Histograms::BLOB_DB_PREV_MICROS; + case 0x29: + return rocksdb::Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS; + case 0x2A: + return rocksdb::Histograms::BLOB_DB_BLOB_FILE_READ_MICROS; + case 0x2B: + return rocksdb::Histograms::BLOB_DB_BLOB_FILE_SYNC_MICROS; + case 0x2C: + return rocksdb::Histograms::BLOB_DB_GC_MICROS; + case 0x2D: + return rocksdb::Histograms::BLOB_DB_COMPRESSION_MICROS; + case 0x2E: + return rocksdb::Histograms::BLOB_DB_DECOMPRESSION_MICROS; + case 0x1F: + // 0x1F for backwards compatibility on current minor version. + return rocksdb::Histograms::HISTOGRAM_ENUM_MAX; + + default: + // undefined/default + return rocksdb::Histograms::DB_GET; + } + } +}; + +// The portal class for org.rocksdb.StatsLevel +class StatsLevelJni { + public: + // Returns the equivalent org.rocksdb.StatsLevel for the provided + // C++ rocksdb::StatsLevel enum + static jbyte toJavaStatsLevel( + const rocksdb::StatsLevel& stats_level) { + switch(stats_level) { + case rocksdb::StatsLevel::kExceptDetailedTimers: + return 0x0; + case rocksdb::StatsLevel::kExceptTimeForMutex: + return 0x1; + case rocksdb::StatsLevel::kAll: + return 0x2; + + default: + // undefined/default + return 0x0; + } + } + + // Returns the equivalent C++ rocksdb::StatsLevel enum for the + // provided Java org.rocksdb.StatsLevel + static rocksdb::StatsLevel toCppStatsLevel(jbyte jstats_level) { + switch(jstats_level) { + case 0x0: + return rocksdb::StatsLevel::kExceptDetailedTimers; + case 0x1: + return rocksdb::StatsLevel::kExceptTimeForMutex; + case 0x2: + return rocksdb::StatsLevel::kAll; + + default: + // undefined/default + return rocksdb::StatsLevel::kExceptDetailedTimers; + } + } +}; + +// The portal class for org.rocksdb.RateLimiterMode +class RateLimiterModeJni { + public: + // Returns the equivalent org.rocksdb.RateLimiterMode for the provided + // C++ rocksdb::RateLimiter::Mode enum + static jbyte toJavaRateLimiterMode( + const rocksdb::RateLimiter::Mode& rate_limiter_mode) { + switch(rate_limiter_mode) { + case rocksdb::RateLimiter::Mode::kReadsOnly: + return 0x0; + case rocksdb::RateLimiter::Mode::kWritesOnly: + return 0x1; + case rocksdb::RateLimiter::Mode::kAllIo: + return 0x2; + + default: + // undefined/default + return 0x1; + } + } + + // Returns the equivalent C++ rocksdb::RateLimiter::Mode enum for the + // provided Java org.rocksdb.RateLimiterMode + static rocksdb::RateLimiter::Mode toCppRateLimiterMode(jbyte jrate_limiter_mode) { + switch(jrate_limiter_mode) { + case 0x0: + return rocksdb::RateLimiter::Mode::kReadsOnly; + case 0x1: + return rocksdb::RateLimiter::Mode::kWritesOnly; + case 0x2: + return rocksdb::RateLimiter::Mode::kAllIo; + default: // undefined/default + return rocksdb::RateLimiter::Mode::kWritesOnly; + } + } +}; + +// The portal class for org.rocksdb.MemoryUsageType +class MemoryUsageTypeJni { +public: + // Returns the equivalent org.rocksdb.MemoryUsageType for the provided + // C++ rocksdb::MemoryUtil::UsageType enum + static jbyte toJavaMemoryUsageType( + const rocksdb::MemoryUtil::UsageType& usage_type) { + switch(usage_type) { + case rocksdb::MemoryUtil::UsageType::kMemTableTotal: return 0x0; + case rocksdb::MemoryUtil::UsageType::kMemTableUnFlushed: + return 0x1; + case rocksdb::MemoryUtil::UsageType::kTableReadersTotal: + return 0x2; + case rocksdb::MemoryUtil::UsageType::kCacheTotal: + return 0x3; + default: + // undefined: use kNumUsageTypes + return 0x4; + } + } + + // Returns the equivalent C++ rocksdb::MemoryUtil::UsageType enum for the + // provided Java org.rocksdb.MemoryUsageType + static rocksdb::MemoryUtil::UsageType toCppMemoryUsageType( + jbyte usage_type) { + switch(usage_type) { + case 0x0: + return rocksdb::MemoryUtil::UsageType::kMemTableTotal; + case 0x1: + return rocksdb::MemoryUtil::UsageType::kMemTableUnFlushed; + case 0x2: + return rocksdb::MemoryUtil::UsageType::kTableReadersTotal; + case 0x3: + return rocksdb::MemoryUtil::UsageType::kCacheTotal; + default: + // undefined/default: use kNumUsageTypes + return rocksdb::MemoryUtil::UsageType::kNumUsageTypes; + } + } +}; + +// The portal class for org.rocksdb.Transaction +class TransactionJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.Transaction + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, + "org/rocksdb/Transaction"); + } + + /** + * Create a new Java org.rocksdb.Transaction.WaitingTransactions object + * + * @param env A pointer to the Java environment + * @param jtransaction A Java org.rocksdb.Transaction object + * @param column_family_id The id of the column family + * @param key The key + * @param transaction_ids The transaction ids + * + * @return A reference to a Java + * org.rocksdb.Transaction.WaitingTransactions object, + * or nullptr if an an exception occurs + */ + static jobject newWaitingTransactions(JNIEnv* env, jobject jtransaction, + const uint32_t column_family_id, const std::string &key, + const std::vector &transaction_ids) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = env->GetMethodID( + jclazz, "newWaitingTransactions", "(JLjava/lang/String;[J)Lorg/rocksdb/Transaction$WaitingTransactions;"); + if(mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + jstring jkey = env->NewStringUTF(key.c_str()); + if(jkey == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + const size_t len = transaction_ids.size(); + jlongArray jtransaction_ids = env->NewLongArray(static_cast(len)); + if(jtransaction_ids == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jkey); + return nullptr; + } + + jlong *body = env->GetLongArrayElements(jtransaction_ids, nullptr); + if(body == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jkey); + env->DeleteLocalRef(jtransaction_ids); + return nullptr; + } + for(size_t i = 0; i < len; ++i) { + body[i] = static_cast(transaction_ids[i]); + } + env->ReleaseLongArrayElements(jtransaction_ids, body, 0); + + jobject jwaiting_transactions = env->CallObjectMethod(jtransaction, + mid, static_cast(column_family_id), jkey, jtransaction_ids); + if(env->ExceptionCheck()) { + // exception thrown: InstantiationException or OutOfMemoryError + env->DeleteLocalRef(jkey); + env->DeleteLocalRef(jtransaction_ids); + return nullptr; + } + + return jwaiting_transactions; + } +}; + +// The portal class for org.rocksdb.TransactionDB +class TransactionDBJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.TransactionDB + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, + "org/rocksdb/TransactionDB"); + } + + /** + * Create a new Java org.rocksdb.TransactionDB.DeadlockInfo object + * + * @param env A pointer to the Java environment + * @param jtransaction A Java org.rocksdb.Transaction object + * @param column_family_id The id of the column family + * @param key The key + * @param transaction_ids The transaction ids + * + * @return A reference to a Java + * org.rocksdb.Transaction.WaitingTransactions object, + * or nullptr if an an exception occurs + */ + static jobject newDeadlockInfo(JNIEnv* env, jobject jtransaction_db, + const rocksdb::TransactionID transaction_id, + const uint32_t column_family_id, const std::string &waiting_key, + const bool exclusive) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = env->GetMethodID( + jclazz, "newDeadlockInfo", "(JJLjava/lang/String;Z)Lorg/rocksdb/TransactionDB$DeadlockInfo;"); + if(mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + jstring jwaiting_key = env->NewStringUTF(waiting_key.c_str()); + if(jwaiting_key == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + // resolve the column family id to a ColumnFamilyHandle + jobject jdeadlock_info = env->CallObjectMethod(jtransaction_db, + mid, transaction_id, static_cast(column_family_id), + jwaiting_key, exclusive); + if(env->ExceptionCheck()) { + // exception thrown: InstantiationException or OutOfMemoryError + env->DeleteLocalRef(jwaiting_key); + return nullptr; + } + + return jdeadlock_info; + } +}; + +// The portal class for org.rocksdb.TxnDBWritePolicy +class TxnDBWritePolicyJni { + public: + // Returns the equivalent org.rocksdb.TxnDBWritePolicy for the provided + // C++ rocksdb::TxnDBWritePolicy enum + static jbyte toJavaTxnDBWritePolicy( + const rocksdb::TxnDBWritePolicy& txndb_write_policy) { + switch(txndb_write_policy) { + case rocksdb::TxnDBWritePolicy::WRITE_COMMITTED: + return 0x0; + case rocksdb::TxnDBWritePolicy::WRITE_PREPARED: + return 0x1; + case rocksdb::TxnDBWritePolicy::WRITE_UNPREPARED: + return 0x2; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::TxnDBWritePolicy enum for the + // provided Java org.rocksdb.TxnDBWritePolicy + static rocksdb::TxnDBWritePolicy toCppTxnDBWritePolicy( + jbyte jtxndb_write_policy) { + switch(jtxndb_write_policy) { + case 0x0: + return rocksdb::TxnDBWritePolicy::WRITE_COMMITTED; + case 0x1: + return rocksdb::TxnDBWritePolicy::WRITE_PREPARED; + case 0x2: + return rocksdb::TxnDBWritePolicy::WRITE_UNPREPARED; + default: + // undefined/default + return rocksdb::TxnDBWritePolicy::WRITE_COMMITTED; + } + } +}; + +// The portal class for org.rocksdb.TransactionDB.KeyLockInfo +class KeyLockInfoJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.TransactionDB.KeyLockInfo + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, + "org/rocksdb/TransactionDB$KeyLockInfo"); + } + + /** + * Create a new Java org.rocksdb.TransactionDB.KeyLockInfo object + * with the same properties as the provided C++ rocksdb::KeyLockInfo object + * + * @param env A pointer to the Java environment + * @param key_lock_info The rocksdb::KeyLockInfo object + * + * @return A reference to a Java + * org.rocksdb.TransactionDB.KeyLockInfo object, + * or nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, + const rocksdb::KeyLockInfo& key_lock_info) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = env->GetMethodID( + jclazz, "", "(Ljava/lang/String;[JZ)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + jstring jkey = env->NewStringUTF(key_lock_info.key.c_str()); + if (jkey == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + const jsize jtransaction_ids_len = static_cast(key_lock_info.ids.size()); + jlongArray jtransactions_ids = env->NewLongArray(jtransaction_ids_len); + if (jtransactions_ids == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jkey); + return nullptr; + } + + const jobject jkey_lock_info = env->NewObject(jclazz, mid, + jkey, jtransactions_ids, key_lock_info.exclusive); + if(jkey_lock_info == nullptr) { + // exception thrown: InstantiationException or OutOfMemoryError + env->DeleteLocalRef(jtransactions_ids); + env->DeleteLocalRef(jkey); + return nullptr; } + + return jkey_lock_info; } +}; - // Returns the equivalent C++ rocksdb::Tickers enum for the - // provided Java org.rocksdb.TickerType - static rocksdb::Tickers toCppTickers(jbyte jticker_type) { - switch(jticker_type) { - case 0x0: - return rocksdb::Tickers::BLOCK_CACHE_MISS; - case 0x1: - return rocksdb::Tickers::BLOCK_CACHE_HIT; - case 0x2: - return rocksdb::Tickers::BLOCK_CACHE_ADD; - case 0x3: - return rocksdb::Tickers::BLOCK_CACHE_ADD_FAILURES; - case 0x4: - return rocksdb::Tickers::BLOCK_CACHE_INDEX_MISS; - case 0x5: - return rocksdb::Tickers::BLOCK_CACHE_INDEX_HIT; - case 0x6: - return rocksdb::Tickers::BLOCK_CACHE_INDEX_ADD; - case 0x7: - return rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT; - case 0x8: - return rocksdb::Tickers::BLOCK_CACHE_INDEX_BYTES_EVICT; - case 0x9: - return rocksdb::Tickers::BLOCK_CACHE_FILTER_MISS; - case 0xA: - return rocksdb::Tickers::BLOCK_CACHE_FILTER_HIT; - case 0xB: - return rocksdb::Tickers::BLOCK_CACHE_FILTER_ADD; - case 0xC: - return rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT; - case 0xD: - return rocksdb::Tickers::BLOCK_CACHE_FILTER_BYTES_EVICT; - case 0xE: - return rocksdb::Tickers::BLOCK_CACHE_DATA_MISS; - case 0xF: - return rocksdb::Tickers::BLOCK_CACHE_DATA_HIT; - case 0x10: - return rocksdb::Tickers::BLOCK_CACHE_DATA_ADD; - case 0x11: - return rocksdb::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT; - case 0x12: - return rocksdb::Tickers::BLOCK_CACHE_BYTES_READ; - case 0x13: - return rocksdb::Tickers::BLOCK_CACHE_BYTES_WRITE; - case 0x14: - return rocksdb::Tickers::BLOOM_FILTER_USEFUL; - case 0x15: - return rocksdb::Tickers::PERSISTENT_CACHE_HIT; - case 0x16: - return rocksdb::Tickers::PERSISTENT_CACHE_MISS; - case 0x17: - return rocksdb::Tickers::SIM_BLOCK_CACHE_HIT; - case 0x18: - return rocksdb::Tickers::SIM_BLOCK_CACHE_MISS; - case 0x19: - return rocksdb::Tickers::MEMTABLE_HIT; - case 0x1A: - return rocksdb::Tickers::MEMTABLE_MISS; - case 0x1B: - return rocksdb::Tickers::GET_HIT_L0; - case 0x1C: - return rocksdb::Tickers::GET_HIT_L1; - case 0x1D: - return rocksdb::Tickers::GET_HIT_L2_AND_UP; - case 0x1E: - return rocksdb::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY; - case 0x1F: - return rocksdb::Tickers::COMPACTION_KEY_DROP_OBSOLETE; - case 0x20: - return rocksdb::Tickers::COMPACTION_KEY_DROP_RANGE_DEL; - case 0x21: - return rocksdb::Tickers::COMPACTION_KEY_DROP_USER; - case 0x22: - return rocksdb::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE; - case 0x23: - return rocksdb::Tickers::NUMBER_KEYS_WRITTEN; - case 0x24: - return rocksdb::Tickers::NUMBER_KEYS_READ; - case 0x25: - return rocksdb::Tickers::NUMBER_KEYS_UPDATED; - case 0x26: - return rocksdb::Tickers::BYTES_WRITTEN; - case 0x27: - return rocksdb::Tickers::BYTES_READ; - case 0x28: - return rocksdb::Tickers::NUMBER_DB_SEEK; - case 0x29: - return rocksdb::Tickers::NUMBER_DB_NEXT; - case 0x2A: - return rocksdb::Tickers::NUMBER_DB_PREV; - case 0x2B: - return rocksdb::Tickers::NUMBER_DB_SEEK_FOUND; - case 0x2C: - return rocksdb::Tickers::NUMBER_DB_NEXT_FOUND; - case 0x2D: - return rocksdb::Tickers::NUMBER_DB_PREV_FOUND; - case 0x2E: - return rocksdb::Tickers::ITER_BYTES_READ; - case 0x2F: - return rocksdb::Tickers::NO_FILE_CLOSES; - case 0x30: - return rocksdb::Tickers::NO_FILE_OPENS; - case 0x31: - return rocksdb::Tickers::NO_FILE_ERRORS; - case 0x32: - return rocksdb::Tickers::STALL_L0_SLOWDOWN_MICROS; - case 0x33: - return rocksdb::Tickers::STALL_MEMTABLE_COMPACTION_MICROS; - case 0x34: - return rocksdb::Tickers::STALL_L0_NUM_FILES_MICROS; - case 0x35: - return rocksdb::Tickers::STALL_MICROS; - case 0x36: - return rocksdb::Tickers::DB_MUTEX_WAIT_MICROS; - case 0x37: - return rocksdb::Tickers::RATE_LIMIT_DELAY_MILLIS; - case 0x38: - return rocksdb::Tickers::NO_ITERATORS; - case 0x39: - return rocksdb::Tickers::NUMBER_MULTIGET_CALLS; - case 0x3A: - return rocksdb::Tickers::NUMBER_MULTIGET_KEYS_READ; - case 0x3B: - return rocksdb::Tickers::NUMBER_MULTIGET_BYTES_READ; - case 0x3C: - return rocksdb::Tickers::NUMBER_FILTERED_DELETES; - case 0x3D: - return rocksdb::Tickers::NUMBER_MERGE_FAILURES; - case 0x3E: - return rocksdb::Tickers::BLOOM_FILTER_PREFIX_CHECKED; - case 0x3F: - return rocksdb::Tickers::BLOOM_FILTER_PREFIX_USEFUL; - case 0x40: - return rocksdb::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION; - case 0x41: - return rocksdb::Tickers::GET_UPDATES_SINCE_CALLS; - case 0x42: - return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_MISS; - case 0x43: - return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_HIT; - case 0x44: - return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD; - case 0x45: - return rocksdb::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES; - case 0x46: - return rocksdb::Tickers::WAL_FILE_SYNCED; - case 0x47: - return rocksdb::Tickers::WAL_FILE_BYTES; - case 0x48: - return rocksdb::Tickers::WRITE_DONE_BY_SELF; - case 0x49: - return rocksdb::Tickers::WRITE_DONE_BY_OTHER; - case 0x4A: - return rocksdb::Tickers::WRITE_TIMEDOUT; - case 0x4B: - return rocksdb::Tickers::WRITE_WITH_WAL; - case 0x4C: - return rocksdb::Tickers::COMPACT_READ_BYTES; - case 0x4D: - return rocksdb::Tickers::COMPACT_WRITE_BYTES; - case 0x4E: - return rocksdb::Tickers::FLUSH_WRITE_BYTES; - case 0x4F: - return rocksdb::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES; - case 0x50: - return rocksdb::Tickers::NUMBER_SUPERVERSION_ACQUIRES; - case 0x51: - return rocksdb::Tickers::NUMBER_SUPERVERSION_RELEASES; - case 0x52: - return rocksdb::Tickers::NUMBER_SUPERVERSION_CLEANUPS; - case 0x53: - return rocksdb::Tickers::NUMBER_BLOCK_COMPRESSED; - case 0x54: - return rocksdb::Tickers::NUMBER_BLOCK_DECOMPRESSED; - case 0x55: - return rocksdb::Tickers::NUMBER_BLOCK_NOT_COMPRESSED; - case 0x56: - return rocksdb::Tickers::MERGE_OPERATION_TOTAL_TIME; - case 0x57: - return rocksdb::Tickers::FILTER_OPERATION_TOTAL_TIME; - case 0x58: - return rocksdb::Tickers::ROW_CACHE_HIT; - case 0x59: - return rocksdb::Tickers::ROW_CACHE_MISS; - case 0x5A: - return rocksdb::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES; - case 0x5B: - return rocksdb::Tickers::READ_AMP_TOTAL_READ_BYTES; - case 0x5C: - return rocksdb::Tickers::NUMBER_RATE_LIMITER_DRAINS; - case 0x5D: - return rocksdb::Tickers::TICKER_ENUM_MAX; +// The portal class for org.rocksdb.TransactionDB.DeadlockInfo +class DeadlockInfoJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.TransactionDB.DeadlockInfo + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env,"org/rocksdb/TransactionDB$DeadlockInfo"); + } +}; + +// The portal class for org.rocksdb.TransactionDB.DeadlockPath +class DeadlockPathJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.TransactionDB.DeadlockPath + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, + "org/rocksdb/TransactionDB$DeadlockPath"); + } + + /** + * Create a new Java org.rocksdb.TransactionDB.DeadlockPath object + * + * @param env A pointer to the Java environment + * + * @return A reference to a Java + * org.rocksdb.TransactionDB.DeadlockPath object, + * or nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, + const jobjectArray jdeadlock_infos, const bool limit_exceeded) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - default: - // undefined/default - return rocksdb::Tickers::BLOCK_CACHE_MISS; + jmethodID mid = env->GetMethodID( + jclazz, "", "([LDeadlockInfo;Z)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + const jobject jdeadlock_path = env->NewObject(jclazz, mid, + jdeadlock_infos, limit_exceeded); + if(jdeadlock_path == nullptr) { + // exception thrown: InstantiationException or OutOfMemoryError + return nullptr; } + + return jdeadlock_path; } }; -// The portal class for org.rocksdb.HistogramType -class HistogramTypeJni { +class AbstractTableFilterJni : public RocksDBNativeClass { public: - // Returns the equivalent org.rocksdb.HistogramType for the provided - // C++ rocksdb::Histograms enum - static jbyte toJavaHistogramsType( - const rocksdb::Histograms& histograms) { - switch(histograms) { - case rocksdb::Histograms::DB_GET: - return 0x0; - case rocksdb::Histograms::DB_WRITE: - return 0x1; - case rocksdb::Histograms::COMPACTION_TIME: - return 0x2; - case rocksdb::Histograms::SUBCOMPACTION_SETUP_TIME: - return 0x3; - case rocksdb::Histograms::TABLE_SYNC_MICROS: - return 0x4; - case rocksdb::Histograms::COMPACTION_OUTFILE_SYNC_MICROS: - return 0x5; - case rocksdb::Histograms::WAL_FILE_SYNC_MICROS: - return 0x6; - case rocksdb::Histograms::MANIFEST_FILE_SYNC_MICROS: - return 0x7; - case rocksdb::Histograms::TABLE_OPEN_IO_MICROS: - return 0x8; - case rocksdb::Histograms::DB_MULTIGET: - return 0x9; - case rocksdb::Histograms::READ_BLOCK_COMPACTION_MICROS: - return 0xA; - case rocksdb::Histograms::READ_BLOCK_GET_MICROS: - return 0xB; - case rocksdb::Histograms::WRITE_RAW_BLOCK_MICROS: - return 0xC; - case rocksdb::Histograms::STALL_L0_SLOWDOWN_COUNT: - return 0xD; - case rocksdb::Histograms::STALL_MEMTABLE_COMPACTION_COUNT: - return 0xE; - case rocksdb::Histograms::STALL_L0_NUM_FILES_COUNT: - return 0xF; - case rocksdb::Histograms::HARD_RATE_LIMIT_DELAY_COUNT: - return 0x10; - case rocksdb::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT: - return 0x11; - case rocksdb::Histograms::NUM_FILES_IN_SINGLE_COMPACTION: - return 0x12; - case rocksdb::Histograms::DB_SEEK: - return 0x13; - case rocksdb::Histograms::WRITE_STALL: - return 0x14; - case rocksdb::Histograms::SST_READ_MICROS: - return 0x15; - case rocksdb::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED: - return 0x16; - case rocksdb::Histograms::BYTES_PER_READ: - return 0x17; - case rocksdb::Histograms::BYTES_PER_WRITE: - return 0x18; - case rocksdb::Histograms::BYTES_PER_MULTIGET: - return 0x19; - case rocksdb::Histograms::BYTES_COMPRESSED: - return 0x1A; - case rocksdb::Histograms::BYTES_DECOMPRESSED: - return 0x1B; - case rocksdb::Histograms::COMPRESSION_TIMES_NANOS: - return 0x1C; - case rocksdb::Histograms::DECOMPRESSION_TIMES_NANOS: - return 0x1D; - case rocksdb::Histograms::READ_NUM_MERGE_OPERANDS: - return 0x1E; - case rocksdb::Histograms::HISTOGRAM_ENUM_MAX: - return 0x1F; + /** + * Get the Java Method: TableFilter#filter(TableProperties) + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getFilterMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - default: - // undefined/default - return 0x0; + static jmethodID mid = + env->GetMethodID(jclazz, "filter", "(Lorg/rocksdb/TableProperties;)Z"); + assert(mid != nullptr); + return mid; + } + + private: + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/TableFilter"); + } +}; + +class TablePropertiesJni : public JavaClass { + public: + /** + * Create a new Java org.rocksdb.TableProperties object. + * + * @param env A pointer to the Java environment + * @param table_properties A Cpp table properties object + * + * @return A reference to a Java org.rocksdb.TableProperties object, or + * nullptr if an an exception occurs + */ + static jobject fromCppTableProperties(JNIEnv* env, const rocksdb::TableProperties& table_properties) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = env->GetMethodID(jclazz, "", "(JJJJJJJJJJJJJJJJJJJ[BLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; } + + jbyteArray jcolumn_family_name = rocksdb::JniUtil::copyBytes(env, table_properties.column_family_name); + if (jcolumn_family_name == nullptr) { + // exception occurred creating java string + return nullptr; + } + + jstring jfilter_policy_name = rocksdb::JniUtil::toJavaString(env, &table_properties.filter_policy_name, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + return nullptr; + } + + jstring jcomparator_name = rocksdb::JniUtil::toJavaString(env, &table_properties.comparator_name, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + return nullptr; + } + + jstring jmerge_operator_name = rocksdb::JniUtil::toJavaString(env, &table_properties.merge_operator_name, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + env->DeleteLocalRef(jcomparator_name); + return nullptr; + } + + jstring jprefix_extractor_name = rocksdb::JniUtil::toJavaString(env, &table_properties.prefix_extractor_name, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + env->DeleteLocalRef(jcomparator_name); + env->DeleteLocalRef(jmerge_operator_name); + return nullptr; + } + + jstring jproperty_collectors_names = rocksdb::JniUtil::toJavaString(env, &table_properties.property_collectors_names, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + env->DeleteLocalRef(jcomparator_name); + env->DeleteLocalRef(jmerge_operator_name); + env->DeleteLocalRef(jprefix_extractor_name); + return nullptr; + } + + jstring jcompression_name = rocksdb::JniUtil::toJavaString(env, &table_properties.compression_name, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + env->DeleteLocalRef(jcomparator_name); + env->DeleteLocalRef(jmerge_operator_name); + env->DeleteLocalRef(jprefix_extractor_name); + env->DeleteLocalRef(jproperty_collectors_names); + return nullptr; + } + + // Map + jobject juser_collected_properties = rocksdb::HashMapJni::fromCppMap(env, &table_properties.user_collected_properties); + if (env->ExceptionCheck()) { + // exception occurred creating java map + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + env->DeleteLocalRef(jcomparator_name); + env->DeleteLocalRef(jmerge_operator_name); + env->DeleteLocalRef(jprefix_extractor_name); + env->DeleteLocalRef(jproperty_collectors_names); + env->DeleteLocalRef(jcompression_name); + return nullptr; + } + + // Map + jobject jreadable_properties = rocksdb::HashMapJni::fromCppMap(env, &table_properties.readable_properties); + if (env->ExceptionCheck()) { + // exception occurred creating java map + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + env->DeleteLocalRef(jcomparator_name); + env->DeleteLocalRef(jmerge_operator_name); + env->DeleteLocalRef(jprefix_extractor_name); + env->DeleteLocalRef(jproperty_collectors_names); + env->DeleteLocalRef(jcompression_name); + env->DeleteLocalRef(juser_collected_properties); + return nullptr; + } + + // Map + jobject jproperties_offsets = rocksdb::HashMapJni::fromCppMap(env, &table_properties.properties_offsets); + if (env->ExceptionCheck()) { + // exception occurred creating java map + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfilter_policy_name); + env->DeleteLocalRef(jcomparator_name); + env->DeleteLocalRef(jmerge_operator_name); + env->DeleteLocalRef(jprefix_extractor_name); + env->DeleteLocalRef(jproperty_collectors_names); + env->DeleteLocalRef(jcompression_name); + env->DeleteLocalRef(juser_collected_properties); + env->DeleteLocalRef(jreadable_properties); + return nullptr; + } + + jobject jtable_properties = env->NewObject(jclazz, mid, + static_cast(table_properties.data_size), + static_cast(table_properties.index_size), + static_cast(table_properties.index_partitions), + static_cast(table_properties.top_level_index_size), + static_cast(table_properties.index_key_is_user_key), + static_cast(table_properties.index_value_is_delta_encoded), + static_cast(table_properties.filter_size), + static_cast(table_properties.raw_key_size), + static_cast(table_properties.raw_value_size), + static_cast(table_properties.num_data_blocks), + static_cast(table_properties.num_entries), + static_cast(table_properties.num_deletions), + static_cast(table_properties.num_merge_operands), + static_cast(table_properties.num_range_deletions), + static_cast(table_properties.format_version), + static_cast(table_properties.fixed_key_len), + static_cast(table_properties.column_family_id), + static_cast(table_properties.creation_time), + static_cast(table_properties.oldest_key_time), + jcolumn_family_name, + jfilter_policy_name, + jcomparator_name, + jmerge_operator_name, + jprefix_extractor_name, + jproperty_collectors_names, + jcompression_name, + juser_collected_properties, + jreadable_properties, + jproperties_offsets + ); + + if (env->ExceptionCheck()) { + return nullptr; + } + + return jtable_properties; } - // Returns the equivalent C++ rocksdb::Histograms enum for the - // provided Java org.rocksdb.HistogramsType - static rocksdb::Histograms toCppHistograms(jbyte jhistograms_type) { - switch(jhistograms_type) { - case 0x0: - return rocksdb::Histograms::DB_GET; - case 0x1: - return rocksdb::Histograms::DB_WRITE; - case 0x2: - return rocksdb::Histograms::COMPACTION_TIME; - case 0x3: - return rocksdb::Histograms::SUBCOMPACTION_SETUP_TIME; - case 0x4: - return rocksdb::Histograms::TABLE_SYNC_MICROS; - case 0x5: - return rocksdb::Histograms::COMPACTION_OUTFILE_SYNC_MICROS; - case 0x6: - return rocksdb::Histograms::WAL_FILE_SYNC_MICROS; - case 0x7: - return rocksdb::Histograms::MANIFEST_FILE_SYNC_MICROS; - case 0x8: - return rocksdb::Histograms::TABLE_OPEN_IO_MICROS; - case 0x9: - return rocksdb::Histograms::DB_MULTIGET; - case 0xA: - return rocksdb::Histograms::READ_BLOCK_COMPACTION_MICROS; - case 0xB: - return rocksdb::Histograms::READ_BLOCK_GET_MICROS; - case 0xC: - return rocksdb::Histograms::WRITE_RAW_BLOCK_MICROS; - case 0xD: - return rocksdb::Histograms::STALL_L0_SLOWDOWN_COUNT; - case 0xE: - return rocksdb::Histograms::STALL_MEMTABLE_COMPACTION_COUNT; - case 0xF: - return rocksdb::Histograms::STALL_L0_NUM_FILES_COUNT; - case 0x10: - return rocksdb::Histograms::HARD_RATE_LIMIT_DELAY_COUNT; - case 0x11: - return rocksdb::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT; - case 0x12: - return rocksdb::Histograms::NUM_FILES_IN_SINGLE_COMPACTION; - case 0x13: - return rocksdb::Histograms::DB_SEEK; - case 0x14: - return rocksdb::Histograms::WRITE_STALL; - case 0x15: - return rocksdb::Histograms::SST_READ_MICROS; - case 0x16: - return rocksdb::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED; - case 0x17: - return rocksdb::Histograms::BYTES_PER_READ; - case 0x18: - return rocksdb::Histograms::BYTES_PER_WRITE; - case 0x19: - return rocksdb::Histograms::BYTES_PER_MULTIGET; - case 0x1A: - return rocksdb::Histograms::BYTES_COMPRESSED; - case 0x1B: - return rocksdb::Histograms::BYTES_DECOMPRESSED; - case 0x1C: - return rocksdb::Histograms::COMPRESSION_TIMES_NANOS; - case 0x1D: - return rocksdb::Histograms::DECOMPRESSION_TIMES_NANOS; - case 0x1E: - return rocksdb::Histograms::READ_NUM_MERGE_OPERANDS; - case 0x1F: - return rocksdb::Histograms::HISTOGRAM_ENUM_MAX; + private: + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/TableProperties"); + } +}; - default: - // undefined/default - return rocksdb::Histograms::DB_GET; +class ColumnFamilyDescriptorJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.ColumnFamilyDescriptor + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/ColumnFamilyDescriptor"); + } + + /** + * Create a new Java org.rocksdb.ColumnFamilyDescriptor object with the same + * properties as the provided C++ rocksdb::ColumnFamilyDescriptor object + * + * @param env A pointer to the Java environment + * @param cfd A pointer to rocksdb::ColumnFamilyDescriptor object + * + * @return A reference to a Java org.rocksdb.ColumnFamilyDescriptor object, or + * nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, ColumnFamilyDescriptor* cfd) { + jbyteArray jcf_name = JniUtil::copyBytes(env, cfd->name); + jobject cfopts = ColumnFamilyOptionsJni::construct(env, &(cfd->options)); + + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } - } -}; -// The portal class for org.rocksdb.StatsLevel -class StatsLevelJni { - public: - // Returns the equivalent org.rocksdb.StatsLevel for the provided - // C++ rocksdb::StatsLevel enum - static jbyte toJavaStatsLevel( - const rocksdb::StatsLevel& stats_level) { - switch(stats_level) { - case rocksdb::StatsLevel::kExceptDetailedTimers: - return 0x0; - case rocksdb::StatsLevel::kExceptTimeForMutex: - return 0x1; - case rocksdb::StatsLevel::kAll: - return 0x2; + jmethodID mid = env->GetMethodID(jclazz, "", + "([BLorg/rocksdb/ColumnFamilyOptions;)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + env->DeleteLocalRef(jcf_name); + return nullptr; + } - default: - // undefined/default - return 0x0; + jobject jcfd = env->NewObject(jclazz, mid, jcf_name, cfopts); + if (env->ExceptionCheck()) { + env->DeleteLocalRef(jcf_name); + return nullptr; } + + return jcfd; } - // Returns the equivalent C++ rocksdb::StatsLevel enum for the - // provided Java org.rocksdb.StatsLevel - static rocksdb::StatsLevel toCppStatsLevel(jbyte jstats_level) { - switch(jstats_level) { - case 0x0: - return rocksdb::StatsLevel::kExceptDetailedTimers; - case 0x1: - return rocksdb::StatsLevel::kExceptTimeForMutex; - case 0x2: - return rocksdb::StatsLevel::kAll; + /** + * Get the Java Method: ColumnFamilyDescriptor#columnFamilyName + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getColumnFamilyNameMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - default: - // undefined/default - return rocksdb::StatsLevel::kExceptDetailedTimers; + static jmethodID mid = env->GetMethodID(jclazz, "columnFamilyName", "()[B"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: ColumnFamilyDescriptor#columnFamilyOptions + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getColumnFamilyOptionsMethod(JNIEnv* env) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } + + static jmethodID mid = env->GetMethodID( + jclazz, "columnFamilyOptions", "()Lorg/rocksdb/ColumnFamilyOptions;"); + assert(mid != nullptr); + return mid; } }; -// various utility functions for working with RocksDB and JNI -class JniUtil { +// The portal class for org.rocksdb.IndexType +class IndexTypeJni { public: - /** - * Obtains a reference to the JNIEnv from - * the JVM - * - * If the current thread is not attached to the JavaVM - * then it will be attached so as to retrieve the JNIEnv - * - * If a thread is attached, it must later be manually - * released by calling JavaVM::DetachCurrentThread. - * This can be handled by always matching calls to this - * function with calls to {@link JniUtil::releaseJniEnv(JavaVM*, jboolean)} - * - * @param jvm (IN) A pointer to the JavaVM instance - * @param attached (OUT) A pointer to a boolean which - * will be set to JNI_TRUE if we had to attach the thread - * - * @return A pointer to the JNIEnv or nullptr if a fatal error - * occurs and the JNIEnv cannot be retrieved - */ - static JNIEnv* getJniEnv(JavaVM* jvm, jboolean* attached) { - assert(jvm != nullptr); + // Returns the equivalent org.rocksdb.IndexType for the provided + // C++ rocksdb::IndexType enum + static jbyte toJavaIndexType( + const rocksdb::BlockBasedTableOptions::IndexType& index_type) { + switch(index_type) { + case rocksdb::BlockBasedTableOptions::IndexType::kBinarySearch: + return 0x0; + case rocksdb::BlockBasedTableOptions::IndexType::kHashSearch: + return 0x1; + case rocksdb::BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch: + return 0x2; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::IndexType enum for the + // provided Java org.rocksdb.IndexType + static rocksdb::BlockBasedTableOptions::IndexType toCppIndexType( + jbyte jindex_type) { + switch(jindex_type) { + case 0x0: + return rocksdb::BlockBasedTableOptions::IndexType::kBinarySearch; + case 0x1: + return rocksdb::BlockBasedTableOptions::IndexType::kHashSearch; + case 0x2: + return rocksdb::BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; + default: + // undefined/default + return rocksdb::BlockBasedTableOptions::IndexType::kBinarySearch; + } + } +}; - JNIEnv *env; - const jint env_rs = jvm->GetEnv(reinterpret_cast(&env), - JNI_VERSION_1_2); +// The portal class for org.rocksdb.DataBlockIndexType +class DataBlockIndexTypeJni { + public: + // Returns the equivalent org.rocksdb.DataBlockIndexType for the provided + // C++ rocksdb::DataBlockIndexType enum + static jbyte toJavaDataBlockIndexType( + const rocksdb::BlockBasedTableOptions::DataBlockIndexType& index_type) { + switch(index_type) { + case rocksdb::BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinarySearch: + return 0x0; + case rocksdb::BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinaryAndHash: + return 0x1; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::DataBlockIndexType enum for the + // provided Java org.rocksdb.DataBlockIndexType + static rocksdb::BlockBasedTableOptions::DataBlockIndexType toCppDataBlockIndexType( + jbyte jindex_type) { + switch(jindex_type) { + case 0x0: + return rocksdb::BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinarySearch; + case 0x1: + return rocksdb::BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinaryAndHash; + default: + // undefined/default + return rocksdb::BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinarySearch; + } + } +}; - if(env_rs == JNI_OK) { - // current thread is already attached, return the JNIEnv - *attached = JNI_FALSE; - return env; - } else if(env_rs == JNI_EDETACHED) { - // current thread is not attached, attempt to attach - const jint rs_attach = jvm->AttachCurrentThread(reinterpret_cast(&env), NULL); - if(rs_attach == JNI_OK) { - *attached = JNI_TRUE; - return env; - } else { - // error, could not attach the thread - std::cerr << "JniUtil::getJinEnv - Fatal: could not attach current thread to JVM!" << std::endl; - return nullptr; - } - } else if(env_rs == JNI_EVERSION) { - // error, JDK does not support JNI_VERSION_1_2+ - std::cerr << "JniUtil::getJinEnv - Fatal: JDK does not support JNI_VERSION_1_2" << std::endl; - return nullptr; - } else { - std::cerr << "JniUtil::getJinEnv - Fatal: Unknown error: env_rs=" << env_rs << std::endl; +// The portal class for org.rocksdb.ChecksumType +class ChecksumTypeJni { + public: + // Returns the equivalent org.rocksdb.ChecksumType for the provided + // C++ rocksdb::ChecksumType enum + static jbyte toJavaChecksumType( + const rocksdb::ChecksumType& checksum_type) { + switch(checksum_type) { + case rocksdb::ChecksumType::kNoChecksum: + return 0x0; + case rocksdb::ChecksumType::kCRC32c: + return 0x1; + case rocksdb::ChecksumType::kxxHash: + return 0x2; + case rocksdb::ChecksumType::kxxHash64: + return 0x3; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::ChecksumType enum for the + // provided Java org.rocksdb.ChecksumType + static rocksdb::ChecksumType toCppChecksumType( + jbyte jchecksum_type) { + switch(jchecksum_type) { + case 0x0: + return rocksdb::ChecksumType::kNoChecksum; + case 0x1: + return rocksdb::ChecksumType::kCRC32c; + case 0x2: + return rocksdb::ChecksumType::kxxHash; + case 0x3: + return rocksdb::ChecksumType::kxxHash64; + default: + // undefined/default + return rocksdb::ChecksumType::kCRC32c; + } + } +}; + +// The portal class for org.rocksdb.Priority +class PriorityJni { + public: + // Returns the equivalent org.rocksdb.Priority for the provided + // C++ rocksdb::Env::Priority enum + static jbyte toJavaPriority( + const rocksdb::Env::Priority& priority) { + switch(priority) { + case rocksdb::Env::Priority::BOTTOM: + return 0x0; + case rocksdb::Env::Priority::LOW: + return 0x1; + case rocksdb::Env::Priority::HIGH: + return 0x2; + case rocksdb::Env::Priority::TOTAL: + return 0x3; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::env::Priority enum for the + // provided Java org.rocksdb.Priority + static rocksdb::Env::Priority toCppPriority( + jbyte jpriority) { + switch(jpriority) { + case 0x0: + return rocksdb::Env::Priority::BOTTOM; + case 0x1: + return rocksdb::Env::Priority::LOW; + case 0x2: + return rocksdb::Env::Priority::HIGH; + case 0x3: + return rocksdb::Env::Priority::TOTAL; + default: + // undefined/default + return rocksdb::Env::Priority::LOW; + } + } +}; + +// The portal class for org.rocksdb.ThreadType +class ThreadTypeJni { + public: + // Returns the equivalent org.rocksdb.ThreadType for the provided + // C++ rocksdb::ThreadStatus::ThreadType enum + static jbyte toJavaThreadType( + const rocksdb::ThreadStatus::ThreadType& thread_type) { + switch(thread_type) { + case rocksdb::ThreadStatus::ThreadType::HIGH_PRIORITY: + return 0x0; + case rocksdb::ThreadStatus::ThreadType::LOW_PRIORITY: + return 0x1; + case rocksdb::ThreadStatus::ThreadType::USER: + return 0x2; + case rocksdb::ThreadStatus::ThreadType::BOTTOM_PRIORITY: + return 0x3; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::ThreadStatus::ThreadType enum for the + // provided Java org.rocksdb.ThreadType + static rocksdb::ThreadStatus::ThreadType toCppThreadType( + jbyte jthread_type) { + switch(jthread_type) { + case 0x0: + return rocksdb::ThreadStatus::ThreadType::HIGH_PRIORITY; + case 0x1: + return rocksdb::ThreadStatus::ThreadType::LOW_PRIORITY; + case 0x2: + return ThreadStatus::ThreadType::USER; + case 0x3: + return rocksdb::ThreadStatus::ThreadType::BOTTOM_PRIORITY; + default: + // undefined/default + return rocksdb::ThreadStatus::ThreadType::LOW_PRIORITY; + } + } +}; + +// The portal class for org.rocksdb.OperationType +class OperationTypeJni { + public: + // Returns the equivalent org.rocksdb.OperationType for the provided + // C++ rocksdb::ThreadStatus::OperationType enum + static jbyte toJavaOperationType( + const rocksdb::ThreadStatus::OperationType& operation_type) { + switch(operation_type) { + case rocksdb::ThreadStatus::OperationType::OP_UNKNOWN: + return 0x0; + case rocksdb::ThreadStatus::OperationType::OP_COMPACTION: + return 0x1; + case rocksdb::ThreadStatus::OperationType::OP_FLUSH: + return 0x2; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::ThreadStatus::OperationType enum for the + // provided Java org.rocksdb.OperationType + static rocksdb::ThreadStatus::OperationType toCppOperationType( + jbyte joperation_type) { + switch(joperation_type) { + case 0x0: + return rocksdb::ThreadStatus::OperationType::OP_UNKNOWN; + case 0x1: + return rocksdb::ThreadStatus::OperationType::OP_COMPACTION; + case 0x2: + return rocksdb::ThreadStatus::OperationType::OP_FLUSH; + default: + // undefined/default + return rocksdb::ThreadStatus::OperationType::OP_UNKNOWN; + } + } +}; + +// The portal class for org.rocksdb.OperationStage +class OperationStageJni { + public: + // Returns the equivalent org.rocksdb.OperationStage for the provided + // C++ rocksdb::ThreadStatus::OperationStage enum + static jbyte toJavaOperationStage( + const rocksdb::ThreadStatus::OperationStage& operation_stage) { + switch(operation_stage) { + case rocksdb::ThreadStatus::OperationStage::STAGE_UNKNOWN: + return 0x0; + case rocksdb::ThreadStatus::OperationStage::STAGE_FLUSH_RUN: + return 0x1; + case rocksdb::ThreadStatus::OperationStage::STAGE_FLUSH_WRITE_L0: + return 0x2; + case rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_PREPARE: + return 0x3; + case rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_RUN: + return 0x4; + case rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_PROCESS_KV: + return 0x5; + case rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_INSTALL: + return 0x6; + case rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_SYNC_FILE: + return 0x7; + case rocksdb::ThreadStatus::OperationStage::STAGE_PICK_MEMTABLES_TO_FLUSH: + return 0x8; + case rocksdb::ThreadStatus::OperationStage::STAGE_MEMTABLE_ROLLBACK: + return 0x9; + case rocksdb::ThreadStatus::OperationStage::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS: + return 0xA; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::ThreadStatus::OperationStage enum for the + // provided Java org.rocksdb.OperationStage + static rocksdb::ThreadStatus::OperationStage toCppOperationStage( + jbyte joperation_stage) { + switch(joperation_stage) { + case 0x0: + return rocksdb::ThreadStatus::OperationStage::STAGE_UNKNOWN; + case 0x1: + return rocksdb::ThreadStatus::OperationStage::STAGE_FLUSH_RUN; + case 0x2: + return rocksdb::ThreadStatus::OperationStage::STAGE_FLUSH_WRITE_L0; + case 0x3: + return rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_PREPARE; + case 0x4: + return rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_RUN; + case 0x5: + return rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_PROCESS_KV; + case 0x6: + return rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_INSTALL; + case 0x7: + return rocksdb::ThreadStatus::OperationStage::STAGE_COMPACTION_SYNC_FILE; + case 0x8: + return rocksdb::ThreadStatus::OperationStage::STAGE_PICK_MEMTABLES_TO_FLUSH; + case 0x9: + return rocksdb::ThreadStatus::OperationStage::STAGE_MEMTABLE_ROLLBACK; + case 0xA: + return rocksdb::ThreadStatus::OperationStage::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS; + default: + // undefined/default + return rocksdb::ThreadStatus::OperationStage::STAGE_UNKNOWN; + } + } +}; + +// The portal class for org.rocksdb.StateType +class StateTypeJni { + public: + // Returns the equivalent org.rocksdb.StateType for the provided + // C++ rocksdb::ThreadStatus::StateType enum + static jbyte toJavaStateType( + const rocksdb::ThreadStatus::StateType& state_type) { + switch(state_type) { + case rocksdb::ThreadStatus::StateType::STATE_UNKNOWN: + return 0x0; + case rocksdb::ThreadStatus::StateType::STATE_MUTEX_WAIT: + return 0x1; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::ThreadStatus::StateType enum for the + // provided Java org.rocksdb.StateType + static rocksdb::ThreadStatus::StateType toCppStateType( + jbyte jstate_type) { + switch(jstate_type) { + case 0x0: + return rocksdb::ThreadStatus::StateType::STATE_UNKNOWN; + case 0x1: + return rocksdb::ThreadStatus::StateType::STATE_MUTEX_WAIT; + default: + // undefined/default + return rocksdb::ThreadStatus::StateType::STATE_UNKNOWN; + } + } +}; + +// The portal class for org.rocksdb.ThreadStatus +class ThreadStatusJni : public JavaClass { + public: + /** + * Get the Java Class org.rocksdb.ThreadStatus + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, + "org/rocksdb/ThreadStatus"); + } + + /** + * Create a new Java org.rocksdb.ThreadStatus object with the same + * properties as the provided C++ rocksdb::ThreadStatus object + * + * @param env A pointer to the Java environment + * @param thread_status A pointer to rocksdb::ThreadStatus object + * + * @return A reference to a Java org.rocksdb.ColumnFamilyOptions object, or + * nullptr if an an exception occurs + */ + static jobject construct(JNIEnv* env, + const rocksdb::ThreadStatus* thread_status) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = env->GetMethodID(jclazz, "", "(JBLjava/lang/String;Ljava/lang/String;BJB[JB)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } + + jstring jdb_name = + JniUtil::toJavaString(env, &(thread_status->db_name), true); + if (env->ExceptionCheck()) { + // an error occurred return nullptr; - } } - /** - * Counterpart to {@link JniUtil::getJniEnv(JavaVM*, jboolean*)} - * - * Detachess the current thread from the JVM if it was previously - * attached - * - * @param jvm (IN) A pointer to the JavaVM instance - * @param attached (IN) JNI_TRUE if we previously had to attach the thread - * to the JavaVM to get the JNIEnv - */ - static void releaseJniEnv(JavaVM* jvm, jboolean& attached) { - assert(jvm != nullptr); - if(attached == JNI_TRUE) { - const jint rs_detach = jvm->DetachCurrentThread(); - assert(rs_detach == JNI_OK); - if(rs_detach != JNI_OK) { - std::cerr << "JniUtil::getJinEnv - Warn: Unable to detach current thread from JVM!" << std::endl; - } - } + jstring jcf_name = + JniUtil::toJavaString(env, &(thread_status->cf_name), true); + if (env->ExceptionCheck()) { + // an error occurred + env->DeleteLocalRef(jdb_name); + return nullptr; } - /** - * Copies a Java String[] to a C++ std::vector - * - * @param env (IN) A pointer to the java environment - * @param jss (IN) The Java String array to copy - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError or ArrayIndexOutOfBoundsException - * exception occurs - * - * @return A std::vector containing copies of the Java strings - */ - static std::vector copyStrings(JNIEnv* env, - jobjectArray jss, jboolean* has_exception) { - return rocksdb::JniUtil::copyStrings(env, jss, - env->GetArrayLength(jss), has_exception); + // long[] + const jsize len = static_cast(rocksdb::ThreadStatus::kNumOperationProperties); + jlongArray joperation_properties = + env->NewLongArray(len); + if (joperation_properties == nullptr) { + // an exception occurred + env->DeleteLocalRef(jdb_name); + env->DeleteLocalRef(jcf_name); + return nullptr; + } + jlong *body = env->GetLongArrayElements(joperation_properties, nullptr); + if (body == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jdb_name); + env->DeleteLocalRef(jcf_name); + env->DeleteLocalRef(joperation_properties); + return nullptr; + } + for (size_t i = 0; i < len; ++i) { + body[i] = static_cast(thread_status->op_properties[i]); + } + env->ReleaseLongArrayElements(joperation_properties, body, 0); + + jobject jcfd = env->NewObject(jclazz, mid, + static_cast(thread_status->thread_id), + ThreadTypeJni::toJavaThreadType(thread_status->thread_type), + jdb_name, + jcf_name, + OperationTypeJni::toJavaOperationType(thread_status->operation_type), + static_cast(thread_status->op_elapsed_micros), + OperationStageJni::toJavaOperationStage(thread_status->operation_stage), + joperation_properties, + StateTypeJni::toJavaStateType(thread_status->state_type)); + if (env->ExceptionCheck()) { + // exception occurred + env->DeleteLocalRef(jdb_name); + env->DeleteLocalRef(jcf_name); + env->DeleteLocalRef(joperation_properties); + return nullptr; } - /** - * Copies a Java String[] to a C++ std::vector - * - * @param env (IN) A pointer to the java environment - * @param jss (IN) The Java String array to copy - * @param jss_len (IN) The length of the Java String array to copy - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError or ArrayIndexOutOfBoundsException - * exception occurs - * - * @return A std::vector containing copies of the Java strings - */ - static std::vector copyStrings(JNIEnv* env, - jobjectArray jss, const jsize jss_len, jboolean* has_exception) { - std::vector strs; - for (jsize i = 0; i < jss_len; i++) { - jobject js = env->GetObjectArrayElement(jss, i); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - *has_exception = JNI_TRUE; - return strs; - } + // cleanup + env->DeleteLocalRef(jdb_name); + env->DeleteLocalRef(jcf_name); + env->DeleteLocalRef(joperation_properties); + + return jcfd; + } +}; + +// The portal class for org.rocksdb.CompactionStyle +class CompactionStyleJni { + public: + // Returns the equivalent org.rocksdb.CompactionStyle for the provided + // C++ rocksdb::CompactionStyle enum + static jbyte toJavaCompactionStyle( + const rocksdb::CompactionStyle& compaction_style) { + switch(compaction_style) { + case rocksdb::CompactionStyle::kCompactionStyleLevel: + return 0x0; + case rocksdb::CompactionStyle::kCompactionStyleUniversal: + return 0x1; + case rocksdb::CompactionStyle::kCompactionStyleFIFO: + return 0x2; + case rocksdb::CompactionStyle::kCompactionStyleNone: + return 0x3; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::CompactionStyle enum for the + // provided Java org.rocksdb.CompactionStyle + static rocksdb::CompactionStyle toCppCompactionStyle( + jbyte jcompaction_style) { + switch(jcompaction_style) { + case 0x0: + return rocksdb::CompactionStyle::kCompactionStyleLevel; + case 0x1: + return rocksdb::CompactionStyle::kCompactionStyleUniversal; + case 0x2: + return rocksdb::CompactionStyle::kCompactionStyleFIFO; + case 0x3: + return rocksdb::CompactionStyle::kCompactionStyleNone; + default: + // undefined/default + return rocksdb::CompactionStyle::kCompactionStyleLevel; + } + } +}; + +// The portal class for org.rocksdb.CompactionReason +class CompactionReasonJni { + public: + // Returns the equivalent org.rocksdb.CompactionReason for the provided + // C++ rocksdb::CompactionReason enum + static jbyte toJavaCompactionReason( + const rocksdb::CompactionReason& compaction_reason) { + switch(compaction_reason) { + case rocksdb::CompactionReason::kUnknown: + return 0x0; + case rocksdb::CompactionReason::kLevelL0FilesNum: + return 0x1; + case rocksdb::CompactionReason::kLevelMaxLevelSize: + return 0x2; + case rocksdb::CompactionReason::kUniversalSizeAmplification: + return 0x3; + case rocksdb::CompactionReason::kUniversalSizeRatio: + return 0x4; + case rocksdb::CompactionReason::kUniversalSortedRunNum: + return 0x5; + case rocksdb::CompactionReason::kFIFOMaxSize: + return 0x6; + case rocksdb::CompactionReason::kFIFOReduceNumFiles: + return 0x7; + case rocksdb::CompactionReason::kFIFOTtl: + return 0x8; + case rocksdb::CompactionReason::kManualCompaction: + return 0x9; + case rocksdb::CompactionReason::kFilesMarkedForCompaction: + return 0x10; + case rocksdb::CompactionReason::kBottommostFiles: + return 0x0A; + case rocksdb::CompactionReason::kTtl: + return 0x0B; + case rocksdb::CompactionReason::kFlush: + return 0x0C; + case rocksdb::CompactionReason::kExternalSstIngestion: + return 0x0D; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::CompactionReason enum for the + // provided Java org.rocksdb.CompactionReason + static rocksdb::CompactionReason toCppCompactionReason( + jbyte jcompaction_reason) { + switch(jcompaction_reason) { + case 0x0: + return rocksdb::CompactionReason::kUnknown; + case 0x1: + return rocksdb::CompactionReason::kLevelL0FilesNum; + case 0x2: + return rocksdb::CompactionReason::kLevelMaxLevelSize; + case 0x3: + return rocksdb::CompactionReason::kUniversalSizeAmplification; + case 0x4: + return rocksdb::CompactionReason::kUniversalSizeRatio; + case 0x5: + return rocksdb::CompactionReason::kUniversalSortedRunNum; + case 0x6: + return rocksdb::CompactionReason::kFIFOMaxSize; + case 0x7: + return rocksdb::CompactionReason::kFIFOReduceNumFiles; + case 0x8: + return rocksdb::CompactionReason::kFIFOTtl; + case 0x9: + return rocksdb::CompactionReason::kManualCompaction; + case 0x10: + return rocksdb::CompactionReason::kFilesMarkedForCompaction; + case 0x0A: + return rocksdb::CompactionReason::kBottommostFiles; + case 0x0B: + return rocksdb::CompactionReason::kTtl; + case 0x0C: + return rocksdb::CompactionReason::kFlush; + case 0x0D: + return rocksdb::CompactionReason::kExternalSstIngestion; + default: + // undefined/default + return rocksdb::CompactionReason::kUnknown; + } + } +}; + +// The portal class for org.rocksdb.WalFileType +class WalFileTypeJni { + public: + // Returns the equivalent org.rocksdb.WalFileType for the provided + // C++ rocksdb::WalFileType enum + static jbyte toJavaWalFileType( + const rocksdb::WalFileType& wal_file_type) { + switch(wal_file_type) { + case rocksdb::WalFileType::kArchivedLogFile: + return 0x0; + case rocksdb::WalFileType::kAliveLogFile: + return 0x1; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::WalFileType enum for the + // provided Java org.rocksdb.WalFileType + static rocksdb::WalFileType toCppWalFileType( + jbyte jwal_file_type) { + switch(jwal_file_type) { + case 0x0: + return rocksdb::WalFileType::kArchivedLogFile; + case 0x1: + return rocksdb::WalFileType::kAliveLogFile; + default: + // undefined/default + return rocksdb::WalFileType::kAliveLogFile; + } + } +}; + +class LogFileJni : public JavaClass { + public: + /** + * Create a new Java org.rocksdb.LogFile object. + * + * @param env A pointer to the Java environment + * @param log_file A Cpp log file object + * + * @return A reference to a Java org.rocksdb.LogFile object, or + * nullptr if an an exception occurs + */ + static jobject fromCppLogFile(JNIEnv* env, rocksdb::LogFile* log_file) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - jstring jstr = static_cast(js); - const char* str = env->GetStringUTFChars(jstr, nullptr); - if(str == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(js); - *has_exception = JNI_TRUE; - return strs; - } + jmethodID mid = env->GetMethodID(jclazz, "", "(Ljava/lang/String;JBJJ)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } - strs.push_back(std::string(str)); + std::string path_name = log_file->PathName(); + jstring jpath_name = rocksdb::JniUtil::toJavaString(env, &path_name, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + return nullptr; + } - env->ReleaseStringUTFChars(jstr, str); - env->DeleteLocalRef(js); - } + jobject jlog_file = env->NewObject(jclazz, mid, + jpath_name, + static_cast(log_file->LogNumber()), + rocksdb::WalFileTypeJni::toJavaWalFileType(log_file->Type()), + static_cast(log_file->StartSequence()), + static_cast(log_file->SizeFileBytes()) + ); - *has_exception = JNI_FALSE; - return strs; + if (env->ExceptionCheck()) { + env->DeleteLocalRef(jpath_name); + return nullptr; } - /** - * Copies a jstring to a std::string - * and releases the original jstring - * - * If an exception occurs, then JNIEnv::ExceptionCheck() - * will have been called - * - * @param env (IN) A pointer to the java environment - * @param js (IN) The java string to copy - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError exception occurs - * - * @return A std:string copy of the jstring, or an - * empty std::string if has_exception == JNI_TRUE - */ - static std::string copyString(JNIEnv* env, jstring js, - jboolean* has_exception) { - const char *utf = env->GetStringUTFChars(js, nullptr); - if(utf == nullptr) { - // exception thrown: OutOfMemoryError - env->ExceptionCheck(); - *has_exception = JNI_TRUE; - return std::string(); - } else if(env->ExceptionCheck()) { - // exception thrown - env->ReleaseStringUTFChars(js, utf); - *has_exception = JNI_TRUE; - return std::string(); - } + // cleanup + env->DeleteLocalRef(jpath_name); - std::string name(utf); - env->ReleaseStringUTFChars(js, utf); - *has_exception = JNI_FALSE; - return name; + return jlog_file; + } + + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/LogFile"); + } +}; + +class LiveFileMetaDataJni : public JavaClass { + public: + /** + * Create a new Java org.rocksdb.LiveFileMetaData object. + * + * @param env A pointer to the Java environment + * @param live_file_meta_data A Cpp live file meta data object + * + * @return A reference to a Java org.rocksdb.LiveFileMetaData object, or + * nullptr if an an exception occurs + */ + static jobject fromCppLiveFileMetaData(JNIEnv* env, + rocksdb::LiveFileMetaData* live_file_meta_data) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } - /** - * Copies bytes from a std::string to a jByteArray - * - * @param env A pointer to the java environment - * @param bytes The bytes to copy - * - * @return the Java byte[] or nullptr if an exception occurs - */ - static jbyteArray copyBytes(JNIEnv* env, std::string bytes) { - const jsize jlen = static_cast(bytes.size()); + jmethodID mid = env->GetMethodID(jclazz, "", "([BILjava/lang/String;Ljava/lang/String;JJJ[B[BJZJJ)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } - jbyteArray jbytes = env->NewByteArray(jlen); - if(jbytes == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } + jbyteArray jcolumn_family_name = rocksdb::JniUtil::copyBytes( + env, live_file_meta_data->column_family_name); + if (jcolumn_family_name == nullptr) { + // exception occurred creating java byte array + return nullptr; + } - env->SetByteArrayRegion(jbytes, 0, jlen, - const_cast(reinterpret_cast(bytes.c_str()))); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jbytes); - return nullptr; - } + jstring jfile_name = rocksdb::JniUtil::toJavaString( + env, &live_file_meta_data->name, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + return nullptr; + } - return jbytes; + jstring jpath = rocksdb::JniUtil::toJavaString( + env, &live_file_meta_data->db_path, true); + if (env->ExceptionCheck()) { + // exception occurred creating java string + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfile_name); + return nullptr; } - /** - * Given a Java byte[][] which is an array of java.lang.Strings - * where each String is a byte[], the passed function `string_fn` - * will be called on each String, the result is the collected by - * calling the passed function `collector_fn` - * - * @param env (IN) A pointer to the java environment - * @param jbyte_strings (IN) A Java array of Strings expressed as bytes - * @param string_fn (IN) A transform function to call for each String - * @param collector_fn (IN) A collector which is called for the result - * of each `string_fn` - * @param has_exception (OUT) will be set to JNI_TRUE - * if an ArrayIndexOutOfBoundsException or OutOfMemoryError - * exception occurs - */ - template static void byteStrings(JNIEnv* env, - jobjectArray jbyte_strings, - std::function string_fn, - std::function collector_fn, - jboolean *has_exception) { - const jsize jlen = env->GetArrayLength(jbyte_strings); + jbyteArray jsmallest_key = rocksdb::JniUtil::copyBytes( + env, live_file_meta_data->smallestkey); + if (jsmallest_key == nullptr) { + // exception occurred creating java byte array + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + return nullptr; + } - for(jsize i = 0; i < jlen; i++) { - jobject jbyte_string_obj = env->GetObjectArrayElement(jbyte_strings, i); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - *has_exception = JNI_TRUE; // signal error - return; - } + jbyteArray jlargest_key = rocksdb::JniUtil::copyBytes( + env, live_file_meta_data->largestkey); + if (jlargest_key == nullptr) { + // exception occurred creating java byte array + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + env->DeleteLocalRef(jsmallest_key); + return nullptr; + } - jbyteArray jbyte_string_ary = - reinterpret_cast(jbyte_string_obj); - T result = byteString(env, jbyte_string_ary, string_fn, has_exception); + jobject jlive_file_meta_data = env->NewObject(jclazz, mid, + jcolumn_family_name, + static_cast(live_file_meta_data->level), + jfile_name, + jpath, + static_cast(live_file_meta_data->size), + static_cast(live_file_meta_data->smallest_seqno), + static_cast(live_file_meta_data->largest_seqno), + jsmallest_key, + jlargest_key, + static_cast(live_file_meta_data->num_reads_sampled), + static_cast(live_file_meta_data->being_compacted), + static_cast(live_file_meta_data->num_entries), + static_cast(live_file_meta_data->num_deletions) + ); + + if (env->ExceptionCheck()) { + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + env->DeleteLocalRef(jsmallest_key); + env->DeleteLocalRef(jlargest_key); + return nullptr; + } - env->DeleteLocalRef(jbyte_string_obj); + // cleanup + env->DeleteLocalRef(jcolumn_family_name); + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + env->DeleteLocalRef(jsmallest_key); + env->DeleteLocalRef(jlargest_key); - if(*has_exception == JNI_TRUE) { - // exception thrown: OutOfMemoryError - return; - } + return jlive_file_meta_data; + } - collector_fn(i, result); - } + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/LiveFileMetaData"); + } +}; - *has_exception = JNI_FALSE; +class SstFileMetaDataJni : public JavaClass { + public: + /** + * Create a new Java org.rocksdb.SstFileMetaData object. + * + * @param env A pointer to the Java environment + * @param sst_file_meta_data A Cpp sst file meta data object + * + * @return A reference to a Java org.rocksdb.SstFileMetaData object, or + * nullptr if an an exception occurs + */ + static jobject fromCppSstFileMetaData(JNIEnv* env, + const rocksdb::SstFileMetaData* sst_file_meta_data) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } - /** - * Given a Java String which is expressed as a Java Byte Array byte[], - * the passed function `string_fn` will be called on the String - * and the result returned - * - * @param env (IN) A pointer to the java environment - * @param jbyte_string_ary (IN) A Java String expressed in bytes - * @param string_fn (IN) A transform function to call on the String - * @param has_exception (OUT) will be set to JNI_TRUE - * if an OutOfMemoryError exception occurs - */ - template static T byteString(JNIEnv* env, - jbyteArray jbyte_string_ary, - std::function string_fn, - jboolean* has_exception) { - const jsize jbyte_string_len = env->GetArrayLength(jbyte_string_ary); - jbyte* jbyte_string = - env->GetByteArrayElements(jbyte_string_ary, nullptr); - if(jbyte_string == nullptr) { - // exception thrown: OutOfMemoryError - *has_exception = JNI_TRUE; - return nullptr; // signal error - } + jmethodID mid = env->GetMethodID(jclazz, "", "(Ljava/lang/String;Ljava/lang/String;JJJ[B[BJZJJ)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } - T result = - string_fn(reinterpret_cast(jbyte_string), jbyte_string_len); + jstring jfile_name = rocksdb::JniUtil::toJavaString( + env, &sst_file_meta_data->name, true); + if (jfile_name == nullptr) { + // exception occurred creating java byte array + return nullptr; + } - env->ReleaseByteArrayElements(jbyte_string_ary, jbyte_string, JNI_ABORT); + jstring jpath = rocksdb::JniUtil::toJavaString( + env, &sst_file_meta_data->db_path, true); + if (jpath == nullptr) { + // exception occurred creating java byte array + env->DeleteLocalRef(jfile_name); + return nullptr; + } - *has_exception = JNI_FALSE; - return result; + jbyteArray jsmallest_key = rocksdb::JniUtil::copyBytes( + env, sst_file_meta_data->smallestkey); + if (jsmallest_key == nullptr) { + // exception occurred creating java byte array + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + return nullptr; } - /** - * Converts a std::vector to a Java byte[][] where each Java String - * is expressed as a Java Byte Array byte[]. - * - * @param env A pointer to the java environment - * @param strings A vector of Strings - * - * @return A Java array of Strings expressed as bytes - */ - static jobjectArray stringsBytes(JNIEnv* env, std::vector strings) { - jclass jcls_ba = ByteJni::getArrayJClass(env); - if(jcls_ba == nullptr) { - // exception occurred - return nullptr; - } + jbyteArray jlargest_key = rocksdb::JniUtil::copyBytes( + env, sst_file_meta_data->largestkey); + if (jlargest_key == nullptr) { + // exception occurred creating java byte array + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + env->DeleteLocalRef(jsmallest_key); + return nullptr; + } - const jsize len = static_cast(strings.size()); + jobject jsst_file_meta_data = env->NewObject(jclazz, mid, + jfile_name, + jpath, + static_cast(sst_file_meta_data->size), + static_cast(sst_file_meta_data->smallest_seqno), + static_cast(sst_file_meta_data->largest_seqno), + jsmallest_key, + jlargest_key, + static_cast(sst_file_meta_data->num_reads_sampled), + static_cast(sst_file_meta_data->being_compacted), + static_cast(sst_file_meta_data->num_entries), + static_cast(sst_file_meta_data->num_deletions) + ); + + if (env->ExceptionCheck()) { + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + env->DeleteLocalRef(jsmallest_key); + env->DeleteLocalRef(jlargest_key); + return nullptr; + } - jobjectArray jbyte_strings = env->NewObjectArray(len, jcls_ba, nullptr); - if(jbyte_strings == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } + // cleanup + env->DeleteLocalRef(jfile_name); + env->DeleteLocalRef(jpath); + env->DeleteLocalRef(jsmallest_key); + env->DeleteLocalRef(jlargest_key); - for (jsize i = 0; i < len; i++) { - std::string *str = &strings[i]; - const jsize str_len = static_cast(str->size()); + return jsst_file_meta_data; + } - jbyteArray jbyte_string_ary = env->NewByteArray(str_len); - if(jbyte_string_ary == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jbyte_strings); - return nullptr; - } + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/SstFileMetaData"); + } +}; - env->SetByteArrayRegion( - jbyte_string_ary, 0, str_len, - const_cast(reinterpret_cast(str->c_str()))); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jbyte_string_ary); - env->DeleteLocalRef(jbyte_strings); - return nullptr; - } +class LevelMetaDataJni : public JavaClass { + public: + /** + * Create a new Java org.rocksdb.LevelMetaData object. + * + * @param env A pointer to the Java environment + * @param level_meta_data A Cpp level meta data object + * + * @return A reference to a Java org.rocksdb.LevelMetaData object, or + * nullptr if an an exception occurs + */ + static jobject fromCppLevelMetaData(JNIEnv* env, + const rocksdb::LevelMetaData* level_meta_data) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + jmethodID mid = env->GetMethodID(jclazz, "", "(IJ[Lorg/rocksdb/SstFileMetaData;)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; + } - env->SetObjectArrayElement(jbyte_strings, i, jbyte_string_ary); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - // or ArrayStoreException - env->DeleteLocalRef(jbyte_string_ary); - env->DeleteLocalRef(jbyte_strings); - return nullptr; - } + const jsize jlen = + static_cast(level_meta_data->files.size()); + jobjectArray jfiles = env->NewObjectArray(jlen, SstFileMetaDataJni::getJClass(env), nullptr); + if (jfiles == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } - env->DeleteLocalRef(jbyte_string_ary); + jsize i = 0; + for (auto it = level_meta_data->files.begin(); + it != level_meta_data->files.end(); ++it) { + jobject jfile = SstFileMetaDataJni::fromCppSstFileMetaData(env, &(*it)); + if (jfile == nullptr) { + // exception occurred + env->DeleteLocalRef(jfiles); + return nullptr; } + env->SetObjectArrayElement(jfiles, i++, jfile); + } - return jbyte_strings; + jobject jlevel_meta_data = env->NewObject(jclazz, mid, + static_cast(level_meta_data->level), + static_cast(level_meta_data->size), + jfiles + ); + + if (env->ExceptionCheck()) { + env->DeleteLocalRef(jfiles); + return nullptr; } - /* - * Helper for operations on a key and value - * for example WriteBatch->Put - * - * TODO(AR) could be extended to cover returning rocksdb::Status - * from `op` and used for RocksDB->Put etc. - */ - static void kv_op( - std::function op, - JNIEnv* env, jobject jobj, - jbyteArray jkey, jint jkey_len, - jbyteArray jentry_value, jint jentry_value_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if(env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - return; - } + // cleanup + env->DeleteLocalRef(jfiles); - jbyte* value = env->GetByteArrayElements(jentry_value, nullptr); - if(env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - if(key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } - return; - } + return jlevel_meta_data; + } - rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); - rocksdb::Slice value_slice(reinterpret_cast(value), - jentry_value_len); + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/LevelMetaData"); + } +}; - op(key_slice, value_slice); +class ColumnFamilyMetaDataJni : public JavaClass { + public: + /** + * Create a new Java org.rocksdb.ColumnFamilyMetaData object. + * + * @param env A pointer to the Java environment + * @param column_famly_meta_data A Cpp live file meta data object + * + * @return A reference to a Java org.rocksdb.ColumnFamilyMetaData object, or + * nullptr if an an exception occurs + */ + static jobject fromCppColumnFamilyMetaData(JNIEnv* env, + const rocksdb::ColumnFamilyMetaData* column_famly_meta_data) { + jclass jclazz = getJClass(env); + if (jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - if(value != nullptr) { - env->ReleaseByteArrayElements(jentry_value, value, JNI_ABORT); - } - if(key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } + jmethodID mid = env->GetMethodID(jclazz, "", "(JJ[B[Lorg/rocksdb/LevelMetaData;)V"); + if (mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return nullptr; } - /* - * Helper for operations on a key - * for example WriteBatch->Delete - * - * TODO(AR) could be extended to cover returning rocksdb::Status - * from `op` and used for RocksDB->Delete etc. - */ - static void k_op( - std::function op, - JNIEnv* env, jobject jobj, - jbyteArray jkey, jint jkey_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if(env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - return; + jbyteArray jname = rocksdb::JniUtil::copyBytes( + env, column_famly_meta_data->name); + if (jname == nullptr) { + // exception occurred creating java byte array + return nullptr; + } + + const jsize jlen = + static_cast(column_famly_meta_data->levels.size()); + jobjectArray jlevels = env->NewObjectArray(jlen, LevelMetaDataJni::getJClass(env), nullptr); + if(jlevels == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jname); + return nullptr; + } + + jsize i = 0; + for (auto it = column_famly_meta_data->levels.begin(); + it != column_famly_meta_data->levels.end(); ++it) { + jobject jlevel = LevelMetaDataJni::fromCppLevelMetaData(env, &(*it)); + if (jlevel == nullptr) { + // exception occurred + env->DeleteLocalRef(jname); + env->DeleteLocalRef(jlevels); + return nullptr; } + env->SetObjectArrayElement(jlevels, i++, jlevel); + } - rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + jobject jcolumn_family_meta_data = env->NewObject(jclazz, mid, + static_cast(column_famly_meta_data->size), + static_cast(column_famly_meta_data->file_count), + jname, + jlevels + ); + + if (env->ExceptionCheck()) { + env->DeleteLocalRef(jname); + env->DeleteLocalRef(jlevels); + return nullptr; + } - op(key_slice); + // cleanup + env->DeleteLocalRef(jname); + env->DeleteLocalRef(jlevels); - if(key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } + return jcolumn_family_meta_data; + } + + static jclass getJClass(JNIEnv* env) { + return JavaClass::getJClass(env, "org/rocksdb/ColumnFamilyMetaData"); + } +}; + +// The portal class for org.rocksdb.AbstractTraceWriter +class AbstractTraceWriterJni : public RocksDBNativeClass< + const rocksdb::TraceWriterJniCallback*, + AbstractTraceWriterJni> { + public: + /** + * Get the Java Class org.rocksdb.AbstractTraceWriter + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/AbstractTraceWriter"); + } + + /** + * Get the Java Method: AbstractTraceWriter#write + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getWriteProxyMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; } - /* - * Helper for operations on a value - * for example WriteBatchWithIndex->GetFromBatch - */ - static jbyteArray v_op( - std::function op, - JNIEnv* env, jbyteArray jkey, jint jkey_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if(env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - return nullptr; - } + static jmethodID mid = env->GetMethodID( + jclazz, "writeProxy", "(J)S"); + assert(mid != nullptr); + return mid; + } - rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + /** + * Get the Java Method: AbstractTraceWriter#closeWriter + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getCloseWriterProxyMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - std::string value; - rocksdb::Status s = op(key_slice, &value); + static jmethodID mid = env->GetMethodID( + jclazz, "closeWriterProxy", "()S"); + assert(mid != nullptr); + return mid; + } - if(key != nullptr) { - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - } + /** + * Get the Java Method: AbstractTraceWriter#getFileSize + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getGetFileSizeMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - if (s.IsNotFound()) { - return nullptr; - } + static jmethodID mid = env->GetMethodID( + jclazz, "getFileSize", "()J"); + assert(mid != nullptr); + return mid; + } +}; - if (s.ok()) { - jbyteArray jret_value = - env->NewByteArray(static_cast(value.size())); - if(jret_value == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } +// The portal class for org.rocksdb.AbstractWalFilter +class AbstractWalFilterJni : public RocksDBNativeClass< + const rocksdb::WalFilterJniCallback*, + AbstractWalFilterJni> { + public: + /** + * Get the Java Class org.rocksdb.AbstractWalFilter + * + * @param env A pointer to the Java environment + * + * @return The Java Class or nullptr if one of the + * ClassFormatError, ClassCircularityError, NoClassDefFoundError, + * OutOfMemoryError or ExceptionInInitializerError exceptions is thrown + */ + static jclass getJClass(JNIEnv* env) { + return RocksDBNativeClass::getJClass(env, + "org/rocksdb/AbstractWalFilter"); + } - env->SetByteArrayRegion(jret_value, 0, static_cast(value.size()), - const_cast(reinterpret_cast(value.c_str()))); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - if(jret_value != nullptr) { - env->DeleteLocalRef(jret_value); - } - return nullptr; - } + /** + * Get the Java Method: AbstractWalFilter#columnFamilyLogNumberMap + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getColumnFamilyLogNumberMapMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } - return jret_value; - } + static jmethodID mid = env->GetMethodID( + jclazz, "columnFamilyLogNumberMap", + "(Ljava/util/Map;Ljava/util/Map;)V"); + assert(mid != nullptr); + return mid; + } - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + /** + * Get the Java Method: AbstractTraceWriter#logRecordFoundProxy + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getLogRecordFoundProxyMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class + return nullptr; + } + + static jmethodID mid = env->GetMethodID( + jclazz, "logRecordFoundProxy", "(JLjava/lang/String;JJ)S"); + assert(mid != nullptr); + return mid; + } + + /** + * Get the Java Method: AbstractTraceWriter#name + * + * @param env A pointer to the Java environment + * + * @return The Java Method ID or nullptr if the class or method id could not + * be retieved + */ + static jmethodID getNameMethodId(JNIEnv* env) { + jclass jclazz = getJClass(env); + if(jclazz == nullptr) { + // exception occurred accessing class return nullptr; } + + static jmethodID mid = env->GetMethodID( + jclazz, "name", "()Ljava/lang/String;"); + assert(mid != nullptr); + return mid; + } }; +// The portal class for org.rocksdb.WalProcessingOption +class WalProcessingOptionJni { + public: + // Returns the equivalent org.rocksdb.WalProcessingOption for the provided + // C++ rocksdb::WalFilter::WalProcessingOption enum + static jbyte toJavaWalProcessingOption( + const rocksdb::WalFilter::WalProcessingOption& wal_processing_option) { + switch(wal_processing_option) { + case rocksdb::WalFilter::WalProcessingOption::kContinueProcessing: + return 0x0; + case rocksdb::WalFilter::WalProcessingOption::kIgnoreCurrentRecord: + return 0x1; + case rocksdb::WalFilter::WalProcessingOption::kStopReplay: + return 0x2; + case rocksdb::WalFilter::WalProcessingOption::kCorruptedRecord: + return 0x3; + default: + return 0x7F; // undefined + } + } + + // Returns the equivalent C++ rocksdb::WalFilter::WalProcessingOption enum for + // the provided Java org.rocksdb.WalProcessingOption + static rocksdb::WalFilter::WalProcessingOption toCppWalProcessingOption( + jbyte jwal_processing_option) { + switch(jwal_processing_option) { + case 0x0: + return rocksdb::WalFilter::WalProcessingOption::kContinueProcessing; + case 0x1: + return rocksdb::WalFilter::WalProcessingOption::kIgnoreCurrentRecord; + case 0x2: + return rocksdb::WalFilter::WalProcessingOption::kStopReplay; + case 0x3: + return rocksdb::WalFilter::WalProcessingOption::kCorruptedRecord; + default: + // undefined/default + return rocksdb::WalFilter::WalProcessingOption::kCorruptedRecord; + } + } +}; } // namespace rocksdb #endif // JAVA_ROCKSJNI_PORTAL_H_ diff --git a/thirdparty/rocksdb/java/rocksjni/ratelimiterjni.cc b/thirdparty/rocksdb/java/rocksjni/ratelimiterjni.cc index b4174ff102..0804c2fbca 100644 --- a/thirdparty/rocksdb/java/rocksjni/ratelimiterjni.cc +++ b/thirdparty/rocksdb/java/rocksjni/ratelimiterjni.cc @@ -5,23 +5,26 @@ // // This file implements the "bridge" between Java and C++ for RateLimiter. -#include "rocksjni/portal.h" #include "include/org_rocksdb_RateLimiter.h" #include "rocksdb/rate_limiter.h" +#include "rocksjni/portal.h" /* * Class: org_rocksdb_RateLimiter * Method: newRateLimiterHandle - * Signature: (JJI)J + * Signature: (JJIBZ)J */ jlong Java_org_rocksdb_RateLimiter_newRateLimiterHandle( - JNIEnv* env, jclass jclazz, jlong jrate_bytes_per_second, - jlong jrefill_period_micros, jint jfairness) { - auto * sptr_rate_limiter = + JNIEnv* /*env*/, jclass /*jclazz*/, jlong jrate_bytes_per_second, + jlong jrefill_period_micros, jint jfairness, jbyte jrate_limiter_mode, + jboolean jauto_tune) { + auto rate_limiter_mode = + rocksdb::RateLimiterModeJni::toCppRateLimiterMode(jrate_limiter_mode); + auto* sptr_rate_limiter = new std::shared_ptr(rocksdb::NewGenericRateLimiter( static_cast(jrate_bytes_per_second), static_cast(jrefill_period_micros), - static_cast(jfairness))); + static_cast(jfairness), rate_limiter_mode, jauto_tune)); return reinterpret_cast(sptr_rate_limiter); } @@ -31,10 +34,11 @@ jlong Java_org_rocksdb_RateLimiter_newRateLimiterHandle( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_RateLimiter_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { +void Java_org_rocksdb_RateLimiter_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* handle = - reinterpret_cast *>(jhandle); + reinterpret_cast*>(jhandle); delete handle; // delete std::shared_ptr } @@ -43,11 +47,26 @@ void Java_org_rocksdb_RateLimiter_disposeInternal( * Method: setBytesPerSecond * Signature: (JJ)V */ -void Java_org_rocksdb_RateLimiter_setBytesPerSecond( - JNIEnv* env, jobject jobj, jlong handle, - jlong jbytes_per_second) { - reinterpret_cast *>(handle)->get()-> - SetBytesPerSecond(jbytes_per_second); +void Java_org_rocksdb_RateLimiter_setBytesPerSecond(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle, + jlong jbytes_per_second) { + reinterpret_cast*>(handle) + ->get() + ->SetBytesPerSecond(jbytes_per_second); +} + +/* + * Class: org_rocksdb_RateLimiter + * Method: getBytesPerSecond + * Signature: (J)J + */ +jlong Java_org_rocksdb_RateLimiter_getBytesPerSecond(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { + return reinterpret_cast*>(handle) + ->get() + ->GetBytesPerSecond(); } /* @@ -55,11 +74,11 @@ void Java_org_rocksdb_RateLimiter_setBytesPerSecond( * Method: request * Signature: (JJ)V */ -void Java_org_rocksdb_RateLimiter_request( - JNIEnv* env, jobject jobj, jlong handle, - jlong jbytes) { - reinterpret_cast *>(handle)->get()-> - Request(jbytes, rocksdb::Env::IO_TOTAL); +void Java_org_rocksdb_RateLimiter_request(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle, jlong jbytes) { + reinterpret_cast*>(handle) + ->get() + ->Request(jbytes, rocksdb::Env::IO_TOTAL); } /* @@ -67,10 +86,12 @@ void Java_org_rocksdb_RateLimiter_request( * Method: getSingleBurstBytes * Signature: (J)J */ -jlong Java_org_rocksdb_RateLimiter_getSingleBurstBytes( - JNIEnv* env, jobject jobj, jlong handle) { - return reinterpret_cast *>(handle)-> - get()->GetSingleBurstBytes(); +jlong Java_org_rocksdb_RateLimiter_getSingleBurstBytes(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { + return reinterpret_cast*>(handle) + ->get() + ->GetSingleBurstBytes(); } /* @@ -78,10 +99,12 @@ jlong Java_org_rocksdb_RateLimiter_getSingleBurstBytes( * Method: getTotalBytesThrough * Signature: (J)J */ -jlong Java_org_rocksdb_RateLimiter_getTotalBytesThrough( - JNIEnv* env, jobject jobj, jlong handle) { - return reinterpret_cast *>(handle)-> - get()->GetTotalBytesThrough(); +jlong Java_org_rocksdb_RateLimiter_getTotalBytesThrough(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { + return reinterpret_cast*>(handle) + ->get() + ->GetTotalBytesThrough(); } /* @@ -89,8 +112,10 @@ jlong Java_org_rocksdb_RateLimiter_getTotalBytesThrough( * Method: getTotalRequests * Signature: (J)J */ -jlong Java_org_rocksdb_RateLimiter_getTotalRequests( - JNIEnv* env, jobject jobj, jlong handle) { - return reinterpret_cast *>(handle)-> - get()->GetTotalRequests(); +jlong Java_org_rocksdb_RateLimiter_getTotalRequests(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { + return reinterpret_cast*>(handle) + ->get() + ->GetTotalRequests(); } diff --git a/thirdparty/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc b/thirdparty/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc index 8c54a46b86..ede150fa62 100644 --- a/thirdparty/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc +++ b/thirdparty/rocksdb/java/rocksjni/remove_emptyvalue_compactionfilterjni.cc @@ -8,16 +8,14 @@ #include "include/org_rocksdb_RemoveEmptyValueCompactionFilter.h" #include "utilities/compaction_filters/remove_emptyvalue_compactionfilter.h" - /* * Class: org_rocksdb_RemoveEmptyValueCompactionFilter * Method: createNewRemoveEmptyValueCompactionFilter0 * Signature: ()J */ jlong Java_org_rocksdb_RemoveEmptyValueCompactionFilter_createNewRemoveEmptyValueCompactionFilter0( - JNIEnv* env, jclass jcls) { - auto* compaction_filter = - new rocksdb::RemoveEmptyValueCompactionFilter(); + JNIEnv* /*env*/, jclass /*jcls*/) { + auto* compaction_filter = new rocksdb::RemoveEmptyValueCompactionFilter(); // set the native handle to our native compaction filter return reinterpret_cast(compaction_filter); diff --git a/thirdparty/rocksdb/java/rocksjni/restorejni.cc b/thirdparty/rocksdb/java/rocksjni/restorejni.cc index eb8e65b4a1..beca74fb56 100644 --- a/thirdparty/rocksdb/java/rocksjni/restorejni.cc +++ b/thirdparty/rocksdb/java/rocksjni/restorejni.cc @@ -7,21 +7,21 @@ // calling C++ rocksdb::RestoreOptions methods // from Java side. +#include #include #include -#include #include #include "include/org_rocksdb_RestoreOptions.h" -#include "rocksjni/portal.h" #include "rocksdb/utilities/backupable_db.h" +#include "rocksjni/portal.h" /* * Class: org_rocksdb_RestoreOptions * Method: newRestoreOptions * Signature: (Z)J */ -jlong Java_org_rocksdb_RestoreOptions_newRestoreOptions(JNIEnv* env, - jclass jcls, jboolean keep_log_files) { +jlong Java_org_rocksdb_RestoreOptions_newRestoreOptions( + JNIEnv* /*env*/, jclass /*jcls*/, jboolean keep_log_files) { auto* ropt = new rocksdb::RestoreOptions(keep_log_files); return reinterpret_cast(ropt); } @@ -31,8 +31,9 @@ jlong Java_org_rocksdb_RestoreOptions_newRestoreOptions(JNIEnv* env, * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_RestoreOptions_disposeInternal(JNIEnv* env, jobject jobj, - jlong jhandle) { +void Java_org_rocksdb_RestoreOptions_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { auto* ropt = reinterpret_cast(jhandle); assert(ropt); delete ropt; diff --git a/thirdparty/rocksdb/java/rocksjni/rocks_callback_object.cc b/thirdparty/rocksdb/java/rocksjni/rocks_callback_object.cc new file mode 100644 index 0000000000..874ef3375a --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/rocks_callback_object.cc @@ -0,0 +1,31 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// JNI Callbacks from C++ to sub-classes or org.rocksdb.RocksCallbackObject + +#include + +#include "include/org_rocksdb_RocksCallbackObject.h" +#include "jnicallback.h" + +/* + * Class: org_rocksdb_RocksCallbackObject + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_RocksCallbackObject_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { + // TODO(AR) is deleting from the super class JniCallback OK, or must we delete + // the subclass? Example hierarchies: + // 1) Comparator -> BaseComparatorJniCallback + JniCallback -> + // DirectComparatorJniCallback 2) Comparator -> BaseComparatorJniCallback + + // JniCallback -> ComparatorJniCallback + // I think this is okay, as Comparator and JniCallback both have virtual + // destructors... + delete reinterpret_cast(handle); + // @lint-ignore TXT4 T25377293 Grandfathered in +} \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/rocksdb_exception_test.cc b/thirdparty/rocksdb/java/rocksjni/rocksdb_exception_test.cc index 339d4c5eda..6e5978121b 100644 --- a/thirdparty/rocksdb/java/rocksjni/rocksdb_exception_test.cc +++ b/thirdparty/rocksdb/java/rocksjni/rocksdb_exception_test.cc @@ -17,7 +17,7 @@ * Signature: ()V */ void Java_org_rocksdb_RocksDBExceptionTest_raiseException(JNIEnv* env, - jobject jobj) { + jobject /*jobj*/) { rocksdb::RocksDBExceptionJni::ThrowNew(env, std::string("test message")); } @@ -27,7 +27,7 @@ void Java_org_rocksdb_RocksDBExceptionTest_raiseException(JNIEnv* env, * Signature: ()V */ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCode( - JNIEnv* env, jobject jobj) { + JNIEnv* env, jobject /*jobj*/) { rocksdb::RocksDBExceptionJni::ThrowNew(env, "test message", rocksdb::Status::NotSupported()); } @@ -38,7 +38,7 @@ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCode( * Signature: ()V */ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionNoMsgWithStatusCode( - JNIEnv* env, jobject jobj) { + JNIEnv* env, jobject /*jobj*/) { rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotSupported()); } @@ -48,7 +48,7 @@ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionNoMsgWithStatusCode( * Signature: ()V */ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCodeSubCode( - JNIEnv* env, jobject jobj) { + JNIEnv* env, jobject /*jobj*/) { rocksdb::RocksDBExceptionJni::ThrowNew( env, "test message", rocksdb::Status::TimedOut(rocksdb::Status::SubCode::kLockTimeout)); @@ -60,7 +60,7 @@ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCodeSubCode( * Signature: ()V */ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionNoMsgWithStatusCodeSubCode( - JNIEnv* env, jobject jobj) { + JNIEnv* env, jobject /*jobj*/) { rocksdb::RocksDBExceptionJni::ThrowNew( env, rocksdb::Status::TimedOut(rocksdb::Status::SubCode::kLockTimeout)); } @@ -71,7 +71,7 @@ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionNoMsgWithStatusCodeSubC * Signature: ()V */ void Java_org_rocksdb_RocksDBExceptionTest_raiseExceptionWithStatusCodeState( - JNIEnv* env, jobject jobj) { + JNIEnv* env, jobject /*jobj*/) { rocksdb::Slice state("test state"); rocksdb::RocksDBExceptionJni::ThrowNew(env, "test message", rocksdb::Status::NotSupported(state)); diff --git a/thirdparty/rocksdb/java/rocksjni/rocksjni.cc b/thirdparty/rocksdb/java/rocksjni/rocksjni.cc index a08a459714..53224232c8 100644 --- a/thirdparty/rocksdb/java/rocksjni/rocksjni.cc +++ b/thirdparty/rocksdb/java/rocksjni/rocksjni.cc @@ -27,14 +27,13 @@ #undef min #endif -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::Open -jlong rocksdb_open_helper(JNIEnv* env, jlong jopt_handle, jstring jdb_path, - std::function open_fn - ) { +jlong rocksdb_open_helper( + JNIEnv* env, jlong jopt_handle, jstring jdb_path, + std::function + open_fn) { const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if(db_path == nullptr) { + if (db_path == nullptr) { // exception thrown: OutOfMemoryError return 0; } @@ -59,12 +58,12 @@ jlong rocksdb_open_helper(JNIEnv* env, jlong jopt_handle, jstring jdb_path, * Signature: (JLjava/lang/String;)J */ jlong Java_org_rocksdb_RocksDB_open__JLjava_lang_String_2( - JNIEnv* env, jclass jcls, jlong jopt_handle, jstring jdb_path) { - return rocksdb_open_helper(env, jopt_handle, jdb_path, - (rocksdb::Status(*) - (const rocksdb::Options&, const std::string&, rocksdb::DB**) - )&rocksdb::DB::Open - ); + JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path) { + return rocksdb_open_helper( + env, jopt_handle, jdb_path, + (rocksdb::Status(*)(const rocksdb::Options&, const std::string&, + rocksdb::DB**)) & + rocksdb::DB::Open); } /* @@ -73,31 +72,32 @@ jlong Java_org_rocksdb_RocksDB_open__JLjava_lang_String_2( * Signature: (JLjava/lang/String;)J */ jlong Java_org_rocksdb_RocksDB_openROnly__JLjava_lang_String_2( - JNIEnv* env, jclass jcls, jlong jopt_handle, jstring jdb_path) { - return rocksdb_open_helper(env, jopt_handle, jdb_path, []( - const rocksdb::Options& options, - const std::string& db_path, rocksdb::DB** db) { - return rocksdb::DB::OpenForReadOnly(options, db_path, db); - }); + JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path) { + return rocksdb_open_helper(env, jopt_handle, jdb_path, + [](const rocksdb::Options& options, + const std::string& db_path, rocksdb::DB** db) { + return rocksdb::DB::OpenForReadOnly(options, + db_path, db); + }); } -jlongArray rocksdb_open_helper(JNIEnv* env, jlong jopt_handle, - jstring jdb_path, jobjectArray jcolumn_names, jlongArray jcolumn_options, +jlongArray rocksdb_open_helper( + JNIEnv* env, jlong jopt_handle, jstring jdb_path, + jobjectArray jcolumn_names, jlongArray jcolumn_options, std::function&, - std::vector*, - rocksdb::DB**)> open_fn - ) { + const rocksdb::DBOptions&, const std::string&, + const std::vector&, + std::vector*, rocksdb::DB**)> + open_fn) { const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if(db_path == nullptr) { + if (db_path == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } const jsize len_cols = env->GetArrayLength(jcolumn_names); jlong* jco = env->GetLongArrayElements(jcolumn_options, nullptr); - if(jco == nullptr) { + if (jco == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseStringUTFChars(jdb_path, db_path); return nullptr; @@ -106,64 +106,62 @@ jlongArray rocksdb_open_helper(JNIEnv* env, jlong jopt_handle, std::vector column_families; jboolean has_exception = JNI_FALSE; rocksdb::JniUtil::byteStrings( - env, - jcolumn_names, - [](const char* str_data, const size_t str_len) { - return std::string(str_data, str_len); - }, - [&jco, &column_families](size_t idx, std::string cf_name) { - rocksdb::ColumnFamilyOptions* cf_options = - reinterpret_cast(jco[idx]); - column_families.push_back( - rocksdb::ColumnFamilyDescriptor(cf_name, *cf_options)); - }, - &has_exception); + env, jcolumn_names, + [](const char* str_data, const size_t str_len) { + return std::string(str_data, str_len); + }, + [&jco, &column_families](size_t idx, std::string cf_name) { + rocksdb::ColumnFamilyOptions* cf_options = + reinterpret_cast(jco[idx]); + column_families.push_back( + rocksdb::ColumnFamilyDescriptor(cf_name, *cf_options)); + }, + &has_exception); env->ReleaseLongArrayElements(jcolumn_options, jco, JNI_ABORT); - if(has_exception == JNI_TRUE) { + if (has_exception == JNI_TRUE) { // exception occurred env->ReleaseStringUTFChars(jdb_path, db_path); return nullptr; } auto* opt = reinterpret_cast(jopt_handle); - std::vector handles; + std::vector cf_handles; rocksdb::DB* db = nullptr; - rocksdb::Status s = open_fn(*opt, db_path, column_families, - &handles, &db); + rocksdb::Status s = open_fn(*opt, db_path, column_families, &cf_handles, &db); // we have now finished with db_path env->ReleaseStringUTFChars(jdb_path, db_path); // check if open operation was successful - if (s.ok()) { - const jsize resultsLen = 1 + len_cols; //db handle + column family handles - std::unique_ptr results = - std::unique_ptr(new jlong[resultsLen]); - results[0] = reinterpret_cast(db); - for(int i = 1; i <= len_cols; i++) { - results[i] = reinterpret_cast(handles[i - 1]); - } + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; + } - jlongArray jresults = env->NewLongArray(resultsLen); - if(jresults == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } + const jsize resultsLen = 1 + len_cols; // db handle + column family handles + std::unique_ptr results = + std::unique_ptr(new jlong[resultsLen]); + results[0] = reinterpret_cast(db); + for (int i = 1; i <= len_cols; i++) { + results[i] = reinterpret_cast(cf_handles[i - 1]); + } - env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresults); - return nullptr; - } + jlongArray jresults = env->NewLongArray(resultsLen); + if (jresults == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } - return jresults; - } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jresults); return nullptr; } + + return jresults; } /* @@ -172,16 +170,16 @@ jlongArray rocksdb_open_helper(JNIEnv* env, jlong jopt_handle, * Signature: (JLjava/lang/String;[[B[J)[J */ jlongArray Java_org_rocksdb_RocksDB_openROnly__JLjava_lang_String_2_3_3B_3J( - JNIEnv* env, jclass jcls, jlong jopt_handle, jstring jdb_path, + JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, jobjectArray jcolumn_names, jlongArray jcolumn_options) { - return rocksdb_open_helper(env, jopt_handle, jdb_path, jcolumn_names, - jcolumn_options, []( - const rocksdb::DBOptions& options, const std::string& db_path, - const std::vector& column_families, - std::vector* handles, rocksdb::DB** db) { - return rocksdb::DB::OpenForReadOnly(options, db_path, column_families, - handles, db); - }); + return rocksdb_open_helper( + env, jopt_handle, jdb_path, jcolumn_names, jcolumn_options, + [](const rocksdb::DBOptions& options, const std::string& db_path, + const std::vector& column_families, + std::vector* handles, rocksdb::DB** db) { + return rocksdb::DB::OpenForReadOnly(options, db_path, column_families, + handles, db); + }); } /* @@ -190,19 +188,41 @@ jlongArray Java_org_rocksdb_RocksDB_openROnly__JLjava_lang_String_2_3_3B_3J( * Signature: (JLjava/lang/String;[[B[J)[J */ jlongArray Java_org_rocksdb_RocksDB_open__JLjava_lang_String_2_3_3B_3J( - JNIEnv* env, jclass jcls, jlong jopt_handle, jstring jdb_path, + JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, jobjectArray jcolumn_names, jlongArray jcolumn_options) { - return rocksdb_open_helper(env, jopt_handle, jdb_path, jcolumn_names, - jcolumn_options, (rocksdb::Status(*) - (const rocksdb::DBOptions&, const std::string&, - const std::vector&, - std::vector*, rocksdb::DB**) - )&rocksdb::DB::Open - ); + return rocksdb_open_helper( + env, jopt_handle, jdb_path, jcolumn_names, jcolumn_options, + (rocksdb::Status(*)(const rocksdb::DBOptions&, const std::string&, + const std::vector&, + std::vector*, + rocksdb::DB**)) & + rocksdb::DB::Open); } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::ListColumnFamilies +/* + * Class: org_rocksdb_RocksDB + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_RocksDB_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* db = reinterpret_cast(jhandle); + assert(db != nullptr); + delete db; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: closeDatabase + * Signature: (J)V + */ +void Java_org_rocksdb_RocksDB_closeDatabase( + JNIEnv* env, jclass, jlong jhandle) { + auto* db = reinterpret_cast(jhandle); + assert(db != nullptr); + rocksdb::Status s = db->Close(); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} /* * Class: org_rocksdb_RocksDB @@ -210,17 +230,17 @@ jlongArray Java_org_rocksdb_RocksDB_open__JLjava_lang_String_2_3_3B_3J( * Signature: (JLjava/lang/String;)[[B */ jobjectArray Java_org_rocksdb_RocksDB_listColumnFamilies( - JNIEnv* env, jclass jclazz, jlong jopt_handle, jstring jdb_path) { + JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path) { std::vector column_family_names; const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if(db_path == nullptr) { + if (db_path == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } auto* opt = reinterpret_cast(jopt_handle); - rocksdb::Status s = rocksdb::DB::ListColumnFamilies(*opt, db_path, - &column_family_names); + rocksdb::Status s = + rocksdb::DB::ListColumnFamilies(*opt, db_path, &column_family_names); env->ReleaseStringUTFChars(jdb_path, db_path); @@ -230,31 +250,225 @@ jobjectArray Java_org_rocksdb_RocksDB_listColumnFamilies( return jcolumn_family_names; } +/* + * Class: org_rocksdb_RocksDB + * Method: createColumnFamily + * Signature: (J[BIJ)J + */ +jlong Java_org_rocksdb_RocksDB_createColumnFamily( + JNIEnv* env, jobject, jlong jhandle, jbyteArray jcf_name, + jint jcf_name_len, jlong jcf_options_handle) { + auto* db = reinterpret_cast(jhandle); + jboolean has_exception = JNI_FALSE; + const std::string cf_name = + rocksdb::JniUtil::byteString(env, jcf_name, jcf_name_len, + [](const char* str, const size_t len) { + return std::string(str, len); + }, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return 0; + } + auto* cf_options = + reinterpret_cast(jcf_options_handle); + rocksdb::ColumnFamilyHandle *cf_handle; + rocksdb::Status s = db->CreateColumnFamily(*cf_options, cf_name, &cf_handle); + if (!s.ok()) { + // error occurred + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return 0; + } + return reinterpret_cast(cf_handle); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: createColumnFamilies + * Signature: (JJ[[B)[J + */ +jlongArray Java_org_rocksdb_RocksDB_createColumnFamilies__JJ_3_3B( + JNIEnv* env, jobject, jlong jhandle, jlong jcf_options_handle, + jobjectArray jcf_names) { + auto* db = reinterpret_cast(jhandle); + auto* cf_options = + reinterpret_cast(jcf_options_handle); + jboolean has_exception = JNI_FALSE; + std::vector cf_names; + rocksdb::JniUtil::byteStrings(env, jcf_names, + [](const char* str, const size_t len) { + return std::string(str, len); + }, + [&cf_names](const size_t, std::string str) { + cf_names.push_back(str); + }, + &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return nullptr; + } + + std::vector cf_handles; + rocksdb::Status s = db->CreateColumnFamilies(*cf_options, cf_names, &cf_handles); + if (!s.ok()) { + // error occurred + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; + } + + jlongArray jcf_handles = rocksdb::JniUtil::toJPointers( + env, cf_handles, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return nullptr; + } + return jcf_handles; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: createColumnFamilies + * Signature: (J[J[[B)[J + */ +jlongArray Java_org_rocksdb_RocksDB_createColumnFamilies__J_3J_3_3B( + JNIEnv* env, jobject, jlong jhandle, jlongArray jcf_options_handles, + jobjectArray jcf_names) { + auto* db = reinterpret_cast(jhandle); + const jsize jlen = env->GetArrayLength(jcf_options_handles); + std::vector cf_descriptors; + cf_descriptors.reserve(jlen); + + jboolean jcf_options_handles_is_copy = JNI_FALSE; + jlong *jcf_options_handles_elems = env->GetLongArrayElements(jcf_options_handles, &jcf_options_handles_is_copy); + if(jcf_options_handles_elems == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + // extract the column family descriptors + jboolean has_exception = JNI_FALSE; + for (jsize i = 0; i < jlen; i++) { + auto* cf_options = reinterpret_cast( + jcf_options_handles_elems[i]); + jbyteArray jcf_name = static_cast( + env->GetObjectArrayElement(jcf_names, i)); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->ReleaseLongArrayElements(jcf_options_handles, jcf_options_handles_elems, JNI_ABORT); + return nullptr; + } + const std::string cf_name = + rocksdb::JniUtil::byteString(env, jcf_name, + [](const char* str, const size_t len) { + return std::string(str, len); + }, + &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + env->DeleteLocalRef(jcf_name); + env->ReleaseLongArrayElements(jcf_options_handles, jcf_options_handles_elems, JNI_ABORT); + return nullptr; + } + + cf_descriptors.push_back(rocksdb::ColumnFamilyDescriptor(cf_name, *cf_options)); + + env->DeleteLocalRef(jcf_name); + } + + std::vector cf_handles; + rocksdb::Status s = db->CreateColumnFamilies(cf_descriptors, &cf_handles); + + env->ReleaseLongArrayElements(jcf_options_handles, jcf_options_handles_elems, JNI_ABORT); + + if (!s.ok()) { + // error occurred + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; + } + + jlongArray jcf_handles = rocksdb::JniUtil::toJPointers( + env, cf_handles, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return nullptr; + } + return jcf_handles; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: dropColumnFamily + * Signature: (JJ)V; + */ +void Java_org_rocksdb_RocksDB_dropColumnFamily( + JNIEnv* env, jobject, jlong jdb_handle, + jlong jcf_handle) { + auto* db_handle = reinterpret_cast(jdb_handle); + auto* cf_handle = reinterpret_cast(jcf_handle); + rocksdb::Status s = db_handle->DropColumnFamily(cf_handle); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: dropColumnFamilies + * Signature: (J[J)V + */ +void Java_org_rocksdb_RocksDB_dropColumnFamilies( + JNIEnv* env, jobject, jlong jdb_handle, + jlongArray jcolumn_family_handles) { + auto* db_handle = reinterpret_cast(jdb_handle); + + std::vector cf_handles; + if (jcolumn_family_handles != nullptr) { + const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); + + jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); + if (jcfh == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + + for (jsize i = 0; i < len_cols; i++) { + auto* cf_handle = reinterpret_cast(jcfh[i]); + cf_handles.push_back(cf_handle); + } + env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); + } + + rocksdb::Status s = db_handle->DropColumnFamilies(cf_handles); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + ////////////////////////////////////////////////////////////////////////////// // rocksdb::DB::Put /** * @return true if the put succeeded, false if a Java Exception was thrown */ -bool rocksdb_put_helper(JNIEnv* env, rocksdb::DB* db, - const rocksdb::WriteOptions& write_options, - rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, - jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { +bool rocksdb_put_helper( + JNIEnv* env, rocksdb::DB* db, + const rocksdb::WriteOptions& write_options, + rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, + jint jkey_off, jint jkey_len, jbyteArray jval, + jint jval_off, jint jval_len) { jbyte* key = new jbyte[jkey_len]; env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException - delete [] key; + delete[] key; return false; } jbyte* value = new jbyte[jval_len]; env->GetByteArrayRegion(jval, jval_off, jval_len, value); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException - delete [] value; - delete [] key; + delete[] value; + delete[] key; return false; } @@ -270,8 +484,8 @@ bool rocksdb_put_helper(JNIEnv* env, rocksdb::DB* db, } // cleanup - delete [] value; - delete [] key; + delete[] value; + delete[] key; if (s.ok()) { return true; @@ -286,17 +500,15 @@ bool rocksdb_put_helper(JNIEnv* env, rocksdb::DB* db, * Method: put * Signature: (J[BII[BII)V */ -void Java_org_rocksdb_RocksDB_put__J_3BII_3BII(JNIEnv* env, jobject jdb, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { +void Java_org_rocksdb_RocksDB_put__J_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len) { auto* db = reinterpret_cast(jdb_handle); static const rocksdb::WriteOptions default_write_options = rocksdb::WriteOptions(); - rocksdb_put_helper(env, db, default_write_options, nullptr, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); + jkey_len, jval, jval_off, jval_len); } /* @@ -304,22 +516,21 @@ void Java_org_rocksdb_RocksDB_put__J_3BII_3BII(JNIEnv* env, jobject jdb, * Method: put * Signature: (J[BII[BIIJ)V */ -void Java_org_rocksdb_RocksDB_put__J_3BII_3BIIJ(JNIEnv* env, jobject jdb, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, - jlong jcf_handle) { +void Java_org_rocksdb_RocksDB_put__J_3BII_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len, + jlong jcf_handle) { auto* db = reinterpret_cast(jdb_handle); static const rocksdb::WriteOptions default_write_options = rocksdb::WriteOptions(); auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { rocksdb_put_helper(env, db, default_write_options, cf_handle, jkey, - jkey_off, jkey_len, jval, jval_off, jval_len); + jkey_off, jkey_len, jval, jval_off, jval_len); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); } } @@ -328,18 +539,16 @@ void Java_org_rocksdb_RocksDB_put__J_3BII_3BIIJ(JNIEnv* env, jobject jdb, * Method: put * Signature: (JJ[BII[BII)V */ -void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BII(JNIEnv* env, jobject jdb, - jlong jdb_handle, - jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { +void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, + jlong jwrite_options_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len) { auto* db = reinterpret_cast(jdb_handle); - auto* write_options = reinterpret_cast( - jwrite_options_handle); - + auto* write_options = + reinterpret_cast(jwrite_options_handle); rocksdb_put_helper(env, db, *write_options, nullptr, jkey, jkey_off, jkey_len, - jval, jval_off, jval_len); + jval, jval_off, jval_len); } /* @@ -348,403 +557,858 @@ void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BII(JNIEnv* env, jobject jdb, * Signature: (JJ[BII[BIIJ)V */ void Java_org_rocksdb_RocksDB_put__JJ_3BII_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, jlong jcf_handle) { + JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len, + jlong jcf_handle) { auto* db = reinterpret_cast(jdb_handle); - auto* write_options = reinterpret_cast( - jwrite_options_handle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { rocksdb_put_helper(env, db, *write_options, cf_handle, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); + jkey_len, jval, jval_off, jval_len); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); } } ////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::Write -/* - * Class: org_rocksdb_RocksDB - * Method: write0 - * Signature: (JJJ)V - */ -void Java_org_rocksdb_RocksDB_write0( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jwrite_options_handle, jlong jwb_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = reinterpret_cast( - jwrite_options_handle); - auto* wb = reinterpret_cast(jwb_handle); - - rocksdb::Status s = db->Write(*write_options, wb); - - if (!s.ok()) { - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - } -} +// rocksdb::DB::Delete() -/* - * Class: org_rocksdb_RocksDB - * Method: write1 - * Signature: (JJJ)V +/** + * @return true if the delete succeeded, false if a Java Exception was thrown */ -void Java_org_rocksdb_RocksDB_write1( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jwrite_options_handle, jlong jwbwi_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = reinterpret_cast( - jwrite_options_handle); - auto* wbwi = reinterpret_cast(jwbwi_handle); - auto* wb = wbwi->GetWriteBatch(); - - rocksdb::Status s = db->Write(*write_options, wb); - - if (!s.ok()) { - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - } -} - -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::KeyMayExist -jboolean key_may_exist_helper(JNIEnv* env, rocksdb::DB* db, - const rocksdb::ReadOptions& read_opt, - rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, jint jkey_off, - jint jkey_len, jobject jstring_builder, bool* has_exception) { - +bool rocksdb_delete_helper( + JNIEnv* env, rocksdb::DB* db, const rocksdb::WriteOptions& write_options, + rocksdb::ColumnFamilyHandle* cf_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len) { jbyte* key = new jbyte[jkey_len]; env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException - delete [] key; - *has_exception = true; + delete[] key; return false; } - rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); - std::string value; - bool value_found = false; - bool keyMayExist; + rocksdb::Status s; if (cf_handle != nullptr) { - keyMayExist = db->KeyMayExist(read_opt, cf_handle, key_slice, - &value, &value_found); + s = db->Delete(write_options, cf_handle, key_slice); } else { - keyMayExist = db->KeyMayExist(read_opt, key_slice, - &value, &value_found); + // backwards compatibility + s = db->Delete(write_options, key_slice); } // cleanup - delete [] key; + delete[] key; - // extract the value - if (value_found && !value.empty()) { - jobject jresult_string_builder = - rocksdb::StringBuilderJni::append(env, jstring_builder, - value.c_str()); - if(jresult_string_builder == nullptr) { - *has_exception = true; - return false; - } + if (s.ok()) { + return true; } - *has_exception = false; - return static_cast(keyMayExist); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return false; } /* * Class: org_rocksdb_RocksDB - * Method: keyMayExist - * Signature: (J[BIILjava/lang/StringBuilder;)Z + * Method: delete + * Signature: (J[BII)V */ -jboolean Java_org_rocksdb_RocksDB_keyMayExist__J_3BIILjava_lang_StringBuilder_2( - JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jkey, jint jkey_off, - jint jkey_len, jobject jstring_builder) { +void Java_org_rocksdb_RocksDB_delete__J_3BII( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len) { auto* db = reinterpret_cast(jdb_handle); - bool has_exception = false; - return key_may_exist_helper(env, db, rocksdb::ReadOptions(), - nullptr, jkey, jkey_off, jkey_len, jstring_builder, &has_exception); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + rocksdb_delete_helper(env, db, default_write_options, nullptr, jkey, jkey_off, + jkey_len); } /* * Class: org_rocksdb_RocksDB - * Method: keyMayExist - * Signature: (J[BIIJLjava/lang/StringBuilder;)Z + * Method: delete + * Signature: (J[BIIJ)V */ -jboolean Java_org_rocksdb_RocksDB_keyMayExist__J_3BIIJLjava_lang_StringBuilder_2( - JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jkey, jint jkey_off, - jint jkey_len, jlong jcf_handle, jobject jstring_builder) { +void Java_org_rocksdb_RocksDB_delete__J_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jlong jcf_handle) { auto* db = reinterpret_cast(jdb_handle); - auto* cf_handle = reinterpret_cast( - jcf_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - bool has_exception = false; - return key_may_exist_helper(env, db, rocksdb::ReadOptions(), - cf_handle, jkey, jkey_off, jkey_len, jstring_builder, &has_exception); + rocksdb_delete_helper(env, db, default_write_options, cf_handle, jkey, + jkey_off, jkey_len); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); - return true; + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); } } /* * Class: org_rocksdb_RocksDB - * Method: keyMayExist - * Signature: (JJ[BIILjava/lang/StringBuilder;)Z + * Method: delete + * Signature: (JJ[BII)V */ -jboolean Java_org_rocksdb_RocksDB_keyMayExist__JJ_3BIILjava_lang_StringBuilder_2( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jread_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jobject jstring_builder) { +void Java_org_rocksdb_RocksDB_delete__JJ_3BII( + JNIEnv* env, jobject, + jlong jdb_handle, + jlong jwrite_options, + jbyteArray jkey, jint jkey_off, jint jkey_len) { auto* db = reinterpret_cast(jdb_handle); - auto& read_options = *reinterpret_cast( - jread_options_handle); - bool has_exception = false; - return key_may_exist_helper(env, db, read_options, - nullptr, jkey, jkey_off, jkey_len, jstring_builder, &has_exception); + auto* write_options = + reinterpret_cast(jwrite_options); + rocksdb_delete_helper(env, db, *write_options, nullptr, jkey, jkey_off, + jkey_len); } /* * Class: org_rocksdb_RocksDB - * Method: keyMayExist - * Signature: (JJ[BIIJLjava/lang/StringBuilder;)Z + * Method: delete + * Signature: (JJ[BIIJ)V */ -jboolean Java_org_rocksdb_RocksDB_keyMayExist__JJ_3BIIJLjava_lang_StringBuilder_2( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jread_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle, - jobject jstring_builder) { +void Java_org_rocksdb_RocksDB_delete__JJ_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, + jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle) { auto* db = reinterpret_cast(jdb_handle); - auto& read_options = *reinterpret_cast( - jread_options_handle); - auto* cf_handle = reinterpret_cast( - jcf_handle); + auto* write_options = + reinterpret_cast(jwrite_options); + auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - bool has_exception = false; - return key_may_exist_helper(env, db, read_options, cf_handle, - jkey, jkey_off, jkey_len, jstring_builder, &has_exception); + rocksdb_delete_helper(env, db, *write_options, cf_handle, jkey, jkey_off, + jkey_len); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); - return true; + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); } } ////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::Get - -jbyteArray rocksdb_get_helper( - JNIEnv* env, rocksdb::DB* db, const rocksdb::ReadOptions& read_opt, - rocksdb::ColumnFamilyHandle* column_family_handle, jbyteArray jkey, - jint jkey_off, jint jkey_len) { - - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete [] key; - return nullptr; +// rocksdb::DB::SingleDelete() +/** + * @return true if the single delete succeeded, false if a Java Exception + * was thrown + */ +bool rocksdb_single_delete_helper( + JNIEnv* env, rocksdb::DB* db, + const rocksdb::WriteOptions& write_options, + rocksdb::ColumnFamilyHandle* cf_handle, + jbyteArray jkey, jint jkey_len) { + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { + // exception thrown: OutOfMemoryError + return false; } + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); - rocksdb::Slice key_slice( - reinterpret_cast(key), jkey_len); - - std::string value; rocksdb::Status s; - if (column_family_handle != nullptr) { - s = db->Get(read_opt, column_family_handle, key_slice, &value); + if (cf_handle != nullptr) { + s = db->SingleDelete(write_options, cf_handle, key_slice); } else { // backwards compatibility - s = db->Get(read_opt, key_slice, &value); + s = db->SingleDelete(write_options, key_slice); } - // cleanup - delete [] key; - - if (s.IsNotFound()) { - return nullptr; - } + // trigger java unref on key and value. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); if (s.ok()) { - jbyteArray jret_value = rocksdb::JniUtil::copyBytes(env, value); - if(jret_value == nullptr) { - // exception occurred - return nullptr; - } - return jret_value; + return true; } rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; + return false; } /* * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BII)[B + * Method: singleDelete + * Signature: (J[BI)V */ -jbyteArray Java_org_rocksdb_RocksDB_get__J_3BII( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len) { - return rocksdb_get_helper(env, - reinterpret_cast(jdb_handle), - rocksdb::ReadOptions(), nullptr, - jkey, jkey_off, jkey_len); +void Java_org_rocksdb_RocksDB_singleDelete__J_3BI( + JNIEnv* env, jobject, + jlong jdb_handle, + jbyteArray jkey, + jint jkey_len) { + auto* db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + rocksdb_single_delete_helper(env, db, default_write_options, nullptr, + jkey, jkey_len); } /* * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BIIJ)[B + * Method: singleDelete + * Signature: (J[BIJ)V */ -jbyteArray Java_org_rocksdb_RocksDB_get__J_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle) { - auto db_handle = reinterpret_cast(jdb_handle); - auto cf_handle = reinterpret_cast(jcf_handle); +void Java_org_rocksdb_RocksDB_singleDelete__J_3BIJ( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_len, jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - return rocksdb_get_helper(env, db_handle, rocksdb::ReadOptions(), - cf_handle, jkey, jkey_off, jkey_len); + rocksdb_single_delete_helper(env, db, default_write_options, cf_handle, + jkey, jkey_len); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); - return nullptr; + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); } } /* * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BII)[B + * Method: singleDelete + * Signature: (JJ[BIJ)V */ -jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BII( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len) { - return rocksdb_get_helper(env, - reinterpret_cast(jdb_handle), - *reinterpret_cast(jropt_handle), nullptr, - jkey, jkey_off, jkey_len); +void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BI( + JNIEnv* env, jobject, jlong jdb_handle, + jlong jwrite_options, + jbyteArray jkey, + jint jkey_len) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options); + rocksdb_single_delete_helper(env, db, *write_options, nullptr, jkey, + jkey_len); } /* * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BIIJ)[B + * Method: singleDelete + * Signature: (JJ[BIJ)V */ -jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto& ro_opt = *reinterpret_cast(jropt_handle); +void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BIJ( + JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, + jbyteArray jkey, jint jkey_len, jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options); auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - return rocksdb_get_helper(env, db_handle, ro_opt, cf_handle, - jkey, jkey_off, jkey_len); + rocksdb_single_delete_helper(env, db, *write_options, cf_handle, jkey, + jkey_len); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); - return nullptr; + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); } } -jint rocksdb_get_helper(JNIEnv* env, rocksdb::DB* db, - const rocksdb::ReadOptions& read_options, - rocksdb::ColumnFamilyHandle* column_family_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, - jbyteArray jval, jint jval_off, jint jval_len, - bool* has_exception) { - static const int kNotFound = -1; - static const int kStatusError = -2; - - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if(env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - delete [] key; - *has_exception = true; - return kStatusError; +////////////////////////////////////////////////////////////////////////////// +// rocksdb::DB::DeleteRange() +/** + * @return true if the delete range succeeded, false if a Java Exception + * was thrown + */ +bool rocksdb_delete_range_helper( + JNIEnv* env, rocksdb::DB* db, + const rocksdb::WriteOptions& write_options, + rocksdb::ColumnFamilyHandle* cf_handle, + jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, + jbyteArray jend_key, jint jend_key_off, jint jend_key_len) { + jbyte* begin_key = new jbyte[jbegin_key_len]; + env->GetByteArrayRegion(jbegin_key, jbegin_key_off, jbegin_key_len, + begin_key); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + delete[] begin_key; + return false; } - rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + rocksdb::Slice begin_key_slice(reinterpret_cast(begin_key), + jbegin_key_len); - // TODO(yhchiang): we might save one memory allocation here by adding - // a DB::Get() function which takes preallocated jbyte* as input. - std::string cvalue; - rocksdb::Status s; - if (column_family_handle != nullptr) { - s = db->Get(read_options, column_family_handle, key_slice, &cvalue); - } else { - // backwards compatibility - s = db->Get(read_options, key_slice, &cvalue); + jbyte* end_key = new jbyte[jend_key_len]; + env->GetByteArrayRegion(jend_key, jend_key_off, jend_key_len, end_key); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + delete[] begin_key; + delete[] end_key; + return false; } + rocksdb::Slice end_key_slice(reinterpret_cast(end_key), jend_key_len); - // cleanup - delete [] key; - - if (s.IsNotFound()) { - *has_exception = false; - return kNotFound; - } else if (!s.ok()) { - *has_exception = true; - // Here since we are throwing a Java exception from c++ side. - // As a result, c++ does not know calling this function will in fact - // throwing an exception. As a result, the execution flow will - // not stop here, and codes after this throw will still be - // executed. - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - - // Return a dummy const value to avoid compilation error, although - // java side might not have a chance to get the return value :) - return kStatusError; - } + rocksdb::Status s = + db->DeleteRange(write_options, cf_handle, begin_key_slice, end_key_slice); - const jint cvalue_len = static_cast(cvalue.size()); - const jint length = std::min(jval_len, cvalue_len); + // cleanup + delete[] begin_key; + delete[] end_key; - env->SetByteArrayRegion(jval, jval_off, length, - const_cast(reinterpret_cast(cvalue.c_str()))); - if(env->ExceptionCheck()) { - // exception thrown: OutOfMemoryError - *has_exception = true; - return kStatusError; + if (s.ok()) { + return true; } - *has_exception = false; - return cvalue_len; + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return false; } -inline void multi_get_helper_release_keys(JNIEnv* env, - std::vector> &keys_to_free) { - auto end = keys_to_free.end(); - for (auto it = keys_to_free.begin(); it != end; ++it) { - delete [] it->first; - env->DeleteLocalRef(it->second); - } - keys_to_free.clear(); +/* + * Class: org_rocksdb_RocksDB + * Method: deleteRange + * Signature: (J[BII[BII)V + */ +void Java_org_rocksdb_RocksDB_deleteRange__J_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, + jbyteArray jend_key, jint jend_key_off, jint jend_key_len) { + auto* db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + rocksdb_delete_range_helper(env, db, default_write_options, nullptr, + jbegin_key, jbegin_key_off, jbegin_key_len, + jend_key, jend_key_off, jend_key_len); } -/** - * cf multi get - * - * @return byte[][] of values or nullptr if an exception occurs +/* + * Class: org_rocksdb_RocksDB + * Method: deleteRange + * Signature: (J[BII[BIIJ)V */ -jobjectArray multi_get_helper(JNIEnv* env, jobject jdb, rocksdb::DB* db, - const rocksdb::ReadOptions& rOpt, jobjectArray jkeys, - jintArray jkey_offs, jintArray jkey_lens, - jlongArray jcolumn_family_handles) { - std::vector cf_handles; +void Java_org_rocksdb_RocksDB_deleteRange__J_3BII_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, + jbyteArray jend_key, jint jend_key_off, jint jend_key_len, + jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + auto* cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + rocksdb_delete_range_helper(env, db, default_write_options, cf_handle, + jbegin_key, jbegin_key_off, jbegin_key_len, + jend_key, jend_key_off, jend_key_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: deleteRange + * Signature: (JJ[BII[BII)V + */ +void Java_org_rocksdb_RocksDB_deleteRange__JJ_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, + jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, + jbyteArray jend_key, jint jend_key_off, jint jend_key_len) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options); + rocksdb_delete_range_helper(env, db, *write_options, nullptr, jbegin_key, + jbegin_key_off, jbegin_key_len, jend_key, + jend_key_off, jend_key_len); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: deleteRange + * Signature: (JJ[BII[BIIJ)V + */ +void Java_org_rocksdb_RocksDB_deleteRange__JJ_3BII_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options, + jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, + jbyteArray jend_key, jint jend_key_off, jint jend_key_len, + jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options); + auto* cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + rocksdb_delete_range_helper(env, db, *write_options, cf_handle, + jbegin_key, jbegin_key_off, jbegin_key_len, + jend_key, jend_key_off, jend_key_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + } +} + +////////////////////////////////////////////////////////////////////////////// +// rocksdb::DB::Merge + +/** + * @return true if the merge succeeded, false if a Java Exception was thrown + */ +bool rocksdb_merge_helper( + JNIEnv* env, rocksdb::DB* db, const rocksdb::WriteOptions& write_options, + rocksdb::ColumnFamilyHandle* cf_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len) { + jbyte* key = new jbyte[jkey_len]; + env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + delete[] key; + return false; + } + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + + jbyte* value = new jbyte[jval_len]; + env->GetByteArrayRegion(jval, jval_off, jval_len, value); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + delete[] value; + delete[] key; + return false; + } + rocksdb::Slice value_slice(reinterpret_cast(value), jval_len); + + rocksdb::Status s; + if (cf_handle != nullptr) { + s = db->Merge(write_options, cf_handle, key_slice, value_slice); + } else { + s = db->Merge(write_options, key_slice, value_slice); + } + + // cleanup + delete[] value; + delete[] key; + + if (s.ok()) { + return true; + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return false; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: merge + * Signature: (J[BII[BII)V + */ +void Java_org_rocksdb_RocksDB_merge__J_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len) { + auto* db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + rocksdb_merge_helper(env, db, default_write_options, nullptr, jkey, jkey_off, + jkey_len, jval, jval_off, jval_len); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: merge + * Signature: (J[BII[BIIJ)V + */ +void Java_org_rocksdb_RocksDB_merge__J_3BII_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len, + jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + auto* cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + rocksdb_merge_helper(env, db, default_write_options, cf_handle, jkey, + jkey_off, jkey_len, jval, jval_off, jval_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: merge + * Signature: (JJ[BII[BII)V + */ +void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + rocksdb_merge_helper(env, db, *write_options, nullptr, jkey, jkey_off, + jkey_len, jval, jval_off, jval_len); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: merge + * Signature: (JJ[BII[BIIJ)V + */ +void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, jlong jwrite_options_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len, jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + rocksdb_merge_helper(env, db, *write_options, cf_handle, jkey, jkey_off, + jkey_len, jval, jval_off, jval_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + } +} + +jlong rocksdb_iterator_helper(rocksdb::DB* db, + rocksdb::ReadOptions read_options, + rocksdb::ColumnFamilyHandle* cf_handle) { + rocksdb::Iterator* iterator = nullptr; + if (cf_handle != nullptr) { + iterator = db->NewIterator(read_options, cf_handle); + } else { + iterator = db->NewIterator(read_options); + } + return reinterpret_cast(iterator); +} + +////////////////////////////////////////////////////////////////////////////// +// rocksdb::DB::Write +/* + * Class: org_rocksdb_RocksDB + * Method: write0 + * Signature: (JJJ)V + */ +void Java_org_rocksdb_RocksDB_write0( + JNIEnv* env, jobject, jlong jdb_handle, + jlong jwrite_options_handle, jlong jwb_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* wb = reinterpret_cast(jwb_handle); + + rocksdb::Status s = db->Write(*write_options, wb); + + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: write1 + * Signature: (JJJ)V + */ +void Java_org_rocksdb_RocksDB_write1( + JNIEnv* env, jobject, jlong jdb_handle, + jlong jwrite_options_handle, jlong jwbwi_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* wbwi = reinterpret_cast(jwbwi_handle); + auto* wb = wbwi->GetWriteBatch(); + + rocksdb::Status s = db->Write(*write_options, wb); + + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +////////////////////////////////////////////////////////////////////////////// +// rocksdb::DB::Get + +jbyteArray rocksdb_get_helper( + JNIEnv* env, rocksdb::DB* db, + const rocksdb::ReadOptions& read_opt, + rocksdb::ColumnFamilyHandle* column_family_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len) { + jbyte* key = new jbyte[jkey_len]; + env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + delete[] key; + return nullptr; + } + + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + + std::string value; + rocksdb::Status s; + if (column_family_handle != nullptr) { + s = db->Get(read_opt, column_family_handle, key_slice, &value); + } else { + // backwards compatibility + s = db->Get(read_opt, key_slice, &value); + } + + // cleanup + delete[] key; + + if (s.IsNotFound()) { + return nullptr; + } + + if (s.ok()) { + jbyteArray jret_value = rocksdb::JniUtil::copyBytes(env, value); + if (jret_value == nullptr) { + // exception occurred + return nullptr; + } + return jret_value; + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (J[BII)[B + */ +jbyteArray Java_org_rocksdb_RocksDB_get__J_3BII( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len) { + return rocksdb_get_helper(env, reinterpret_cast(jdb_handle), + rocksdb::ReadOptions(), nullptr, jkey, jkey_off, jkey_len); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (J[BIIJ)[B + */ +jbyteArray Java_org_rocksdb_RocksDB_get__J_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle) { + auto db_handle = reinterpret_cast(jdb_handle); + auto cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + return rocksdb_get_helper(env, db_handle, rocksdb::ReadOptions(), cf_handle, + jkey, jkey_off, jkey_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + return nullptr; + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (JJ[BII)[B + */ +jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BII( + JNIEnv* env, jobject, + jlong jdb_handle, jlong jropt_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len) { + return rocksdb_get_helper( + env, reinterpret_cast(jdb_handle), + *reinterpret_cast(jropt_handle), nullptr, jkey, + jkey_off, jkey_len); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (JJ[BIIJ)[B + */ +jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, jlong jropt_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle) { + auto* db_handle = reinterpret_cast(jdb_handle); + auto& ro_opt = *reinterpret_cast(jropt_handle); + auto* cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + return rocksdb_get_helper( + env, db_handle, ro_opt, cf_handle, jkey, jkey_off, jkey_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + return nullptr; + } +} + +jint rocksdb_get_helper( + JNIEnv* env, rocksdb::DB* db, const rocksdb::ReadOptions& read_options, + rocksdb::ColumnFamilyHandle* column_family_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len, + bool* has_exception) { + static const int kNotFound = -1; + static const int kStatusError = -2; + + jbyte* key = new jbyte[jkey_len]; + env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); + if (env->ExceptionCheck()) { + // exception thrown: OutOfMemoryError + delete[] key; + *has_exception = true; + return kStatusError; + } + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + + // TODO(yhchiang): we might save one memory allocation here by adding + // a DB::Get() function which takes preallocated jbyte* as input. + std::string cvalue; + rocksdb::Status s; + if (column_family_handle != nullptr) { + s = db->Get(read_options, column_family_handle, key_slice, &cvalue); + } else { + // backwards compatibility + s = db->Get(read_options, key_slice, &cvalue); + } + + // cleanup + delete[] key; + + if (s.IsNotFound()) { + *has_exception = false; + return kNotFound; + } else if (!s.ok()) { + *has_exception = true; + // Here since we are throwing a Java exception from c++ side. + // As a result, c++ does not know calling this function will in fact + // throwing an exception. As a result, the execution flow will + // not stop here, and codes after this throw will still be + // executed. + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + + // Return a dummy const value to avoid compilation error, although + // java side might not have a chance to get the return value :) + return kStatusError; + } + + const jint cvalue_len = static_cast(cvalue.size()); + const jint length = std::min(jval_len, cvalue_len); + + env->SetByteArrayRegion( + jval, jval_off, length, + const_cast(reinterpret_cast(cvalue.c_str()))); + if (env->ExceptionCheck()) { + // exception thrown: OutOfMemoryError + *has_exception = true; + return kStatusError; + } + + *has_exception = false; + return cvalue_len; +} + + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (J[BII[BII)I + */ +jint Java_org_rocksdb_RocksDB_get__J_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len) { + bool has_exception = false; + return rocksdb_get_helper(env, reinterpret_cast(jdb_handle), + rocksdb::ReadOptions(), nullptr, jkey, jkey_off, + jkey_len, jval, jval_off, jval_len, &has_exception); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (J[BII[BIIJ)I + */ +jint Java_org_rocksdb_RocksDB_get__J_3BII_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len, + jlong jcf_handle) { + auto* db_handle = reinterpret_cast(jdb_handle); + auto* cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + bool has_exception = false; + return rocksdb_get_helper(env, db_handle, rocksdb::ReadOptions(), cf_handle, + jkey, jkey_off, jkey_len, jval, jval_off, + jval_len, &has_exception); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + // will never be evaluated + return 0; + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (JJ[BII[BII)I + */ +jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BII( + JNIEnv* env, jobject, jlong jdb_handle, jlong jropt_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len) { + bool has_exception = false; + return rocksdb_get_helper( + env, reinterpret_cast(jdb_handle), + *reinterpret_cast(jropt_handle), nullptr, jkey, + jkey_off, jkey_len, jval, jval_off, jval_len, &has_exception); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: (JJ[BII[BIIJ)I + */ +jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ( + JNIEnv* env, jobject, jlong jdb_handle, jlong jropt_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jbyteArray jval, jint jval_off, jint jval_len, + jlong jcf_handle) { + auto* db_handle = reinterpret_cast(jdb_handle); + auto& ro_opt = *reinterpret_cast(jropt_handle); + auto* cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + bool has_exception = false; + return rocksdb_get_helper(env, db_handle, ro_opt, cf_handle, + jkey, jkey_off, jkey_len, + jval, jval_off, jval_len, + &has_exception); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + // will never be evaluated + return 0; + } +} + +inline void multi_get_helper_release_keys( + JNIEnv* env, std::vector>& keys_to_free) { + auto end = keys_to_free.end(); + for (auto it = keys_to_free.begin(); it != end; ++it) { + delete[] it->first; + env->DeleteLocalRef(it->second); + } + keys_to_free.clear(); +} + +/** + * cf multi get + * + * @return byte[][] of values or nullptr if an exception occurs + */ +jobjectArray multi_get_helper( + JNIEnv* env, jobject, rocksdb::DB* db, const rocksdb::ReadOptions& rOpt, + jobjectArray jkeys, jintArray jkey_offs, jintArray jkey_lens, + jlongArray jcolumn_family_handles) { + std::vector cf_handles; if (jcolumn_family_handles != nullptr) { const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); - if(jcfh == nullptr) { + if (jcfh == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } for (jsize i = 0; i < len_cols; i++) { - auto* cf_handle = - reinterpret_cast(jcfh[i]); + auto* cf_handle = reinterpret_cast(jcfh[i]); cf_handles.push_back(cf_handle); } env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); @@ -757,13 +1421,13 @@ jobjectArray multi_get_helper(JNIEnv* env, jobject jdb, rocksdb::DB* db, } jint* jkey_off = env->GetIntArrayElements(jkey_offs, nullptr); - if(jkey_off == nullptr) { + if (jkey_off == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } jint* jkey_len = env->GetIntArrayElements(jkey_lens, nullptr); - if(jkey_len == nullptr) { + if (jkey_len == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); return nullptr; @@ -773,7 +1437,7 @@ jobjectArray multi_get_helper(JNIEnv* env, jobject jdb, rocksdb::DB* db, std::vector> keys_to_free; for (jsize i = 0; i < len_keys; i++) { jobject jkey = env->GetObjectArrayElement(jkeys, i); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT); env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); @@ -786,9 +1450,9 @@ jobjectArray multi_get_helper(JNIEnv* env, jobject jdb, rocksdb::DB* db, const jint len_key = jkey_len[i]; jbyte* key = new jbyte[len_key]; env->GetByteArrayRegion(jkey_ba, jkey_off[i], len_key, key); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException - delete [] key; + delete[] key; env->DeleteLocalRef(jkey); env->ReleaseIntArrayElements(jkey_lens, jkey_len, JNI_ABORT); env->ReleaseIntArrayElements(jkey_offs, jkey_off, JNI_ABORT); @@ -820,7 +1484,7 @@ jobjectArray multi_get_helper(JNIEnv* env, jobject jdb, rocksdb::DB* db, // prepare the results jobjectArray jresults = rocksdb::ByteJni::new2dByteArray(env, static_cast(s.size())); - if(jresults == nullptr) { + if (jresults == nullptr) { // exception occurred return nullptr; } @@ -837,21 +1501,22 @@ jobjectArray multi_get_helper(JNIEnv* env, jobject jdb, rocksdb::DB* db, std::string* value = &values[i]; const jsize jvalue_len = static_cast(value->size()); jbyteArray jentry_value = env->NewByteArray(jvalue_len); - if(jentry_value == nullptr) { + if (jentry_value == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } - env->SetByteArrayRegion(jentry_value, 0, static_cast(jvalue_len), + env->SetByteArrayRegion( + jentry_value, 0, static_cast(jvalue_len), const_cast(reinterpret_cast(value->c_str()))); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(jentry_value); return nullptr; } env->SetObjectArrayElement(jresults, static_cast(i), jentry_value); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(jentry_value); return nullptr; @@ -870,10 +1535,11 @@ jobjectArray multi_get_helper(JNIEnv* env, jobject jdb, rocksdb::DB* db, * Signature: (J[[B[I[I)[[B */ jobjectArray Java_org_rocksdb_RocksDB_multiGet__J_3_3B_3I_3I( - JNIEnv* env, jobject jdb, jlong jdb_handle, jobjectArray jkeys, - jintArray jkey_offs, jintArray jkey_lens) { + JNIEnv* env, jobject jdb, jlong jdb_handle, + jobjectArray jkeys, jintArray jkey_offs, jintArray jkey_lens) { return multi_get_helper(env, jdb, reinterpret_cast(jdb_handle), - rocksdb::ReadOptions(), jkeys, jkey_offs, jkey_lens, nullptr); + rocksdb::ReadOptions(), jkeys, jkey_offs, jkey_lens, + nullptr); } /* @@ -882,8 +1548,8 @@ jobjectArray Java_org_rocksdb_RocksDB_multiGet__J_3_3B_3I_3I( * Signature: (J[[B[I[I[J)[[B */ jobjectArray Java_org_rocksdb_RocksDB_multiGet__J_3_3B_3I_3I_3J( - JNIEnv* env, jobject jdb, jlong jdb_handle, jobjectArray jkeys, - jintArray jkey_offs, jintArray jkey_lens, + JNIEnv* env, jobject jdb, jlong jdb_handle, + jobjectArray jkeys, jintArray jkey_offs, jintArray jkey_lens, jlongArray jcolumn_family_handles) { return multi_get_helper(env, jdb, reinterpret_cast(jdb_handle), rocksdb::ReadOptions(), jkeys, jkey_offs, jkey_lens, @@ -898,7 +1564,8 @@ jobjectArray Java_org_rocksdb_RocksDB_multiGet__J_3_3B_3I_3I_3J( jobjectArray Java_org_rocksdb_RocksDB_multiGet__JJ_3_3B_3I_3I( JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, jobjectArray jkeys, jintArray jkey_offs, jintArray jkey_lens) { - return multi_get_helper(env, jdb, reinterpret_cast(jdb_handle), + return multi_get_helper( + env, jdb, reinterpret_cast(jdb_handle), *reinterpret_cast(jropt_handle), jkeys, jkey_offs, jkey_lens, nullptr); } @@ -912,1286 +1579,1467 @@ jobjectArray Java_org_rocksdb_RocksDB_multiGet__JJ_3_3B_3I_3I_3J( JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, jobjectArray jkeys, jintArray jkey_offs, jintArray jkey_lens, jlongArray jcolumn_family_handles) { - return multi_get_helper(env, jdb, reinterpret_cast(jdb_handle), + return multi_get_helper( + env, jdb, reinterpret_cast(jdb_handle), *reinterpret_cast(jropt_handle), jkeys, jkey_offs, jkey_lens, jcolumn_family_handles); } -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BII[BII)I - */ -jint Java_org_rocksdb_RocksDB_get__J_3BII_3BII(JNIEnv* env, jobject jdb, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - bool has_exception = false; - return rocksdb_get_helper(env, reinterpret_cast(jdb_handle), - rocksdb::ReadOptions(), nullptr, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len, - &has_exception); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (J[BII[BIIJ)I - */ -jint Java_org_rocksdb_RocksDB_get__J_3BII_3BIIJ(JNIEnv* env, jobject jdb, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, - jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - bool has_exception = false; - return rocksdb_get_helper(env, db_handle, rocksdb::ReadOptions(), cf_handle, - jkey, jkey_off, jkey_len, jval, jval_off, - jval_len, &has_exception); - } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); - // will never be evaluated - return 0; - } -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BII[BII)I - */ -jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BII(JNIEnv* env, jobject jdb, - jlong jdb_handle, - jlong jropt_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - bool has_exception = false; - return rocksdb_get_helper( - env, reinterpret_cast(jdb_handle), - *reinterpret_cast(jropt_handle), nullptr, jkey, - jkey_off, jkey_len, jval, jval_off, jval_len, &has_exception); -} - -/* - * Class: org_rocksdb_RocksDB - * Method: get - * Signature: (JJ[BII[BIIJ)I - */ -jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jropt_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, jlong jcf_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto& ro_opt = *reinterpret_cast(jropt_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - bool has_exception = false; - return rocksdb_get_helper(env, db_handle, ro_opt, cf_handle, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len, - &has_exception); - } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); - // will never be evaluated - return 0; - } -} - ////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::Delete() - -/** - * @return true if the delete succeeded, false if a Java Exception was thrown - */ -bool rocksdb_delete_helper( - JNIEnv* env, rocksdb::DB* db, const rocksdb::WriteOptions& write_options, - rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, jint jkey_off, - jint jkey_len) { +// rocksdb::DB::KeyMayExist +jboolean key_may_exist_helper(JNIEnv* env, rocksdb::DB* db, + const rocksdb::ReadOptions& read_opt, + rocksdb::ColumnFamilyHandle* cf_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jobject jstring_builder, bool* has_exception) { jbyte* key = new jbyte[jkey_len]; env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException - delete [] key; + delete[] key; + *has_exception = true; return false; } + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); - rocksdb::Status s; + std::string value; + bool value_found = false; + bool keyMayExist; if (cf_handle != nullptr) { - s = db->Delete(write_options, cf_handle, key_slice); + keyMayExist = + db->KeyMayExist(read_opt, cf_handle, key_slice, &value, &value_found); } else { - // backwards compatibility - s = db->Delete(write_options, key_slice); + keyMayExist = db->KeyMayExist(read_opt, key_slice, &value, &value_found); } // cleanup - delete [] key; + delete[] key; - if (s.ok()) { - return true; + // extract the value + if (value_found && !value.empty()) { + jobject jresult_string_builder = + rocksdb::StringBuilderJni::append(env, jstring_builder, value.c_str()); + if (jresult_string_builder == nullptr) { + *has_exception = true; + return false; + } } - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return false; + *has_exception = false; + return static_cast(keyMayExist); } /* * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (J[BII)V + * Method: keyMayExist + * Signature: (J[BIILjava/lang/StringBuilder;)Z */ -void Java_org_rocksdb_RocksDB_delete__J_3BII( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len) { +jboolean Java_org_rocksdb_RocksDB_keyMayExist__J_3BIILjava_lang_StringBuilder_2( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, jobject jstring_builder) { auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); - rocksdb_delete_helper(env, db, default_write_options, nullptr, - jkey, jkey_off, jkey_len); + bool has_exception = false; + return key_may_exist_helper(env, db, rocksdb::ReadOptions(), nullptr, jkey, + jkey_off, jkey_len, jstring_builder, &has_exception); } /* * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (J[BIIJ)V + * Method: keyMayExist + * Signature: (J[BIIJLjava/lang/StringBuilder;)Z */ -void Java_org_rocksdb_RocksDB_delete__J_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle) { +jboolean +Java_org_rocksdb_RocksDB_keyMayExist__J_3BIIJLjava_lang_StringBuilder_2( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, + jlong jcf_handle, jobject jstring_builder) { auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - rocksdb_delete_helper(env, db, default_write_options, cf_handle, - jkey, jkey_off, jkey_len); + bool has_exception = false; + return key_may_exist_helper(env, db, rocksdb::ReadOptions(), cf_handle, + jkey, jkey_off, jkey_len, jstring_builder, + &has_exception); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + return true; } } /* * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (JJ[BII)V + * Method: keyMayExist + * Signature: (JJ[BIILjava/lang/StringBuilder;)Z */ -void Java_org_rocksdb_RocksDB_delete__JJ_3BII( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jwrite_options, jbyteArray jkey, jint jkey_off, jint jkey_len) { +jboolean +Java_org_rocksdb_RocksDB_keyMayExist__JJ_3BIILjava_lang_StringBuilder_2( + JNIEnv* env, jobject, jlong jdb_handle, jlong jread_options_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, jobject jstring_builder) { auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - rocksdb_delete_helper(env, db, *write_options, nullptr, jkey, jkey_off, - jkey_len); + auto& read_options = + *reinterpret_cast(jread_options_handle); + bool has_exception = false; + return key_may_exist_helper(env, db, read_options, nullptr, jkey, jkey_off, + jkey_len, jstring_builder, &has_exception); } /* * Class: org_rocksdb_RocksDB - * Method: delete - * Signature: (JJ[BIIJ)V + * Method: keyMayExist + * Signature: (JJ[BIIJLjava/lang/StringBuilder;)Z */ -void Java_org_rocksdb_RocksDB_delete__JJ_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jwrite_options, jbyteArray jkey, jint jkey_off, jint jkey_len, - jlong jcf_handle) { +jboolean +Java_org_rocksdb_RocksDB_keyMayExist__JJ_3BIIJLjava_lang_StringBuilder_2( + JNIEnv* env, jobject, jlong jdb_handle, jlong jread_options_handle, + jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle, + jobject jstring_builder) { auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); + auto& read_options = + *reinterpret_cast(jread_options_handle); auto* cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - rocksdb_delete_helper(env, db, *write_options, cf_handle, jkey, jkey_off, - jkey_len); + bool has_exception = false; + return key_may_exist_helper(env, db, read_options, cf_handle, jkey, + jkey_off, jkey_len, jstring_builder, &has_exception); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + return true; } } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::SingleDelete() -/** - * @return true if the single delete succeeded, false if a Java Exception - * was thrown +/* + * Class: org_rocksdb_RocksDB + * Method: iterator + * Signature: (J)J */ -bool rocksdb_single_delete_helper( - JNIEnv* env, rocksdb::DB* db, const rocksdb::WriteOptions& write_options, - rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, jint jkey_len) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if(key == nullptr) { - // exception thrown: OutOfMemoryError - return false; - } - rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); - - rocksdb::Status s; - if (cf_handle != nullptr) { - s = db->SingleDelete(write_options, cf_handle, key_slice); - } else { - // backwards compatibility - s = db->SingleDelete(write_options, key_slice); - } - - // trigger java unref on key and value. - // by passing JNI_ABORT, it will simply release the reference without - // copying the result back to the java byte array. - env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); - - if (s.ok()) { - return true; - } - - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return false; +jlong Java_org_rocksdb_RocksDB_iterator__J( + JNIEnv*, jobject, jlong db_handle) { + auto* db = reinterpret_cast(db_handle); + return rocksdb_iterator_helper(db, rocksdb::ReadOptions(), nullptr); } /* * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (J[BI)V + * Method: iterator + * Signature: (JJ)J */ -void Java_org_rocksdb_RocksDB_singleDelete__J_3BI( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jbyteArray jkey, jint jkey_len) { - auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); - rocksdb_single_delete_helper(env, db, default_write_options, nullptr, - jkey, jkey_len); +jlong Java_org_rocksdb_RocksDB_iterator__JJ( + JNIEnv*, jobject, jlong db_handle, jlong jread_options_handle) { + auto* db = reinterpret_cast(db_handle); + auto& read_options = + *reinterpret_cast(jread_options_handle); + return rocksdb_iterator_helper(db, read_options, nullptr); } /* * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (J[BIJ)V + * Method: iteratorCF + * Signature: (JJ)J */ -void Java_org_rocksdb_RocksDB_singleDelete__J_3BIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jbyteArray jkey, jint jkey_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); +jlong Java_org_rocksdb_RocksDB_iteratorCF__JJ( + JNIEnv*, jobject, jlong db_handle, jlong jcf_handle) { + auto* db = reinterpret_cast(db_handle); auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_single_delete_helper(env, db, default_write_options, cf_handle, - jkey, jkey_len); - } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); - } + return rocksdb_iterator_helper(db, rocksdb::ReadOptions(), cf_handle); } /* * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (JJ[BIJ)V + * Method: iteratorCF + * Signature: (JJJ)J */ -void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BI( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jwrite_options, jbyteArray jkey, jint jkey_len) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - rocksdb_single_delete_helper(env, db, *write_options, nullptr, jkey, - jkey_len); +jlong Java_org_rocksdb_RocksDB_iteratorCF__JJJ( + JNIEnv*, jobject, + jlong db_handle, jlong jcf_handle, jlong jread_options_handle) { + auto* db = reinterpret_cast(db_handle); + auto* cf_handle = reinterpret_cast(jcf_handle); + auto& read_options = + *reinterpret_cast(jread_options_handle); + return rocksdb_iterator_helper(db, read_options, cf_handle); } /* * Class: org_rocksdb_RocksDB - * Method: singleDelete - * Signature: (JJ[BIJ)V + * Method: iterators + * Signature: (J[JJ)[J */ -void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jwrite_options, jbyteArray jkey, jint jkey_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_single_delete_helper(env, db, *write_options, cf_handle, jkey, - jkey_len); +jlongArray Java_org_rocksdb_RocksDB_iterators( + JNIEnv* env, jobject, jlong db_handle, + jlongArray jcolumn_family_handles, + jlong jread_options_handle) { + auto* db = reinterpret_cast(db_handle); + auto& read_options = + *reinterpret_cast(jread_options_handle); + + std::vector cf_handles; + if (jcolumn_family_handles != nullptr) { + const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); + jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); + if (jcfh == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + for (jsize i = 0; i < len_cols; i++) { + auto* cf_handle = reinterpret_cast(jcfh[i]); + cf_handles.push_back(cf_handle); + } + + env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); + } + + std::vector iterators; + rocksdb::Status s = db->NewIterators(read_options, cf_handles, &iterators); + if (s.ok()) { + jlongArray jLongArray = + env->NewLongArray(static_cast(iterators.size())); + if (jLongArray == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + for (std::vector::size_type i = 0; i < iterators.size(); + i++) { + env->SetLongArrayRegion( + jLongArray, static_cast(i), 1, + const_cast(reinterpret_cast(&iterators[i]))); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jLongArray); + return nullptr; + } + } + + return jLongArray; } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; } } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::DeleteRange() -/** - * @return true if the delete range succeeded, false if a Java Exception - * was thrown +/* + * Method: getSnapshot + * Signature: (J)J */ -bool rocksdb_delete_range_helper(JNIEnv* env, rocksdb::DB* db, - const rocksdb::WriteOptions& write_options, - rocksdb::ColumnFamilyHandle* cf_handle, - jbyteArray jbegin_key, jint jbegin_key_off, - jint jbegin_key_len, jbyteArray jend_key, - jint jend_key_off, jint jend_key_len) { - jbyte* begin_key = new jbyte[jbegin_key_len]; - env->GetByteArrayRegion(jbegin_key, jbegin_key_off, jbegin_key_len, - begin_key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] begin_key; - return false; - } - rocksdb::Slice begin_key_slice(reinterpret_cast(begin_key), - jbegin_key_len); +jlong Java_org_rocksdb_RocksDB_getSnapshot( + JNIEnv*, jobject, jlong db_handle) { + auto* db = reinterpret_cast(db_handle); + const rocksdb::Snapshot* snapshot = db->GetSnapshot(); + return reinterpret_cast(snapshot); +} - jbyte* end_key = new jbyte[jend_key_len]; - env->GetByteArrayRegion(jend_key, jend_key_off, jend_key_len, end_key); - if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete[] begin_key; - delete[] end_key; - return false; +/* + * Method: releaseSnapshot + * Signature: (JJ)V + */ +void Java_org_rocksdb_RocksDB_releaseSnapshot( + JNIEnv*, jobject, jlong db_handle, + jlong snapshot_handle) { + auto* db = reinterpret_cast(db_handle); + auto* snapshot = reinterpret_cast(snapshot_handle); + db->ReleaseSnapshot(snapshot); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: getProperty + * Signature: (JJLjava/lang/String;I)Ljava/lang/String; + */ +jstring Java_org_rocksdb_RocksDB_getProperty( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jstring jproperty, jint jproperty_len) { + const char* property = env->GetStringUTFChars(jproperty, nullptr); + if (property == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; } - rocksdb::Slice end_key_slice(reinterpret_cast(end_key), jend_key_len); + rocksdb::Slice property_name(property, jproperty_len); - rocksdb::Status s = - db->DeleteRange(write_options, cf_handle, begin_key_slice, end_key_slice); + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } - // cleanup - delete[] begin_key; - delete[] end_key; + std::string property_value; + bool retCode = db->GetProperty(cf_handle, property_name, &property_value); + env->ReleaseStringUTFChars(jproperty, property); - if (s.ok()) { - return true; + if (retCode) { + return env->NewStringUTF(property_value.c_str()); } - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return false; + rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); + return nullptr; } /* * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (J[BII[BII)V + * Method: getMapProperty + * Signature: (JJLjava/lang/String;I)Ljava/util/Map; */ -void Java_org_rocksdb_RocksDB_deleteRange__J_3BII_3BII( - JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jbegin_key, - jint jbegin_key_off, jint jbegin_key_len, jbyteArray jend_key, - jint jend_key_off, jint jend_key_len) { +jobject Java_org_rocksdb_RocksDB_getMapProperty( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jstring jproperty, jint jproperty_len) { + const char* property = env->GetStringUTFChars(jproperty, nullptr); + if (property == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + rocksdb::Slice property_name(property, jproperty_len); + auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); - rocksdb_delete_range_helper(env, db, default_write_options, nullptr, - jbegin_key, jbegin_key_off, jbegin_key_len, - jend_key, jend_key_off, jend_key_len); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } + + std::map property_value; + bool retCode = db->GetMapProperty(cf_handle, property_name, &property_value); + env->ReleaseStringUTFChars(jproperty, property); + + if (retCode) { + return rocksdb::HashMapJni::fromCppMap(env, &property_value); + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); + return nullptr; } /* * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (J[BII[BIIJ)V + * Method: getLongProperty + * Signature: (JJLjava/lang/String;I)J */ -void Java_org_rocksdb_RocksDB_deleteRange__J_3BII_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jbegin_key, - jint jbegin_key_off, jint jbegin_key_len, jbyteArray jend_key, - jint jend_key_off, jint jend_key_len, jlong jcf_handle) { +jlong Java_org_rocksdb_RocksDB_getLongProperty( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jstring jproperty, jint jproperty_len) { + const char* property = env->GetStringUTFChars(jproperty, nullptr); + if (property == nullptr) { + // exception thrown: OutOfMemoryError + return 0; + } + rocksdb::Slice property_name(property, jproperty_len); + auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); - auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_delete_range_helper(env, db, default_write_options, cf_handle, - jbegin_key, jbegin_key_off, jbegin_key_len, - jend_key, jend_key_off, jend_key_len); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); } else { - rocksdb::RocksDBExceptionJni::ThrowNew( - env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + cf_handle = + reinterpret_cast(jcf_handle); + } + + uint64_t property_value; + bool retCode = db->GetIntProperty(cf_handle, property_name, &property_value); + env->ReleaseStringUTFChars(jproperty, property); + + if (retCode) { + return property_value; } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); + return 0; } /* * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (JJ[BII[BII)V + * Method: resetStats + * Signature: (J)V */ -void Java_org_rocksdb_RocksDB_deleteRange__JJ_3BII_3BII( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jwrite_options, - jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, - jbyteArray jend_key, jint jend_key_off, jint jend_key_len) { +void Java_org_rocksdb_RocksDB_resetStats( + JNIEnv *, jobject, jlong jdb_handle) { auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - rocksdb_delete_range_helper(env, db, *write_options, nullptr, jbegin_key, - jbegin_key_off, jbegin_key_len, jend_key, - jend_key_off, jend_key_len); + db->ResetStats(); } /* * Class: org_rocksdb_RocksDB - * Method: deleteRange - * Signature: (JJ[BII[BIIJ)V + * Method: getAggregatedLongProperty + * Signature: (JLjava/lang/String;I)J */ -void Java_org_rocksdb_RocksDB_deleteRange__JJ_3BII_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jwrite_options, - jbyteArray jbegin_key, jint jbegin_key_off, jint jbegin_key_len, - jbyteArray jend_key, jint jend_key_off, jint jend_key_len, - jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options); - auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_delete_range_helper(env, db, *write_options, cf_handle, jbegin_key, - jbegin_key_off, jbegin_key_len, jend_key, - jend_key_off, jend_key_len); - } else { - rocksdb::RocksDBExceptionJni::ThrowNew( - env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); +jlong Java_org_rocksdb_RocksDB_getAggregatedLongProperty( + JNIEnv* env, jobject, jlong db_handle, + jstring jproperty, jint jproperty_len) { + const char* property = env->GetStringUTFChars(jproperty, nullptr); + if (property == nullptr) { + return 0; } -} + rocksdb::Slice property_name(property, jproperty_len); + auto* db = reinterpret_cast(db_handle); + uint64_t property_value = 0; + bool retCode = db->GetAggregatedIntProperty(property_name, &property_value); + env->ReleaseStringUTFChars(jproperty, property); -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::Merge + if (retCode) { + return property_value; + } -/** - * @return true if the merge succeeded, false if a Java Exception was thrown + rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); + return 0; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: getApproximateSizes + * Signature: (JJ[JB)[J */ -bool rocksdb_merge_helper(JNIEnv* env, rocksdb::DB* db, - const rocksdb::WriteOptions& write_options, - rocksdb::ColumnFamilyHandle* cf_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, - jbyteArray jval, jint jval_off, jint jval_len) { - jbyte* key = new jbyte[jkey_len]; - env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete [] key; - return false; - } - rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); +jlongArray Java_org_rocksdb_RocksDB_getApproximateSizes( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jlongArray jrange_slice_handles, jbyte jinclude_flags) { + const jsize jlen = env->GetArrayLength(jrange_slice_handles); + const size_t range_count = jlen / 2; - jbyte* value = new jbyte[jval_len]; - env->GetByteArrayRegion(jval, jval_off, jval_len, value); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - delete [] value; - delete [] key; - return false; + jboolean jranges_is_copy = JNI_FALSE; + jlong* jranges = env->GetLongArrayElements(jrange_slice_handles, + &jranges_is_copy); + if (jranges == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; } - rocksdb::Slice value_slice(reinterpret_cast(value), jval_len); - rocksdb::Status s; - if (cf_handle != nullptr) { - s = db->Merge(write_options, cf_handle, key_slice, value_slice); + auto ranges = std::unique_ptr( + new rocksdb::Range[range_count]); + for (jsize i = 0; i < jlen; ++i) { + auto* start = reinterpret_cast(jranges[i]); + auto* limit = reinterpret_cast(jranges[++i]); + ranges.get()[i] = rocksdb::Range(*start, *limit); + } + + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); } else { - s = db->Merge(write_options, key_slice, value_slice); + cf_handle = + reinterpret_cast(jcf_handle); } - // cleanup - delete [] value; - delete [] key; + auto sizes = std::unique_ptr(new uint64_t[range_count]); + db->GetApproximateSizes(cf_handle, ranges.get(), + static_cast(range_count), sizes.get(), + static_cast(jinclude_flags)); - if (s.ok()) { - return true; + // release LongArrayElements + env->ReleaseLongArrayElements(jrange_slice_handles, jranges, JNI_ABORT); + + // prepare results + auto results = std::unique_ptr(new jlong[range_count]); + for (size_t i = 0; i < range_count; ++i) { + results.get()[i] = static_cast(sizes.get()[i]); } - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return false; -} + const jsize jrange_count = jlen / 2; + jlongArray jresults = env->NewLongArray(jrange_count); + if (jresults == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } -/* - * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (J[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_merge__J_3BII_3BII(JNIEnv* env, jobject jdb, - jlong jdb_handle, - jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); + env->SetLongArrayRegion(jresults, 0, jrange_count, results.get()); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jresults); + return nullptr; + } - rocksdb_merge_helper(env, db, default_write_options, nullptr, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); + return jresults; } /* * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (J[BII[BIIJ)V + * Method: getApproximateMemTableStats + * Signature: (JJJJ)[J */ -void Java_org_rocksdb_RocksDB_merge__J_3BII_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jkey, jint jkey_off, - jint jkey_len, jbyteArray jval, jint jval_off, jint jval_len, - jlong jcf_handle) { +jlongArray Java_org_rocksdb_RocksDB_getApproximateMemTableStats( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jlong jstartHandle, jlong jlimitHandle) { + auto* start = reinterpret_cast(jstartHandle); + auto* limit = reinterpret_cast( jlimitHandle); + const rocksdb::Range range(*start, *limit); + auto* db = reinterpret_cast(jdb_handle); - static const rocksdb::WriteOptions default_write_options = - rocksdb::WriteOptions(); - auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_merge_helper(env, db, default_write_options, cf_handle, jkey, - jkey_off, jkey_len, jval, jval_off, jval_len); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + cf_handle = + reinterpret_cast(jcf_handle); } -} -/* - * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (JJ[BII[BII)V - */ -void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BII( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); + uint64_t count = 0; + uint64_t sizes = 0; + db->GetApproximateMemTableStats(cf_handle, range, &count, &sizes); - rocksdb_merge_helper(env, db, *write_options, nullptr, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); + // prepare results + jlong results[2] = { + static_cast(count), + static_cast(sizes)}; + + const jsize jcount = static_cast(count); + jlongArray jsizes = env->NewLongArray(jcount); + if (jsizes == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + env->SetLongArrayRegion(jsizes, 0, jcount, results); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jsizes); + return nullptr; + } + + return jsizes; } /* * Class: org_rocksdb_RocksDB - * Method: merge - * Signature: (JJ[BII[BIIJ)V + * Method: compactRange + * Signature: (J[BI[BIJJ)V */ -void Java_org_rocksdb_RocksDB_merge__JJ_3BII_3BIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jwrite_options_handle, - jbyteArray jkey, jint jkey_off, jint jkey_len, jbyteArray jval, - jint jval_off, jint jval_len, jlong jcf_handle) { - auto* db = reinterpret_cast(jdb_handle); - auto* write_options = - reinterpret_cast(jwrite_options_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - if (cf_handle != nullptr) { - rocksdb_merge_helper(env, db, *write_options, cf_handle, jkey, jkey_off, - jkey_len, jval, jval_off, jval_len); +void Java_org_rocksdb_RocksDB_compactRange( + JNIEnv* env, jobject, jlong jdb_handle, + jbyteArray jbegin, jint jbegin_len, + jbyteArray jend, jint jend_len, + jlong jcompact_range_opts_handle, + jlong jcf_handle) { + jboolean has_exception = JNI_FALSE; + + std::string str_begin; + if (jbegin_len > 0) { + str_begin = rocksdb::JniUtil::byteString(env, jbegin, jbegin_len, + [](const char* str, const size_t len) { + return std::string(str, len); + }, + &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return; + } + } + + std::string str_end; + if (jend_len > 0) { + str_end = rocksdb::JniUtil::byteString(env, jend, jend_len, + [](const char* str, const size_t len) { + return std::string(str, len); + }, + &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return; + } + } + + rocksdb::CompactRangeOptions *compact_range_opts = nullptr; + if (jcompact_range_opts_handle == 0) { + // NOTE: we DO own the pointer! + compact_range_opts = new rocksdb::CompactRangeOptions(); } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + // NOTE: we do NOT own the pointer! + compact_range_opts = + reinterpret_cast(jcompact_range_opts_handle); } -} -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::~DB() + auto* db = reinterpret_cast(jdb_handle); -/* - * Class: org_rocksdb_RocksDB - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_RocksDB_disposeInternal( - JNIEnv* env, jobject java_db, jlong jhandle) { - auto* db = reinterpret_cast(jhandle); - assert(db != nullptr); - delete db; -} + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } -jlong rocksdb_iterator_helper( - rocksdb::DB* db, rocksdb::ReadOptions read_options, - rocksdb::ColumnFamilyHandle* cf_handle) { - rocksdb::Iterator* iterator = nullptr; - if (cf_handle != nullptr) { - iterator = db->NewIterator(read_options, cf_handle); + rocksdb::Status s; + if (jbegin_len > 0 || jend_len > 0) { + const rocksdb::Slice begin(str_begin); + const rocksdb::Slice end(str_end); + s = db->CompactRange(*compact_range_opts, cf_handle, &begin, &end); } else { - iterator = db->NewIterator(read_options); + s = db->CompactRange(*compact_range_opts, cf_handle, nullptr, nullptr); } - return reinterpret_cast(iterator); -} -/* - * Class: org_rocksdb_RocksDB - * Method: iterator - * Signature: (J)J - */ -jlong Java_org_rocksdb_RocksDB_iterator__J( - JNIEnv* env, jobject jdb, jlong db_handle) { - auto* db = reinterpret_cast(db_handle); - return rocksdb_iterator_helper(db, rocksdb::ReadOptions(), - nullptr); + if (jcompact_range_opts_handle == 0) { + delete compact_range_opts; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } /* * Class: org_rocksdb_RocksDB - * Method: iterator - * Signature: (JJ)J + * Method: setOptions + * Signature: (JJ[Ljava/lang/String;[Ljava/lang/String;)V */ -jlong Java_org_rocksdb_RocksDB_iterator__JJ( - JNIEnv* env, jobject jdb, jlong db_handle, - jlong jread_options_handle) { - auto* db = reinterpret_cast(db_handle); - auto& read_options = *reinterpret_cast( - jread_options_handle); - return rocksdb_iterator_helper(db, read_options, - nullptr); -} +void Java_org_rocksdb_RocksDB_setOptions( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jobjectArray jkeys, jobjectArray jvalues) { + const jsize len = env->GetArrayLength(jkeys); + assert(len == env->GetArrayLength(jvalues)); -/* - * Class: org_rocksdb_RocksDB - * Method: iteratorCF - * Signature: (JJ)J - */ -jlong Java_org_rocksdb_RocksDB_iteratorCF__JJ( - JNIEnv* env, jobject jdb, jlong db_handle, jlong jcf_handle) { - auto* db = reinterpret_cast(db_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - return rocksdb_iterator_helper(db, rocksdb::ReadOptions(), - cf_handle); -} + std::unordered_map options_map; + for (jsize i = 0; i < len; i++) { + jobject jobj_key = env->GetObjectArrayElement(jkeys, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + return; + } -/* - * Class: org_rocksdb_RocksDB - * Method: iteratorCF - * Signature: (JJJ)J - */ -jlong Java_org_rocksdb_RocksDB_iteratorCF__JJJ( - JNIEnv* env, jobject jdb, jlong db_handle, jlong jcf_handle, - jlong jread_options_handle) { - auto* db = reinterpret_cast(db_handle); + jobject jobj_value = env->GetObjectArrayElement(jvalues, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jobj_key); + return; + } + + jboolean has_exception = JNI_FALSE; + std::string s_key = + rocksdb::JniUtil::copyStdString( + env, reinterpret_cast(jobj_key), &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + env->DeleteLocalRef(jobj_value); + env->DeleteLocalRef(jobj_key); + return; + } + + std::string s_value = + rocksdb::JniUtil::copyStdString( + env, reinterpret_cast(jobj_value), &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + env->DeleteLocalRef(jobj_value); + env->DeleteLocalRef(jobj_key); + return; + } + + options_map[s_key] = s_value; + + env->DeleteLocalRef(jobj_key); + env->DeleteLocalRef(jobj_value); + } + + auto* db = reinterpret_cast(jdb_handle); auto* cf_handle = reinterpret_cast(jcf_handle); - auto& read_options = *reinterpret_cast( - jread_options_handle); - return rocksdb_iterator_helper(db, read_options, - cf_handle); + auto s = db->SetOptions(cf_handle, options_map); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } } /* * Class: org_rocksdb_RocksDB - * Method: iterators - * Signature: (J[JJ)[J + * Method: setDBOptions + * Signature: (J[Ljava/lang/String;[Ljava/lang/String;)V */ -jlongArray Java_org_rocksdb_RocksDB_iterators( - JNIEnv* env, jobject jdb, jlong db_handle, - jlongArray jcolumn_family_handles, jlong jread_options_handle) { - auto* db = reinterpret_cast(db_handle); - auto& read_options = *reinterpret_cast( - jread_options_handle); +void Java_org_rocksdb_RocksDB_setDBOptions( + JNIEnv* env, jobject, jlong jdb_handle, + jobjectArray jkeys, jobjectArray jvalues) { + const jsize len = env->GetArrayLength(jkeys); + assert(len == env->GetArrayLength(jvalues)); - std::vector cf_handles; - if (jcolumn_family_handles != nullptr) { - const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); - jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); - if(jcfh == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; + std::unordered_map options_map; + for (jsize i = 0; i < len; i++) { + jobject jobj_key = env->GetObjectArrayElement(jkeys, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + return; } - for (jsize i = 0; i < len_cols; i++) { - auto* cf_handle = - reinterpret_cast(jcfh[i]); - cf_handles.push_back(cf_handle); + jobject jobj_value = env->GetObjectArrayElement(jvalues, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jobj_key); + return; } - env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); - } - - std::vector iterators; - rocksdb::Status s = db->NewIterators(read_options, - cf_handles, &iterators); - if (s.ok()) { - jlongArray jLongArray = - env->NewLongArray(static_cast(iterators.size())); - if(jLongArray == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; + jboolean has_exception = JNI_FALSE; + std::string s_key = + rocksdb::JniUtil::copyStdString( + env, reinterpret_cast(jobj_key), &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + env->DeleteLocalRef(jobj_value); + env->DeleteLocalRef(jobj_key); + return; } - for (std::vector::size_type i = 0; - i < iterators.size(); i++) { - env->SetLongArrayRegion(jLongArray, static_cast(i), 1, - const_cast(reinterpret_cast(&iterators[i]))); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jLongArray); - return nullptr; - } + std::string s_value = + rocksdb::JniUtil::copyStdString( + env, reinterpret_cast(jobj_value), &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + env->DeleteLocalRef(jobj_value); + env->DeleteLocalRef(jobj_key); + return; } - return jLongArray; - } else { - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return nullptr; + options_map[s_key] = s_value; + + env->DeleteLocalRef(jobj_key); + env->DeleteLocalRef(jobj_value); } -} -/* - * Class: org_rocksdb_RocksDB - * Method: getDefaultColumnFamily - * Signature: (J)J - */ -jlong Java_org_rocksdb_RocksDB_getDefaultColumnFamily( - JNIEnv* env, jobject jobj, jlong jdb_handle) { - auto* db_handle = reinterpret_cast(jdb_handle); - auto* cf_handle = db_handle->DefaultColumnFamily(); - return reinterpret_cast(cf_handle); + auto* db = reinterpret_cast(jdb_handle); + auto s = db->SetDBOptions(options_map); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } } /* * Class: org_rocksdb_RocksDB - * Method: createColumnFamily - * Signature: (J[BJ)J + * Method: compactFiles + * Signature: (JJJ[Ljava/lang/String;IIJ)[Ljava/lang/String; */ -jlong Java_org_rocksdb_RocksDB_createColumnFamily( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jbyteArray jcolumn_name, jlong jcolumn_options) { - rocksdb::ColumnFamilyHandle* handle; +jobjectArray Java_org_rocksdb_RocksDB_compactFiles( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcompaction_opts_handle, + jlong jcf_handle, jobjectArray jinput_file_names, jint joutput_level, + jint joutput_path_id, jlong jcompaction_job_info_handle) { jboolean has_exception = JNI_FALSE; - std::string column_name = rocksdb::JniUtil::byteString(env, - jcolumn_name, - [](const char* str, const size_t len) { return std::string(str, len); }, - &has_exception); - if(has_exception == JNI_TRUE) { + const std::vector input_file_names = + rocksdb::JniUtil::copyStrings(env, jinput_file_names, &has_exception); + if (has_exception == JNI_TRUE) { // exception occurred - return 0; + return nullptr; } - auto* db_handle = reinterpret_cast(jdb_handle); - auto* cfOptions = - reinterpret_cast(jcolumn_options); + auto* compaction_opts = + reinterpret_cast(jcompaction_opts_handle); + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } - rocksdb::Status s = db_handle->CreateColumnFamily( - *cfOptions, column_name, &handle); + rocksdb::CompactionJobInfo* compaction_job_info = nullptr; + if (jcompaction_job_info_handle != 0) { + compaction_job_info = + reinterpret_cast(jcompaction_job_info_handle); + } - if (s.ok()) { - return reinterpret_cast(handle); + std::vector output_file_names; + auto s = db->CompactFiles(*compaction_opts, cf_handle, input_file_names, + static_cast(joutput_level), static_cast(joutput_path_id), + &output_file_names, compaction_job_info); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; } - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return 0; + return rocksdb::JniUtil::toJavaStrings(env, &output_file_names); } /* * Class: org_rocksdb_RocksDB - * Method: dropColumnFamily - * Signature: (JJ)V; + * Method: pauseBackgroundWork + * Signature: (J)V */ -void Java_org_rocksdb_RocksDB_dropColumnFamily( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jcf_handle) { - auto* cf_handle = reinterpret_cast(jcf_handle); - auto* db_handle = reinterpret_cast(jdb_handle); - rocksdb::Status s = db_handle->DropColumnFamily(cf_handle); +void Java_org_rocksdb_RocksDB_pauseBackgroundWork( + JNIEnv* env, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto s = db->PauseBackgroundWork(); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } } /* - * Method: getSnapshot - * Signature: (J)J + * Class: org_rocksdb_RocksDB + * Method: continueBackgroundWork + * Signature: (J)V */ -jlong Java_org_rocksdb_RocksDB_getSnapshot( - JNIEnv* env, jobject jdb, jlong db_handle) { - auto* db = reinterpret_cast(db_handle); - const rocksdb::Snapshot* snapshot = db->GetSnapshot(); - return reinterpret_cast(snapshot); +void Java_org_rocksdb_RocksDB_continueBackgroundWork( + JNIEnv* env, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto s = db->ContinueBackgroundWork(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } } /* - * Method: releaseSnapshot - * Signature: (JJ)V + * Class: org_rocksdb_RocksDB + * Method: enableAutoCompaction + * Signature: (J[J)V */ -void Java_org_rocksdb_RocksDB_releaseSnapshot( - JNIEnv* env, jobject jdb, jlong db_handle, jlong snapshot_handle) { - auto* db = reinterpret_cast(db_handle); - auto* snapshot = reinterpret_cast(snapshot_handle); - db->ReleaseSnapshot(snapshot); +void Java_org_rocksdb_RocksDB_enableAutoCompaction( + JNIEnv* env, jobject, jlong jdb_handle, jlongArray jcf_handles) { + auto* db = reinterpret_cast(jdb_handle); + jboolean has_exception = JNI_FALSE; + const std::vector cf_handles = + rocksdb::JniUtil::fromJPointers(env, jcf_handles, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return; + } + db->EnableAutoCompaction(cf_handles); } /* * Class: org_rocksdb_RocksDB - * Method: getProperty0 - * Signature: (JLjava/lang/String;I)Ljava/lang/String; + * Method: numberLevels + * Signature: (JJ)I */ -jstring Java_org_rocksdb_RocksDB_getProperty0__JLjava_lang_String_2I( - JNIEnv* env, jobject jdb, jlong db_handle, jstring jproperty, - jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if(property == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - rocksdb::Slice property_slice(property, jproperty_len); - - auto *db = reinterpret_cast(db_handle); - std::string property_value; - bool retCode = db->GetProperty(property_slice, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return env->NewStringUTF(property_value.c_str()); +jint Java_org_rocksdb_RocksDB_numberLevels( + JNIEnv*, jobject, jlong jdb_handle, jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); } - - rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); - return nullptr; + return static_cast(db->NumberLevels(cf_handle)); } /* * Class: org_rocksdb_RocksDB - * Method: getProperty0 - * Signature: (JJLjava/lang/String;I)Ljava/lang/String; + * Method: maxMemCompactionLevel + * Signature: (JJ)I */ -jstring Java_org_rocksdb_RocksDB_getProperty0__JJLjava_lang_String_2I( - JNIEnv* env, jobject jdb, jlong db_handle, jlong jcf_handle, - jstring jproperty, jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if(property == nullptr) { - // exception thrown: OutOfMemoryError - return nullptr; - } - rocksdb::Slice property_slice(property, jproperty_len); - - auto* db = reinterpret_cast(db_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - std::string property_value; - bool retCode = db->GetProperty(cf_handle, property_slice, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return env->NewStringUTF(property_value.c_str()); +jint Java_org_rocksdb_RocksDB_maxMemCompactionLevel( + JNIEnv*, jobject, jlong jdb_handle, jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); } - - rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); - return nullptr; + return static_cast(db->MaxMemCompactionLevel(cf_handle)); } /* * Class: org_rocksdb_RocksDB - * Method: getLongProperty - * Signature: (JLjava/lang/String;I)L; + * Method: level0StopWriteTrigger + * Signature: (JJ)I */ -jlong Java_org_rocksdb_RocksDB_getLongProperty__JLjava_lang_String_2I( - JNIEnv* env, jobject jdb, jlong db_handle, jstring jproperty, - jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if(property == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - rocksdb::Slice property_slice(property, jproperty_len); - - auto* db = reinterpret_cast(db_handle); - uint64_t property_value = 0; - bool retCode = db->GetIntProperty(property_slice, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return property_value; +jint Java_org_rocksdb_RocksDB_level0StopWriteTrigger( + JNIEnv*, jobject, jlong jdb_handle, jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); } - - rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); - return 0; + return static_cast(db->Level0StopWriteTrigger(cf_handle)); } /* * Class: org_rocksdb_RocksDB - * Method: getLongProperty - * Signature: (JJLjava/lang/String;I)L; + * Method: getName + * Signature: (J)Ljava/lang/String; */ -jlong Java_org_rocksdb_RocksDB_getLongProperty__JJLjava_lang_String_2I( - JNIEnv* env, jobject jdb, jlong db_handle, jlong jcf_handle, - jstring jproperty, jint jproperty_len) { - const char* property = env->GetStringUTFChars(jproperty, nullptr); - if(property == nullptr) { - // exception thrown: OutOfMemoryError - return 0; - } - rocksdb::Slice property_slice(property, jproperty_len); - - auto* db = reinterpret_cast(db_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - uint64_t property_value; - bool retCode = db->GetIntProperty(cf_handle, property_slice, &property_value); - env->ReleaseStringUTFChars(jproperty, property); - - if (retCode) { - return property_value; - } - - rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::NotFound()); - return 0; +jstring Java_org_rocksdb_RocksDB_getName( + JNIEnv* env, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + std::string name = db->GetName(); + return rocksdb::JniUtil::toJavaString(env, &name, false); } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::Flush +/* + * Class: org_rocksdb_RocksDB + * Method: getEnv + * Signature: (J)J + */ +jlong Java_org_rocksdb_RocksDB_getEnv( + JNIEnv*, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + return reinterpret_cast(db->GetEnv()); +} -void rocksdb_flush_helper( - JNIEnv* env, rocksdb::DB* db, const rocksdb::FlushOptions& flush_options, - rocksdb::ColumnFamilyHandle* column_family_handle) { - rocksdb::Status s; - if (column_family_handle != nullptr) { - s = db->Flush(flush_options, column_family_handle); +/* + * Class: org_rocksdb_RocksDB + * Method: flush + * Signature: (JJ[J)V + */ +void Java_org_rocksdb_RocksDB_flush( + JNIEnv* env, jobject, jlong jdb_handle, jlong jflush_opts_handle, + jlongArray jcf_handles) { + auto* db = reinterpret_cast(jdb_handle); + auto* flush_opts = + reinterpret_cast(jflush_opts_handle); + std::vector cf_handles; + if (jcf_handles == nullptr) { + cf_handles.push_back(db->DefaultColumnFamily()); } else { - s = db->Flush(flush_options); + jboolean has_exception = JNI_FALSE; + cf_handles = + rocksdb::JniUtil::fromJPointers( + env, jcf_handles, &has_exception); + if (has_exception) { + // exception occurred + return; + } } + auto s = db->Flush(*flush_opts, cf_handles); if (!s.ok()) { - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } } /* * Class: org_rocksdb_RocksDB - * Method: flush - * Signature: (JJ)V + * Method: flushWal + * Signature: (JZ)V */ -void Java_org_rocksdb_RocksDB_flush__JJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jflush_options) { +void Java_org_rocksdb_RocksDB_flushWal( + JNIEnv* env, jobject, jlong jdb_handle, jboolean jsync) { auto* db = reinterpret_cast(jdb_handle); - auto* flush_options = - reinterpret_cast(jflush_options); - rocksdb_flush_helper(env, db, *flush_options, nullptr); + auto s = db->FlushWAL(jsync == JNI_TRUE); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } } /* * Class: org_rocksdb_RocksDB - * Method: flush - * Signature: (JJJ)V + * Method: syncWal + * Signature: (J)V */ -void Java_org_rocksdb_RocksDB_flush__JJJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jlong jflush_options, jlong jcf_handle) { +void Java_org_rocksdb_RocksDB_syncWal( + JNIEnv* env, jobject, jlong jdb_handle) { auto* db = reinterpret_cast(jdb_handle); - auto* flush_options = - reinterpret_cast(jflush_options); - auto* cf_handle = reinterpret_cast(jcf_handle); - rocksdb_flush_helper(env, db, *flush_options, cf_handle); + auto s = db->SyncWAL(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::CompactRange - Full - -void rocksdb_compactrange_helper(JNIEnv* env, rocksdb::DB* db, - rocksdb::ColumnFamilyHandle* cf_handle, jboolean jreduce_level, - jint jtarget_level, jint jtarget_path_id) { +/* + * Class: org_rocksdb_RocksDB + * Method: getLatestSequenceNumber + * Signature: (J)V + */ +jlong Java_org_rocksdb_RocksDB_getLatestSequenceNumber( + JNIEnv*, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + return db->GetLatestSequenceNumber(); +} - rocksdb::Status s; - rocksdb::CompactRangeOptions compact_options; - compact_options.change_level = jreduce_level; - compact_options.target_level = jtarget_level; - compact_options.target_path_id = static_cast(jtarget_path_id); - if (cf_handle != nullptr) { - s = db->CompactRange(compact_options, cf_handle, nullptr, nullptr); +/* + * Class: org_rocksdb_RocksDB + * Method: setPreserveDeletesSequenceNumber + * Signature: (JJ)Z + */ +jboolean JNICALL Java_org_rocksdb_RocksDB_setPreserveDeletesSequenceNumber( + JNIEnv*, jobject, jlong jdb_handle, jlong jseq_number) { + auto* db = reinterpret_cast(jdb_handle); + if (db->SetPreserveDeletesSequenceNumber( + static_cast(jseq_number))) { + return JNI_TRUE; } else { - // backwards compatibility - s = db->CompactRange(compact_options, nullptr, nullptr); + return JNI_FALSE; } +} - if (s.ok()) { - return; +/* + * Class: org_rocksdb_RocksDB + * Method: disableFileDeletions + * Signature: (J)V + */ +void Java_org_rocksdb_RocksDB_disableFileDeletions( + JNIEnv* env, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + rocksdb::Status s = db->DisableFileDeletions(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } /* * Class: org_rocksdb_RocksDB - * Method: compactRange0 - * Signature: (JZII)V + * Method: enableFileDeletions + * Signature: (JZ)V */ -void Java_org_rocksdb_RocksDB_compactRange0__JZII(JNIEnv* env, - jobject jdb, jlong jdb_handle, jboolean jreduce_level, - jint jtarget_level, jint jtarget_path_id) { +void Java_org_rocksdb_RocksDB_enableFileDeletions( + JNIEnv* env, jobject, jlong jdb_handle, jboolean jforce) { auto* db = reinterpret_cast(jdb_handle); - rocksdb_compactrange_helper(env, db, nullptr, jreduce_level, - jtarget_level, jtarget_path_id); + rocksdb::Status s = db->EnableFileDeletions(jforce); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } } /* * Class: org_rocksdb_RocksDB - * Method: compactRange - * Signature: (JZIIJ)V + * Method: getLiveFiles + * Signature: (JZ)[Ljava/lang/String; */ -void Java_org_rocksdb_RocksDB_compactRange__JZIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, - jboolean jreduce_level, jint jtarget_level, - jint jtarget_path_id, jlong jcf_handle) { +jobjectArray Java_org_rocksdb_RocksDB_getLiveFiles( + JNIEnv* env, jobject, jlong jdb_handle, jboolean jflush_memtable) { auto* db = reinterpret_cast(jdb_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - rocksdb_compactrange_helper(env, db, cf_handle, jreduce_level, - jtarget_level, jtarget_path_id); -} + std::vector live_files; + uint64_t manifest_file_size = 0; + auto s = db->GetLiveFiles( + live_files, &manifest_file_size, jflush_memtable == JNI_TRUE); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; + } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::CompactRange - Range + // append the manifest_file_size to the vector + // for passing back to java + live_files.push_back(std::to_string(manifest_file_size)); -/** - * @return true if the compact range succeeded, false if a Java Exception - * was thrown - */ -bool rocksdb_compactrange_helper(JNIEnv* env, rocksdb::DB* db, - rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jbegin, jint jbegin_len, - jbyteArray jend, jint jend_len, jboolean jreduce_level, jint jtarget_level, - jint jtarget_path_id) { + return rocksdb::JniUtil::toJavaStrings(env, &live_files); +} - jbyte* begin = env->GetByteArrayElements(jbegin, nullptr); - if(begin == nullptr) { - // exception thrown: OutOfMemoryError - return false; +/* + * Class: org_rocksdb_RocksDB + * Method: getSortedWalFiles + * Signature: (J)[Lorg/rocksdb/LogFile; + */ +jobjectArray Java_org_rocksdb_RocksDB_getSortedWalFiles( + JNIEnv* env, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + std::vector> sorted_wal_files; + auto s = db->GetSortedWalFiles(sorted_wal_files); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; } - jbyte* end = env->GetByteArrayElements(jend, nullptr); - if(end == nullptr) { + // convert to Java type + const jsize jlen = static_cast(sorted_wal_files.size()); + jobjectArray jsorted_wal_files = env->NewObjectArray( + jlen, rocksdb::LogFileJni::getJClass(env), nullptr); + if(jsorted_wal_files == nullptr) { // exception thrown: OutOfMemoryError - env->ReleaseByteArrayElements(jbegin, begin, JNI_ABORT); - return false; + return nullptr; } - const rocksdb::Slice begin_slice(reinterpret_cast(begin), jbegin_len); - const rocksdb::Slice end_slice(reinterpret_cast(end), jend_len); + jsize i = 0; + for (auto it = sorted_wal_files.begin(); it != sorted_wal_files.end(); ++it) { + jobject jlog_file = rocksdb::LogFileJni::fromCppLogFile(env, it->get()); + if (jlog_file == nullptr) { + // exception occurred + env->DeleteLocalRef(jsorted_wal_files); + return nullptr; + } + + env->SetObjectArrayElement(jsorted_wal_files, i++, jlog_file); + if (env->ExceptionCheck()) { + // exception occurred + env->DeleteLocalRef(jlog_file); + env->DeleteLocalRef(jsorted_wal_files); + return nullptr; + } - rocksdb::Status s; - rocksdb::CompactRangeOptions compact_options; - compact_options.change_level = jreduce_level; - compact_options.target_level = jtarget_level; - compact_options.target_path_id = static_cast(jtarget_path_id); - if (cf_handle != nullptr) { - s = db->CompactRange(compact_options, cf_handle, &begin_slice, &end_slice); - } else { - // backwards compatibility - s = db->CompactRange(compact_options, &begin_slice, &end_slice); + env->DeleteLocalRef(jlog_file); } - env->ReleaseByteArrayElements(jend, end, JNI_ABORT); - env->ReleaseByteArrayElements(jbegin, begin, JNI_ABORT); + return jsorted_wal_files; +} +/* + * Class: org_rocksdb_RocksDB + * Method: getUpdatesSince + * Signature: (JJ)J + */ +jlong Java_org_rocksdb_RocksDB_getUpdatesSince( + JNIEnv* env, jobject, jlong jdb_handle, jlong jsequence_number) { + auto* db = reinterpret_cast(jdb_handle); + rocksdb::SequenceNumber sequence_number = + static_cast(jsequence_number); + std::unique_ptr iter; + rocksdb::Status s = db->GetUpdatesSince(sequence_number, &iter); if (s.ok()) { - return true; + return reinterpret_cast(iter.release()); } rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return false; + return 0; } /* * Class: org_rocksdb_RocksDB - * Method: compactRange0 - * Signature: (J[BI[BIZII)V + * Method: deleteFile + * Signature: (JLjava/lang/String;)V */ -void Java_org_rocksdb_RocksDB_compactRange0__J_3BI_3BIZII(JNIEnv* env, - jobject jdb, jlong jdb_handle, jbyteArray jbegin, jint jbegin_len, - jbyteArray jend, jint jend_len, jboolean jreduce_level, - jint jtarget_level, jint jtarget_path_id) { +void Java_org_rocksdb_RocksDB_deleteFile( + JNIEnv* env, jobject, jlong jdb_handle, jstring jname) { auto* db = reinterpret_cast(jdb_handle); - rocksdb_compactrange_helper(env, db, nullptr, jbegin, jbegin_len, - jend, jend_len, jreduce_level, jtarget_level, jtarget_path_id); + jboolean has_exception = JNI_FALSE; + std::string name = + rocksdb::JniUtil::copyStdString(env, jname, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return; + } + db->DeleteFile(name); } /* * Class: org_rocksdb_RocksDB - * Method: compactRange - * Signature: (JJ[BI[BIZII)V + * Method: getLiveFilesMetaData + * Signature: (J)[Lorg/rocksdb/LiveFileMetaData; */ -void Java_org_rocksdb_RocksDB_compactRange__J_3BI_3BIZIIJ( - JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jbegin, - jint jbegin_len, jbyteArray jend, jint jend_len, - jboolean jreduce_level, jint jtarget_level, - jint jtarget_path_id, jlong jcf_handle) { +jobjectArray Java_org_rocksdb_RocksDB_getLiveFilesMetaData( + JNIEnv* env, jobject, jlong jdb_handle) { auto* db = reinterpret_cast(jdb_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - rocksdb_compactrange_helper(env, db, cf_handle, jbegin, jbegin_len, - jend, jend_len, jreduce_level, jtarget_level, jtarget_path_id); + std::vector live_files_meta_data; + db->GetLiveFilesMetaData(&live_files_meta_data); + + // convert to Java type + const jsize jlen = static_cast(live_files_meta_data.size()); + jobjectArray jlive_files_meta_data = env->NewObjectArray( + jlen, rocksdb::LiveFileMetaDataJni::getJClass(env), nullptr); + if(jlive_files_meta_data == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + jsize i = 0; + for (auto it = live_files_meta_data.begin(); it != live_files_meta_data.end(); ++it) { + jobject jlive_file_meta_data = + rocksdb::LiveFileMetaDataJni::fromCppLiveFileMetaData(env, &(*it)); + if (jlive_file_meta_data == nullptr) { + // exception occurred + env->DeleteLocalRef(jlive_files_meta_data); + return nullptr; + } + + env->SetObjectArrayElement(jlive_files_meta_data, i++, jlive_file_meta_data); + if (env->ExceptionCheck()) { + // exception occurred + env->DeleteLocalRef(jlive_file_meta_data); + env->DeleteLocalRef(jlive_files_meta_data); + return nullptr; + } + + env->DeleteLocalRef(jlive_file_meta_data); + } + + return jlive_files_meta_data; } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::PauseBackgroundWork +/* + * Class: org_rocksdb_RocksDB + * Method: getColumnFamilyMetaData + * Signature: (JJ)Lorg/rocksdb/ColumnFamilyMetaData; + */ +jobject Java_org_rocksdb_RocksDB_getColumnFamilyMetaData( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle) { + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } + rocksdb::ColumnFamilyMetaData cf_metadata; + db->GetColumnFamilyMetaData(cf_handle, &cf_metadata); + return rocksdb::ColumnFamilyMetaDataJni::fromCppColumnFamilyMetaData( + env, &cf_metadata); +} /* * Class: org_rocksdb_RocksDB - * Method: pauseBackgroundWork - * Signature: (J)V + * Method: ingestExternalFile + * Signature: (JJ[Ljava/lang/String;IJ)V */ -void Java_org_rocksdb_RocksDB_pauseBackgroundWork( - JNIEnv* env, jobject jobj, jlong jdb_handle) { +void Java_org_rocksdb_RocksDB_ingestExternalFile( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jobjectArray jfile_path_list, jint jfile_path_list_len, + jlong jingest_external_file_options_handle) { + jboolean has_exception = JNI_FALSE; + std::vector file_path_list = rocksdb::JniUtil::copyStrings( + env, jfile_path_list, jfile_path_list_len, &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return; + } + auto* db = reinterpret_cast(jdb_handle); - auto s = db->PauseBackgroundWork(); + auto* column_family = + reinterpret_cast(jcf_handle); + auto* ifo = reinterpret_cast( + jingest_external_file_options_handle); + rocksdb::Status s = + db->IngestExternalFile(column_family, file_path_list, *ifo); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::ContinueBackgroundWork - /* * Class: org_rocksdb_RocksDB - * Method: continueBackgroundWork + * Method: verifyChecksum * Signature: (J)V */ -void Java_org_rocksdb_RocksDB_continueBackgroundWork( - JNIEnv* env, jobject jobj, jlong jdb_handle) { +void Java_org_rocksdb_RocksDB_verifyChecksum( + JNIEnv* env, jobject, jlong jdb_handle) { auto* db = reinterpret_cast(jdb_handle); - auto s = db->ContinueBackgroundWork(); + auto s = db->VerifyChecksum(); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::GetLatestSequenceNumber - /* * Class: org_rocksdb_RocksDB - * Method: getLatestSequenceNumber - * Signature: (J)V + * Method: getDefaultColumnFamily + * Signature: (J)J */ -jlong Java_org_rocksdb_RocksDB_getLatestSequenceNumber(JNIEnv* env, - jobject jdb, jlong jdb_handle) { - auto* db = reinterpret_cast(jdb_handle); - return db->GetLatestSequenceNumber(); +jlong Java_org_rocksdb_RocksDB_getDefaultColumnFamily( + JNIEnv*, jobject, jlong jdb_handle) { + auto* db_handle = reinterpret_cast(jdb_handle); + auto* cf_handle = db_handle->DefaultColumnFamily(); + return reinterpret_cast(cf_handle); } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB enable/disable file deletions - /* * Class: org_rocksdb_RocksDB - * Method: enableFileDeletions - * Signature: (J)V + * Method: getPropertiesOfAllTables + * Signature: (JJ)Ljava/util/Map; */ -void Java_org_rocksdb_RocksDB_disableFileDeletions(JNIEnv* env, - jobject jdb, jlong jdb_handle) { +jobject Java_org_rocksdb_RocksDB_getPropertiesOfAllTables( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle) { auto* db = reinterpret_cast(jdb_handle); - rocksdb::Status s = db->DisableFileDeletions(); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } + rocksdb::TablePropertiesCollection table_properties_collection; + auto s = db->GetPropertiesOfAllTables(cf_handle, + &table_properties_collection); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } + + // convert to Java type + jobject jhash_map = rocksdb::HashMapJni::construct( + env, static_cast(table_properties_collection.size())); + if (jhash_map == nullptr) { + // exception occurred + return nullptr; + } + + const rocksdb::HashMapJni::FnMapKV, jobject, jobject> fn_map_kv = + [env](const std::pair>& kv) { + jstring jkey = rocksdb::JniUtil::toJavaString(env, &(kv.first), false); + if (env->ExceptionCheck()) { + // an error occurred + return std::unique_ptr>(nullptr); + } + + jobject jtable_properties = rocksdb::TablePropertiesJni::fromCppTableProperties(env, *(kv.second.get())); + if (jtable_properties == nullptr) { + // an error occurred + env->DeleteLocalRef(jkey); + return std::unique_ptr>(nullptr); + } + + return std::unique_ptr>(new std::pair(static_cast(jkey), static_cast(jtable_properties))); + }; + + if (!rocksdb::HashMapJni::putAll(env, jhash_map, table_properties_collection.begin(), table_properties_collection.end(), fn_map_kv)) { + // exception occurred + return nullptr; + } + + return jhash_map; } /* * Class: org_rocksdb_RocksDB - * Method: enableFileDeletions - * Signature: (JZ)V + * Method: getPropertiesOfTablesInRange + * Signature: (JJ[J)Ljava/util/Map; */ -void Java_org_rocksdb_RocksDB_enableFileDeletions(JNIEnv* env, - jobject jdb, jlong jdb_handle, jboolean jforce) { +jobject Java_org_rocksdb_RocksDB_getPropertiesOfTablesInRange( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jlongArray jrange_slice_handles) { auto* db = reinterpret_cast(jdb_handle); - rocksdb::Status s = db->EnableFileDeletions(jforce); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } + const jsize jlen = env->GetArrayLength(jrange_slice_handles); + jboolean jrange_slice_handles_is_copy = JNI_FALSE; + jlong *jrange_slice_handle = env->GetLongArrayElements( + jrange_slice_handles, &jrange_slice_handles_is_copy); + if (jrange_slice_handle == nullptr) { + // exception occurred + return nullptr; + } + + const size_t ranges_len = static_cast(jlen / 2); + auto ranges = std::unique_ptr(new rocksdb::Range[ranges_len]); + for (jsize i = 0, j = 0; i < jlen; ++i) { + auto* start = reinterpret_cast( + jrange_slice_handle[i]); + auto* limit = reinterpret_cast( + jrange_slice_handle[++i]); + ranges[j++] = rocksdb::Range(*start, *limit); + } + + rocksdb::TablePropertiesCollection table_properties_collection; + auto s = db->GetPropertiesOfTablesInRange( + cf_handle, ranges.get(), ranges_len, &table_properties_collection); if (!s.ok()) { + // error occurred + env->ReleaseLongArrayElements(jrange_slice_handles, jrange_slice_handle, JNI_ABORT); rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; } -} -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::GetUpdatesSince + // cleanup + env->ReleaseLongArrayElements(jrange_slice_handles, jrange_slice_handle, JNI_ABORT); + + return jrange_slice_handles; +} /* * Class: org_rocksdb_RocksDB - * Method: getUpdatesSince - * Signature: (JJ)J + * Method: suggestCompactRange + * Signature: (JJ)[J */ -jlong Java_org_rocksdb_RocksDB_getUpdatesSince(JNIEnv* env, - jobject jdb, jlong jdb_handle, jlong jsequence_number) { +jlongArray Java_org_rocksdb_RocksDB_suggestCompactRange( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle) { auto* db = reinterpret_cast(jdb_handle); - rocksdb::SequenceNumber sequence_number = - static_cast(jsequence_number); - std::unique_ptr iter; - rocksdb::Status s = db->GetUpdatesSince(sequence_number, &iter); - if (s.ok()) { - return reinterpret_cast(iter.release()); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } + auto* begin = new rocksdb::Slice(); + auto* end = new rocksdb::Slice(); + auto s = db->SuggestCompactRange(cf_handle, begin, end); + if (!s.ok()) { + // error occurred + delete begin; + delete end; + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; } - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); - return 0; + jlongArray jslice_handles = env->NewLongArray(2); + if (jslice_handles == nullptr) { + // exception thrown: OutOfMemoryError + delete begin; + delete end; + return nullptr; + } + + jlong slice_handles[2]; + slice_handles[0] = reinterpret_cast(begin); + slice_handles[1] = reinterpret_cast(end); + env->SetLongArrayRegion(jslice_handles, 0, 2, slice_handles); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + delete begin; + delete end; + env->DeleteLocalRef(jslice_handles); + return nullptr; + } + + return jslice_handles; } /* * Class: org_rocksdb_RocksDB - * Method: setOptions - * Signature: (JJ[Ljava/lang/String;[Ljava/lang/String;)V + * Method: promoteL0 + * Signature: (JJI)V */ -void Java_org_rocksdb_RocksDB_setOptions(JNIEnv* env, jobject jdb, - jlong jdb_handle, jlong jcf_handle, jobjectArray jkeys, - jobjectArray jvalues) { - const jsize len = env->GetArrayLength(jkeys); - assert(len == env->GetArrayLength(jvalues)); - - std::unordered_map options_map; - for (jsize i = 0; i < len; i++) { - jobject jobj_key = env->GetObjectArrayElement(jkeys, i); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - return; - } - - jobject jobj_value = env->GetObjectArrayElement(jvalues, i); - if(env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jobj_key); - return; - } - - jstring jkey = reinterpret_cast(jobj_key); - jstring jval = reinterpret_cast(jobj_value); - - const char* key = env->GetStringUTFChars(jkey, nullptr); - if(key == nullptr) { - // exception thrown: OutOfMemoryError - env->DeleteLocalRef(jobj_value); - env->DeleteLocalRef(jobj_key); - return; - } - - const char* value = env->GetStringUTFChars(jval, nullptr); - if(value == nullptr) { - // exception thrown: OutOfMemoryError - env->ReleaseStringUTFChars(jkey, key); - env->DeleteLocalRef(jobj_value); - env->DeleteLocalRef(jobj_key); - return; - } - - std::string s_key(key); - std::string s_value(value); - options_map[s_key] = s_value; - - env->ReleaseStringUTFChars(jkey, key); - env->ReleaseStringUTFChars(jval, value); - env->DeleteLocalRef(jobj_key); - env->DeleteLocalRef(jobj_value); +void Java_org_rocksdb_RocksDB_promoteL0( + JNIEnv*, jobject, jlong jdb_handle, jlong jcf_handle, jint jtarget_level) { + auto* db = reinterpret_cast(jdb_handle); + rocksdb::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); } + db->PromoteL0(cf_handle, static_cast(jtarget_level)); +} +/* + * Class: org_rocksdb_RocksDB + * Method: startTrace + * Signature: (JJJ)V + */ +void Java_org_rocksdb_RocksDB_startTrace( + JNIEnv* env, jobject, jlong jdb_handle, jlong jmax_trace_file_size, + jlong jtrace_writer_jnicallback_handle) { auto* db = reinterpret_cast(jdb_handle); - auto* cf_handle = reinterpret_cast(jcf_handle); - db->SetOptions(cf_handle, options_map); + rocksdb::TraceOptions trace_options; + trace_options.max_trace_file_size = + static_cast(jmax_trace_file_size); + // transfer ownership of trace writer from Java to C++ + auto trace_writer = std::unique_ptr( + reinterpret_cast( + jtrace_writer_jnicallback_handle)); + auto s = db->StartTrace(trace_options, std::move(trace_writer)); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } } -////////////////////////////////////////////////////////////////////////////// -// rocksdb::DB::IngestExternalFile +/* + * Class: org_rocksdb_RocksDB + * Method: endTrace + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_rocksdb_RocksDB_endTrace( + JNIEnv* env, jobject, jlong jdb_handle) { + auto* db = reinterpret_cast(jdb_handle); + auto s = db->EndTrace(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} /* * Class: org_rocksdb_RocksDB - * Method: ingestExternalFile - * Signature: (JJ[Ljava/lang/String;IJ)V + * Method: destroyDB + * Signature: (Ljava/lang/String;J)V */ -void Java_org_rocksdb_RocksDB_ingestExternalFile( - JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jcf_handle, - jobjectArray jfile_path_list, jint jfile_path_list_len, - jlong jingest_external_file_options_handle) { - jboolean has_exception = JNI_FALSE; - std::vector file_path_list = - rocksdb::JniUtil::copyStrings(env, jfile_path_list, jfile_path_list_len, - &has_exception); - if(has_exception == JNI_TRUE) { - // exception occurred +void Java_org_rocksdb_RocksDB_destroyDB( + JNIEnv* env, jclass, jstring jdb_path, jlong joptions_handle) { + const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); + if (db_path == nullptr) { + // exception thrown: OutOfMemoryError return; } - auto* db = reinterpret_cast(jdb_handle); - auto* column_family = - reinterpret_cast(jcf_handle); - auto* ifo = - reinterpret_cast( - jingest_external_file_options_handle); - rocksdb::Status s = - db->IngestExternalFile(column_family, file_path_list, *ifo); + auto* options = reinterpret_cast(joptions_handle); + if (options == nullptr) { + rocksdb::RocksDBExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Invalid Options.")); + } + + rocksdb::Status s = rocksdb::DestroyDB(db_path, *options); + env->ReleaseStringUTFChars(jdb_path, db_path); + if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } diff --git a/thirdparty/rocksdb/java/rocksjni/slice.cc b/thirdparty/rocksdb/java/rocksjni/slice.cc index ef0e384f1a..e617cde25a 100644 --- a/thirdparty/rocksdb/java/rocksjni/slice.cc +++ b/thirdparty/rocksdb/java/rocksjni/slice.cc @@ -6,14 +6,14 @@ // This file implements the "bridge" between Java and C++ for // rocksdb::Slice. +#include #include #include -#include #include #include "include/org_rocksdb_AbstractSlice.h" -#include "include/org_rocksdb_Slice.h" #include "include/org_rocksdb_DirectSlice.h" +#include "include/org_rocksdb_Slice.h" #include "rocksdb/slice.h" #include "rocksjni/portal.h" @@ -24,10 +24,11 @@ * Method: createNewSliceFromString * Signature: (Ljava/lang/String;)J */ -jlong Java_org_rocksdb_AbstractSlice_createNewSliceFromString( - JNIEnv * env, jclass jcls, jstring jstr) { +jlong Java_org_rocksdb_AbstractSlice_createNewSliceFromString(JNIEnv* env, + jclass /*jcls*/, + jstring jstr) { const auto* str = env->GetStringUTFChars(jstr, nullptr); - if(str == nullptr) { + if (str == nullptr) { // exception thrown: OutOfMemoryError return 0; } @@ -51,8 +52,8 @@ jlong Java_org_rocksdb_AbstractSlice_createNewSliceFromString( * Method: size0 * Signature: (J)I */ -jint Java_org_rocksdb_AbstractSlice_size0( - JNIEnv* env, jobject jobj, jlong handle) { +jint Java_org_rocksdb_AbstractSlice_size0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle) { const auto* slice = reinterpret_cast(handle); return static_cast(slice->size()); } @@ -62,8 +63,8 @@ jint Java_org_rocksdb_AbstractSlice_size0( * Method: empty0 * Signature: (J)Z */ -jboolean Java_org_rocksdb_AbstractSlice_empty0( - JNIEnv* env, jobject jobj, jlong handle) { +jboolean Java_org_rocksdb_AbstractSlice_empty0(JNIEnv* /*env*/, + jobject /*jobj*/, jlong handle) { const auto* slice = reinterpret_cast(handle); return slice->empty(); } @@ -73,8 +74,8 @@ jboolean Java_org_rocksdb_AbstractSlice_empty0( * Method: toString0 * Signature: (JZ)Ljava/lang/String; */ -jstring Java_org_rocksdb_AbstractSlice_toString0( - JNIEnv* env, jobject jobj, jlong handle, jboolean hex) { +jstring Java_org_rocksdb_AbstractSlice_toString0(JNIEnv* env, jobject /*jobj*/, + jlong handle, jboolean hex) { const auto* slice = reinterpret_cast(handle); const std::string s = slice->ToString(hex); return env->NewStringUTF(s.c_str()); @@ -85,11 +86,10 @@ jstring Java_org_rocksdb_AbstractSlice_toString0( * Method: compare0 * Signature: (JJ)I; */ -jint Java_org_rocksdb_AbstractSlice_compare0( - JNIEnv* env, jobject jobj, jlong handle, jlong otherHandle) { +jint Java_org_rocksdb_AbstractSlice_compare0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle, jlong otherHandle) { const auto* slice = reinterpret_cast(handle); - const auto* otherSlice = - reinterpret_cast(otherHandle); + const auto* otherSlice = reinterpret_cast(otherHandle); return slice->compare(*otherSlice); } @@ -98,11 +98,12 @@ jint Java_org_rocksdb_AbstractSlice_compare0( * Method: startsWith0 * Signature: (JJ)Z; */ -jboolean Java_org_rocksdb_AbstractSlice_startsWith0( - JNIEnv* env, jobject jobj, jlong handle, jlong otherHandle) { +jboolean Java_org_rocksdb_AbstractSlice_startsWith0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle, + jlong otherHandle) { const auto* slice = reinterpret_cast(handle); - const auto* otherSlice = - reinterpret_cast(otherHandle); + const auto* otherSlice = reinterpret_cast(otherHandle); return slice->starts_with(*otherSlice); } @@ -111,8 +112,9 @@ jboolean Java_org_rocksdb_AbstractSlice_startsWith0( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_AbstractSlice_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_AbstractSlice_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { delete reinterpret_cast(handle); } @@ -125,15 +127,16 @@ void Java_org_rocksdb_AbstractSlice_disposeInternal( * Method: createNewSlice0 * Signature: ([BI)J */ -jlong Java_org_rocksdb_Slice_createNewSlice0( - JNIEnv * env, jclass jcls, jbyteArray data, jint offset) { +jlong Java_org_rocksdb_Slice_createNewSlice0(JNIEnv* env, jclass /*jcls*/, + jbyteArray data, jint offset) { const jsize dataSize = env->GetArrayLength(data); const int len = dataSize - offset; - // NOTE: buf will be deleted in the Java_org_rocksdb_Slice_disposeInternalBuf method + // NOTE: buf will be deleted in the Java_org_rocksdb_Slice_disposeInternalBuf + // method jbyte* buf = new jbyte[len]; env->GetByteArrayRegion(data, offset, len, buf); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException return 0; } @@ -147,22 +150,22 @@ jlong Java_org_rocksdb_Slice_createNewSlice0( * Method: createNewSlice1 * Signature: ([B)J */ -jlong Java_org_rocksdb_Slice_createNewSlice1( - JNIEnv * env, jclass jcls, jbyteArray data) { +jlong Java_org_rocksdb_Slice_createNewSlice1(JNIEnv* env, jclass /*jcls*/, + jbyteArray data) { jbyte* ptrData = env->GetByteArrayElements(data, nullptr); - if(ptrData == nullptr) { + if (ptrData == nullptr) { // exception thrown: OutOfMemoryError return 0; } const int len = env->GetArrayLength(data) + 1; - // NOTE: buf will be deleted in the Java_org_rocksdb_Slice_disposeInternalBuf method + // NOTE: buf will be deleted in the Java_org_rocksdb_Slice_disposeInternalBuf + // method char* buf = new char[len]; memcpy(buf, ptrData, len - 1); - buf[len-1] = '\0'; + buf[len - 1] = '\0'; - const auto* slice = - new rocksdb::Slice(buf, len - 1); + const auto* slice = new rocksdb::Slice(buf, len - 1); env->ReleaseByteArrayElements(data, ptrData, JNI_ABORT); @@ -174,19 +177,20 @@ jlong Java_org_rocksdb_Slice_createNewSlice1( * Method: data0 * Signature: (J)[B */ -jbyteArray Java_org_rocksdb_Slice_data0( - JNIEnv* env, jobject jobj, jlong handle) { +jbyteArray Java_org_rocksdb_Slice_data0(JNIEnv* env, jobject /*jobj*/, + jlong handle) { const auto* slice = reinterpret_cast(handle); const jsize len = static_cast(slice->size()); const jbyteArray data = env->NewByteArray(len); - if(data == nullptr) { + if (data == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } - - env->SetByteArrayRegion(data, 0, len, - const_cast(reinterpret_cast(slice->data()))); - if(env->ExceptionCheck()) { + + env->SetByteArrayRegion( + data, 0, len, + const_cast(reinterpret_cast(slice->data()))); + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(data); return nullptr; @@ -200,13 +204,13 @@ jbyteArray Java_org_rocksdb_Slice_data0( * Method: clear0 * Signature: (JZJ)V */ -void Java_org_rocksdb_Slice_clear0( - JNIEnv * env, jobject jobj, jlong handle, jboolean shouldRelease, - jlong internalBufferOffset) { +void Java_org_rocksdb_Slice_clear0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle, jboolean shouldRelease, + jlong internalBufferOffset) { auto* slice = reinterpret_cast(handle); - if(shouldRelease == JNI_TRUE) { + if (shouldRelease == JNI_TRUE) { const char* buf = slice->data_ - internalBufferOffset; - delete [] buf; + delete[] buf; } slice->clear(); } @@ -216,8 +220,8 @@ void Java_org_rocksdb_Slice_clear0( * Method: removePrefix0 * Signature: (JI)V */ -void Java_org_rocksdb_Slice_removePrefix0( - JNIEnv * env, jobject jobj, jlong handle, jint length) { +void Java_org_rocksdb_Slice_removePrefix0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle, jint length) { auto* slice = reinterpret_cast(handle); slice->remove_prefix(length); } @@ -227,11 +231,12 @@ void Java_org_rocksdb_Slice_removePrefix0( * Method: disposeInternalBuf * Signature: (JJ)V */ -void Java_org_rocksdb_Slice_disposeInternalBuf( - JNIEnv * env, jobject jobj, jlong handle, jlong internalBufferOffset) { +void Java_org_rocksdb_Slice_disposeInternalBuf(JNIEnv* /*env*/, + jobject /*jobj*/, jlong handle, + jlong internalBufferOffset) { const auto* slice = reinterpret_cast(handle); const char* buf = slice->data_ - internalBufferOffset; - delete [] buf; + delete[] buf; } // @@ -243,21 +248,21 @@ void Java_org_rocksdb_Slice_disposeInternalBuf( * Method: createNewDirectSlice0 * Signature: (Ljava/nio/ByteBuffer;I)J */ -jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice0( - JNIEnv* env, jclass jcls, jobject data, jint length) { +jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice0(JNIEnv* env, + jclass /*jcls*/, + jobject data, + jint length) { assert(data != nullptr); void* data_addr = env->GetDirectBufferAddress(data); - if(data_addr == nullptr) { + if (data_addr == nullptr) { // error: memory region is undefined, given object is not a direct // java.nio.Buffer, or JNI access to direct buffers is not supported by JVM - rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument( - "Could not access DirectBuffer")); + rocksdb::IllegalArgumentExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Could not access DirectBuffer")); return 0; } - const auto* ptrData = - reinterpret_cast(data_addr); + const auto* ptrData = reinterpret_cast(data_addr); const auto* slice = new rocksdb::Slice(ptrData, length); return reinterpret_cast(slice); } @@ -267,15 +272,15 @@ jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice0( * Method: createNewDirectSlice1 * Signature: (Ljava/nio/ByteBuffer;)J */ -jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice1( - JNIEnv* env, jclass jcls, jobject data) { +jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice1(JNIEnv* env, + jclass /*jcls*/, + jobject data) { void* data_addr = env->GetDirectBufferAddress(data); - if(data_addr == nullptr) { + if (data_addr == nullptr) { // error: memory region is undefined, given object is not a direct // java.nio.Buffer, or JNI access to direct buffers is not supported by JVM - rocksdb::IllegalArgumentExceptionJni::ThrowNew(env, - rocksdb::Status::InvalidArgument( - "Could not access DirectBuffer")); + rocksdb::IllegalArgumentExceptionJni::ThrowNew( + env, rocksdb::Status::InvalidArgument("Could not access DirectBuffer")); return 0; } @@ -289,11 +294,11 @@ jlong Java_org_rocksdb_DirectSlice_createNewDirectSlice1( * Method: data0 * Signature: (J)Ljava/lang/Object; */ -jobject Java_org_rocksdb_DirectSlice_data0( - JNIEnv* env, jobject jobj, jlong handle) { +jobject Java_org_rocksdb_DirectSlice_data0(JNIEnv* env, jobject /*jobj*/, + jlong handle) { const auto* slice = reinterpret_cast(handle); return env->NewDirectByteBuffer(const_cast(slice->data()), - slice->size()); + slice->size()); } /* @@ -301,8 +306,8 @@ jobject Java_org_rocksdb_DirectSlice_data0( * Method: get0 * Signature: (JI)B */ -jbyte Java_org_rocksdb_DirectSlice_get0( - JNIEnv* env, jobject jobj, jlong handle, jint offset) { +jbyte Java_org_rocksdb_DirectSlice_get0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle, jint offset) { const auto* slice = reinterpret_cast(handle); return (*slice)[offset]; } @@ -312,13 +317,13 @@ jbyte Java_org_rocksdb_DirectSlice_get0( * Method: clear0 * Signature: (JZJ)V */ -void Java_org_rocksdb_DirectSlice_clear0( - JNIEnv* env, jobject jobj, jlong handle, - jboolean shouldRelease, jlong internalBufferOffset) { +void Java_org_rocksdb_DirectSlice_clear0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle, jboolean shouldRelease, + jlong internalBufferOffset) { auto* slice = reinterpret_cast(handle); - if(shouldRelease == JNI_TRUE) { + if (shouldRelease == JNI_TRUE) { const char* buf = slice->data_ - internalBufferOffset; - delete [] buf; + delete[] buf; } slice->clear(); } @@ -328,8 +333,9 @@ void Java_org_rocksdb_DirectSlice_clear0( * Method: removePrefix0 * Signature: (JI)V */ -void Java_org_rocksdb_DirectSlice_removePrefix0( - JNIEnv* env, jobject jobj, jlong handle, jint length) { +void Java_org_rocksdb_DirectSlice_removePrefix0(JNIEnv* /*env*/, + jobject /*jobj*/, jlong handle, + jint length) { auto* slice = reinterpret_cast(handle); slice->remove_prefix(length); } @@ -340,10 +346,11 @@ void Java_org_rocksdb_DirectSlice_removePrefix0( * Signature: (JJ)V */ void Java_org_rocksdb_DirectSlice_disposeInternalBuf( - JNIEnv* env, jobject jobj, jlong handle, jlong internalBufferOffset) { + JNIEnv* /*env*/, jobject /*jobj*/, jlong handle, + jlong internalBufferOffset) { const auto* slice = reinterpret_cast(handle); const char* buf = slice->data_ - internalBufferOffset; - delete [] buf; + delete[] buf; } // diff --git a/thirdparty/rocksdb/java/rocksjni/snapshot.cc b/thirdparty/rocksdb/java/rocksjni/snapshot.cc index 04a0ebfbaf..679271db87 100644 --- a/thirdparty/rocksdb/java/rocksjni/snapshot.cc +++ b/thirdparty/rocksdb/java/rocksjni/snapshot.cc @@ -18,9 +18,9 @@ * Method: getSequenceNumber * Signature: (J)J */ -jlong Java_org_rocksdb_Snapshot_getSequenceNumber(JNIEnv* env, - jobject jobj, jlong jsnapshot_handle) { - auto* snapshot = reinterpret_cast( - jsnapshot_handle); +jlong Java_org_rocksdb_Snapshot_getSequenceNumber(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jsnapshot_handle) { + auto* snapshot = reinterpret_cast(jsnapshot_handle); return snapshot->GetSequenceNumber(); } diff --git a/thirdparty/rocksdb/java/rocksjni/sst_file_manager.cc b/thirdparty/rocksdb/java/rocksjni/sst_file_manager.cc new file mode 100644 index 0000000000..3df3c9966c --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/sst_file_manager.cc @@ -0,0 +1,232 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ and enables +// calling C++ rocksdb::SstFileManager methods +// from Java side. + +#include +#include + +#include "include/org_rocksdb_SstFileManager.h" +#include "rocksdb/sst_file_manager.h" +#include "rocksjni/portal.h" + +/* + * Class: org_rocksdb_SstFileManager + * Method: newSstFileManager + * Signature: (JJJDJ)J + */ +jlong Java_org_rocksdb_SstFileManager_newSstFileManager( + JNIEnv* jnienv, jclass /*jcls*/, jlong jenv_handle, jlong jlogger_handle, + jlong jrate_bytes, jdouble jmax_trash_db_ratio, + jlong jmax_delete_chunk_bytes) { + auto* env = reinterpret_cast(jenv_handle); + rocksdb::Status s; + rocksdb::SstFileManager* sst_file_manager = nullptr; + + if (jlogger_handle != 0) { + auto* sptr_logger = + reinterpret_cast*>(jlogger_handle); + sst_file_manager = rocksdb::NewSstFileManager( + env, *sptr_logger, "", jrate_bytes, true, &s, jmax_trash_db_ratio, + jmax_delete_chunk_bytes); + } else { + sst_file_manager = rocksdb::NewSstFileManager(env, nullptr, "", jrate_bytes, + true, &s, jmax_trash_db_ratio, + jmax_delete_chunk_bytes); + } + + if (!s.ok()) { + if (sst_file_manager != nullptr) { + delete sst_file_manager; + } + rocksdb::RocksDBExceptionJni::ThrowNew(jnienv, s); + } + auto* sptr_sst_file_manager = + new std::shared_ptr(sst_file_manager); + + return reinterpret_cast(sptr_sst_file_manager); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: setMaxAllowedSpaceUsage + * Signature: (JJ)V + */ +void Java_org_rocksdb_SstFileManager_setMaxAllowedSpaceUsage( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jmax_allowed_space) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + sptr_sst_file_manager->get()->SetMaxAllowedSpaceUsage(jmax_allowed_space); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: setCompactionBufferSize + * Signature: (JJ)V + */ +void Java_org_rocksdb_SstFileManager_setCompactionBufferSize( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jcompaction_buffer_size) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + sptr_sst_file_manager->get()->SetCompactionBufferSize( + jcompaction_buffer_size); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: isMaxAllowedSpaceReached + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_SstFileManager_isMaxAllowedSpaceReached( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + return sptr_sst_file_manager->get()->IsMaxAllowedSpaceReached(); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: isMaxAllowedSpaceReachedIncludingCompactions + * Signature: (J)Z + */ +jboolean +Java_org_rocksdb_SstFileManager_isMaxAllowedSpaceReachedIncludingCompactions( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + return sptr_sst_file_manager->get() + ->IsMaxAllowedSpaceReachedIncludingCompactions(); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: getTotalSize + * Signature: (J)J + */ +jlong Java_org_rocksdb_SstFileManager_getTotalSize(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + return sptr_sst_file_manager->get()->GetTotalSize(); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: getTrackedFiles + * Signature: (J)Ljava/util/Map; + */ +jobject Java_org_rocksdb_SstFileManager_getTrackedFiles(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + auto tracked_files = sptr_sst_file_manager->get()->GetTrackedFiles(); + + //TODO(AR) could refactor to share code with rocksdb::HashMapJni::fromCppMap(env, tracked_files); + + const jobject jtracked_files = rocksdb::HashMapJni::construct( + env, static_cast(tracked_files.size())); + if (jtracked_files == nullptr) { + // exception occurred + return nullptr; + } + + const rocksdb::HashMapJni::FnMapKV + fn_map_kv = + [env](const std::pair& pair) { + const jstring jtracked_file_path = + env->NewStringUTF(pair.first.c_str()); + if (jtracked_file_path == nullptr) { + // an error occurred + return std::unique_ptr>(nullptr); + } + const jobject jtracked_file_size = + rocksdb::LongJni::valueOf(env, pair.second); + if (jtracked_file_size == nullptr) { + // an error occurred + return std::unique_ptr>(nullptr); + } + return std::unique_ptr>( + new std::pair(jtracked_file_path, + jtracked_file_size)); + }; + + if (!rocksdb::HashMapJni::putAll(env, jtracked_files, tracked_files.begin(), + tracked_files.end(), fn_map_kv)) { + // exception occcurred + return nullptr; + } + + return jtracked_files; +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: getDeleteRateBytesPerSecond + * Signature: (J)J + */ +jlong Java_org_rocksdb_SstFileManager_getDeleteRateBytesPerSecond( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + return sptr_sst_file_manager->get()->GetDeleteRateBytesPerSecond(); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: setDeleteRateBytesPerSecond + * Signature: (JJ)V + */ +void Java_org_rocksdb_SstFileManager_setDeleteRateBytesPerSecond( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jlong jdelete_rate) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + sptr_sst_file_manager->get()->SetDeleteRateBytesPerSecond(jdelete_rate); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: getMaxTrashDBRatio + * Signature: (J)D + */ +jdouble Java_org_rocksdb_SstFileManager_getMaxTrashDBRatio(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + return sptr_sst_file_manager->get()->GetMaxTrashDBRatio(); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: setMaxTrashDBRatio + * Signature: (JD)V + */ +void Java_org_rocksdb_SstFileManager_setMaxTrashDBRatio(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jdouble jratio) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + sptr_sst_file_manager->get()->SetMaxTrashDBRatio(jratio); +} + +/* + * Class: org_rocksdb_SstFileManager + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_SstFileManager_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* sptr_sst_file_manager = + reinterpret_cast*>(jhandle); + delete sptr_sst_file_manager; +} diff --git a/thirdparty/rocksdb/java/rocksjni/sst_file_writerjni.cc b/thirdparty/rocksdb/java/rocksjni/sst_file_writerjni.cc index ceb93384ac..76212ed899 100644 --- a/thirdparty/rocksdb/java/rocksjni/sst_file_writerjni.cc +++ b/thirdparty/rocksdb/java/rocksjni/sst_file_writerjni.cc @@ -20,16 +20,33 @@ /* * Class: org_rocksdb_SstFileWriter * Method: newSstFileWriter - * Signature: (JJJ)J + * Signature: (JJJB)J */ -jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJJ(JNIEnv *env, jclass jcls, - jlong jenvoptions, - jlong joptions, - jlong jcomparator) { +jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJJB( + JNIEnv * /*env*/, jclass /*jcls*/, jlong jenvoptions, jlong joptions, + jlong jcomparator_handle, jbyte jcomparator_type) { + rocksdb::Comparator *comparator = nullptr; + switch (jcomparator_type) { + // JAVA_COMPARATOR + case 0x0: + comparator = reinterpret_cast( + jcomparator_handle); + break; + + // JAVA_DIRECT_COMPARATOR + case 0x1: + comparator = reinterpret_cast( + jcomparator_handle); + break; + + // JAVA_NATIVE_COMPARATOR_WRAPPER + case 0x2: + comparator = reinterpret_cast(jcomparator_handle); + break; + } auto *env_options = reinterpret_cast(jenvoptions); auto *options = reinterpret_cast(joptions); - auto *comparator = reinterpret_cast(jcomparator); rocksdb::SstFileWriter *sst_file_writer = new rocksdb::SstFileWriter(*env_options, *options, comparator); return reinterpret_cast(sst_file_writer); @@ -40,9 +57,10 @@ jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJJ(JNIEnv *env, jclass j * Method: newSstFileWriter * Signature: (JJ)J */ -jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJ(JNIEnv *env, jclass jcls, - jlong jenvoptions, - jlong joptions) { +jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJ(JNIEnv * /*env*/, + jclass /*jcls*/, + jlong jenvoptions, + jlong joptions) { auto *env_options = reinterpret_cast(jenvoptions); auto *options = reinterpret_cast(joptions); @@ -56,10 +74,10 @@ jlong Java_org_rocksdb_SstFileWriter_newSstFileWriter__JJ(JNIEnv *env, jclass jc * Method: open * Signature: (JLjava/lang/String;)V */ -void Java_org_rocksdb_SstFileWriter_open(JNIEnv *env, jobject jobj, +void Java_org_rocksdb_SstFileWriter_open(JNIEnv *env, jobject /*jobj*/, jlong jhandle, jstring jfile_path) { const char *file_path = env->GetStringUTFChars(jfile_path, nullptr); - if(file_path == nullptr) { + if (file_path == nullptr) { // exception thrown: OutOfMemoryError return; } @@ -77,14 +95,13 @@ void Java_org_rocksdb_SstFileWriter_open(JNIEnv *env, jobject jobj, * Method: put * Signature: (JJJ)V */ -void Java_org_rocksdb_SstFileWriter_put__JJJ(JNIEnv *env, jobject jobj, +void Java_org_rocksdb_SstFileWriter_put__JJJ(JNIEnv *env, jobject /*jobj*/, jlong jhandle, jlong jkey_handle, jlong jvalue_handle) { auto *key_slice = reinterpret_cast(jkey_handle); auto *value_slice = reinterpret_cast(jvalue_handle); - rocksdb::Status s = - reinterpret_cast(jhandle)->Put(*key_slice, - *value_slice); + rocksdb::Status s = reinterpret_cast(jhandle)->Put( + *key_slice, *value_slice); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } @@ -95,29 +112,28 @@ void Java_org_rocksdb_SstFileWriter_put__JJJ(JNIEnv *env, jobject jobj, * Method: put * Signature: (JJJ)V */ - void Java_org_rocksdb_SstFileWriter_put__J_3B_3B(JNIEnv *env, jobject jobj, - jlong jhandle, jbyteArray jkey, - jbyteArray jval) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if(key == nullptr) { +void Java_org_rocksdb_SstFileWriter_put__J_3B_3B(JNIEnv *env, jobject /*jobj*/, + jlong jhandle, jbyteArray jkey, + jbyteArray jval) { + jbyte *key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { // exception thrown: OutOfMemoryError return; } - rocksdb::Slice key_slice( - reinterpret_cast(key), env->GetArrayLength(jkey)); + rocksdb::Slice key_slice(reinterpret_cast(key), + env->GetArrayLength(jkey)); - jbyte* value = env->GetByteArrayElements(jval, nullptr); - if(value == nullptr) { + jbyte *value = env->GetByteArrayElements(jval, nullptr); + if (value == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); return; } - rocksdb::Slice value_slice( - reinterpret_cast(value), env->GetArrayLength(jval)); + rocksdb::Slice value_slice(reinterpret_cast(value), + env->GetArrayLength(jval)); - rocksdb::Status s = - reinterpret_cast(jhandle)->Put(key_slice, - value_slice); + rocksdb::Status s = reinterpret_cast(jhandle)->Put( + key_slice, value_slice); env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); env->ReleaseByteArrayElements(jval, value, JNI_ABORT); @@ -132,14 +148,14 @@ void Java_org_rocksdb_SstFileWriter_put__JJJ(JNIEnv *env, jobject jobj, * Method: merge * Signature: (JJJ)V */ -void Java_org_rocksdb_SstFileWriter_merge__JJJ(JNIEnv *env, jobject jobj, +void Java_org_rocksdb_SstFileWriter_merge__JJJ(JNIEnv *env, jobject /*jobj*/, jlong jhandle, jlong jkey_handle, jlong jvalue_handle) { auto *key_slice = reinterpret_cast(jkey_handle); auto *value_slice = reinterpret_cast(jvalue_handle); rocksdb::Status s = - reinterpret_cast(jhandle)->Merge(*key_slice, - *value_slice); + reinterpret_cast(jhandle)->Merge(*key_slice, + *value_slice); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } @@ -150,30 +166,31 @@ void Java_org_rocksdb_SstFileWriter_merge__JJJ(JNIEnv *env, jobject jobj, * Method: merge * Signature: (J[B[B)V */ -void Java_org_rocksdb_SstFileWriter_merge__J_3B_3B(JNIEnv *env, jobject jobj, - jlong jhandle, jbyteArray jkey, +void Java_org_rocksdb_SstFileWriter_merge__J_3B_3B(JNIEnv *env, + jobject /*jobj*/, + jlong jhandle, + jbyteArray jkey, jbyteArray jval) { - - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if(key == nullptr) { + jbyte *key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { // exception thrown: OutOfMemoryError return; } - rocksdb::Slice key_slice( - reinterpret_cast(key), env->GetArrayLength(jkey)); + rocksdb::Slice key_slice(reinterpret_cast(key), + env->GetArrayLength(jkey)); - jbyte* value = env->GetByteArrayElements(jval, nullptr); - if(value == nullptr) { + jbyte *value = env->GetByteArrayElements(jval, nullptr); + if (value == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); return; } - rocksdb::Slice value_slice( - reinterpret_cast(value), env->GetArrayLength(jval)); + rocksdb::Slice value_slice(reinterpret_cast(value), + env->GetArrayLength(jval)); rocksdb::Status s = - reinterpret_cast(jhandle)->Merge(key_slice, - value_slice); + reinterpret_cast(jhandle)->Merge(key_slice, + value_slice); env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); env->ReleaseByteArrayElements(jval, value, JNI_ABORT); @@ -188,18 +205,19 @@ void Java_org_rocksdb_SstFileWriter_merge__J_3B_3B(JNIEnv *env, jobject jobj, * Method: delete * Signature: (JJJ)V */ -void Java_org_rocksdb_SstFileWriter_delete__J_3B(JNIEnv *env, jobject jobj, - jlong jhandle, jbyteArray jkey) { - jbyte* key = env->GetByteArrayElements(jkey, nullptr); - if(key == nullptr) { +void Java_org_rocksdb_SstFileWriter_delete__J_3B(JNIEnv *env, jobject /*jobj*/, + jlong jhandle, + jbyteArray jkey) { + jbyte *key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { // exception thrown: OutOfMemoryError return; } - rocksdb::Slice key_slice( - reinterpret_cast(key), env->GetArrayLength(jkey)); + rocksdb::Slice key_slice(reinterpret_cast(key), + env->GetArrayLength(jkey)); rocksdb::Status s = - reinterpret_cast(jhandle)->Delete(key_slice); + reinterpret_cast(jhandle)->Delete(key_slice); env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); @@ -213,11 +231,12 @@ void Java_org_rocksdb_SstFileWriter_delete__J_3B(JNIEnv *env, jobject jobj, * Method: delete * Signature: (JJJ)V */ - void Java_org_rocksdb_SstFileWriter_delete__JJ(JNIEnv *env, jobject jobj, - jlong jhandle, jlong jkey_handle) { +void Java_org_rocksdb_SstFileWriter_delete__JJ(JNIEnv *env, jobject /*jobj*/, + jlong jhandle, + jlong jkey_handle) { auto *key_slice = reinterpret_cast(jkey_handle); rocksdb::Status s = - reinterpret_cast(jhandle)->Delete(*key_slice); + reinterpret_cast(jhandle)->Delete(*key_slice); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } @@ -228,7 +247,7 @@ void Java_org_rocksdb_SstFileWriter_delete__J_3B(JNIEnv *env, jobject jobj, * Method: finish * Signature: (J)V */ -void Java_org_rocksdb_SstFileWriter_finish(JNIEnv *env, jobject jobj, +void Java_org_rocksdb_SstFileWriter_finish(JNIEnv *env, jobject /*jobj*/, jlong jhandle) { rocksdb::Status s = reinterpret_cast(jhandle)->Finish(); @@ -242,7 +261,8 @@ void Java_org_rocksdb_SstFileWriter_finish(JNIEnv *env, jobject jobj, * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_SstFileWriter_disposeInternal(JNIEnv *env, jobject jobj, +void Java_org_rocksdb_SstFileWriter_disposeInternal(JNIEnv * /*env*/, + jobject /*jobj*/, jlong jhandle) { delete reinterpret_cast(jhandle); } diff --git a/thirdparty/rocksdb/java/rocksjni/statistics.cc b/thirdparty/rocksdb/java/rocksjni/statistics.cc index 7b657ada7b..ae7ad53529 100644 --- a/thirdparty/rocksdb/java/rocksjni/statistics.cc +++ b/thirdparty/rocksdb/java/rocksjni/statistics.cc @@ -11,16 +11,17 @@ #include #include "include/org_rocksdb_Statistics.h" +#include "rocksdb/statistics.h" #include "rocksjni/portal.h" #include "rocksjni/statisticsjni.h" -#include "rocksdb/statistics.h" /* * Class: org_rocksdb_Statistics * Method: newStatistics * Signature: ()J */ -jlong Java_org_rocksdb_Statistics_newStatistics__(JNIEnv* env, jclass jcls) { +jlong Java_org_rocksdb_Statistics_newStatistics__( + JNIEnv* env, jclass jcls) { return Java_org_rocksdb_Statistics_newStatistics___3BJ( env, jcls, nullptr, 0); } @@ -53,9 +54,7 @@ jlong Java_org_rocksdb_Statistics_newStatistics___3B( * Signature: ([BJ)J */ jlong Java_org_rocksdb_Statistics_newStatistics___3BJ( - JNIEnv* env, jclass jcls, jbyteArray jhistograms, - jlong jother_statistics_handle) { - + JNIEnv* env, jclass, jbyteArray jhistograms, jlong jother_statistics_handle) { std::shared_ptr* pSptr_other_statistics = nullptr; if (jother_statistics_handle > 0) { pSptr_other_statistics = @@ -68,7 +67,7 @@ jlong Java_org_rocksdb_Statistics_newStatistics___3BJ( const jsize len = env->GetArrayLength(jhistograms); if (len > 0) { jbyte* jhistogram = env->GetByteArrayElements(jhistograms, nullptr); - if (jhistogram == nullptr ) { + if (jhistogram == nullptr) { // exception thrown: OutOfMemoryError return 0; } @@ -85,7 +84,7 @@ jlong Java_org_rocksdb_Statistics_newStatistics___3BJ( std::shared_ptr sptr_other_statistics = nullptr; if (pSptr_other_statistics != nullptr) { - sptr_other_statistics = *pSptr_other_statistics; + sptr_other_statistics = *pSptr_other_statistics; } auto* pSptr_statistics = new std::shared_ptr( @@ -100,8 +99,8 @@ jlong Java_org_rocksdb_Statistics_newStatistics___3BJ( * Signature: (J)V */ void Java_org_rocksdb_Statistics_disposeInternal( - JNIEnv* env, jobject jobj, jlong jhandle) { - if(jhandle > 0) { + JNIEnv*, jobject, jlong jhandle) { + if (jhandle > 0) { auto* pSptr_statistics = reinterpret_cast*>(jhandle); delete pSptr_statistics; @@ -114,11 +113,12 @@ void Java_org_rocksdb_Statistics_disposeInternal( * Signature: (J)B */ jbyte Java_org_rocksdb_Statistics_statsLevel( - JNIEnv* env, jobject jobj, jlong jhandle) { + JNIEnv*, jobject, jlong jhandle) { auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); - return rocksdb::StatsLevelJni::toJavaStatsLevel(pSptr_statistics->get()->stats_level_); + return rocksdb::StatsLevelJni::toJavaStatsLevel( + pSptr_statistics->get()->get_stats_level()); } /* @@ -127,12 +127,12 @@ jbyte Java_org_rocksdb_Statistics_statsLevel( * Signature: (JB)V */ void Java_org_rocksdb_Statistics_setStatsLevel( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jstats_level) { + JNIEnv*, jobject, jlong jhandle, jbyte jstats_level) { auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); auto stats_level = rocksdb::StatsLevelJni::toCppStatsLevel(jstats_level); - pSptr_statistics->get()->stats_level_ = stats_level; + pSptr_statistics->get()->set_stats_level(stats_level); } /* @@ -141,12 +141,13 @@ void Java_org_rocksdb_Statistics_setStatsLevel( * Signature: (JB)J */ jlong Java_org_rocksdb_Statistics_getTickerCount( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jticker_type) { + JNIEnv*, jobject, jlong jhandle, jbyte jticker_type) { auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); auto ticker = rocksdb::TickerTypeJni::toCppTickers(jticker_type); - return pSptr_statistics->get()->getTickerCount(ticker); + uint64_t count = pSptr_statistics->get()->getTickerCount(ticker); + return static_cast(count); } /* @@ -155,7 +156,7 @@ jlong Java_org_rocksdb_Statistics_getTickerCount( * Signature: (JB)J */ jlong Java_org_rocksdb_Statistics_getAndResetTickerCount( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jticker_type) { + JNIEnv*, jobject, jlong jhandle, jbyte jticker_type) { auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); @@ -169,33 +170,35 @@ jlong Java_org_rocksdb_Statistics_getAndResetTickerCount( * Signature: (JB)Lorg/rocksdb/HistogramData; */ jobject Java_org_rocksdb_Statistics_getHistogramData( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jhistogram_type) { + JNIEnv* env, jobject, jlong jhandle, jbyte jhistogram_type) { auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); - rocksdb::HistogramData data; // TODO(AR) perhaps better to construct a Java Object Wrapper that uses ptr to C++ `new HistogramData` + // TODO(AR) perhaps better to construct a Java Object Wrapper that + // uses ptr to C++ `new HistogramData` + rocksdb::HistogramData data; + auto histogram = rocksdb::HistogramTypeJni::toCppHistograms(jhistogram_type); pSptr_statistics->get()->histogramData( static_cast(histogram), &data); jclass jclazz = rocksdb::HistogramDataJni::getJClass(env); - if(jclazz == nullptr) { + if (jclazz == nullptr) { // exception occurred accessing class return nullptr; } - jmethodID mid = rocksdb::HistogramDataJni::getConstructorMethodId( - env); - if(mid == nullptr) { + jmethodID mid = rocksdb::HistogramDataJni::getConstructorMethodId(env); + if (mid == nullptr) { // exception occurred accessing method return nullptr; } - return env->NewObject( - jclazz, - mid, data.median, data.percentile95,data.percentile99, data.average, - data.standard_deviation); + return env->NewObject(jclazz, mid, data.median, data.percentile95, + data.percentile99, data.average, + data.standard_deviation, data.max, data.count, + data.sum, data.min); } /* @@ -204,7 +207,7 @@ jobject Java_org_rocksdb_Statistics_getHistogramData( * Signature: (JB)Ljava/lang/String; */ jstring Java_org_rocksdb_Statistics_getHistogramString( - JNIEnv* env, jobject jobj, jlong jhandle, jbyte jhistogram_type) { + JNIEnv* env, jobject, jlong jhandle, jbyte jhistogram_type) { auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); @@ -219,13 +222,13 @@ jstring Java_org_rocksdb_Statistics_getHistogramString( * Signature: (J)V */ void Java_org_rocksdb_Statistics_reset( - JNIEnv* env, jobject jobj, jlong jhandle) { - auto* pSptr_statistics = + JNIEnv* env, jobject, jlong jhandle) { + auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); rocksdb::Status s = pSptr_statistics->get()->Reset(); if (!s.ok()) { - rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } } @@ -235,8 +238,8 @@ void Java_org_rocksdb_Statistics_reset( * Signature: (J)Ljava/lang/String; */ jstring Java_org_rocksdb_Statistics_toString( - JNIEnv* env, jobject jobj, jlong jhandle) { - auto* pSptr_statistics = + JNIEnv* env, jobject, jlong jhandle) { + auto* pSptr_statistics = reinterpret_cast*>(jhandle); assert(pSptr_statistics != nullptr); auto str = pSptr_statistics->get()->ToString(); diff --git a/thirdparty/rocksdb/java/rocksjni/statisticsjni.cc b/thirdparty/rocksdb/java/rocksjni/statisticsjni.cc index 584ab5aa61..f59ace4dfc 100644 --- a/thirdparty/rocksdb/java/rocksjni/statisticsjni.cc +++ b/thirdparty/rocksdb/java/rocksjni/statisticsjni.cc @@ -10,24 +10,23 @@ namespace rocksdb { - StatisticsJni::StatisticsJni(std::shared_ptr stats) - : StatisticsImpl(stats, false), m_ignore_histograms() { - } +StatisticsJni::StatisticsJni(std::shared_ptr stats) + : StatisticsImpl(stats), m_ignore_histograms() {} - StatisticsJni::StatisticsJni(std::shared_ptr stats, - const std::set ignore_histograms) : StatisticsImpl(stats, false), - m_ignore_histograms(ignore_histograms) { - } +StatisticsJni::StatisticsJni(std::shared_ptr stats, + const std::set ignore_histograms) + : StatisticsImpl(stats), m_ignore_histograms(ignore_histograms) {} - bool StatisticsJni::HistEnabledForType(uint32_t type) const { - if (type >= HISTOGRAM_ENUM_MAX) { - return false; - } - - if (m_ignore_histograms.count(type) > 0) { - return false; - } +bool StatisticsJni::HistEnabledForType(uint32_t type) const { + if (type >= HISTOGRAM_ENUM_MAX) { + return false; + } - return true; + if (m_ignore_histograms.count(type) > 0) { + return false; } + + return true; +} +// @lint-ignore TXT4 T25377293 Grandfathered in }; \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/statisticsjni.h b/thirdparty/rocksdb/java/rocksjni/statisticsjni.h index 600d9a6763..56186789a9 100644 --- a/thirdparty/rocksdb/java/rocksjni/statisticsjni.h +++ b/thirdparty/rocksdb/java/rocksjni/statisticsjni.h @@ -30,4 +30,5 @@ namespace rocksdb { } // namespace rocksdb +// @lint-ignore TXT4 T25377293 Grandfathered in #endif // JAVA_ROCKSJNI_STATISTICSJNI_H_ \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/table.cc b/thirdparty/rocksdb/java/rocksjni/table.cc index 5f0a4735fe..1ccc550ab6 100644 --- a/thirdparty/rocksdb/java/rocksjni/table.cc +++ b/thirdparty/rocksdb/java/rocksjni/table.cc @@ -5,10 +5,11 @@ // // This file implements the "bridge" between Java and C++ for rocksdb::Options. +#include "rocksdb/table.h" #include -#include "include/org_rocksdb_PlainTableConfig.h" #include "include/org_rocksdb_BlockBasedTableConfig.h" -#include "rocksdb/table.h" +#include "include/org_rocksdb_PlainTableConfig.h" +#include "portal.h" #include "rocksdb/cache.h" #include "rocksdb/filter_policy.h" @@ -18,18 +19,17 @@ * Signature: (IIDIIBZZ)J */ jlong Java_org_rocksdb_PlainTableConfig_newTableFactoryHandle( - JNIEnv* env, jobject jobj, jint jkey_size, jint jbloom_bits_per_key, - jdouble jhash_table_ratio, jint jindex_sparseness, - jint jhuge_page_tlb_size, jbyte jencoding_type, - jboolean jfull_scan_mode, jboolean jstore_index_in_file) { + JNIEnv * /*env*/, jobject /*jobj*/, jint jkey_size, + jint jbloom_bits_per_key, jdouble jhash_table_ratio, jint jindex_sparseness, + jint jhuge_page_tlb_size, jbyte jencoding_type, jboolean jfull_scan_mode, + jboolean jstore_index_in_file) { rocksdb::PlainTableOptions options = rocksdb::PlainTableOptions(); options.user_key_len = jkey_size; options.bloom_bits_per_key = jbloom_bits_per_key; options.hash_table_ratio = jhash_table_ratio; options.index_sparseness = jindex_sparseness; options.huge_page_tlb_size = jhuge_page_tlb_size; - options.encoding_type = static_cast( - jencoding_type); + options.encoding_type = static_cast(jencoding_type); options.full_scan_mode = jfull_scan_mode; options.store_index_in_file = jstore_index_in_file; return reinterpret_cast(rocksdb::NewPlainTableFactory(options)); @@ -38,55 +38,102 @@ jlong Java_org_rocksdb_PlainTableConfig_newTableFactoryHandle( /* * Class: org_rocksdb_BlockBasedTableConfig * Method: newTableFactoryHandle - * Signature: (ZJIJIIZIZZZJIBBI)J + * Signature: (ZZZZBBDBZJJJJIIIJZZJZZIIZZJIJI)J */ jlong Java_org_rocksdb_BlockBasedTableConfig_newTableFactoryHandle( - JNIEnv* env, jobject jobj, jboolean no_block_cache, jlong block_cache_size, - jint block_cache_num_shardbits, jlong block_size, jint block_size_deviation, - jint block_restart_interval, jboolean whole_key_filtering, - jlong jfilterPolicy, jboolean cache_index_and_filter_blocks, - jboolean pin_l0_filter_and_index_blocks_in_cache, - jboolean hash_index_allow_collision, jlong block_cache_compressed_size, - jint block_cache_compressd_num_shard_bits, jbyte jchecksum_type, - jbyte jindex_type, jint jformat_version) { + JNIEnv*, jobject, jboolean jcache_index_and_filter_blocks, + jboolean jcache_index_and_filter_blocks_with_high_priority, + jboolean jpin_l0_filter_and_index_blocks_in_cache, + jboolean jpin_top_level_index_and_filter, jbyte jindex_type_value, + jbyte jdata_block_index_type_value, + jdouble jdata_block_hash_table_util_ratio, jbyte jchecksum_type_value, + jboolean jno_block_cache, jlong jblock_cache_handle, + jlong jpersistent_cache_handle, + jlong jblock_cache_compressed_handle, jlong jblock_size, + jint jblock_size_deviation, jint jblock_restart_interval, + jint jindex_block_restart_interval, jlong jmetadata_block_size, + jboolean jpartition_filters, jboolean juse_delta_encoding, + jlong jfilter_policy_handle, jboolean jwhole_key_filtering, + jboolean jverify_compression, jint jread_amp_bytes_per_bit, + jint jformat_version, jboolean jenable_index_compression, + jboolean jblock_align, jlong jblock_cache_size, + jint jblock_cache_num_shard_bits, jlong jblock_cache_compressed_size, + jint jblock_cache_compressed_num_shard_bits) { rocksdb::BlockBasedTableOptions options; - options.no_block_cache = no_block_cache; - - if (!no_block_cache && block_cache_size > 0) { - if (block_cache_num_shardbits > 0) { - options.block_cache = - rocksdb::NewLRUCache(block_cache_size, block_cache_num_shardbits); + options.cache_index_and_filter_blocks = + static_cast(jcache_index_and_filter_blocks); + options.cache_index_and_filter_blocks_with_high_priority = + static_cast(jcache_index_and_filter_blocks_with_high_priority); + options.pin_l0_filter_and_index_blocks_in_cache = + static_cast(jpin_l0_filter_and_index_blocks_in_cache); + options.pin_top_level_index_and_filter = + static_cast(jpin_top_level_index_and_filter); + options.index_type = + rocksdb::IndexTypeJni::toCppIndexType(jindex_type_value); + options.data_block_index_type = + rocksdb::DataBlockIndexTypeJni::toCppDataBlockIndexType( + jdata_block_index_type_value); + options.data_block_hash_table_util_ratio = + static_cast(jdata_block_hash_table_util_ratio); + options.checksum = + rocksdb::ChecksumTypeJni::toCppChecksumType(jchecksum_type_value); + options.no_block_cache = static_cast(jno_block_cache); + if (options.no_block_cache) { + options.block_cache = nullptr; + } else { + if (jblock_cache_handle > 0) { + std::shared_ptr *pCache = + reinterpret_cast *>(jblock_cache_handle); + options.block_cache = *pCache; + } else if (jblock_cache_size > 0) { + if (jblock_cache_num_shard_bits > 0) { + options.block_cache = rocksdb::NewLRUCache( + static_cast(jblock_cache_size), + static_cast(jblock_cache_num_shard_bits)); + } else { + options.block_cache = rocksdb::NewLRUCache( + static_cast(jblock_cache_size)); + } + } + } + if (jpersistent_cache_handle > 0) { + std::shared_ptr *pCache = + reinterpret_cast *>(jpersistent_cache_handle); + options.persistent_cache = *pCache; + } + if (jblock_cache_compressed_handle > 0) { + std::shared_ptr *pCache = + reinterpret_cast *>(jblock_cache_compressed_handle); + options.block_cache_compressed = *pCache; + } else if (jblock_cache_compressed_size > 0) { + if (jblock_cache_compressed_num_shard_bits > 0) { + options.block_cache_compressed = rocksdb::NewLRUCache( + static_cast(jblock_cache_compressed_size), + static_cast(jblock_cache_compressed_num_shard_bits)); } else { - options.block_cache = rocksdb::NewLRUCache(block_cache_size); + options.block_cache_compressed = rocksdb::NewLRUCache( + static_cast(jblock_cache_compressed_size)); } } - options.block_size = block_size; - options.block_size_deviation = block_size_deviation; - options.block_restart_interval = block_restart_interval; - options.whole_key_filtering = whole_key_filtering; - if (jfilterPolicy > 0) { + options.block_size = static_cast(jblock_size); + options.block_size_deviation = static_cast(jblock_size_deviation); + options.block_restart_interval = static_cast(jblock_restart_interval); + options.index_block_restart_interval = static_cast(jindex_block_restart_interval); + options.metadata_block_size = static_cast(jmetadata_block_size); + options.partition_filters = static_cast(jpartition_filters); + options.use_delta_encoding = static_cast(juse_delta_encoding); + if (jfilter_policy_handle > 0) { std::shared_ptr *pFilterPolicy = reinterpret_cast *>( - jfilterPolicy); + jfilter_policy_handle); options.filter_policy = *pFilterPolicy; } - options.cache_index_and_filter_blocks = cache_index_and_filter_blocks; - options.pin_l0_filter_and_index_blocks_in_cache = - pin_l0_filter_and_index_blocks_in_cache; - options.hash_index_allow_collision = hash_index_allow_collision; - if (block_cache_compressed_size > 0) { - if (block_cache_compressd_num_shard_bits > 0) { - options.block_cache = - rocksdb::NewLRUCache(block_cache_compressed_size, - block_cache_compressd_num_shard_bits); - } else { - options.block_cache = rocksdb::NewLRUCache(block_cache_compressed_size); - } - } - options.checksum = static_cast(jchecksum_type); - options.index_type = static_cast< - rocksdb::BlockBasedTableOptions::IndexType>(jindex_type); - options.format_version = jformat_version; + options.whole_key_filtering = static_cast(jwhole_key_filtering); + options.verify_compression = static_cast(jverify_compression); + options.read_amp_bytes_per_bit = static_cast(jread_amp_bytes_per_bit); + options.format_version = static_cast(jformat_version); + options.enable_index_compression = static_cast(jenable_index_compression); + options.block_align = static_cast(jblock_align); return reinterpret_cast(rocksdb::NewBlockBasedTableFactory(options)); } diff --git a/thirdparty/rocksdb/java/rocksjni/table_filter.cc b/thirdparty/rocksdb/java/rocksjni/table_filter.cc new file mode 100644 index 0000000000..e5b3556213 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/table_filter.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// org.rocksdb.AbstractTableFilter. + +#include +#include + +#include "include/org_rocksdb_AbstractTableFilter.h" +#include "rocksjni/table_filter_jnicallback.h" + +/* + * Class: org_rocksdb_AbstractTableFilter + * Method: createNewTableFilter + * Signature: ()J + */ +jlong Java_org_rocksdb_AbstractTableFilter_createNewTableFilter( + JNIEnv* env, jobject jtable_filter) { + auto* table_filter_jnicallback = + new rocksdb::TableFilterJniCallback(env, jtable_filter); + return reinterpret_cast(table_filter_jnicallback); +} \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/table_filter_jnicallback.cc b/thirdparty/rocksdb/java/rocksjni/table_filter_jnicallback.cc new file mode 100644 index 0000000000..680c014459 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/table_filter_jnicallback.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::TableFilter. + +#include "rocksjni/table_filter_jnicallback.h" +#include "rocksjni/portal.h" + +namespace rocksdb { +TableFilterJniCallback::TableFilterJniCallback( + JNIEnv* env, jobject jtable_filter) + : JniCallback(env, jtable_filter) { + m_jfilter_methodid = + AbstractTableFilterJni::getFilterMethod(env); + if(m_jfilter_methodid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } + + // create the function reference + /* + Note the JNI ENV must be obtained/release + on each call to the function itself as + it may be called from multiple threads + */ + m_table_filter_function = [this](const rocksdb::TableProperties& table_properties) { + jboolean attached_thread = JNI_FALSE; + JNIEnv* thread_env = getJniEnv(&attached_thread); + assert(thread_env != nullptr); + + // create a Java TableProperties object + jobject jtable_properties = TablePropertiesJni::fromCppTableProperties(thread_env, table_properties); + if (jtable_properties == nullptr) { + // exception thrown from fromCppTableProperties + thread_env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return false; + } + + jboolean result = thread_env->CallBooleanMethod(m_jcallback_obj, m_jfilter_methodid, jtable_properties); + if (thread_env->ExceptionCheck()) { + // exception thrown from CallBooleanMethod + thread_env->DeleteLocalRef(jtable_properties); + thread_env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return false; + } + + // ok... cleanup and then return + releaseJniEnv(attached_thread); + return static_cast(result); + }; +} + +std::function TableFilterJniCallback::GetTableFilterFunction() { + return m_table_filter_function; +} + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/java/rocksjni/table_filter_jnicallback.h b/thirdparty/rocksdb/java/rocksjni/table_filter_jnicallback.h new file mode 100644 index 0000000000..39a0c90e0e --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/table_filter_jnicallback.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::TableFilter. + +#ifndef JAVA_ROCKSJNI_TABLE_FILTER_JNICALLBACK_H_ +#define JAVA_ROCKSJNI_TABLE_FILTER_JNICALLBACK_H_ + +#include +#include +#include + +#include "rocksdb/table_properties.h" +#include "rocksjni/jnicallback.h" + +namespace rocksdb { + +class TableFilterJniCallback : public JniCallback { + public: + TableFilterJniCallback( + JNIEnv* env, jobject jtable_filter); + std::function GetTableFilterFunction(); + + private: + jmethodID m_jfilter_methodid; + std::function m_table_filter_function; +}; + +} //namespace rocksdb + +#endif // JAVA_ROCKSJNI_TABLE_FILTER_JNICALLBACK_H_ diff --git a/thirdparty/rocksdb/java/rocksjni/thread_status.cc b/thirdparty/rocksdb/java/rocksjni/thread_status.cc new file mode 100644 index 0000000000..f70d515a5b --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/thread_status.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ and enables +// calling c++ rocksdb::ThreadStatus methods from Java side. + +#include + +#include "portal.h" +#include "include/org_rocksdb_ThreadStatus.h" +#include "rocksdb/thread_status.h" + +/* + * Class: org_rocksdb_ThreadStatus + * Method: getThreadTypeName + * Signature: (B)Ljava/lang/String; + */ +jstring Java_org_rocksdb_ThreadStatus_getThreadTypeName( + JNIEnv* env, jclass, jbyte jthread_type_value) { + auto name = rocksdb::ThreadStatus::GetThreadTypeName( + rocksdb::ThreadTypeJni::toCppThreadType(jthread_type_value)); + return rocksdb::JniUtil::toJavaString(env, &name, true); +} + +/* + * Class: org_rocksdb_ThreadStatus + * Method: getOperationName + * Signature: (B)Ljava/lang/String; + */ +jstring Java_org_rocksdb_ThreadStatus_getOperationName( + JNIEnv* env, jclass, jbyte joperation_type_value) { + auto name = rocksdb::ThreadStatus::GetOperationName( + rocksdb::OperationTypeJni::toCppOperationType(joperation_type_value)); + return rocksdb::JniUtil::toJavaString(env, &name, true); +} + +/* + * Class: org_rocksdb_ThreadStatus + * Method: microsToStringNative + * Signature: (J)Ljava/lang/String; + */ +jstring Java_org_rocksdb_ThreadStatus_microsToStringNative( + JNIEnv* env, jclass, jlong jmicros) { + auto str = + rocksdb::ThreadStatus::MicrosToString(static_cast(jmicros)); + return rocksdb::JniUtil::toJavaString(env, &str, true); +} + +/* + * Class: org_rocksdb_ThreadStatus + * Method: getOperationStageName + * Signature: (B)Ljava/lang/String; + */ +jstring Java_org_rocksdb_ThreadStatus_getOperationStageName( + JNIEnv* env, jclass, jbyte joperation_stage_value) { + auto name = rocksdb::ThreadStatus::GetOperationStageName( + rocksdb::OperationStageJni::toCppOperationStage(joperation_stage_value)); + return rocksdb::JniUtil::toJavaString(env, &name, true); +} + +/* + * Class: org_rocksdb_ThreadStatus + * Method: getOperationPropertyName + * Signature: (BI)Ljava/lang/String; + */ +jstring Java_org_rocksdb_ThreadStatus_getOperationPropertyName( + JNIEnv* env, jclass, jbyte joperation_type_value, jint jindex) { + auto name = rocksdb::ThreadStatus::GetOperationPropertyName( + rocksdb::OperationTypeJni::toCppOperationType(joperation_type_value), + static_cast(jindex)); + return rocksdb::JniUtil::toJavaString(env, &name, true); +} + +/* + * Class: org_rocksdb_ThreadStatus + * Method: interpretOperationProperties + * Signature: (B[J)Ljava/util/Map; + */ +jobject Java_org_rocksdb_ThreadStatus_interpretOperationProperties( + JNIEnv* env, jclass, jbyte joperation_type_value, + jlongArray joperation_properties) { + + //convert joperation_properties + const jsize len = env->GetArrayLength(joperation_properties); + const std::unique_ptr op_properties(new uint64_t[len]); + jlong* jop = env->GetLongArrayElements(joperation_properties, nullptr); + if (jop == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + for (jsize i = 0; i < len; i++) { + op_properties[i] = static_cast(jop[i]); + } + env->ReleaseLongArrayElements(joperation_properties, jop, JNI_ABORT); + + // call the function + auto result = rocksdb::ThreadStatus::InterpretOperationProperties( + rocksdb::OperationTypeJni::toCppOperationType(joperation_type_value), + op_properties.get()); + jobject jresult = rocksdb::HashMapJni::fromCppMap(env, &result); + if (env->ExceptionCheck()) { + // exception occurred + return nullptr; + } + + return jresult; +} + +/* + * Class: org_rocksdb_ThreadStatus + * Method: getStateName + * Signature: (B)Ljava/lang/String; + */ +jstring Java_org_rocksdb_ThreadStatus_getStateName( + JNIEnv* env, jclass, jbyte jstate_type_value) { + auto name = rocksdb::ThreadStatus::GetStateName( + rocksdb::StateTypeJni::toCppStateType(jstate_type_value)); + return rocksdb::JniUtil::toJavaString(env, &name, true); +} \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/trace_writer.cc b/thirdparty/rocksdb/java/rocksjni/trace_writer.cc new file mode 100644 index 0000000000..5d47cfcb3a --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/trace_writer.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::CompactionFilterFactory. + +#include + +#include "include/org_rocksdb_AbstractTraceWriter.h" +#include "rocksjni/trace_writer_jnicallback.h" + +/* + * Class: org_rocksdb_AbstractTraceWriter + * Method: createNewTraceWriter + * Signature: ()J + */ +jlong Java_org_rocksdb_AbstractTraceWriter_createNewTraceWriter( + JNIEnv* env, jobject jobj) { + auto* trace_writer = new rocksdb::TraceWriterJniCallback(env, jobj); + return reinterpret_cast(trace_writer); +} diff --git a/thirdparty/rocksdb/java/rocksjni/trace_writer_jnicallback.cc b/thirdparty/rocksdb/java/rocksjni/trace_writer_jnicallback.cc new file mode 100644 index 0000000000..d547fb3f87 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/trace_writer_jnicallback.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::TraceWriter. + +#include "rocksjni/trace_writer_jnicallback.h" +#include "rocksjni/portal.h" + +namespace rocksdb { +TraceWriterJniCallback::TraceWriterJniCallback( + JNIEnv* env, jobject jtrace_writer) + : JniCallback(env, jtrace_writer) { + m_jwrite_proxy_methodid = + AbstractTraceWriterJni::getWriteProxyMethodId(env); + if(m_jwrite_proxy_methodid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } + + m_jclose_writer_proxy_methodid = + AbstractTraceWriterJni::getCloseWriterProxyMethodId(env); + if(m_jclose_writer_proxy_methodid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } + + m_jget_file_size_methodid = + AbstractTraceWriterJni::getGetFileSizeMethodId(env); + if(m_jget_file_size_methodid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } +} + +Status TraceWriterJniCallback::Write(const Slice& data) { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + if (env == nullptr) { + return Status::IOError("Unable to attach JNI Environment"); + } + + jshort jstatus = env->CallShortMethod(m_jcallback_obj, + m_jwrite_proxy_methodid, + &data); + + if(env->ExceptionCheck()) { + // exception thrown from CallShortMethod + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return Status::IOError("Unable to call AbstractTraceWriter#writeProxy(long)"); + } + + // unpack status code and status sub-code from jstatus + jbyte jcode_value = (jstatus >> 8) & 0xFF; + jbyte jsub_code_value = jstatus & 0xFF; + std::unique_ptr s = StatusJni::toCppStatus(jcode_value, jsub_code_value); + + releaseJniEnv(attached_thread); + + return Status(*s); +} + +Status TraceWriterJniCallback::Close() { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + if (env == nullptr) { + return Status::IOError("Unable to attach JNI Environment"); + } + + jshort jstatus = env->CallShortMethod(m_jcallback_obj, + m_jclose_writer_proxy_methodid); + + if(env->ExceptionCheck()) { + // exception thrown from CallShortMethod + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return Status::IOError("Unable to call AbstractTraceWriter#closeWriterProxy()"); + } + + // unpack status code and status sub-code from jstatus + jbyte code_value = (jstatus >> 8) & 0xFF; + jbyte sub_code_value = jstatus & 0xFF; + std::unique_ptr s = StatusJni::toCppStatus(code_value, sub_code_value); + + releaseJniEnv(attached_thread); + + return Status(*s); +} + +uint64_t TraceWriterJniCallback::GetFileSize() { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + if (env == nullptr) { + return 0; + } + + jlong jfile_size = env->CallLongMethod(m_jcallback_obj, + m_jget_file_size_methodid); + + if(env->ExceptionCheck()) { + // exception thrown from CallLongMethod + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return 0; + } + + releaseJniEnv(attached_thread); + + return static_cast(jfile_size); +} + +} // namespace rocksdb \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/trace_writer_jnicallback.h b/thirdparty/rocksdb/java/rocksjni/trace_writer_jnicallback.h new file mode 100644 index 0000000000..610b6c465f --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/trace_writer_jnicallback.h @@ -0,0 +1,36 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::TraceWriter. + +#ifndef JAVA_ROCKSJNI_TRACE_WRITER_JNICALLBACK_H_ +#define JAVA_ROCKSJNI_TRACE_WRITER_JNICALLBACK_H_ + +#include +#include + +#include "rocksdb/trace_reader_writer.h" +#include "rocksjni/jnicallback.h" + +namespace rocksdb { + +class TraceWriterJniCallback : public JniCallback, public TraceWriter { + public: + TraceWriterJniCallback( + JNIEnv* env, jobject jtrace_writer); + virtual Status Write(const Slice& data); + virtual Status Close(); + virtual uint64_t GetFileSize(); + + private: + jmethodID m_jwrite_proxy_methodid; + jmethodID m_jclose_writer_proxy_methodid; + jmethodID m_jget_file_size_methodid; +}; + +} //namespace rocksdb + +#endif // JAVA_ROCKSJNI_TRACE_WRITER_JNICALLBACK_H_ diff --git a/thirdparty/rocksdb/java/rocksjni/transaction.cc b/thirdparty/rocksdb/java/rocksjni/transaction.cc new file mode 100644 index 0000000000..04eb654df7 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/transaction.cc @@ -0,0 +1,1584 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ +// for rocksdb::Transaction. + +#include +#include + +#include "include/org_rocksdb_Transaction.h" + +#include "rocksdb/utilities/transaction.h" +#include "rocksjni/portal.h" + +using namespace std::placeholders; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4503) // identifier' : decorated name length + // exceeded, name was truncated +#endif + +/* + * Class: org_rocksdb_Transaction + * Method: setSnapshot + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_setSnapshot(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + txn->SetSnapshot(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: setSnapshotOnNextOperation + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_setSnapshotOnNextOperation__J( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + txn->SetSnapshotOnNextOperation(nullptr); +} + +/* + * Class: org_rocksdb_Transaction + * Method: setSnapshotOnNextOperation + * Signature: (JJ)V + */ +void Java_org_rocksdb_Transaction_setSnapshotOnNextOperation__JJ( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jtxn_notifier_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* txn_notifier = + reinterpret_cast*>( + jtxn_notifier_handle); + txn->SetSnapshotOnNextOperation(*txn_notifier); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getSnapshot + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getSnapshot(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + const rocksdb::Snapshot* snapshot = txn->GetSnapshot(); + return reinterpret_cast(snapshot); +} + +/* + * Class: org_rocksdb_Transaction + * Method: clearSnapshot + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_clearSnapshot(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + txn->ClearSnapshot(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: prepare + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_prepare(JNIEnv* env, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + rocksdb::Status s = txn->Prepare(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_Transaction + * Method: commit + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_commit(JNIEnv* env, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + rocksdb::Status s = txn->Commit(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_Transaction + * Method: rollback + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_rollback(JNIEnv* env, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + rocksdb::Status s = txn->Rollback(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_Transaction + * Method: setSavePoint + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_setSavePoint(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + txn->SetSavePoint(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: rollbackToSavePoint + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_rollbackToSavePoint(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + rocksdb::Status s = txn->RollbackToSavePoint(); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +typedef std::function + FnGet; + +// TODO(AR) consider refactoring to share this between here and rocksjni.cc +jbyteArray txn_get_helper(JNIEnv* env, const FnGet& fn_get, + const jlong& jread_options_handle, + const jbyteArray& jkey, const jint& jkey_part_len) { + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_part_len); + + auto* read_options = + reinterpret_cast(jread_options_handle); + std::string value; + rocksdb::Status s = fn_get(*read_options, key_slice, &value); + + // trigger java unref on key. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + + if (s.IsNotFound()) { + return nullptr; + } + + if (s.ok()) { + jbyteArray jret_value = env->NewByteArray(static_cast(value.size())); + if (jret_value == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + env->SetByteArrayRegion(jret_value, 0, static_cast(value.size()), + const_cast(reinterpret_cast(value.c_str()))); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + return nullptr; + } + return jret_value; + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; +} + +/* + * Class: org_rocksdb_Transaction + * Method: get + * Signature: (JJ[BIJ)[B + */ +jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BIJ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jbyteArray jkey, jint jkey_part_len, jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnGet fn_get = std::bind(&rocksdb::Transaction::Get, txn, _1, + column_family_handle, _2, _3); + return txn_get_helper(env, fn_get, jread_options_handle, jkey, jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: get + * Signature: (JJ[BI)[B + */ +jbyteArray Java_org_rocksdb_Transaction_get__JJ_3BI( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jbyteArray jkey, jint jkey_part_len) { + auto* txn = reinterpret_cast(jhandle); + FnGet fn_get = std::bind( + &rocksdb::Transaction::Get, txn, _1, _2, _3); + return txn_get_helper(env, fn_get, jread_options_handle, jkey, jkey_part_len); +} + +// TODO(AR) consider refactoring to share this between here and rocksjni.cc +// used by txn_multi_get_helper below +std::vector txn_column_families_helper( + JNIEnv* env, jlongArray jcolumn_family_handles, bool* has_exception) { + std::vector cf_handles; + if (jcolumn_family_handles != nullptr) { + const jsize len_cols = env->GetArrayLength(jcolumn_family_handles); + if (len_cols > 0) { + if (env->EnsureLocalCapacity(len_cols) != 0) { + // out of memory + *has_exception = JNI_TRUE; + return std::vector(); + } + + jlong* jcfh = env->GetLongArrayElements(jcolumn_family_handles, nullptr); + if (jcfh == nullptr) { + // exception thrown: OutOfMemoryError + *has_exception = JNI_TRUE; + return std::vector(); + } + for (int i = 0; i < len_cols; i++) { + auto* cf_handle = + reinterpret_cast(jcfh[i]); + cf_handles.push_back(cf_handle); + } + env->ReleaseLongArrayElements(jcolumn_family_handles, jcfh, JNI_ABORT); + } + } + return cf_handles; +} + +typedef std::function( + const rocksdb::ReadOptions&, const std::vector&, + std::vector*)> + FnMultiGet; + +void free_parts( + JNIEnv* env, + std::vector>& parts_to_free) { + for (auto& value : parts_to_free) { + jobject jk; + jbyteArray jk_ba; + jbyte* jk_val; + std::tie(jk_ba, jk_val, jk) = value; + env->ReleaseByteArrayElements(jk_ba, jk_val, JNI_ABORT); + env->DeleteLocalRef(jk); + } +} + +// TODO(AR) consider refactoring to share this between here and rocksjni.cc +// cf multi get +jobjectArray txn_multi_get_helper(JNIEnv* env, const FnMultiGet& fn_multi_get, + const jlong& jread_options_handle, + const jobjectArray& jkey_parts) { + const jsize len_key_parts = env->GetArrayLength(jkey_parts); + if (env->EnsureLocalCapacity(len_key_parts) != 0) { + // out of memory + return nullptr; + } + + std::vector key_parts; + std::vector> key_parts_to_free; + for (int i = 0; i < len_key_parts; i++) { + const jobject jk = env->GetObjectArrayElement(jkey_parts, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + free_parts(env, key_parts_to_free); + return nullptr; + } + jbyteArray jk_ba = reinterpret_cast(jk); + const jsize len_key = env->GetArrayLength(jk_ba); + if (env->EnsureLocalCapacity(len_key) != 0) { + // out of memory + env->DeleteLocalRef(jk); + free_parts(env, key_parts_to_free); + return nullptr; + } + jbyte* jk_val = env->GetByteArrayElements(jk_ba, nullptr); + if (jk_val == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jk); + free_parts(env, key_parts_to_free); + return nullptr; + } + + rocksdb::Slice key_slice(reinterpret_cast(jk_val), len_key); + key_parts.push_back(key_slice); + + key_parts_to_free.push_back(std::make_tuple(jk_ba, jk_val, jk)); + } + + auto* read_options = + reinterpret_cast(jread_options_handle); + std::vector value_parts; + std::vector s = + fn_multi_get(*read_options, key_parts, &value_parts); + + // free up allocated byte arrays + free_parts(env, key_parts_to_free); + + // prepare the results + const jclass jcls_ba = env->FindClass("[B"); + jobjectArray jresults = + env->NewObjectArray(static_cast(s.size()), jcls_ba, nullptr); + if (jresults == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + // add to the jresults + for (std::vector::size_type i = 0; i != s.size(); i++) { + if (s[i].ok()) { + jbyteArray jentry_value = + env->NewByteArray(static_cast(value_parts[i].size())); + if (jentry_value == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + env->SetByteArrayRegion( + jentry_value, 0, static_cast(value_parts[i].size()), + const_cast(reinterpret_cast(value_parts[i].c_str()))); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jentry_value); + return nullptr; + } + + env->SetObjectArrayElement(jresults, static_cast(i), jentry_value); + env->DeleteLocalRef(jentry_value); + } + } + + return jresults; +} + +/* + * Class: org_rocksdb_Transaction + * Method: multiGet + * Signature: (JJ[[B[J)[[B + */ +jobjectArray Java_org_rocksdb_Transaction_multiGet__JJ_3_3B_3J( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jobjectArray jkey_parts, jlongArray jcolumn_family_handles) { + bool has_exception = false; + const std::vector column_family_handles = + txn_column_families_helper(env, jcolumn_family_handles, &has_exception); + if (has_exception) { + // exception thrown: OutOfMemoryError + return nullptr; + } + auto* txn = reinterpret_cast(jhandle); + FnMultiGet fn_multi_get = + std::bind (rocksdb::Transaction::*)( + const rocksdb::ReadOptions&, + const std::vector&, + const std::vector&, std::vector*)>( + &rocksdb::Transaction::MultiGet, txn, _1, column_family_handles, _2, + _3); + return txn_multi_get_helper(env, fn_multi_get, jread_options_handle, + jkey_parts); +} + +/* + * Class: org_rocksdb_Transaction + * Method: multiGet + * Signature: (JJ[[B)[[B + */ +jobjectArray Java_org_rocksdb_Transaction_multiGet__JJ_3_3B( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jobjectArray jkey_parts) { + auto* txn = reinterpret_cast(jhandle); + FnMultiGet fn_multi_get = + std::bind (rocksdb::Transaction::*)( + const rocksdb::ReadOptions&, const std::vector&, + std::vector*)>(&rocksdb::Transaction::MultiGet, txn, _1, + _2, _3); + return txn_multi_get_helper(env, fn_multi_get, jread_options_handle, + jkey_parts); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getForUpdate + * Signature: (JJ[BIJZZ)[B + */ +jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIJZZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jbyteArray jkey, jint jkey_part_len, jlong jcolumn_family_handle, + jboolean jexclusive, jboolean jdo_validate) { + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + auto* txn = reinterpret_cast(jhandle); + FnGet fn_get_for_update = std::bind( + &rocksdb::Transaction::GetForUpdate, txn, _1, column_family_handle, _2, + _3, jexclusive, jdo_validate); + return txn_get_helper(env, fn_get_for_update, jread_options_handle, jkey, + jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getForUpdate + * Signature: (JJ[BIZZ)[B + */ +jbyteArray Java_org_rocksdb_Transaction_getForUpdate__JJ_3BIZZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jbyteArray jkey, jint jkey_part_len, jboolean jexclusive, + jboolean jdo_validate) { + auto* txn = reinterpret_cast(jhandle); + FnGet fn_get_for_update = std::bind(&rocksdb::Transaction::GetForUpdate, txn, _1, _2, _3, jexclusive, + jdo_validate); + return txn_get_helper(env, fn_get_for_update, jread_options_handle, jkey, + jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: multiGetForUpdate + * Signature: (JJ[[B[J)[[B + */ +jobjectArray Java_org_rocksdb_Transaction_multiGetForUpdate__JJ_3_3B_3J( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jobjectArray jkey_parts, jlongArray jcolumn_family_handles) { + bool has_exception = false; + const std::vector column_family_handles = + txn_column_families_helper(env, jcolumn_family_handles, &has_exception); + if (has_exception) { + // exception thrown: OutOfMemoryError + return nullptr; + } + auto* txn = reinterpret_cast(jhandle); + FnMultiGet fn_multi_get_for_update = + std::bind (rocksdb::Transaction::*)( + const rocksdb::ReadOptions&, + const std::vector&, + const std::vector&, std::vector*)>( + &rocksdb::Transaction::MultiGetForUpdate, txn, _1, + column_family_handles, _2, _3); + return txn_multi_get_helper(env, fn_multi_get_for_update, + jread_options_handle, jkey_parts); +} + +/* + * Class: org_rocksdb_Transaction + * Method: multiGetForUpdate + * Signature: (JJ[[B)[[B + */ +jobjectArray Java_org_rocksdb_Transaction_multiGetForUpdate__JJ_3_3B( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jread_options_handle, + jobjectArray jkey_parts) { + auto* txn = reinterpret_cast(jhandle); + FnMultiGet fn_multi_get_for_update = + std::bind (rocksdb::Transaction::*)( + const rocksdb::ReadOptions&, const std::vector&, + std::vector*)>(&rocksdb::Transaction::MultiGetForUpdate, + txn, _1, _2, _3); + return txn_multi_get_helper(env, fn_multi_get_for_update, + jread_options_handle, jkey_parts); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getIterator + * Signature: (JJ)J + */ +jlong Java_org_rocksdb_Transaction_getIterator__JJ(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jlong jread_options_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* read_options = + reinterpret_cast(jread_options_handle); + return reinterpret_cast(txn->GetIterator(*read_options)); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getIterator + * Signature: (JJJ)J + */ +jlong Java_org_rocksdb_Transaction_getIterator__JJJ( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jread_options_handle, jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* read_options = + reinterpret_cast(jread_options_handle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + return reinterpret_cast( + txn->GetIterator(*read_options, column_family_handle)); +} + +typedef std::function + FnWriteKV; + +// TODO(AR) consider refactoring to share this between here and rocksjni.cc +void txn_write_kv_helper(JNIEnv* env, const FnWriteKV& fn_write_kv, + const jbyteArray& jkey, const jint& jkey_part_len, + const jbyteArray& jval, const jint& jval_len) { + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + jbyte* value = env->GetByteArrayElements(jval, nullptr); + if (value == nullptr) { + // exception thrown: OutOfMemoryError + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + return; + } + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_part_len); + rocksdb::Slice value_slice(reinterpret_cast(value), jval_len); + + rocksdb::Status s = fn_write_kv(key_slice, value_slice); + + // trigger java unref on key. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jval, value, JNI_ABORT); + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + + if (s.ok()) { + return; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_Transaction + * Method: put + * Signature: (J[BI[BIJZ)V + */ +void Java_org_rocksdb_Transaction_put__J_3BI_3BIJZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jbyteArray jval, jint jval_len, + jlong jcolumn_family_handle, jboolean jassume_tracked) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKV fn_put = std::bind(&rocksdb::Transaction::Put, txn, + column_family_handle, _1, _2, + jassume_tracked); + txn_write_kv_helper(env, fn_put, jkey, jkey_part_len, jval, jval_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: put + * Signature: (J[BI[BI)V + */ +void Java_org_rocksdb_Transaction_put__J_3BI_3BI(JNIEnv* env, jobject /*jobj*/, + jlong jhandle, jbyteArray jkey, + jint jkey_part_len, + jbyteArray jval, + jint jval_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKV fn_put = std::bind(&rocksdb::Transaction::Put, + txn, _1, _2); + txn_write_kv_helper(env, fn_put, jkey, jkey_part_len, jval, jval_len); +} + +typedef std::function + FnWriteKVParts; + +// TODO(AR) consider refactoring to share this between here and rocksjni.cc +void txn_write_kv_parts_helper(JNIEnv* env, + const FnWriteKVParts& fn_write_kv_parts, + const jobjectArray& jkey_parts, + const jint& jkey_parts_len, + const jobjectArray& jvalue_parts, + const jint& jvalue_parts_len) { +#ifndef DEBUG + (void) jvalue_parts_len; +#else + assert(jkey_parts_len == jvalue_parts_len); +#endif + + auto key_parts = std::vector(); + auto value_parts = std::vector(); + auto jparts_to_free = std::vector>(); + + // convert java key_parts/value_parts byte[][] to Slice(s) + for (jsize i = 0; i < jkey_parts_len; ++i) { + const jobject jobj_key_part = env->GetObjectArrayElement(jkey_parts, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + free_parts(env, jparts_to_free); + return; + } + const jobject jobj_value_part = env->GetObjectArrayElement(jvalue_parts, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jobj_key_part); + free_parts(env, jparts_to_free); + return; + } + + const jbyteArray jba_key_part = reinterpret_cast(jobj_key_part); + const jsize jkey_part_len = env->GetArrayLength(jba_key_part); + if (env->EnsureLocalCapacity(jkey_part_len) != 0) { + // out of memory + env->DeleteLocalRef(jobj_value_part); + env->DeleteLocalRef(jobj_key_part); + free_parts(env, jparts_to_free); + return; + } + jbyte* jkey_part = env->GetByteArrayElements(jba_key_part, nullptr); + if (jkey_part == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jobj_value_part); + env->DeleteLocalRef(jobj_key_part); + free_parts(env, jparts_to_free); + return; + } + + const jbyteArray jba_value_part = + reinterpret_cast(jobj_value_part); + const jsize jvalue_part_len = env->GetArrayLength(jba_value_part); + if (env->EnsureLocalCapacity(jvalue_part_len) != 0) { + // out of memory + env->DeleteLocalRef(jobj_value_part); + env->DeleteLocalRef(jobj_key_part); + free_parts(env, jparts_to_free); + return; + } + jbyte* jvalue_part = env->GetByteArrayElements(jba_value_part, nullptr); + if (jvalue_part == nullptr) { + // exception thrown: OutOfMemoryError + env->ReleaseByteArrayElements(jba_value_part, jvalue_part, JNI_ABORT); + env->DeleteLocalRef(jobj_value_part); + env->DeleteLocalRef(jobj_key_part); + free_parts(env, jparts_to_free); + return; + } + + jparts_to_free.push_back( + std::make_tuple(jba_key_part, jkey_part, jobj_key_part)); + jparts_to_free.push_back( + std::make_tuple(jba_value_part, jvalue_part, jobj_value_part)); + + key_parts.push_back( + rocksdb::Slice(reinterpret_cast(jkey_part), jkey_part_len)); + value_parts.push_back( + rocksdb::Slice(reinterpret_cast(jvalue_part), jvalue_part_len)); + } + + // call the write_multi function + rocksdb::Status s = fn_write_kv_parts( + rocksdb::SliceParts(key_parts.data(), (int)key_parts.size()), + rocksdb::SliceParts(value_parts.data(), (int)value_parts.size())); + + // cleanup temporary memory + free_parts(env, jparts_to_free); + + // return + if (s.ok()) { + return; + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_Transaction + * Method: put + * Signature: (J[[BI[[BIJZ)V + */ +void Java_org_rocksdb_Transaction_put__J_3_3BI_3_3BIJZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len, + jlong jcolumn_family_handle, jboolean jassume_tracked) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKVParts fn_put_parts = + std::bind(&rocksdb::Transaction::Put, txn, + column_family_handle, _1, _2, + jassume_tracked); + txn_write_kv_parts_helper(env, fn_put_parts, jkey_parts, jkey_parts_len, + jvalue_parts, jvalue_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: put + * Signature: (J[[BI[[BI)V + */ +void Java_org_rocksdb_Transaction_put__J_3_3BI_3_3BI( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKVParts fn_put_parts = + std::bind( + &rocksdb::Transaction::Put, txn, _1, _2); + txn_write_kv_parts_helper(env, fn_put_parts, jkey_parts, jkey_parts_len, + jvalue_parts, jvalue_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: merge + * Signature: (J[BI[BIJZ)V + */ +void Java_org_rocksdb_Transaction_merge__J_3BI_3BIJZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jbyteArray jval, jint jval_len, + jlong jcolumn_family_handle, jboolean jassume_tracked) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKV fn_merge = std::bind(&rocksdb::Transaction::Merge, txn, + column_family_handle, _1, _2, + jassume_tracked); + txn_write_kv_helper(env, fn_merge, jkey, jkey_part_len, jval, jval_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: merge + * Signature: (J[BI[BI)V + */ +void Java_org_rocksdb_Transaction_merge__J_3BI_3BI( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jbyteArray jval, jint jval_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKV fn_merge = std::bind( + &rocksdb::Transaction::Merge, txn, _1, _2); + txn_write_kv_helper(env, fn_merge, jkey, jkey_part_len, jval, jval_len); +} + +typedef std::function FnWriteK; + +// TODO(AR) consider refactoring to share this between here and rocksjni.cc +void txn_write_k_helper(JNIEnv* env, const FnWriteK& fn_write_k, + const jbyteArray& jkey, const jint& jkey_part_len) { + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_part_len); + + rocksdb::Status s = fn_write_k(key_slice); + + // trigger java unref on key. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + + if (s.ok()) { + return; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_Transaction + * Method: delete + * Signature: (J[BIJZ)V + */ +void Java_org_rocksdb_Transaction_delete__J_3BIJZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jlong jcolumn_family_handle, jboolean jassume_tracked) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteK fn_delete = std::bind( + &rocksdb::Transaction::Delete, txn, column_family_handle, _1, + jassume_tracked); + txn_write_k_helper(env, fn_delete, jkey, jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: delete + * Signature: (J[BI)V + */ +void Java_org_rocksdb_Transaction_delete__J_3BI(JNIEnv* env, jobject /*jobj*/, + jlong jhandle, jbyteArray jkey, + jint jkey_part_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteK fn_delete = std::bind(&rocksdb::Transaction::Delete, txn, _1); + txn_write_k_helper(env, fn_delete, jkey, jkey_part_len); +} + +typedef std::function + FnWriteKParts; + +// TODO(AR) consider refactoring to share this between here and rocksjni.cc +void txn_write_k_parts_helper(JNIEnv* env, + const FnWriteKParts& fn_write_k_parts, + const jobjectArray& jkey_parts, + const jint& jkey_parts_len) { + std::vector key_parts; + std::vector> jkey_parts_to_free; + + // convert java key_parts byte[][] to Slice(s) + for (jint i = 0; i < jkey_parts_len; ++i) { + const jobject jobj_key_part = env->GetObjectArrayElement(jkey_parts, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + free_parts(env, jkey_parts_to_free); + return; + } + + const jbyteArray jba_key_part = reinterpret_cast(jobj_key_part); + const jsize jkey_part_len = env->GetArrayLength(jba_key_part); + if (env->EnsureLocalCapacity(jkey_part_len) != 0) { + // out of memory + env->DeleteLocalRef(jobj_key_part); + free_parts(env, jkey_parts_to_free); + return; + } + jbyte* jkey_part = env->GetByteArrayElements(jba_key_part, nullptr); + if (jkey_part == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jobj_key_part); + free_parts(env, jkey_parts_to_free); + return; + } + + jkey_parts_to_free.push_back(std::tuple( + jba_key_part, jkey_part, jobj_key_part)); + + key_parts.push_back( + rocksdb::Slice(reinterpret_cast(jkey_part), jkey_part_len)); + } + + // call the write_multi function + rocksdb::Status s = fn_write_k_parts( + rocksdb::SliceParts(key_parts.data(), (int)key_parts.size())); + + // cleanup temporary memory + free_parts(env, jkey_parts_to_free); + + // return + if (s.ok()) { + return; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_Transaction + * Method: delete + * Signature: (J[[BIJZ)V + */ +void Java_org_rocksdb_Transaction_delete__J_3_3BIJZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len, jlong jcolumn_family_handle, + jboolean jassume_tracked) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKParts fn_delete_parts = + std::bind( + &rocksdb::Transaction::Delete, txn, column_family_handle, _1, + jassume_tracked); + txn_write_k_parts_helper(env, fn_delete_parts, jkey_parts, jkey_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: delete + * Signature: (J[[BI)V + */ +void Java_org_rocksdb_Transaction_delete__J_3_3BI(JNIEnv* env, jobject /*jobj*/, + jlong jhandle, + jobjectArray jkey_parts, + jint jkey_parts_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKParts fn_delete_parts = + std::bind(&rocksdb::Transaction::Delete, txn, _1); + txn_write_k_parts_helper(env, fn_delete_parts, jkey_parts, jkey_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: singleDelete + * Signature: (J[BIJZ)V + */ +void Java_org_rocksdb_Transaction_singleDelete__J_3BIJZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jlong jcolumn_family_handle, jboolean jassume_tracked) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteK fn_single_delete = + std::bind( + &rocksdb::Transaction::SingleDelete, txn, column_family_handle, _1, + jassume_tracked); + txn_write_k_helper(env, fn_single_delete, jkey, jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: singleDelete + * Signature: (J[BI)V + */ +void Java_org_rocksdb_Transaction_singleDelete__J_3BI(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle, + jbyteArray jkey, + jint jkey_part_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteK fn_single_delete = + std::bind(&rocksdb::Transaction::SingleDelete, txn, _1); + txn_write_k_helper(env, fn_single_delete, jkey, jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: singleDelete + * Signature: (J[[BIJZ)V + */ +void Java_org_rocksdb_Transaction_singleDelete__J_3_3BIJZ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len, jlong jcolumn_family_handle, + jboolean jassume_tracked) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKParts fn_single_delete_parts = + std::bind( + &rocksdb::Transaction::SingleDelete, txn, column_family_handle, _1, + jassume_tracked); + txn_write_k_parts_helper(env, fn_single_delete_parts, jkey_parts, + jkey_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: singleDelete + * Signature: (J[[BI)V + */ +void Java_org_rocksdb_Transaction_singleDelete__J_3_3BI(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle, + jobjectArray jkey_parts, + jint jkey_parts_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKParts fn_single_delete_parts = std::bind( + &rocksdb::Transaction::SingleDelete, txn, _1); + txn_write_k_parts_helper(env, fn_single_delete_parts, jkey_parts, + jkey_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: putUntracked + * Signature: (J[BI[BIJ)V + */ +void Java_org_rocksdb_Transaction_putUntracked__J_3BI_3BIJ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jbyteArray jval, jint jval_len, + jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKV fn_put_untracked = std::bind( + &rocksdb::Transaction::PutUntracked, txn, column_family_handle, _1, _2); + txn_write_kv_helper(env, fn_put_untracked, jkey, jkey_part_len, jval, + jval_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: putUntracked + * Signature: (J[BI[BI)V + */ +void Java_org_rocksdb_Transaction_putUntracked__J_3BI_3BI( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jbyteArray jval, jint jval_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKV fn_put_untracked = std::bind( + &rocksdb::Transaction::PutUntracked, txn, _1, _2); + txn_write_kv_helper(env, fn_put_untracked, jkey, jkey_part_len, jval, + jval_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: putUntracked + * Signature: (J[[BI[[BIJ)V + */ +void Java_org_rocksdb_Transaction_putUntracked__J_3_3BI_3_3BIJ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len, + jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKVParts fn_put_parts_untracked = + std::bind(&rocksdb::Transaction::PutUntracked, txn, + column_family_handle, _1, _2); + txn_write_kv_parts_helper(env, fn_put_parts_untracked, jkey_parts, + jkey_parts_len, jvalue_parts, jvalue_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: putUntracked + * Signature: (J[[BI[[BI)V + */ +void Java_org_rocksdb_Transaction_putUntracked__J_3_3BI_3_3BI( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len, jobjectArray jvalue_parts, jint jvalue_parts_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKVParts fn_put_parts_untracked = + std::bind( + &rocksdb::Transaction::PutUntracked, txn, _1, _2); + txn_write_kv_parts_helper(env, fn_put_parts_untracked, jkey_parts, + jkey_parts_len, jvalue_parts, jvalue_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: mergeUntracked + * Signature: (J[BI[BIJ)V + */ +void Java_org_rocksdb_Transaction_mergeUntracked__J_3BI_3BIJ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jbyteArray jval, jint jval_len, + jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKV fn_merge_untracked = std::bind( + &rocksdb::Transaction::MergeUntracked, txn, column_family_handle, _1, _2); + txn_write_kv_helper(env, fn_merge_untracked, jkey, jkey_part_len, jval, + jval_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: mergeUntracked + * Signature: (J[BI[BI)V + */ +void Java_org_rocksdb_Transaction_mergeUntracked__J_3BI_3BI( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jbyteArray jval, jint jval_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKV fn_merge_untracked = std::bind( + &rocksdb::Transaction::MergeUntracked, txn, _1, _2); + txn_write_kv_helper(env, fn_merge_untracked, jkey, jkey_part_len, jval, + jval_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: deleteUntracked + * Signature: (J[BIJ)V + */ +void Java_org_rocksdb_Transaction_deleteUntracked__J_3BIJ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteK fn_delete_untracked = + std::bind( + &rocksdb::Transaction::DeleteUntracked, txn, column_family_handle, + _1); + txn_write_k_helper(env, fn_delete_untracked, jkey, jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: deleteUntracked + * Signature: (J[BI)V + */ +void Java_org_rocksdb_Transaction_deleteUntracked__J_3BI(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle, + jbyteArray jkey, + jint jkey_part_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteK fn_delete_untracked = std::bind( + &rocksdb::Transaction::DeleteUntracked, txn, _1); + txn_write_k_helper(env, fn_delete_untracked, jkey, jkey_part_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: deleteUntracked + * Signature: (J[[BIJ)V + */ +void Java_org_rocksdb_Transaction_deleteUntracked__J_3_3BIJ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len, jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + FnWriteKParts fn_delete_untracked_parts = + std::bind( + &rocksdb::Transaction::DeleteUntracked, txn, column_family_handle, + _1); + txn_write_k_parts_helper(env, fn_delete_untracked_parts, jkey_parts, + jkey_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: deleteUntracked + * Signature: (J[[BI)V + */ +void Java_org_rocksdb_Transaction_deleteUntracked__J_3_3BI( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jobjectArray jkey_parts, + jint jkey_parts_len) { + auto* txn = reinterpret_cast(jhandle); + FnWriteKParts fn_delete_untracked_parts = std::bind( + &rocksdb::Transaction::DeleteUntracked, txn, _1); + txn_write_k_parts_helper(env, fn_delete_untracked_parts, jkey_parts, + jkey_parts_len); +} + +/* + * Class: org_rocksdb_Transaction + * Method: putLogData + * Signature: (J[BI)V + */ +void Java_org_rocksdb_Transaction_putLogData(JNIEnv* env, jobject /*jobj*/, + jlong jhandle, jbyteArray jkey, + jint jkey_part_len) { + auto* txn = reinterpret_cast(jhandle); + + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_part_len); + txn->PutLogData(key_slice); + + // trigger java unref on key. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); +} + +/* + * Class: org_rocksdb_Transaction + * Method: disableIndexing + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_disableIndexing(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + txn->DisableIndexing(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: enableIndexing + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_enableIndexing(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + txn->EnableIndexing(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getNumKeys + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getNumKeys(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return txn->GetNumKeys(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getNumPuts + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getNumPuts(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return txn->GetNumPuts(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getNumDeletes + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getNumDeletes(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return txn->GetNumDeletes(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getNumMerges + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getNumMerges(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return txn->GetNumMerges(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getElapsedTime + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getElapsedTime(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return txn->GetElapsedTime(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getWriteBatch + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getWriteBatch(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return reinterpret_cast(txn->GetWriteBatch()); +} + +/* + * Class: org_rocksdb_Transaction + * Method: setLockTimeout + * Signature: (JJ)V + */ +void Java_org_rocksdb_Transaction_setLockTimeout(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jlong jlock_timeout) { + auto* txn = reinterpret_cast(jhandle); + txn->SetLockTimeout(jlock_timeout); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getWriteOptions + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getWriteOptions(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return reinterpret_cast(txn->GetWriteOptions()); +} + +/* + * Class: org_rocksdb_Transaction + * Method: setWriteOptions + * Signature: (JJ)V + */ +void Java_org_rocksdb_Transaction_setWriteOptions(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jlong jwrite_options_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + txn->SetWriteOptions(*write_options); +} + +/* + * Class: org_rocksdb_Transaction + * Method: undo + * Signature: (J[BIJ)V + */ +void Java_org_rocksdb_Transaction_undoGetForUpdate__J_3BIJ( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jbyteArray jkey, + jint jkey_part_len, jlong jcolumn_family_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* column_family_handle = + reinterpret_cast(jcolumn_family_handle); + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_part_len); + txn->UndoGetForUpdate(column_family_handle, key_slice); + + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); +} + +/* + * Class: org_rocksdb_Transaction + * Method: undoGetForUpdate + * Signature: (J[BI)V + */ +void Java_org_rocksdb_Transaction_undoGetForUpdate__J_3BI(JNIEnv* env, + jobject /*jobj*/, + jlong jhandle, + jbyteArray jkey, + jint jkey_part_len) { + auto* txn = reinterpret_cast(jhandle); + jbyte* key = env->GetByteArrayElements(jkey, nullptr); + if (key == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_part_len); + txn->UndoGetForUpdate(key_slice); + + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); +} + +/* + * Class: org_rocksdb_Transaction + * Method: rebuildFromWriteBatch + * Signature: (JJ)V + */ +void Java_org_rocksdb_Transaction_rebuildFromWriteBatch( + JNIEnv* env, jobject /*jobj*/, jlong jhandle, jlong jwrite_batch_handle) { + auto* txn = reinterpret_cast(jhandle); + auto* write_batch = + reinterpret_cast(jwrite_batch_handle); + rocksdb::Status s = txn->RebuildFromWriteBatch(write_batch); + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_Transaction + * Method: getCommitTimeWriteBatch + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getCommitTimeWriteBatch(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return reinterpret_cast(txn->GetCommitTimeWriteBatch()); +} + +/* + * Class: org_rocksdb_Transaction + * Method: setLogNumber + * Signature: (JJ)V + */ +void Java_org_rocksdb_Transaction_setLogNumber(JNIEnv* /*env*/, + jobject /*jobj*/, jlong jhandle, + jlong jlog_number) { + auto* txn = reinterpret_cast(jhandle); + txn->SetLogNumber(jlog_number); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getLogNumber + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getLogNumber(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return txn->GetLogNumber(); +} + +/* + * Class: org_rocksdb_Transaction + * Method: setName + * Signature: (JLjava/lang/String;)V + */ +void Java_org_rocksdb_Transaction_setName(JNIEnv* env, jobject /*jobj*/, + jlong jhandle, jstring jname) { + auto* txn = reinterpret_cast(jhandle); + const char* name = env->GetStringUTFChars(jname, nullptr); + if (name == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + + rocksdb::Status s = txn->SetName(name); + + env->ReleaseStringUTFChars(jname, name); + + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_Transaction + * Method: getName + * Signature: (J)Ljava/lang/String; + */ +jstring Java_org_rocksdb_Transaction_getName(JNIEnv* env, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + rocksdb::TransactionName name = txn->GetName(); + return env->NewStringUTF(name.data()); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getID + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getID(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + rocksdb::TransactionID id = txn->GetID(); + return static_cast(id); +} + +/* + * Class: org_rocksdb_Transaction + * Method: isDeadlockDetect + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_Transaction_isDeadlockDetect(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + return static_cast(txn->IsDeadlockDetect()); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getWaitingTxns + * Signature: (J)Lorg/rocksdb/Transaction/WaitingTransactions; + */ +jobject Java_org_rocksdb_Transaction_getWaitingTxns(JNIEnv* env, + jobject jtransaction_obj, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + uint32_t column_family_id; + std::string key; + std::vector waiting_txns = + txn->GetWaitingTxns(&column_family_id, &key); + jobject jwaiting_txns = rocksdb::TransactionJni::newWaitingTransactions( + env, jtransaction_obj, column_family_id, key, waiting_txns); + return jwaiting_txns; +} + +/* + * Class: org_rocksdb_Transaction + * Method: getState + * Signature: (J)B + */ +jbyte Java_org_rocksdb_Transaction_getState(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + rocksdb::Transaction::TransactionState txn_status = txn->GetState(); + switch (txn_status) { + case rocksdb::Transaction::TransactionState::STARTED: + return 0x0; + + case rocksdb::Transaction::TransactionState::AWAITING_PREPARE: + return 0x1; + + case rocksdb::Transaction::TransactionState::PREPARED: + return 0x2; + + case rocksdb::Transaction::TransactionState::AWAITING_COMMIT: + return 0x3; + + case rocksdb::Transaction::TransactionState::COMMITED: + return 0x4; + + case rocksdb::Transaction::TransactionState::AWAITING_ROLLBACK: + return 0x5; + + case rocksdb::Transaction::TransactionState::ROLLEDBACK: + return 0x6; + + case rocksdb::Transaction::TransactionState::LOCKS_STOLEN: + return 0x7; + } + + assert(false); + return static_cast(-1); +} + +/* + * Class: org_rocksdb_Transaction + * Method: getId + * Signature: (J)J + */ +jlong Java_org_rocksdb_Transaction_getId(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jhandle) { + auto* txn = reinterpret_cast(jhandle); + uint64_t id = txn->GetId(); + return static_cast(id); +} + +/* + * Class: org_rocksdb_Transaction + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_Transaction_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + delete reinterpret_cast(jhandle); +} diff --git a/thirdparty/rocksdb/java/rocksjni/transaction_db.cc b/thirdparty/rocksdb/java/rocksjni/transaction_db.cc new file mode 100644 index 0000000000..c2c40bf10e --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/transaction_db.cc @@ -0,0 +1,453 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ +// for rocksdb::TransactionDB. + +#include +#include +#include +#include + +#include "include/org_rocksdb_TransactionDB.h" + +#include "rocksdb/options.h" +#include "rocksdb/utilities/transaction.h" +#include "rocksdb/utilities/transaction_db.h" + +#include "rocksjni/portal.h" + +/* + * Class: org_rocksdb_TransactionDB + * Method: open + * Signature: (JJLjava/lang/String;)J + */ +jlong Java_org_rocksdb_TransactionDB_open__JJLjava_lang_String_2( + JNIEnv* env, jclass, jlong joptions_handle, + jlong jtxn_db_options_handle, jstring jdb_path) { + auto* options = reinterpret_cast(joptions_handle); + auto* txn_db_options = + reinterpret_cast(jtxn_db_options_handle); + rocksdb::TransactionDB* tdb = nullptr; + const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); + if (db_path == nullptr) { + // exception thrown: OutOfMemoryError + return 0; + } + rocksdb::Status s = + rocksdb::TransactionDB::Open(*options, *txn_db_options, db_path, &tdb); + env->ReleaseStringUTFChars(jdb_path, db_path); + + if (s.ok()) { + return reinterpret_cast(tdb); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return 0; + } +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: open + * Signature: (JJLjava/lang/String;[[B[J)[J + */ +jlongArray Java_org_rocksdb_TransactionDB_open__JJLjava_lang_String_2_3_3B_3J( + JNIEnv* env, jclass, jlong jdb_options_handle, + jlong jtxn_db_options_handle, jstring jdb_path, jobjectArray jcolumn_names, + jlongArray jcolumn_options_handles) { + const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); + if (db_path == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + + const jsize len_cols = env->GetArrayLength(jcolumn_names); + if (env->EnsureLocalCapacity(len_cols) != 0) { + // out of memory + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + + jlong* jco = env->GetLongArrayElements(jcolumn_options_handles, nullptr); + if (jco == nullptr) { + // exception thrown: OutOfMemoryError + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + std::vector column_families; + for (int i = 0; i < len_cols; i++) { + const jobject jcn = env->GetObjectArrayElement(jcolumn_names, i); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + const jbyteArray jcn_ba = reinterpret_cast(jcn); + jbyte* jcf_name = env->GetByteArrayElements(jcn_ba, nullptr); + if (jcf_name == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jcn); + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + + const int jcf_name_len = env->GetArrayLength(jcn_ba); + if (env->EnsureLocalCapacity(jcf_name_len) != 0) { + // out of memory + env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT); + env->DeleteLocalRef(jcn); + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + env->ReleaseStringUTFChars(jdb_path, db_path); + return nullptr; + } + const std::string cf_name(reinterpret_cast(jcf_name), jcf_name_len); + const rocksdb::ColumnFamilyOptions* cf_options = + reinterpret_cast(jco[i]); + column_families.push_back( + rocksdb::ColumnFamilyDescriptor(cf_name, *cf_options)); + + env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT); + env->DeleteLocalRef(jcn); + } + env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT); + + auto* db_options = reinterpret_cast(jdb_options_handle); + auto* txn_db_options = + reinterpret_cast(jtxn_db_options_handle); + std::vector handles; + rocksdb::TransactionDB* tdb = nullptr; + const rocksdb::Status s = rocksdb::TransactionDB::Open( + *db_options, *txn_db_options, db_path, column_families, &handles, &tdb); + + // check if open operation was successful + if (s.ok()) { + const jsize resultsLen = 1 + len_cols; // db handle + column family handles + std::unique_ptr results = + std::unique_ptr(new jlong[resultsLen]); + results[0] = reinterpret_cast(tdb); + for (int i = 1; i <= len_cols; i++) { + results[i] = reinterpret_cast(handles[i - 1]); + } + + jlongArray jresults = env->NewLongArray(resultsLen); + if (jresults == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jresults); + return nullptr; + } + return jresults; + } else { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + return nullptr; + } +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_TransactionDB_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* txn_db = reinterpret_cast(jhandle); + assert(txn_db != nullptr); + delete txn_db; +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: closeDatabase + * Signature: (J)V + */ +void Java_org_rocksdb_TransactionDB_closeDatabase( + JNIEnv* env, jclass, jlong jhandle) { + auto* txn_db = reinterpret_cast(jhandle); + assert(txn_db != nullptr); + rocksdb::Status s = txn_db->Close(); + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: beginTransaction + * Signature: (JJ)J + */ +jlong Java_org_rocksdb_TransactionDB_beginTransaction__JJ( + JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle) { + auto* txn_db = reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + rocksdb::Transaction* txn = txn_db->BeginTransaction(*write_options); + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: beginTransaction + * Signature: (JJJ)J + */ +jlong Java_org_rocksdb_TransactionDB_beginTransaction__JJJ( + JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, + jlong jtxn_options_handle) { + auto* txn_db = reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* txn_options = + reinterpret_cast(jtxn_options_handle); + rocksdb::Transaction* txn = + txn_db->BeginTransaction(*write_options, *txn_options); + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: beginTransaction_withOld + * Signature: (JJJ)J + */ +jlong Java_org_rocksdb_TransactionDB_beginTransaction_1withOld__JJJ( + JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, + jlong jold_txn_handle) { + auto* txn_db = reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* old_txn = reinterpret_cast(jold_txn_handle); + rocksdb::TransactionOptions txn_options; + rocksdb::Transaction* txn = + txn_db->BeginTransaction(*write_options, txn_options, old_txn); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_txn + assert(txn == old_txn); + + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: beginTransaction_withOld + * Signature: (JJJJ)J + */ +jlong Java_org_rocksdb_TransactionDB_beginTransaction_1withOld__JJJJ( + JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle, + jlong jtxn_options_handle, jlong jold_txn_handle) { + auto* txn_db = reinterpret_cast(jhandle); + auto* write_options = + reinterpret_cast(jwrite_options_handle); + auto* txn_options = + reinterpret_cast(jtxn_options_handle); + auto* old_txn = reinterpret_cast(jold_txn_handle); + rocksdb::Transaction* txn = + txn_db->BeginTransaction(*write_options, *txn_options, old_txn); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_txn + assert(txn == old_txn); + + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: getTransactionByName + * Signature: (JLjava/lang/String;)J + */ +jlong Java_org_rocksdb_TransactionDB_getTransactionByName( + JNIEnv* env, jobject, jlong jhandle, jstring jname) { + auto* txn_db = reinterpret_cast(jhandle); + const char* name = env->GetStringUTFChars(jname, nullptr); + if (name == nullptr) { + // exception thrown: OutOfMemoryError + return 0; + } + rocksdb::Transaction* txn = txn_db->GetTransactionByName(name); + env->ReleaseStringUTFChars(jname, name); + return reinterpret_cast(txn); +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: getAllPreparedTransactions + * Signature: (J)[J + */ +jlongArray Java_org_rocksdb_TransactionDB_getAllPreparedTransactions( + JNIEnv* env, jobject, jlong jhandle) { + auto* txn_db = reinterpret_cast(jhandle); + std::vector txns; + txn_db->GetAllPreparedTransactions(&txns); + + const size_t size = txns.size(); + assert(size < UINT32_MAX); // does it fit in a jint? + + const jsize len = static_cast(size); + std::vector tmp(len); + for (jsize i = 0; i < len; ++i) { + tmp[i] = reinterpret_cast(txns[i]); + } + + jlongArray jtxns = env->NewLongArray(len); + if (jtxns == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + env->SetLongArrayRegion(jtxns, 0, len, tmp.data()); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jtxns); + return nullptr; + } + + return jtxns; +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: getLockStatusData + * Signature: (J)Ljava/util/Map; + */ +jobject Java_org_rocksdb_TransactionDB_getLockStatusData( + JNIEnv* env, jobject, jlong jhandle) { + auto* txn_db = reinterpret_cast(jhandle); + const std::unordered_multimap + lock_status_data = txn_db->GetLockStatusData(); + const jobject jlock_status_data = rocksdb::HashMapJni::construct( + env, static_cast(lock_status_data.size())); + if (jlock_status_data == nullptr) { + // exception occurred + return nullptr; + } + + const rocksdb::HashMapJni::FnMapKV + fn_map_kv = + [env]( + const std::pair& + pair) { + const jobject jlong_column_family_id = + rocksdb::LongJni::valueOf(env, pair.first); + if (jlong_column_family_id == nullptr) { + // an error occurred + return std::unique_ptr>(nullptr); + } + const jobject jkey_lock_info = + rocksdb::KeyLockInfoJni::construct(env, pair.second); + if (jkey_lock_info == nullptr) { + // an error occurred + return std::unique_ptr>(nullptr); + } + return std::unique_ptr>( + new std::pair(jlong_column_family_id, + jkey_lock_info)); + }; + + if (!rocksdb::HashMapJni::putAll(env, jlock_status_data, + lock_status_data.begin(), + lock_status_data.end(), fn_map_kv)) { + // exception occcurred + return nullptr; + } + + return jlock_status_data; +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: getDeadlockInfoBuffer + * Signature: (J)[Lorg/rocksdb/TransactionDB/DeadlockPath; + */ +jobjectArray Java_org_rocksdb_TransactionDB_getDeadlockInfoBuffer( + JNIEnv* env, jobject jobj, jlong jhandle) { + auto* txn_db = reinterpret_cast(jhandle); + const std::vector deadlock_info_buffer = + txn_db->GetDeadlockInfoBuffer(); + + const jsize deadlock_info_buffer_len = + static_cast(deadlock_info_buffer.size()); + jobjectArray jdeadlock_info_buffer = + env->NewObjectArray(deadlock_info_buffer_len, + rocksdb::DeadlockPathJni::getJClass(env), nullptr); + if (jdeadlock_info_buffer == nullptr) { + // exception thrown: OutOfMemoryError + return nullptr; + } + jsize jdeadlock_info_buffer_offset = 0; + + auto buf_end = deadlock_info_buffer.end(); + for (auto buf_it = deadlock_info_buffer.begin(); buf_it != buf_end; + ++buf_it) { + const rocksdb::DeadlockPath deadlock_path = *buf_it; + const std::vector deadlock_infos = + deadlock_path.path; + const jsize deadlock_infos_len = + static_cast(deadlock_info_buffer.size()); + jobjectArray jdeadlock_infos = env->NewObjectArray( + deadlock_infos_len, rocksdb::DeadlockInfoJni::getJClass(env), nullptr); + if (jdeadlock_infos == nullptr) { + // exception thrown: OutOfMemoryError + env->DeleteLocalRef(jdeadlock_info_buffer); + return nullptr; + } + jsize jdeadlock_infos_offset = 0; + + auto infos_end = deadlock_infos.end(); + for (auto infos_it = deadlock_infos.begin(); infos_it != infos_end; + ++infos_it) { + const rocksdb::DeadlockInfo deadlock_info = *infos_it; + const jobject jdeadlock_info = rocksdb::TransactionDBJni::newDeadlockInfo( + env, jobj, deadlock_info.m_txn_id, deadlock_info.m_cf_id, + deadlock_info.m_waiting_key, deadlock_info.m_exclusive); + if (jdeadlock_info == nullptr) { + // exception occcurred + env->DeleteLocalRef(jdeadlock_info_buffer); + return nullptr; + } + env->SetObjectArrayElement(jdeadlock_infos, jdeadlock_infos_offset++, + jdeadlock_info); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException or + // ArrayStoreException + env->DeleteLocalRef(jdeadlock_info); + env->DeleteLocalRef(jdeadlock_info_buffer); + return nullptr; + } + } + + const jobject jdeadlock_path = rocksdb::DeadlockPathJni::construct( + env, jdeadlock_infos, deadlock_path.limit_exceeded); + if (jdeadlock_path == nullptr) { + // exception occcurred + env->DeleteLocalRef(jdeadlock_info_buffer); + return nullptr; + } + env->SetObjectArrayElement(jdeadlock_info_buffer, + jdeadlock_info_buffer_offset++, jdeadlock_path); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException or ArrayStoreException + env->DeleteLocalRef(jdeadlock_path); + env->DeleteLocalRef(jdeadlock_info_buffer); + return nullptr; + } + } + + return jdeadlock_info_buffer; +} + +/* + * Class: org_rocksdb_TransactionDB + * Method: setDeadlockInfoBufferSize + * Signature: (JI)V + */ +void Java_org_rocksdb_TransactionDB_setDeadlockInfoBufferSize( + JNIEnv*, jobject, jlong jhandle, jint jdeadlock_info_buffer_size) { + auto* txn_db = reinterpret_cast(jhandle); + txn_db->SetDeadlockInfoBufferSize(jdeadlock_info_buffer_size); +} diff --git a/thirdparty/rocksdb/java/rocksjni/transaction_db_options.cc b/thirdparty/rocksdb/java/rocksjni/transaction_db_options.cc new file mode 100644 index 0000000000..391accc375 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/transaction_db_options.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ +// for rocksdb::TransactionDBOptions. + +#include + +#include "include/org_rocksdb_TransactionDBOptions.h" + +#include "rocksdb/utilities/transaction_db.h" + +#include "rocksjni/portal.h" + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: newTransactionDBOptions + * Signature: ()J + */ +jlong Java_org_rocksdb_TransactionDBOptions_newTransactionDBOptions( + JNIEnv* /*env*/, jclass /*jcls*/) { + rocksdb::TransactionDBOptions* opts = new rocksdb::TransactionDBOptions(); + return reinterpret_cast(opts); +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: getMaxNumLocks + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionDBOptions_getMaxNumLocks(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->max_num_locks; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: setMaxNumLocks + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionDBOptions_setMaxNumLocks( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jlong jmax_num_locks) { + auto* opts = reinterpret_cast(jhandle); + opts->max_num_locks = jmax_num_locks; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: getNumStripes + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionDBOptions_getNumStripes(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->num_stripes; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: setNumStripes + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionDBOptions_setNumStripes(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jlong jnum_stripes) { + auto* opts = reinterpret_cast(jhandle); + opts->num_stripes = jnum_stripes; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: getTransactionLockTimeout + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionDBOptions_getTransactionLockTimeout( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->transaction_lock_timeout; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: setTransactionLockTimeout + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionDBOptions_setTransactionLockTimeout( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jtransaction_lock_timeout) { + auto* opts = reinterpret_cast(jhandle); + opts->transaction_lock_timeout = jtransaction_lock_timeout; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: getDefaultLockTimeout + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionDBOptions_getDefaultLockTimeout( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->default_lock_timeout; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: setDefaultLockTimeout + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionDBOptions_setDefaultLockTimeout( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jdefault_lock_timeout) { + auto* opts = reinterpret_cast(jhandle); + opts->default_lock_timeout = jdefault_lock_timeout; +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: getWritePolicy + * Signature: (J)B + */ +jbyte Java_org_rocksdb_TransactionDBOptions_getWritePolicy(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return rocksdb::TxnDBWritePolicyJni::toJavaTxnDBWritePolicy( + opts->write_policy); +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: setWritePolicy + * Signature: (JB)V + */ +void Java_org_rocksdb_TransactionDBOptions_setWritePolicy(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jbyte jwrite_policy) { + auto* opts = reinterpret_cast(jhandle); + opts->write_policy = + rocksdb::TxnDBWritePolicyJni::toCppTxnDBWritePolicy(jwrite_policy); +} + +/* + * Class: org_rocksdb_TransactionDBOptions + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_TransactionDBOptions_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + delete reinterpret_cast(jhandle); +} diff --git a/thirdparty/rocksdb/java/rocksjni/transaction_log.cc b/thirdparty/rocksdb/java/rocksjni/transaction_log.cc index a5049e3b26..8186a846bd 100644 --- a/thirdparty/rocksdb/java/rocksjni/transaction_log.cc +++ b/thirdparty/rocksdb/java/rocksjni/transaction_log.cc @@ -19,8 +19,9 @@ * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_TransactionLogIterator_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_TransactionLogIterator_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { delete reinterpret_cast(handle); } @@ -29,8 +30,9 @@ void Java_org_rocksdb_TransactionLogIterator_disposeInternal( * Method: isValid * Signature: (J)Z */ -jboolean Java_org_rocksdb_TransactionLogIterator_isValid( - JNIEnv* env, jobject jobj, jlong handle) { +jboolean Java_org_rocksdb_TransactionLogIterator_isValid(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { return reinterpret_cast(handle)->Valid(); } @@ -39,8 +41,9 @@ jboolean Java_org_rocksdb_TransactionLogIterator_isValid( * Method: next * Signature: (J)V */ -void Java_org_rocksdb_TransactionLogIterator_next( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_TransactionLogIterator_next(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->Next(); } @@ -49,10 +52,11 @@ void Java_org_rocksdb_TransactionLogIterator_next( * Method: status * Signature: (J)V */ -void Java_org_rocksdb_TransactionLogIterator_status( - JNIEnv* env, jobject jobj, jlong handle) { - rocksdb::Status s = reinterpret_cast< - rocksdb::TransactionLogIterator*>(handle)->status(); +void Java_org_rocksdb_TransactionLogIterator_status(JNIEnv* env, + jobject /*jobj*/, + jlong handle) { + rocksdb::Status s = + reinterpret_cast(handle)->status(); if (!s.ok()) { rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } @@ -63,8 +67,9 @@ void Java_org_rocksdb_TransactionLogIterator_status( * Method: getBatch * Signature: (J)Lorg/rocksdb/TransactionLogIterator$BatchResult */ -jobject Java_org_rocksdb_TransactionLogIterator_getBatch( - JNIEnv* env, jobject jobj, jlong handle) { +jobject Java_org_rocksdb_TransactionLogIterator_getBatch(JNIEnv* env, + jobject /*jobj*/, + jlong handle) { rocksdb::BatchResult batch_result = reinterpret_cast(handle)->GetBatch(); return rocksdb::BatchResultJni::construct(env, batch_result); diff --git a/thirdparty/rocksdb/java/rocksjni/transaction_notifier.cc b/thirdparty/rocksdb/java/rocksjni/transaction_notifier.cc new file mode 100644 index 0000000000..b60076e100 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/transaction_notifier.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ +// for rocksdb::TransactionNotifier. + +#include + +#include "include/org_rocksdb_AbstractTransactionNotifier.h" +#include "rocksjni/transaction_notifier_jnicallback.h" + +/* + * Class: org_rocksdb_AbstractTransactionNotifier + * Method: createNewTransactionNotifier + * Signature: ()J + */ +jlong Java_org_rocksdb_AbstractTransactionNotifier_createNewTransactionNotifier( + JNIEnv* env, jobject jobj) { + auto* transaction_notifier = + new rocksdb::TransactionNotifierJniCallback(env, jobj); + auto* sptr_transaction_notifier = + new std::shared_ptr( + transaction_notifier); + return reinterpret_cast(sptr_transaction_notifier); +} + +/* + * Class: org_rocksdb_AbstractTransactionNotifier + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_AbstractTransactionNotifier_disposeInternal( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + // TODO(AR) refactor to use JniCallback::JniCallback + // when https://github.com/facebook/rocksdb/pull/1241/ is merged + std::shared_ptr* handle = + reinterpret_cast< + std::shared_ptr*>(jhandle); + delete handle; +} diff --git a/thirdparty/rocksdb/java/rocksjni/transaction_notifier_jnicallback.cc b/thirdparty/rocksdb/java/rocksjni/transaction_notifier_jnicallback.cc new file mode 100644 index 0000000000..85f2a194be --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/transaction_notifier_jnicallback.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::TransactionNotifier. + +#include "rocksjni/transaction_notifier_jnicallback.h" +#include "rocksjni/portal.h" + +namespace rocksdb { + +TransactionNotifierJniCallback::TransactionNotifierJniCallback(JNIEnv* env, + jobject jtransaction_notifier) : JniCallback(env, jtransaction_notifier) { + // we cache the method id for the JNI callback + m_jsnapshot_created_methodID = + AbstractTransactionNotifierJni::getSnapshotCreatedMethodId(env); +} + +void TransactionNotifierJniCallback::SnapshotCreated( + const Snapshot* newSnapshot) { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + assert(env != nullptr); + + env->CallVoidMethod(m_jcallback_obj, + m_jsnapshot_created_methodID, reinterpret_cast(newSnapshot)); + + if(env->ExceptionCheck()) { + // exception thrown from CallVoidMethod + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return; + } + + releaseJniEnv(attached_thread); +} +} // namespace rocksdb diff --git a/thirdparty/rocksdb/java/rocksjni/transaction_notifier_jnicallback.h b/thirdparty/rocksdb/java/rocksjni/transaction_notifier_jnicallback.h new file mode 100644 index 0000000000..8f67cdb8bc --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/transaction_notifier_jnicallback.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::TransactionNotifier. + +#ifndef JAVA_ROCKSJNI_TRANSACTION_NOTIFIER_JNICALLBACK_H_ +#define JAVA_ROCKSJNI_TRANSACTION_NOTIFIER_JNICALLBACK_H_ + +#include + +#include "rocksdb/utilities/transaction.h" +#include "rocksjni/jnicallback.h" + +namespace rocksdb { + +/** + * This class acts as a bridge between C++ + * and Java. The methods in this class will be + * called back from the RocksDB TransactionDB or OptimisticTransactionDB (C++), + * we then callback to the appropriate Java method + * this enables TransactionNotifier to be implemented in Java. + * + * Unlike RocksJava's Comparator JNI Callback, we do not attempt + * to reduce Java object allocations by caching the Snapshot object + * presented to the callback. This could be revisited in future + * if performance is lacking. + */ +class TransactionNotifierJniCallback: public JniCallback, + public TransactionNotifier { + public: + TransactionNotifierJniCallback(JNIEnv* env, jobject jtransaction_notifier); + virtual void SnapshotCreated(const Snapshot* newSnapshot); + + private: + jmethodID m_jsnapshot_created_methodID; +}; +} // namespace rocksdb + +#endif // JAVA_ROCKSJNI_TRANSACTION_NOTIFIER_JNICALLBACK_H_ diff --git a/thirdparty/rocksdb/java/rocksjni/transaction_options.cc b/thirdparty/rocksdb/java/rocksjni/transaction_options.cc new file mode 100644 index 0000000000..d18a5294af --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/transaction_options.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ +// for rocksdb::TransactionOptions. + +#include + +#include "include/org_rocksdb_TransactionOptions.h" + +#include "rocksdb/utilities/transaction_db.h" + +/* + * Class: org_rocksdb_TransactionOptions + * Method: newTransactionOptions + * Signature: ()J + */ +jlong Java_org_rocksdb_TransactionOptions_newTransactionOptions( + JNIEnv* /*env*/, jclass /*jcls*/) { + auto* opts = new rocksdb::TransactionOptions(); + return reinterpret_cast(opts); +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: isSetSnapshot + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_TransactionOptions_isSetSnapshot(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->set_snapshot; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: setSetSnapshot + * Signature: (JZ)V + */ +void Java_org_rocksdb_TransactionOptions_setSetSnapshot( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, jboolean jset_snapshot) { + auto* opts = reinterpret_cast(jhandle); + opts->set_snapshot = jset_snapshot; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: isDeadlockDetect + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_TransactionOptions_isDeadlockDetect(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->deadlock_detect; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: setDeadlockDetect + * Signature: (JZ)V + */ +void Java_org_rocksdb_TransactionOptions_setDeadlockDetect( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jboolean jdeadlock_detect) { + auto* opts = reinterpret_cast(jhandle); + opts->deadlock_detect = jdeadlock_detect; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: getLockTimeout + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionOptions_getLockTimeout(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->lock_timeout; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: setLockTimeout + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionOptions_setLockTimeout(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jlong jlock_timeout) { + auto* opts = reinterpret_cast(jhandle); + opts->lock_timeout = jlock_timeout; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: getExpiration + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionOptions_getExpiration(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->expiration; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: setExpiration + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionOptions_setExpiration(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle, + jlong jexpiration) { + auto* opts = reinterpret_cast(jhandle); + opts->expiration = jexpiration; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: getDeadlockDetectDepth + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionOptions_getDeadlockDetectDepth( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->deadlock_detect_depth; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: setDeadlockDetectDepth + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionOptions_setDeadlockDetectDepth( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jdeadlock_detect_depth) { + auto* opts = reinterpret_cast(jhandle); + opts->deadlock_detect_depth = jdeadlock_detect_depth; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: getMaxWriteBatchSize + * Signature: (J)J + */ +jlong Java_org_rocksdb_TransactionOptions_getMaxWriteBatchSize(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + auto* opts = reinterpret_cast(jhandle); + return opts->max_write_batch_size; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: setMaxWriteBatchSize + * Signature: (JJ)V + */ +void Java_org_rocksdb_TransactionOptions_setMaxWriteBatchSize( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle, + jlong jmax_write_batch_size) { + auto* opts = reinterpret_cast(jhandle); + opts->max_write_batch_size = jmax_write_batch_size; +} + +/* + * Class: org_rocksdb_TransactionOptions + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_TransactionOptions_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jhandle) { + delete reinterpret_cast(jhandle); +} diff --git a/thirdparty/rocksdb/java/rocksjni/ttl.cc b/thirdparty/rocksdb/java/rocksjni/ttl.cc index a66ad86d62..4b071e7b33 100644 --- a/thirdparty/rocksdb/java/rocksjni/ttl.cc +++ b/thirdparty/rocksdb/java/rocksjni/ttl.cc @@ -10,9 +10,9 @@ #include #include #include +#include #include #include -#include #include "include/org_rocksdb_TtlDB.h" #include "rocksdb/utilities/db_ttl.h" @@ -23,19 +23,19 @@ * Method: open * Signature: (JLjava/lang/String;IZ)J */ -jlong Java_org_rocksdb_TtlDB_open(JNIEnv* env, - jclass jcls, jlong joptions_handle, jstring jdb_path, - jint jttl, jboolean jread_only) { +jlong Java_org_rocksdb_TtlDB_open( + JNIEnv* env, jclass, jlong joptions_handle, jstring jdb_path, jint jttl, + jboolean jread_only) { const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if(db_path == nullptr) { + if (db_path == nullptr) { // exception thrown: OutOfMemoryError return 0; } auto* opt = reinterpret_cast(joptions_handle); rocksdb::DBWithTTL* db = nullptr; - rocksdb::Status s = rocksdb::DBWithTTL::Open(*opt, db_path, &db, - jttl, jread_only); + rocksdb::Status s = + rocksdb::DBWithTTL::Open(*opt, db_path, &db, jttl, jread_only); env->ReleaseStringUTFChars(jdb_path, db_path); // as TTLDB extends RocksDB on the java side, we can reuse @@ -53,20 +53,19 @@ jlong Java_org_rocksdb_TtlDB_open(JNIEnv* env, * Method: openCF * Signature: (JLjava/lang/String;[[B[J[IZ)[J */ -jlongArray - Java_org_rocksdb_TtlDB_openCF( - JNIEnv* env, jclass jcls, jlong jopt_handle, jstring jdb_path, +jlongArray Java_org_rocksdb_TtlDB_openCF( + JNIEnv* env, jclass, jlong jopt_handle, jstring jdb_path, jobjectArray jcolumn_names, jlongArray jcolumn_options, jintArray jttls, jboolean jread_only) { const char* db_path = env->GetStringUTFChars(jdb_path, nullptr); - if(db_path == nullptr) { + if (db_path == nullptr) { // exception thrown: OutOfMemoryError return 0; } const jsize len_cols = env->GetArrayLength(jcolumn_names); jlong* jco = env->GetLongArrayElements(jcolumn_options, nullptr); - if(jco == nullptr) { + if (jco == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseStringUTFChars(jdb_path, db_path); return nullptr; @@ -75,22 +74,21 @@ jlongArray std::vector column_families; jboolean has_exception = JNI_FALSE; rocksdb::JniUtil::byteStrings( - env, - jcolumn_names, - [](const char* str_data, const size_t str_len) { - return std::string(str_data, str_len); - }, - [&jco, &column_families](size_t idx, std::string cf_name) { - rocksdb::ColumnFamilyOptions* cf_options = - reinterpret_cast(jco[idx]); - column_families.push_back( - rocksdb::ColumnFamilyDescriptor(cf_name, *cf_options)); - }, - &has_exception); + env, jcolumn_names, + [](const char* str_data, const size_t str_len) { + return std::string(str_data, str_len); + }, + [&jco, &column_families](size_t idx, std::string cf_name) { + rocksdb::ColumnFamilyOptions* cf_options = + reinterpret_cast(jco[idx]); + column_families.push_back( + rocksdb::ColumnFamilyDescriptor(cf_name, *cf_options)); + }, + &has_exception); env->ReleaseLongArrayElements(jcolumn_options, jco, JNI_ABORT); - if(has_exception == JNI_TRUE) { + if (has_exception == JNI_TRUE) { // exception occurred env->ReleaseStringUTFChars(jdb_path, db_path); return nullptr; @@ -98,13 +96,13 @@ jlongArray std::vector ttl_values; jint* jttlv = env->GetIntArrayElements(jttls, nullptr); - if(jttlv == nullptr) { + if (jttlv == nullptr) { // exception thrown: OutOfMemoryError env->ReleaseStringUTFChars(jdb_path, db_path); return nullptr; } const jsize len_ttls = env->GetArrayLength(jttls); - for(jsize i = 0; i < len_ttls; i++) { + for (jsize i = 0; i < len_ttls; i++) { ttl_values.push_back(jttlv[i]); } env->ReleaseIntArrayElements(jttls, jttlv, JNI_ABORT); @@ -112,30 +110,30 @@ jlongArray auto* opt = reinterpret_cast(jopt_handle); std::vector handles; rocksdb::DBWithTTL* db = nullptr; - rocksdb::Status s = rocksdb::DBWithTTL::Open(*opt, db_path, column_families, - &handles, &db, ttl_values, jread_only); + rocksdb::Status s = rocksdb::DBWithTTL::Open( + *opt, db_path, column_families, &handles, &db, ttl_values, jread_only); // we have now finished with db_path env->ReleaseStringUTFChars(jdb_path, db_path); // check if open operation was successful if (s.ok()) { - const jsize resultsLen = 1 + len_cols; //db handle + column family handles + const jsize resultsLen = 1 + len_cols; // db handle + column family handles std::unique_ptr results = std::unique_ptr(new jlong[resultsLen]); results[0] = reinterpret_cast(db); - for(int i = 1; i <= len_cols; i++) { + for (int i = 1; i <= len_cols; i++) { results[i] = reinterpret_cast(handles[i - 1]); } jlongArray jresults = env->NewLongArray(resultsLen); - if(jresults == nullptr) { + if (jresults == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } env->SetLongArrayRegion(jresults, 0, resultsLen, results.get()); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(jresults); return nullptr; @@ -148,17 +146,43 @@ jlongArray } } +/* + * Class: org_rocksdb_TtlDB + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_TtlDB_disposeInternal( + JNIEnv*, jobject, jlong jhandle) { + auto* ttl_db = reinterpret_cast(jhandle); + assert(ttl_db != nullptr); + delete ttl_db; +} + +/* + * Class: org_rocksdb_TtlDB + * Method: closeDatabase + * Signature: (J)V + */ +void Java_org_rocksdb_TtlDB_closeDatabase( + JNIEnv* /* env */, jclass, jlong /* jhandle */) { + //auto* ttl_db = reinterpret_cast(jhandle); + //assert(ttl_db != nullptr); + //rocksdb::Status s = ttl_db->Close(); + //rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + + //TODO(AR) this is disabled until https://github.com/facebook/rocksdb/issues/4818 is resolved! +} + /* * Class: org_rocksdb_TtlDB * Method: createColumnFamilyWithTtl * Signature: (JLorg/rocksdb/ColumnFamilyDescriptor;[BJI)J; */ jlong Java_org_rocksdb_TtlDB_createColumnFamilyWithTtl( - JNIEnv* env, jobject jobj, jlong jdb_handle, - jbyteArray jcolumn_name, jlong jcolumn_options, jint jttl) { - + JNIEnv* env, jobject, jlong jdb_handle, jbyteArray jcolumn_name, + jlong jcolumn_options, jint jttl) { jbyte* cfname = env->GetByteArrayElements(jcolumn_name, nullptr); - if(cfname == nullptr) { + if (cfname == nullptr) { // exception thrown: OutOfMemoryError return 0; } @@ -170,8 +194,8 @@ jlong Java_org_rocksdb_TtlDB_createColumnFamilyWithTtl( auto* db_handle = reinterpret_cast(jdb_handle); rocksdb::ColumnFamilyHandle* handle; rocksdb::Status s = db_handle->CreateColumnFamilyWithTtl( - *cfOptions, std::string(reinterpret_cast(cfname), - len), &handle, jttl); + *cfOptions, std::string(reinterpret_cast(cfname), len), &handle, + jttl); env->ReleaseByteArrayElements(jcolumn_name, cfname, 0); diff --git a/thirdparty/rocksdb/java/rocksjni/wal_filter.cc b/thirdparty/rocksdb/java/rocksjni/wal_filter.cc new file mode 100644 index 0000000000..c74e54252e --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/wal_filter.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the "bridge" between Java and C++ for +// rocksdb::WalFilter. + +#include + +#include "include/org_rocksdb_AbstractWalFilter.h" +#include "rocksjni/wal_filter_jnicallback.h" + +/* + * Class: org_rocksdb_AbstractWalFilter + * Method: createNewWalFilter + * Signature: ()J + */ +jlong Java_org_rocksdb_AbstractWalFilter_createNewWalFilter( + JNIEnv* env, jobject jobj) { + auto* wal_filter = new rocksdb::WalFilterJniCallback(env, jobj); + return reinterpret_cast(wal_filter); +} \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/wal_filter_jnicallback.cc b/thirdparty/rocksdb/java/rocksjni/wal_filter_jnicallback.cc new file mode 100644 index 0000000000..8fd909258f --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/wal_filter_jnicallback.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::WalFilter. + +#include "rocksjni/wal_filter_jnicallback.h" +#include "rocksjni/portal.h" + +namespace rocksdb { +WalFilterJniCallback::WalFilterJniCallback( + JNIEnv* env, jobject jwal_filter) + : JniCallback(env, jwal_filter) { + // Note: The name of a WalFilter will not change during it's lifetime, + // so we cache it in a global var + jmethodID jname_mid = AbstractWalFilterJni::getNameMethodId(env); + if(jname_mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } + jstring jname = (jstring)env->CallObjectMethod(m_jcallback_obj, jname_mid); + if(env->ExceptionCheck()) { + // exception thrown + return; + } + jboolean has_exception = JNI_FALSE; + m_name = JniUtil::copyString(env, jname, + &has_exception); // also releases jname + if (has_exception == JNI_TRUE) { + // exception thrown + return; + } + + m_column_family_log_number_map_mid = + AbstractWalFilterJni::getColumnFamilyLogNumberMapMethodId(env); + if(m_column_family_log_number_map_mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } + + m_log_record_found_proxy_mid = + AbstractWalFilterJni::getLogRecordFoundProxyMethodId(env); + if(m_log_record_found_proxy_mid == nullptr) { + // exception thrown: NoSuchMethodException or OutOfMemoryError + return; + } +} + +void WalFilterJniCallback::ColumnFamilyLogNumberMap( + const std::map& cf_lognumber_map, + const std::map& cf_name_id_map) { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + if (env == nullptr) { + return; + } + + jobject jcf_lognumber_map = + rocksdb::HashMapJni::fromCppMap(env, &cf_lognumber_map); + if (jcf_lognumber_map == nullptr) { + // exception occurred + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return; + } + + jobject jcf_name_id_map = + rocksdb::HashMapJni::fromCppMap(env, &cf_name_id_map); + if (jcf_name_id_map == nullptr) { + // exception occurred + env->ExceptionDescribe(); // print out exception to stderr + env->DeleteLocalRef(jcf_lognumber_map); + releaseJniEnv(attached_thread); + return; + } + + env->CallVoidMethod(m_jcallback_obj, + m_column_family_log_number_map_mid, + jcf_lognumber_map, + jcf_name_id_map); + + env->DeleteLocalRef(jcf_lognumber_map); + env->DeleteLocalRef(jcf_name_id_map); + + if(env->ExceptionCheck()) { + // exception thrown from CallVoidMethod + env->ExceptionDescribe(); // print out exception to stderr + } + + releaseJniEnv(attached_thread); +} + + WalFilter::WalProcessingOption WalFilterJniCallback::LogRecordFound( + unsigned long long log_number, const std::string& log_file_name, + const WriteBatch& batch, WriteBatch* new_batch, bool* batch_changed) { + jboolean attached_thread = JNI_FALSE; + JNIEnv* env = getJniEnv(&attached_thread); + if (env == nullptr) { + return WalFilter::WalProcessingOption::kCorruptedRecord; + } + + jstring jlog_file_name = JniUtil::toJavaString(env, &log_file_name); + if (jlog_file_name == nullptr) { + // exception occcurred + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return WalFilter::WalProcessingOption::kCorruptedRecord; + } + + jshort jlog_record_found_result = env->CallShortMethod(m_jcallback_obj, + m_log_record_found_proxy_mid, + static_cast(log_number), + jlog_file_name, + reinterpret_cast(&batch), + reinterpret_cast(new_batch)); + + env->DeleteLocalRef(jlog_file_name); + + if (env->ExceptionCheck()) { + // exception thrown from CallShortMethod + env->ExceptionDescribe(); // print out exception to stderr + releaseJniEnv(attached_thread); + return WalFilter::WalProcessingOption::kCorruptedRecord; + } + + // unpack WalProcessingOption and batch_changed from jlog_record_found_result + jbyte jwal_processing_option_value = (jlog_record_found_result >> 8) & 0xFF; + jbyte jbatch_changed_value = jlog_record_found_result & 0xFF; + + releaseJniEnv(attached_thread); + + *batch_changed = jbatch_changed_value == JNI_TRUE; + + return WalProcessingOptionJni::toCppWalProcessingOption( + jwal_processing_option_value); +} + +const char* WalFilterJniCallback::Name() const { + return m_name.get(); +} + +} // namespace rocksdb \ No newline at end of file diff --git a/thirdparty/rocksdb/java/rocksjni/wal_filter_jnicallback.h b/thirdparty/rocksdb/java/rocksjni/wal_filter_jnicallback.h new file mode 100644 index 0000000000..df6394cef2 --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/wal_filter_jnicallback.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// This file implements the callback "bridge" between Java and C++ for +// rocksdb::WalFilter. + +#ifndef JAVA_ROCKSJNI_WAL_FILTER_JNICALLBACK_H_ +#define JAVA_ROCKSJNI_WAL_FILTER_JNICALLBACK_H_ + +#include +#include +#include +#include + +#include "rocksdb/wal_filter.h" +#include "rocksjni/jnicallback.h" + +namespace rocksdb { + +class WalFilterJniCallback : public JniCallback, public WalFilter { + public: + WalFilterJniCallback( + JNIEnv* env, jobject jwal_filter); + virtual void ColumnFamilyLogNumberMap( + const std::map& cf_lognumber_map, + const std::map& cf_name_id_map); + virtual WalFilter::WalProcessingOption LogRecordFound( + unsigned long long log_number, const std::string& log_file_name, + const WriteBatch& batch, WriteBatch* new_batch, bool* batch_changed); + virtual const char* Name() const; + + private: + std::unique_ptr m_name; + jmethodID m_column_family_log_number_map_mid; + jmethodID m_log_record_found_proxy_mid; +}; + +} //namespace rocksdb + +#endif // JAVA_ROCKSJNI_WAL_FILTER_JNICALLBACK_H_ diff --git a/thirdparty/rocksdb/java/rocksjni/write_batch.cc b/thirdparty/rocksdb/java/rocksjni/write_batch.cc index e84f6ed7d1..f1b77446c0 100644 --- a/thirdparty/rocksdb/java/rocksjni/write_batch.cc +++ b/thirdparty/rocksdb/java/rocksjni/write_batch.cc @@ -27,19 +27,43 @@ * Method: newWriteBatch * Signature: (I)J */ -jlong Java_org_rocksdb_WriteBatch_newWriteBatch( - JNIEnv* env, jclass jcls, jint jreserved_bytes) { +jlong Java_org_rocksdb_WriteBatch_newWriteBatch__I(JNIEnv* /*env*/, + jclass /*jcls*/, + jint jreserved_bytes) { auto* wb = new rocksdb::WriteBatch(static_cast(jreserved_bytes)); return reinterpret_cast(wb); } +/* + * Class: org_rocksdb_WriteBatch + * Method: newWriteBatch + * Signature: ([BI)J + */ +jlong Java_org_rocksdb_WriteBatch_newWriteBatch___3BI(JNIEnv* env, + jclass /*jcls*/, + jbyteArray jserialized, + jint jserialized_length) { + jboolean has_exception = JNI_FALSE; + std::string serialized = rocksdb::JniUtil::byteString( + env, jserialized, jserialized_length, + [](const char* str, const size_t len) { return std::string(str, len); }, + &has_exception); + if (has_exception == JNI_TRUE) { + // exception occurred + return 0; + } + + auto* wb = new rocksdb::WriteBatch(serialized); + return reinterpret_cast(wb); +} + /* * Class: org_rocksdb_WriteBatch * Method: count0 * Signature: (J)I */ -jint Java_org_rocksdb_WriteBatch_count0(JNIEnv* env, jobject jobj, - jlong jwb_handle) { +jint Java_org_rocksdb_WriteBatch_count0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jwb_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); @@ -51,8 +75,8 @@ jint Java_org_rocksdb_WriteBatch_count0(JNIEnv* env, jobject jobj, * Method: clear0 * Signature: (J)V */ -void Java_org_rocksdb_WriteBatch_clear0(JNIEnv* env, jobject jobj, - jlong jwb_handle) { +void Java_org_rocksdb_WriteBatch_clear0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jwb_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); @@ -64,8 +88,9 @@ void Java_org_rocksdb_WriteBatch_clear0(JNIEnv* env, jobject jobj, * Method: setSavePoint0 * Signature: (J)V */ -void Java_org_rocksdb_WriteBatch_setSavePoint0( - JNIEnv* env, jobject jobj, jlong jwb_handle) { +void Java_org_rocksdb_WriteBatch_setSavePoint0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwb_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); @@ -77,8 +102,9 @@ void Java_org_rocksdb_WriteBatch_setSavePoint0( * Method: rollbackToSavePoint0 * Signature: (J)V */ -void Java_org_rocksdb_WriteBatch_rollbackToSavePoint0( - JNIEnv* env, jobject jobj, jlong jwb_handle) { +void Java_org_rocksdb_WriteBatch_rollbackToSavePoint0(JNIEnv* env, + jobject /*jobj*/, + jlong jwb_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); @@ -90,22 +116,58 @@ void Java_org_rocksdb_WriteBatch_rollbackToSavePoint0( rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } +/* + * Class: org_rocksdb_WriteBatch + * Method: popSavePoint + * Signature: (J)V + */ +void Java_org_rocksdb_WriteBatch_popSavePoint(JNIEnv* env, jobject /*jobj*/, + jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + auto s = wb->PopSavePoint(); + + if (s.ok()) { + return; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: setMaxBytes + * Signature: (JJ)V + */ +void Java_org_rocksdb_WriteBatch_setMaxBytes(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jwb_handle, + jlong jmax_bytes) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + wb->SetMaxBytes(static_cast(jmax_bytes)); +} + /* * Class: org_rocksdb_WriteBatch * Method: put * Signature: (J[BI[BI)V */ -void Java_org_rocksdb_WriteBatch_put__J_3BI_3BI( - JNIEnv* env, jobject jobj, jlong jwb_handle, - jbyteArray jkey, jint jkey_len, - jbyteArray jentry_value, jint jentry_value_len) { +void Java_org_rocksdb_WriteBatch_put__J_3BI_3BI(JNIEnv* env, jobject jobj, + jlong jwb_handle, + jbyteArray jkey, jint jkey_len, + jbyteArray jentry_value, + jint jentry_value_len) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); - auto put = [&wb] (rocksdb::Slice key, rocksdb::Slice value) { - wb->Put(key, value); + auto put = [&wb](rocksdb::Slice key, rocksdb::Slice value) { + return wb->Put(key, value); }; - rocksdb::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = rocksdb::JniUtil::kv_op( + put, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -114,18 +176,20 @@ void Java_org_rocksdb_WriteBatch_put__J_3BI_3BI( * Signature: (J[BI[BIJ)V */ void Java_org_rocksdb_WriteBatch_put__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwb_handle, - jbyteArray jkey, jint jkey_len, + JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jkey, jint jkey_len, jbyteArray jentry_value, jint jentry_value_len, jlong jcf_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); auto* cf_handle = reinterpret_cast(jcf_handle); assert(cf_handle != nullptr); - auto put = [&wb, &cf_handle] (rocksdb::Slice key, rocksdb::Slice value) { - wb->Put(cf_handle, key, value); + auto put = [&wb, &cf_handle](rocksdb::Slice key, rocksdb::Slice value) { + return wb->Put(cf_handle, key, value); }; - rocksdb::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = rocksdb::JniUtil::kv_op( + put, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -134,16 +198,18 @@ void Java_org_rocksdb_WriteBatch_put__J_3BI_3BIJ( * Signature: (J[BI[BI)V */ void Java_org_rocksdb_WriteBatch_merge__J_3BI_3BI( - JNIEnv* env, jobject jobj, jlong jwb_handle, - jbyteArray jkey, jint jkey_len, + JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jkey, jint jkey_len, jbyteArray jentry_value, jint jentry_value_len) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); - auto merge = [&wb] (rocksdb::Slice key, rocksdb::Slice value) { - wb->Merge(key, value); + auto merge = [&wb](rocksdb::Slice key, rocksdb::Slice value) { + return wb->Merge(key, value); }; - rocksdb::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = rocksdb::JniUtil::kv_op( + merge, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -152,52 +218,106 @@ void Java_org_rocksdb_WriteBatch_merge__J_3BI_3BI( * Signature: (J[BI[BIJ)V */ void Java_org_rocksdb_WriteBatch_merge__J_3BI_3BIJ( - JNIEnv* env, jobject jobj, jlong jwb_handle, - jbyteArray jkey, jint jkey_len, + JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jkey, jint jkey_len, jbyteArray jentry_value, jint jentry_value_len, jlong jcf_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); auto* cf_handle = reinterpret_cast(jcf_handle); assert(cf_handle != nullptr); - auto merge = [&wb, &cf_handle] (rocksdb::Slice key, rocksdb::Slice value) { - wb->Merge(cf_handle, key, value); + auto merge = [&wb, &cf_handle](rocksdb::Slice key, rocksdb::Slice value) { + return wb->Merge(cf_handle, key, value); }; - rocksdb::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = rocksdb::JniUtil::kv_op( + merge, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* * Class: org_rocksdb_WriteBatch - * Method: remove + * Method: delete * Signature: (J[BI)V */ -void Java_org_rocksdb_WriteBatch_remove__J_3BI( - JNIEnv* env, jobject jobj, jlong jwb_handle, - jbyteArray jkey, jint jkey_len) { +void Java_org_rocksdb_WriteBatch_delete__J_3BI(JNIEnv* env, jobject jobj, + jlong jwb_handle, + jbyteArray jkey, jint jkey_len) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); - auto remove = [&wb] (rocksdb::Slice key) { - wb->Delete(key); + auto remove = [&wb](rocksdb::Slice key) { return wb->Delete(key); }; + std::unique_ptr status = + rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: delete + * Signature: (J[BIJ)V + */ +void Java_org_rocksdb_WriteBatch_delete__J_3BIJ(JNIEnv* env, jobject jobj, + jlong jwb_handle, + jbyteArray jkey, jint jkey_len, + jlong jcf_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + auto* cf_handle = reinterpret_cast(jcf_handle); + assert(cf_handle != nullptr); + auto remove = [&wb, &cf_handle](rocksdb::Slice key) { + return wb->Delete(cf_handle, key); + }; + std::unique_ptr status = + rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: singleDelete + * Signature: (J[BI)V + */ +void Java_org_rocksdb_WriteBatch_singleDelete__J_3BI(JNIEnv* env, jobject jobj, + jlong jwb_handle, + jbyteArray jkey, + jint jkey_len) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + auto single_delete = [&wb](rocksdb::Slice key) { + return wb->SingleDelete(key); }; - rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + std::unique_ptr status = + rocksdb::JniUtil::k_op(single_delete, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* * Class: org_rocksdb_WriteBatch - * Method: remove + * Method: singleDelete * Signature: (J[BIJ)V */ -void Java_org_rocksdb_WriteBatch_remove__J_3BIJ( - JNIEnv* env, jobject jobj, jlong jwb_handle, - jbyteArray jkey, jint jkey_len, jlong jcf_handle) { +void Java_org_rocksdb_WriteBatch_singleDelete__J_3BIJ(JNIEnv* env, jobject jobj, + jlong jwb_handle, + jbyteArray jkey, + jint jkey_len, + jlong jcf_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); auto* cf_handle = reinterpret_cast(jcf_handle); assert(cf_handle != nullptr); - auto remove = [&wb, &cf_handle] (rocksdb::Slice key) { - wb->Delete(cf_handle, key); + auto single_delete = [&wb, &cf_handle](rocksdb::Slice key) { + return wb->SingleDelete(cf_handle, key); }; - rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + std::unique_ptr status = + rocksdb::JniUtil::k_op(single_delete, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -205,19 +325,20 @@ void Java_org_rocksdb_WriteBatch_remove__J_3BIJ( * Method: deleteRange * Signature: (J[BI[BI)V */ -JNIEXPORT void JNICALL Java_org_rocksdb_WriteBatch_deleteRange__J_3BI_3BI( - JNIEnv*, jobject, jlong, jbyteArray, jint, jbyteArray, jint); - void Java_org_rocksdb_WriteBatch_deleteRange__J_3BI_3BI( JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jbegin_key, jint jbegin_key_len, jbyteArray jend_key, jint jend_key_len) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); auto deleteRange = [&wb](rocksdb::Slice beginKey, rocksdb::Slice endKey) { - wb->DeleteRange(beginKey, endKey); + return wb->DeleteRange(beginKey, endKey); }; - rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, jbegin_key_len, - jend_key, jend_key_len); + std::unique_ptr status = + rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, + jbegin_key_len, jend_key, jend_key_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -235,10 +356,14 @@ void Java_org_rocksdb_WriteBatch_deleteRange__J_3BI_3BIJ( assert(cf_handle != nullptr); auto deleteRange = [&wb, &cf_handle](rocksdb::Slice beginKey, rocksdb::Slice endKey) { - wb->DeleteRange(cf_handle, beginKey, endKey); + return wb->DeleteRange(cf_handle, beginKey, endKey); }; - rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, jbegin_key_len, - jend_key, jend_key_len); + std::unique_ptr status = + rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, + jbegin_key_len, jend_key, jend_key_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -246,15 +371,17 @@ void Java_org_rocksdb_WriteBatch_deleteRange__J_3BI_3BIJ( * Method: putLogData * Signature: (J[BI)V */ -void Java_org_rocksdb_WriteBatch_putLogData( - JNIEnv* env, jobject jobj, jlong jwb_handle, jbyteArray jblob, - jint jblob_len) { +void Java_org_rocksdb_WriteBatch_putLogData(JNIEnv* env, jobject jobj, + jlong jwb_handle, jbyteArray jblob, + jint jblob_len) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); - auto putLogData = [&wb] (rocksdb::Slice blob) { - wb->PutLogData(blob); - }; - rocksdb::JniUtil::k_op(putLogData, env, jobj, jblob, jblob_len); + auto putLogData = [&wb](rocksdb::Slice blob) { return wb->PutLogData(blob); }; + std::unique_ptr status = + rocksdb::JniUtil::k_op(putLogData, env, jobj, jblob, jblob_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -262,13 +389,14 @@ void Java_org_rocksdb_WriteBatch_putLogData( * Method: iterate * Signature: (JJ)V */ -void Java_org_rocksdb_WriteBatch_iterate( - JNIEnv* env , jobject jobj, jlong jwb_handle, jlong handlerHandle) { +void Java_org_rocksdb_WriteBatch_iterate(JNIEnv* env, jobject /*jobj*/, + jlong jwb_handle, + jlong handlerHandle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); rocksdb::Status s = wb->Iterate( - reinterpret_cast(handlerHandle)); + reinterpret_cast(handlerHandle)); if (s.ok()) { return; @@ -276,13 +404,189 @@ void Java_org_rocksdb_WriteBatch_iterate( rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } +/* + * Class: org_rocksdb_WriteBatch + * Method: data + * Signature: (J)[B + */ +jbyteArray Java_org_rocksdb_WriteBatch_data(JNIEnv* env, jobject /*jobj*/, + jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + auto data = wb->Data(); + return rocksdb::JniUtil::copyBytes(env, data); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: getDataSize + * Signature: (J)J + */ +jlong Java_org_rocksdb_WriteBatch_getDataSize(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + auto data_size = wb->GetDataSize(); + return static_cast(data_size); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasPut + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_WriteBatch_hasPut(JNIEnv* /*env*/, jobject /*jobj*/, + jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasPut(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasDelete + * Signature: (J)Z + */ +jboolean Java_org_rocksdb_WriteBatch_hasDelete(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasDelete(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasSingleDelete + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasSingleDelete( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasSingleDelete(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasDeleteRange + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasDeleteRange( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasDeleteRange(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasMerge + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasMerge( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasMerge(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasBeginPrepare + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasBeginPrepare( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasBeginPrepare(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasEndPrepare + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasEndPrepare( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasEndPrepare(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasCommit + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasCommit( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasCommit(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: hasRollback + * Signature: (J)Z + */ +JNIEXPORT jboolean JNICALL Java_org_rocksdb_WriteBatch_hasRollback( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + return wb->HasRollback(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: markWalTerminationPoint + * Signature: (J)V + */ +void Java_org_rocksdb_WriteBatch_markWalTerminationPoint(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + wb->MarkWalTerminationPoint(); +} + +/* + * Class: org_rocksdb_WriteBatch + * Method: getWalTerminationPoint + * Signature: (J)Lorg/rocksdb/WriteBatch/SavePoint; + */ +jobject Java_org_rocksdb_WriteBatch_getWalTerminationPoint(JNIEnv* env, + jobject /*jobj*/, + jlong jwb_handle) { + auto* wb = reinterpret_cast(jwb_handle); + assert(wb != nullptr); + + auto save_point = wb->GetWalTerminationPoint(); + return rocksdb::WriteBatchSavePointJni::construct(env, save_point); +} + /* * Class: org_rocksdb_WriteBatch * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_WriteBatch_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WriteBatch_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { auto* wb = reinterpret_cast(handle); assert(wb != nullptr); delete wb; @@ -293,21 +597,8 @@ void Java_org_rocksdb_WriteBatch_disposeInternal( * Method: createNewHandler0 * Signature: ()J */ -jlong Java_org_rocksdb_WriteBatch_00024Handler_createNewHandler0( - JNIEnv* env, jobject jobj) { +jlong Java_org_rocksdb_WriteBatch_00024Handler_createNewHandler0(JNIEnv* env, + jobject jobj) { auto* wbjnic = new rocksdb::WriteBatchHandlerJniCallback(env, jobj); return reinterpret_cast(wbjnic); } - -/* - * Class: org_rocksdb_WriteBatch_Handler - * Method: disposeInternal - * Signature: (J)V - */ -void Java_org_rocksdb_WriteBatch_00024Handler_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { - auto* wbjnic = - reinterpret_cast(handle); - assert(wbjnic != nullptr); - delete wbjnic; -} diff --git a/thirdparty/rocksdb/java/rocksjni/write_batch_test.cc b/thirdparty/rocksdb/java/rocksjni/write_batch_test.cc index 199ad239d7..266fb4abf7 100644 --- a/thirdparty/rocksdb/java/rocksjni/write_batch_test.cc +++ b/thirdparty/rocksdb/java/rocksjni/write_batch_test.cc @@ -30,8 +30,9 @@ * Method: getContents * Signature: (J)[B */ -jbyteArray Java_org_rocksdb_WriteBatchTest_getContents( - JNIEnv* env, jclass jclazz, jlong jwb_handle) { +jbyteArray Java_org_rocksdb_WriteBatchTest_getContents(JNIEnv* env, + jclass /*jclazz*/, + jlong jwb_handle) { auto* b = reinterpret_cast(jwb_handle); assert(b != nullptr); @@ -55,8 +56,8 @@ jbyteArray Java_org_rocksdb_WriteBatchTest_getContents( rocksdb::WriteBatchInternal::InsertInto(b, &cf_mems_default, nullptr); int count = 0; rocksdb::Arena arena; - rocksdb::ScopedArenaIterator iter(mem->NewIterator( - rocksdb::ReadOptions(), &arena)); + rocksdb::ScopedArenaIterator iter( + mem->NewIterator(rocksdb::ReadOptions(), &arena)); for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { rocksdb::ParsedInternalKey ikey; ikey.clear(); @@ -87,8 +88,32 @@ jbyteArray Java_org_rocksdb_WriteBatchTest_getContents( state.append(")"); count++; break; + case rocksdb::kTypeSingleDeletion: + state.append("SingleDelete("); + state.append(ikey.user_key.ToString()); + state.append(")"); + count++; + break; + case rocksdb::kTypeRangeDeletion: + state.append("DeleteRange("); + state.append(ikey.user_key.ToString()); + state.append(", "); + state.append(iter->value().ToString()); + state.append(")"); + count++; + break; + case rocksdb::kTypeLogData: + state.append("LogData("); + state.append(ikey.user_key.ToString()); + state.append(")"); + count++; + break; default: assert(false); + state.append("Err:Expected("); + state.append(std::to_string(ikey.type)); + state.append(")"); + count++; break; } state.append("@"); @@ -96,20 +121,25 @@ jbyteArray Java_org_rocksdb_WriteBatchTest_getContents( } if (!s.ok()) { state.append(s.ToString()); - } else if (count != rocksdb::WriteBatchInternal::Count(b)) { - state.append("CountMismatch()"); + } else if (rocksdb::WriteBatchInternal::Count(b) != count) { + state.append("Err:CountMismatch(expected="); + state.append(std::to_string(rocksdb::WriteBatchInternal::Count(b))); + state.append(", actual="); + state.append(std::to_string(count)); + state.append(")"); } delete mem->Unref(); jbyteArray jstate = env->NewByteArray(static_cast(state.size())); - if(jstate == nullptr) { + if (jstate == nullptr) { // exception thrown: OutOfMemoryError return nullptr; } - env->SetByteArrayRegion(jstate, 0, static_cast(state.size()), - const_cast(reinterpret_cast(state.c_str()))); - if(env->ExceptionCheck()) { + env->SetByteArrayRegion( + jstate, 0, static_cast(state.size()), + const_cast(reinterpret_cast(state.c_str()))); + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(jstate); return nullptr; @@ -124,7 +154,7 @@ jbyteArray Java_org_rocksdb_WriteBatchTest_getContents( * Signature: (JJ)V */ void Java_org_rocksdb_WriteBatchTestInternalHelper_setSequence( - JNIEnv* env, jclass jclazz, jlong jwb_handle, jlong jsn) { + JNIEnv* /*env*/, jclass /*jclazz*/, jlong jwb_handle, jlong jsn) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); @@ -137,8 +167,9 @@ void Java_org_rocksdb_WriteBatchTestInternalHelper_setSequence( * Method: sequence * Signature: (J)J */ -jlong Java_org_rocksdb_WriteBatchTestInternalHelper_sequence( - JNIEnv* env, jclass jclazz, jlong jwb_handle) { +jlong Java_org_rocksdb_WriteBatchTestInternalHelper_sequence(JNIEnv* /*env*/, + jclass /*jclazz*/, + jlong jwb_handle) { auto* wb = reinterpret_cast(jwb_handle); assert(wb != nullptr); @@ -150,8 +181,10 @@ jlong Java_org_rocksdb_WriteBatchTestInternalHelper_sequence( * Method: append * Signature: (JJ)V */ -void Java_org_rocksdb_WriteBatchTestInternalHelper_append( - JNIEnv* env, jclass jclazz, jlong jwb_handle_1, jlong jwb_handle_2) { +void Java_org_rocksdb_WriteBatchTestInternalHelper_append(JNIEnv* /*env*/, + jclass /*jclazz*/, + jlong jwb_handle_1, + jlong jwb_handle_2) { auto* wb1 = reinterpret_cast(jwb_handle_1); assert(wb1 != nullptr); auto* wb2 = reinterpret_cast(jwb_handle_2); diff --git a/thirdparty/rocksdb/java/rocksjni/write_batch_with_index.cc b/thirdparty/rocksdb/java/rocksjni/write_batch_with_index.cc index 53f2a11d12..12ca299a9d 100644 --- a/thirdparty/rocksdb/java/rocksjni/write_batch_with_index.cc +++ b/thirdparty/rocksdb/java/rocksjni/write_batch_with_index.cc @@ -6,10 +6,10 @@ // This file implements the "bridge" between Java and C++ and enables // calling c++ rocksdb::WriteBatchWithIndex methods from Java side. +#include "rocksdb/utilities/write_batch_with_index.h" #include "include/org_rocksdb_WBWIRocksIterator.h" #include "include/org_rocksdb_WriteBatchWithIndex.h" #include "rocksdb/comparator.h" -#include "rocksdb/utilities/write_batch_with_index.h" #include "rocksjni/portal.h" /* @@ -18,7 +18,7 @@ * Signature: ()J */ jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__( - JNIEnv* env, jclass jcls) { + JNIEnv* /*env*/, jclass /*jcls*/) { auto* wbwi = new rocksdb::WriteBatchWithIndex(); return reinterpret_cast(wbwi); } @@ -29,25 +29,44 @@ jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__( * Signature: (Z)J */ jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__Z( - JNIEnv* env, jclass jcls, jboolean joverwrite_key) { - auto* wbwi = - new rocksdb::WriteBatchWithIndex(rocksdb::BytewiseComparator(), 0, - static_cast(joverwrite_key)); + JNIEnv* /*env*/, jclass /*jcls*/, jboolean joverwrite_key) { + auto* wbwi = new rocksdb::WriteBatchWithIndex( + rocksdb::BytewiseComparator(), 0, static_cast(joverwrite_key)); return reinterpret_cast(wbwi); } /* * Class: org_rocksdb_WriteBatchWithIndex * Method: newWriteBatchWithIndex - * Signature: (JIZ)J - */ -jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__JIZ( - JNIEnv* env, jclass jcls, jlong jfallback_index_comparator_handle, - jint jreserved_bytes, jboolean joverwrite_key) { - auto* wbwi = - new rocksdb::WriteBatchWithIndex( - reinterpret_cast(jfallback_index_comparator_handle), - static_cast(jreserved_bytes), static_cast(joverwrite_key)); + * Signature: (JBIZ)J + */ +jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__JBIZ( + JNIEnv* /*env*/, jclass /*jcls*/, jlong jfallback_index_comparator_handle, + jbyte jcomparator_type, jint jreserved_bytes, jboolean joverwrite_key) { + rocksdb::Comparator* fallback_comparator = nullptr; + switch (jcomparator_type) { + // JAVA_COMPARATOR + case 0x0: + fallback_comparator = reinterpret_cast( + jfallback_index_comparator_handle); + break; + + // JAVA_DIRECT_COMPARATOR + case 0x1: + fallback_comparator = + reinterpret_cast( + jfallback_index_comparator_handle); + break; + + // JAVA_NATIVE_COMPARATOR_WRAPPER + case 0x2: + fallback_comparator = reinterpret_cast( + jfallback_index_comparator_handle); + break; + } + auto* wbwi = new rocksdb::WriteBatchWithIndex( + fallback_comparator, static_cast(jreserved_bytes), + static_cast(joverwrite_key)); return reinterpret_cast(wbwi); } @@ -56,8 +75,9 @@ jlong Java_org_rocksdb_WriteBatchWithIndex_newWriteBatchWithIndex__JIZ( * Method: count0 * Signature: (J)I */ -jint Java_org_rocksdb_WriteBatchWithIndex_count0( - JNIEnv* env, jobject jobj, jlong jwbwi_handle) { +jint Java_org_rocksdb_WriteBatchWithIndex_count0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwbwi_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); @@ -74,11 +94,14 @@ void Java_org_rocksdb_WriteBatchWithIndex_put__J_3BI_3BI( jint jkey_len, jbyteArray jentry_value, jint jentry_value_len) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); - auto put = [&wbwi] (rocksdb::Slice key, rocksdb::Slice value) { - wbwi->Put(key, value); + auto put = [&wbwi](rocksdb::Slice key, rocksdb::Slice value) { + return wbwi->Put(key, value); }; - rocksdb::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = rocksdb::JniUtil::kv_op( + put, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -94,11 +117,14 @@ void Java_org_rocksdb_WriteBatchWithIndex_put__J_3BI_3BIJ( assert(wbwi != nullptr); auto* cf_handle = reinterpret_cast(jcf_handle); assert(cf_handle != nullptr); - auto put = [&wbwi, &cf_handle] (rocksdb::Slice key, rocksdb::Slice value) { - wbwi->Put(cf_handle, key, value); + auto put = [&wbwi, &cf_handle](rocksdb::Slice key, rocksdb::Slice value) { + return wbwi->Put(cf_handle, key, value); }; - rocksdb::JniUtil::kv_op(put, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = rocksdb::JniUtil::kv_op( + put, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -111,11 +137,14 @@ void Java_org_rocksdb_WriteBatchWithIndex_merge__J_3BI_3BI( jint jkey_len, jbyteArray jentry_value, jint jentry_value_len) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); - auto merge = [&wbwi] (rocksdb::Slice key, rocksdb::Slice value) { - wbwi->Merge(key, value); + auto merge = [&wbwi](rocksdb::Slice key, rocksdb::Slice value) { + return wbwi->Merge(key, value); }; - rocksdb::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = rocksdb::JniUtil::kv_op( + merge, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -131,45 +160,98 @@ void Java_org_rocksdb_WriteBatchWithIndex_merge__J_3BI_3BIJ( assert(wbwi != nullptr); auto* cf_handle = reinterpret_cast(jcf_handle); assert(cf_handle != nullptr); - auto merge = [&wbwi, &cf_handle] (rocksdb::Slice key, rocksdb::Slice value) { - wbwi->Merge(cf_handle, key, value); + auto merge = [&wbwi, &cf_handle](rocksdb::Slice key, rocksdb::Slice value) { + return wbwi->Merge(cf_handle, key, value); + }; + std::unique_ptr status = rocksdb::JniUtil::kv_op( + merge, env, jobj, jkey, jkey_len, jentry_value, jentry_value_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } +} + +/* + * Class: org_rocksdb_WriteBatchWithIndex + * Method: delete + * Signature: (J[BI)V + */ +void Java_org_rocksdb_WriteBatchWithIndex_delete__J_3BI(JNIEnv* env, + jobject jobj, + jlong jwbwi_handle, + jbyteArray jkey, + jint jkey_len) { + auto* wbwi = reinterpret_cast(jwbwi_handle); + assert(wbwi != nullptr); + auto remove = [&wbwi](rocksdb::Slice key) { return wbwi->Delete(key); }; + std::unique_ptr status = + rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } +} + +/* + * Class: org_rocksdb_WriteBatchWithIndex + * Method: delete + * Signature: (J[BIJ)V + */ +void Java_org_rocksdb_WriteBatchWithIndex_delete__J_3BIJ( + JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, + jint jkey_len, jlong jcf_handle) { + auto* wbwi = reinterpret_cast(jwbwi_handle); + assert(wbwi != nullptr); + auto* cf_handle = reinterpret_cast(jcf_handle); + assert(cf_handle != nullptr); + auto remove = [&wbwi, &cf_handle](rocksdb::Slice key) { + return wbwi->Delete(cf_handle, key); }; - rocksdb::JniUtil::kv_op(merge, env, jobj, jkey, jkey_len, jentry_value, - jentry_value_len); + std::unique_ptr status = + rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* * Class: org_rocksdb_WriteBatchWithIndex - * Method: remove + * Method: singleDelete * Signature: (J[BI)V */ -void Java_org_rocksdb_WriteBatchWithIndex_remove__J_3BI( +void Java_org_rocksdb_WriteBatchWithIndex_singleDelete__J_3BI( JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, jint jkey_len) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); - auto remove = [&wbwi] (rocksdb::Slice key) { - wbwi->Delete(key); + auto single_delete = [&wbwi](rocksdb::Slice key) { + return wbwi->SingleDelete(key); }; - rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + std::unique_ptr status = + rocksdb::JniUtil::k_op(single_delete, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* * Class: org_rocksdb_WriteBatchWithIndex - * Method: remove + * Method: singleDelete * Signature: (J[BIJ)V */ -void Java_org_rocksdb_WriteBatchWithIndex_remove__J_3BIJ( +void Java_org_rocksdb_WriteBatchWithIndex_singleDelete__J_3BIJ( JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jkey, jint jkey_len, jlong jcf_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); auto* cf_handle = reinterpret_cast(jcf_handle); assert(cf_handle != nullptr); - auto remove = [&wbwi, &cf_handle] (rocksdb::Slice key) { - wbwi->Delete(cf_handle, key); + auto single_delete = [&wbwi, &cf_handle](rocksdb::Slice key) { + return wbwi->SingleDelete(cf_handle, key); }; - rocksdb::JniUtil::k_op(remove, env, jobj, jkey, jkey_len); + std::unique_ptr status = + rocksdb::JniUtil::k_op(single_delete, env, jobj, jkey, jkey_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -183,10 +265,14 @@ void Java_org_rocksdb_WriteBatchWithIndex_deleteRange__J_3BI_3BI( auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); auto deleteRange = [&wbwi](rocksdb::Slice beginKey, rocksdb::Slice endKey) { - wbwi->DeleteRange(beginKey, endKey); + return wbwi->DeleteRange(beginKey, endKey); }; - rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, jbegin_key_len, - jend_key, jend_key_len); + std::unique_ptr status = + rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, + jbegin_key_len, jend_key, jend_key_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -204,10 +290,14 @@ void Java_org_rocksdb_WriteBatchWithIndex_deleteRange__J_3BI_3BIJ( assert(cf_handle != nullptr); auto deleteRange = [&wbwi, &cf_handle](rocksdb::Slice beginKey, rocksdb::Slice endKey) { - wbwi->DeleteRange(cf_handle, beginKey, endKey); + return wbwi->DeleteRange(cf_handle, beginKey, endKey); }; - rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, jbegin_key_len, - jend_key, jend_key_len); + std::unique_ptr status = + rocksdb::JniUtil::kv_op(deleteRange, env, jobj, jbegin_key, + jbegin_key_len, jend_key, jend_key_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -215,15 +305,20 @@ void Java_org_rocksdb_WriteBatchWithIndex_deleteRange__J_3BI_3BIJ( * Method: putLogData * Signature: (J[BI)V */ -void Java_org_rocksdb_WriteBatchWithIndex_putLogData( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jbyteArray jblob, - jint jblob_len) { +void Java_org_rocksdb_WriteBatchWithIndex_putLogData(JNIEnv* env, jobject jobj, + jlong jwbwi_handle, + jbyteArray jblob, + jint jblob_len) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); - auto putLogData = [&wbwi] (rocksdb::Slice blob) { - wbwi->PutLogData(blob); + auto putLogData = [&wbwi](rocksdb::Slice blob) { + return wbwi->PutLogData(blob); }; - rocksdb::JniUtil::k_op(putLogData, env, jobj, jblob, jblob_len); + std::unique_ptr status = + rocksdb::JniUtil::k_op(putLogData, env, jobj, jblob, jblob_len); + if (status != nullptr && !status->ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, status); + } } /* @@ -231,8 +326,9 @@ void Java_org_rocksdb_WriteBatchWithIndex_putLogData( * Method: clear * Signature: (J)V */ -void Java_org_rocksdb_WriteBatchWithIndex_clear0( - JNIEnv* env, jobject jobj, jlong jwbwi_handle) { +void Java_org_rocksdb_WriteBatchWithIndex_clear0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwbwi_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); @@ -244,8 +340,9 @@ void Java_org_rocksdb_WriteBatchWithIndex_clear0( * Method: setSavePoint0 * Signature: (J)V */ -void Java_org_rocksdb_WriteBatchWithIndex_setSavePoint0( - JNIEnv* env, jobject jobj, jlong jwbwi_handle) { +void Java_org_rocksdb_WriteBatchWithIndex_setSavePoint0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwbwi_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); @@ -258,7 +355,7 @@ void Java_org_rocksdb_WriteBatchWithIndex_setSavePoint0( * Signature: (J)V */ void Java_org_rocksdb_WriteBatchWithIndex_rollbackToSavePoint0( - JNIEnv* env, jobject jobj, jlong jwbwi_handle) { + JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); assert(wbwi != nullptr); @@ -271,13 +368,66 @@ void Java_org_rocksdb_WriteBatchWithIndex_rollbackToSavePoint0( rocksdb::RocksDBExceptionJni::ThrowNew(env, s); } +/* + * Class: org_rocksdb_WriteBatchWithIndex + * Method: popSavePoint + * Signature: (J)V + */ +void Java_org_rocksdb_WriteBatchWithIndex_popSavePoint(JNIEnv* env, + jobject /*jobj*/, + jlong jwbwi_handle) { + auto* wbwi = reinterpret_cast(jwbwi_handle); + assert(wbwi != nullptr); + + auto s = wbwi->PopSavePoint(); + + if (s.ok()) { + return; + } + + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_WriteBatchWithIndex + * Method: setMaxBytes + * Signature: (JJ)V + */ +void Java_org_rocksdb_WriteBatchWithIndex_setMaxBytes(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwbwi_handle, + jlong jmax_bytes) { + auto* wbwi = reinterpret_cast(jwbwi_handle); + assert(wbwi != nullptr); + + wbwi->SetMaxBytes(static_cast(jmax_bytes)); +} + +/* + * Class: org_rocksdb_WriteBatchWithIndex + * Method: getWriteBatch + * Signature: (J)Lorg/rocksdb/WriteBatch; + */ +jobject Java_org_rocksdb_WriteBatchWithIndex_getWriteBatch(JNIEnv* env, + jobject /*jobj*/, + jlong jwbwi_handle) { + auto* wbwi = reinterpret_cast(jwbwi_handle); + assert(wbwi != nullptr); + + auto* wb = wbwi->GetWriteBatch(); + + // TODO(AR) is the `wb` object owned by us? + return rocksdb::WriteBatchJni::construct(env, wb); +} + /* * Class: org_rocksdb_WriteBatchWithIndex * Method: iterator0 * Signature: (J)J */ -jlong Java_org_rocksdb_WriteBatchWithIndex_iterator0( - JNIEnv* env, jobject jobj, jlong jwbwi_handle) { +jlong Java_org_rocksdb_WriteBatchWithIndex_iterator0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwbwi_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); auto* wbwi_iterator = wbwi->NewIterator(); return reinterpret_cast(wbwi_iterator); @@ -288,8 +438,10 @@ jlong Java_org_rocksdb_WriteBatchWithIndex_iterator0( * Method: iterator1 * Signature: (JJ)J */ -jlong Java_org_rocksdb_WriteBatchWithIndex_iterator1( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jlong jcf_handle) { +jlong Java_org_rocksdb_WriteBatchWithIndex_iterator1(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwbwi_handle, + jlong jcf_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); auto* cf_handle = reinterpret_cast(jcf_handle); auto* wbwi_iterator = wbwi->NewIterator(cf_handle); @@ -301,9 +453,11 @@ jlong Java_org_rocksdb_WriteBatchWithIndex_iterator1( * Method: iteratorWithBase * Signature: (JJJ)J */ -jlong Java_org_rocksdb_WriteBatchWithIndex_iteratorWithBase( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jlong jcf_handle, - jlong jbi_handle) { +jlong Java_org_rocksdb_WriteBatchWithIndex_iteratorWithBase(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong jwbwi_handle, + jlong jcf_handle, + jlong jbi_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); auto* cf_handle = reinterpret_cast(jcf_handle); auto* base_iterator = reinterpret_cast(jbi_handle); @@ -317,7 +471,7 @@ jlong Java_org_rocksdb_WriteBatchWithIndex_iteratorWithBase( * Signature: (JJ[BI)[B */ jbyteArray JNICALL Java_org_rocksdb_WriteBatchWithIndex_getFromBatch__JJ_3BI( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jlong jdbopt_handle, + JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdbopt_handle, jbyteArray jkey, jint jkey_len) { auto* wbwi = reinterpret_cast(jwbwi_handle); auto* dbopt = reinterpret_cast(jdbopt_handle); @@ -335,17 +489,16 @@ jbyteArray JNICALL Java_org_rocksdb_WriteBatchWithIndex_getFromBatch__JJ_3BI( * Signature: (JJ[BIJ)[B */ jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatch__JJ_3BIJ( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jlong jdbopt_handle, + JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdbopt_handle, jbyteArray jkey, jint jkey_len, jlong jcf_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); auto* dbopt = reinterpret_cast(jdbopt_handle); auto* cf_handle = reinterpret_cast(jcf_handle); - auto getter = - [&wbwi, &cf_handle, &dbopt](const rocksdb::Slice& key, - std::string* value) { - return wbwi->GetFromBatch(cf_handle, *dbopt, key, value); - }; + auto getter = [&wbwi, &cf_handle, &dbopt](const rocksdb::Slice& key, + std::string* value) { + return wbwi->GetFromBatch(cf_handle, *dbopt, key, value); + }; return rocksdb::JniUtil::v_op(getter, env, jkey, jkey_len); } @@ -356,16 +509,16 @@ jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatch__JJ_3BIJ( * Signature: (JJJ[BI)[B */ jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatchAndDB__JJJ_3BI( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jlong jdb_handle, + JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdb_handle, jlong jreadopt_handle, jbyteArray jkey, jint jkey_len) { auto* wbwi = reinterpret_cast(jwbwi_handle); auto* db = reinterpret_cast(jdb_handle); auto* readopt = reinterpret_cast(jreadopt_handle); - auto getter = - [&wbwi, &db, &readopt](const rocksdb::Slice& key, std::string* value) { - return wbwi->GetFromBatchAndDB(db, *readopt, key, value); - }; + auto getter = [&wbwi, &db, &readopt](const rocksdb::Slice& key, + std::string* value) { + return wbwi->GetFromBatchAndDB(db, *readopt, key, value); + }; return rocksdb::JniUtil::v_op(getter, env, jkey, jkey_len); } @@ -376,18 +529,17 @@ jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatchAndDB__JJJ_3BI( * Signature: (JJJ[BIJ)[B */ jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatchAndDB__JJJ_3BIJ( - JNIEnv* env, jobject jobj, jlong jwbwi_handle, jlong jdb_handle, + JNIEnv* env, jobject /*jobj*/, jlong jwbwi_handle, jlong jdb_handle, jlong jreadopt_handle, jbyteArray jkey, jint jkey_len, jlong jcf_handle) { auto* wbwi = reinterpret_cast(jwbwi_handle); auto* db = reinterpret_cast(jdb_handle); auto* readopt = reinterpret_cast(jreadopt_handle); auto* cf_handle = reinterpret_cast(jcf_handle); - auto getter = - [&wbwi, &db, &cf_handle, &readopt](const rocksdb::Slice& key, - std::string* value) { - return wbwi->GetFromBatchAndDB(db, *readopt, cf_handle, key, value); - }; + auto getter = [&wbwi, &db, &cf_handle, &readopt](const rocksdb::Slice& key, + std::string* value) { + return wbwi->GetFromBatchAndDB(db, *readopt, cf_handle, key, value); + }; return rocksdb::JniUtil::v_op(getter, env, jkey, jkey_len); } @@ -397,8 +549,9 @@ jbyteArray Java_org_rocksdb_WriteBatchWithIndex_getFromBatchAndDB__JJJ_3BIJ( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_WriteBatchWithIndex_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WriteBatchWithIndex_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { auto* wbwi = reinterpret_cast(handle); assert(wbwi != nullptr); delete wbwi; @@ -411,8 +564,9 @@ void Java_org_rocksdb_WriteBatchWithIndex_disposeInternal( * Method: disposeInternal * Signature: (J)V */ -void Java_org_rocksdb_WBWIRocksIterator_disposeInternal( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WBWIRocksIterator_disposeInternal(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { auto* it = reinterpret_cast(handle); assert(it != nullptr); delete it; @@ -423,8 +577,9 @@ void Java_org_rocksdb_WBWIRocksIterator_disposeInternal( * Method: isValid0 * Signature: (J)Z */ -jboolean Java_org_rocksdb_WBWIRocksIterator_isValid0( - JNIEnv* env, jobject jobj, jlong handle) { +jboolean Java_org_rocksdb_WBWIRocksIterator_isValid0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { return reinterpret_cast(handle)->Valid(); } @@ -433,8 +588,9 @@ jboolean Java_org_rocksdb_WBWIRocksIterator_isValid0( * Method: seekToFirst0 * Signature: (J)V */ -void Java_org_rocksdb_WBWIRocksIterator_seekToFirst0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WBWIRocksIterator_seekToFirst0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->SeekToFirst(); } @@ -443,8 +599,9 @@ void Java_org_rocksdb_WBWIRocksIterator_seekToFirst0( * Method: seekToLast0 * Signature: (J)V */ -void Java_org_rocksdb_WBWIRocksIterator_seekToLast0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WBWIRocksIterator_seekToLast0(JNIEnv* /*env*/, + jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->SeekToLast(); } @@ -453,8 +610,8 @@ void Java_org_rocksdb_WBWIRocksIterator_seekToLast0( * Method: next0 * Signature: (J)V */ -void Java_org_rocksdb_WBWIRocksIterator_next0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WBWIRocksIterator_next0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->Next(); } @@ -463,8 +620,8 @@ void Java_org_rocksdb_WBWIRocksIterator_next0( * Method: prev0 * Signature: (J)V */ -void Java_org_rocksdb_WBWIRocksIterator_prev0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WBWIRocksIterator_prev0(JNIEnv* /*env*/, jobject /*jobj*/, + jlong handle) { reinterpret_cast(handle)->Prev(); } @@ -473,31 +630,54 @@ void Java_org_rocksdb_WBWIRocksIterator_prev0( * Method: seek0 * Signature: (J[BI)V */ -void Java_org_rocksdb_WBWIRocksIterator_seek0( - JNIEnv* env, jobject jobj, jlong handle, jbyteArray jtarget, - jint jtarget_len) { +void Java_org_rocksdb_WBWIRocksIterator_seek0(JNIEnv* env, jobject /*jobj*/, + jlong handle, jbyteArray jtarget, + jint jtarget_len) { auto* it = reinterpret_cast(handle); jbyte* target = env->GetByteArrayElements(jtarget, nullptr); - if(target == nullptr) { + if (target == nullptr) { // exception thrown: OutOfMemoryError return; } - rocksdb::Slice target_slice( - reinterpret_cast(target), jtarget_len); + rocksdb::Slice target_slice(reinterpret_cast(target), jtarget_len); it->Seek(target_slice); env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT); } +/* + * Class: org_rocksdb_WBWIRocksIterator + * Method: seekForPrev0 + * Signature: (J[BI)V + */ +void Java_org_rocksdb_WBWIRocksIterator_seekForPrev0(JNIEnv* env, + jobject /*jobj*/, + jlong handle, + jbyteArray jtarget, + jint jtarget_len) { + auto* it = reinterpret_cast(handle); + jbyte* target = env->GetByteArrayElements(jtarget, nullptr); + if (target == nullptr) { + // exception thrown: OutOfMemoryError + return; + } + + rocksdb::Slice target_slice(reinterpret_cast(target), jtarget_len); + + it->SeekForPrev(target_slice); + + env->ReleaseByteArrayElements(jtarget, target, JNI_ABORT); +} + /* * Class: org_rocksdb_WBWIRocksIterator * Method: status0 * Signature: (J)V */ -void Java_org_rocksdb_WBWIRocksIterator_status0( - JNIEnv* env, jobject jobj, jlong handle) { +void Java_org_rocksdb_WBWIRocksIterator_status0(JNIEnv* env, jobject /*jobj*/, + jlong handle) { auto* it = reinterpret_cast(handle); rocksdb::Status s = it->status(); @@ -513,41 +693,25 @@ void Java_org_rocksdb_WBWIRocksIterator_status0( * Method: entry1 * Signature: (J)[J */ -jlongArray Java_org_rocksdb_WBWIRocksIterator_entry1( - JNIEnv* env, jobject jobj, jlong handle) { +jlongArray Java_org_rocksdb_WBWIRocksIterator_entry1(JNIEnv* env, + jobject /*jobj*/, + jlong handle) { auto* it = reinterpret_cast(handle); const rocksdb::WriteEntry& we = it->Entry(); jlong results[3]; - //set the type of the write entry - switch (we.type) { - case rocksdb::kPutRecord: - results[0] = 0x1; - break; - - case rocksdb::kMergeRecord: - results[0] = 0x2; - break; - - case rocksdb::kDeleteRecord: - results[0] = 0x4; - break; - - case rocksdb::kLogDataRecord: - results[0] = 0x8; - break; - - default: - results[0] = 0x0; - } + // set the type of the write entry + results[0] = rocksdb::WriteTypeJni::toJavaWriteType(we.type); - // key_slice and value_slice will be freed by org.rocksdb.DirectSlice#close + // NOTE: key_slice and value_slice will be freed by + // org.rocksdb.DirectSlice#close auto* key_slice = new rocksdb::Slice(we.key.data(), we.key.size()); results[1] = reinterpret_cast(key_slice); - if (we.type == rocksdb::kDeleteRecord - || we.type == rocksdb::kLogDataRecord) { + if (we.type == rocksdb::kDeleteRecord || + we.type == rocksdb::kSingleDeleteRecord || + we.type == rocksdb::kLogDataRecord) { // set native handle of value slice to null if no value available results[2] = 0; } else { @@ -556,9 +720,9 @@ jlongArray Java_org_rocksdb_WBWIRocksIterator_entry1( } jlongArray jresults = env->NewLongArray(3); - if(jresults == nullptr) { + if (jresults == nullptr) { // exception thrown: OutOfMemoryError - if(results[2] != 0) { + if (results[2] != 0) { auto* value_slice = reinterpret_cast(results[2]); delete value_slice; } @@ -567,10 +731,10 @@ jlongArray Java_org_rocksdb_WBWIRocksIterator_entry1( } env->SetLongArrayRegion(jresults, 0, 3, results); - if(env->ExceptionCheck()) { + if (env->ExceptionCheck()) { // exception thrown: ArrayIndexOutOfBoundsException env->DeleteLocalRef(jresults); - if(results[2] != 0) { + if (results[2] != 0) { auto* value_slice = reinterpret_cast(results[2]); delete value_slice; } diff --git a/thirdparty/rocksdb/java/rocksjni/write_buffer_manager.cc b/thirdparty/rocksdb/java/rocksjni/write_buffer_manager.cc new file mode 100644 index 0000000000..043f69031c --- /dev/null +++ b/thirdparty/rocksdb/java/rocksjni/write_buffer_manager.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#include + +#include "include/org_rocksdb_WriteBufferManager.h" + +#include "rocksdb/cache.h" +#include "rocksdb/write_buffer_manager.h" + +/* + * Class: org_rocksdb_WriteBufferManager + * Method: newWriteBufferManager + * Signature: (JJ)J + */ +jlong Java_org_rocksdb_WriteBufferManager_newWriteBufferManager( + JNIEnv* /*env*/, jclass /*jclazz*/, jlong jbuffer_size, jlong jcache_handle) { + auto* cache_ptr = + reinterpret_cast *>(jcache_handle); + auto* write_buffer_manager = new std::shared_ptr( + std::make_shared(jbuffer_size, *cache_ptr)); + return reinterpret_cast(write_buffer_manager); +} + +/* + * Class: org_rocksdb_WriteBufferManager + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_WriteBufferManager_disposeInternal( + JNIEnv* /*env*/, jobject /*jobj*/, jlong jhandle) { + auto* write_buffer_manager = + reinterpret_cast *>(jhandle); + assert(write_buffer_manager != nullptr); + delete write_buffer_manager; +} diff --git a/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc b/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc index 0f00766c53..bf9001110a 100644 --- a/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc +++ b/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.cc @@ -12,14 +12,11 @@ namespace rocksdb { WriteBatchHandlerJniCallback::WriteBatchHandlerJniCallback( JNIEnv* env, jobject jWriteBatchHandler) - : m_env(env) { - - // Note: we want to access the Java WriteBatchHandler instance - // across multiple method calls, so we create a global ref - assert(jWriteBatchHandler != nullptr); - m_jWriteBatchHandler = env->NewGlobalRef(jWriteBatchHandler); - if(m_jWriteBatchHandler == nullptr) { - // exception thrown: OutOfMemoryError + : JniCallback(env, jWriteBatchHandler), m_env(env) { + + m_jPutCfMethodId = WriteBatchHandlerJni::getPutCfMethodId(env); + if(m_jPutCfMethodId == nullptr) { + // exception thrown return; } @@ -29,18 +26,50 @@ WriteBatchHandlerJniCallback::WriteBatchHandlerJniCallback( return; } + m_jMergeCfMethodId = WriteBatchHandlerJni::getMergeCfMethodId(env); + if(m_jMergeCfMethodId == nullptr) { + // exception thrown + return; + } + m_jMergeMethodId = WriteBatchHandlerJni::getMergeMethodId(env); if(m_jMergeMethodId == nullptr) { // exception thrown return; } + m_jDeleteCfMethodId = WriteBatchHandlerJni::getDeleteCfMethodId(env); + if(m_jDeleteCfMethodId == nullptr) { + // exception thrown + return; + } + m_jDeleteMethodId = WriteBatchHandlerJni::getDeleteMethodId(env); if(m_jDeleteMethodId == nullptr) { // exception thrown return; } + m_jSingleDeleteCfMethodId = + WriteBatchHandlerJni::getSingleDeleteCfMethodId(env); + if(m_jSingleDeleteCfMethodId == nullptr) { + // exception thrown + return; + } + + m_jSingleDeleteMethodId = WriteBatchHandlerJni::getSingleDeleteMethodId(env); + if(m_jSingleDeleteMethodId == nullptr) { + // exception thrown + return; + } + + m_jDeleteRangeCfMethodId = + WriteBatchHandlerJni::getDeleteRangeCfMethodId(env); + if (m_jDeleteRangeCfMethodId == nullptr) { + // exception thrown + return; + } + m_jDeleteRangeMethodId = WriteBatchHandlerJni::getDeleteRangeMethodId(env); if (m_jDeleteRangeMethodId == nullptr) { // exception thrown @@ -53,209 +82,329 @@ WriteBatchHandlerJniCallback::WriteBatchHandlerJniCallback( return; } - m_jContinueMethodId = WriteBatchHandlerJni::getContinueMethodId(env); - if(m_jContinueMethodId == nullptr) { + m_jPutBlobIndexCfMethodId = + WriteBatchHandlerJni::getPutBlobIndexCfMethodId(env); + if(m_jPutBlobIndexCfMethodId == nullptr) { // exception thrown return; } -} -void WriteBatchHandlerJniCallback::Put(const Slice& key, const Slice& value) { - const jbyteArray j_key = sliceToJArray(key); - if(j_key == nullptr) { + m_jMarkBeginPrepareMethodId = + WriteBatchHandlerJni::getMarkBeginPrepareMethodId(env); + if(m_jMarkBeginPrepareMethodId == nullptr) { // exception thrown - if(m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } return; } - const jbyteArray j_value = sliceToJArray(value); - if(j_value == nullptr) { + m_jMarkEndPrepareMethodId = + WriteBatchHandlerJni::getMarkEndPrepareMethodId(env); + if(m_jMarkEndPrepareMethodId == nullptr) { // exception thrown - if(m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } return; } - m_env->CallVoidMethod( - m_jWriteBatchHandler, - m_jPutMethodId, - j_key, - j_value); - if(m_env->ExceptionCheck()) { + m_jMarkNoopMethodId = WriteBatchHandlerJni::getMarkNoopMethodId(env); + if(m_jMarkNoopMethodId == nullptr) { // exception thrown - m_env->ExceptionDescribe(); - if(j_value != nullptr) { - m_env->DeleteLocalRef(j_value); - } - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } return; } - - if(j_value != nullptr) { - m_env->DeleteLocalRef(j_value); - } - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); + + m_jMarkRollbackMethodId = WriteBatchHandlerJni::getMarkRollbackMethodId(env); + if(m_jMarkRollbackMethodId == nullptr) { + // exception thrown + return; } -} -void WriteBatchHandlerJniCallback::Merge(const Slice& key, const Slice& value) { - const jbyteArray j_key = sliceToJArray(key); - if(j_key == nullptr) { + m_jMarkCommitMethodId = WriteBatchHandlerJni::getMarkCommitMethodId(env); + if(m_jMarkCommitMethodId == nullptr) { // exception thrown - if(m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } return; } - const jbyteArray j_value = sliceToJArray(value); - if(j_value == nullptr) { + m_jContinueMethodId = WriteBatchHandlerJni::getContinueMethodId(env); + if(m_jContinueMethodId == nullptr) { // exception thrown - if(m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } return; } +} - m_env->CallVoidMethod( - m_jWriteBatchHandler, - m_jMergeMethodId, +rocksdb::Status WriteBatchHandlerJniCallback::PutCF(uint32_t column_family_id, + const Slice& key, const Slice& value) { + auto put = [this, column_family_id] ( + jbyteArray j_key, jbyteArray j_value) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jPutCfMethodId, + static_cast(column_family_id), j_key, j_value); - if(m_env->ExceptionCheck()) { - // exception thrown - m_env->ExceptionDescribe(); - if(j_value != nullptr) { - m_env->DeleteLocalRef(j_value); - } - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } - return; + }; + auto status = WriteBatchHandlerJniCallback::kv_op(key, value, put); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } +} - if(j_value != nullptr) { - m_env->DeleteLocalRef(j_value); - } - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); +void WriteBatchHandlerJniCallback::Put(const Slice& key, const Slice& value) { + auto put = [this] ( + jbyteArray j_key, jbyteArray j_value) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jPutMethodId, + j_key, + j_value); + }; + WriteBatchHandlerJniCallback::kv_op(key, value, put); +} + +rocksdb::Status WriteBatchHandlerJniCallback::MergeCF(uint32_t column_family_id, + const Slice& key, const Slice& value) { + auto merge = [this, column_family_id] ( + jbyteArray j_key, jbyteArray j_value) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jMergeCfMethodId, + static_cast(column_family_id), + j_key, + j_value); + }; + auto status = WriteBatchHandlerJniCallback::kv_op(key, value, merge); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } } -void WriteBatchHandlerJniCallback::Delete(const Slice& key) { - const jbyteArray j_key = sliceToJArray(key); - if(j_key == nullptr) { - // exception thrown - if(m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - return; +void WriteBatchHandlerJniCallback::Merge(const Slice& key, const Slice& value) { + auto merge = [this] ( + jbyteArray j_key, jbyteArray j_value) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jMergeMethodId, + j_key, + j_value); + }; + WriteBatchHandlerJniCallback::kv_op(key, value, merge); +} + +rocksdb::Status WriteBatchHandlerJniCallback::DeleteCF(uint32_t column_family_id, + const Slice& key) { + auto remove = [this, column_family_id] (jbyteArray j_key) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jDeleteCfMethodId, + static_cast(column_family_id), + j_key); + }; + auto status = WriteBatchHandlerJniCallback::k_op(key, remove); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } +} - m_env->CallVoidMethod( - m_jWriteBatchHandler, +void WriteBatchHandlerJniCallback::Delete(const Slice& key) { + auto remove = [this] (jbyteArray j_key) { + m_env->CallVoidMethod( + m_jcallback_obj, m_jDeleteMethodId, j_key); - if(m_env->ExceptionCheck()) { - // exception thrown - m_env->ExceptionDescribe(); - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); - } - return; + }; + WriteBatchHandlerJniCallback::k_op(key, remove); +} + +rocksdb::Status WriteBatchHandlerJniCallback::SingleDeleteCF(uint32_t column_family_id, + const Slice& key) { + auto singleDelete = [this, column_family_id] (jbyteArray j_key) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jSingleDeleteCfMethodId, + static_cast(column_family_id), + j_key); + }; + auto status = WriteBatchHandlerJniCallback::k_op(key, singleDelete); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } +} - if(j_key != nullptr) { - m_env->DeleteLocalRef(j_key); +void WriteBatchHandlerJniCallback::SingleDelete(const Slice& key) { + auto singleDelete = [this] (jbyteArray j_key) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jSingleDeleteMethodId, + j_key); + }; + WriteBatchHandlerJniCallback::k_op(key, singleDelete); +} + +rocksdb::Status WriteBatchHandlerJniCallback::DeleteRangeCF(uint32_t column_family_id, + const Slice& beginKey, const Slice& endKey) { + auto deleteRange = [this, column_family_id] ( + jbyteArray j_beginKey, jbyteArray j_endKey) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jDeleteRangeCfMethodId, + static_cast(column_family_id), + j_beginKey, + j_endKey); + }; + auto status = WriteBatchHandlerJniCallback::kv_op(beginKey, endKey, deleteRange); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } } void WriteBatchHandlerJniCallback::DeleteRange(const Slice& beginKey, - const Slice& endKey) { - const jbyteArray j_beginKey = sliceToJArray(beginKey); - if (j_beginKey == nullptr) { - // exception thrown - if (m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - return; - } + const Slice& endKey) { + auto deleteRange = [this] ( + jbyteArray j_beginKey, jbyteArray j_endKey) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jDeleteRangeMethodId, + j_beginKey, + j_endKey); + }; + WriteBatchHandlerJniCallback::kv_op(beginKey, endKey, deleteRange); +} - const jbyteArray j_endKey = sliceToJArray(beginKey); - if (j_endKey == nullptr) { - // exception thrown - if (m_env->ExceptionCheck()) { - m_env->ExceptionDescribe(); - } - return; +void WriteBatchHandlerJniCallback::LogData(const Slice& blob) { + auto logData = [this] (jbyteArray j_blob) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jLogDataMethodId, + j_blob); + }; + WriteBatchHandlerJniCallback::k_op(blob, logData); +} + +rocksdb::Status WriteBatchHandlerJniCallback::PutBlobIndexCF(uint32_t column_family_id, + const Slice& key, const Slice& value) { + auto putBlobIndex = [this, column_family_id] ( + jbyteArray j_key, jbyteArray j_value) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jPutBlobIndexCfMethodId, + static_cast(column_family_id), + j_key, + j_value); + }; + auto status = WriteBatchHandlerJniCallback::kv_op(key, value, putBlobIndex); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } +} + +rocksdb::Status WriteBatchHandlerJniCallback::MarkBeginPrepare(bool unprepare) { +#ifndef DEBUG + (void) unprepare; +#else + assert(!unprepare); +#endif + m_env->CallVoidMethod(m_jcallback_obj, m_jMarkBeginPrepareMethodId); - m_env->CallVoidMethod(m_jWriteBatchHandler, m_jDeleteRangeMethodId, - j_beginKey, j_endKey); + // check for Exception, in-particular RocksDBException if (m_env->ExceptionCheck()) { // exception thrown - m_env->ExceptionDescribe(); - if (j_beginKey != nullptr) { - m_env->DeleteLocalRef(j_beginKey); - } - if (j_endKey != nullptr) { - m_env->DeleteLocalRef(j_endKey); + jthrowable exception = m_env->ExceptionOccurred(); + std::unique_ptr status = rocksdb::RocksDBExceptionJni::toCppStatus(m_env, exception); + if (status == nullptr) { + // unkown status or exception occurred extracting status + m_env->ExceptionDescribe(); + return rocksdb::Status::OK(); // TODO(AR) probably need a better error code here + + } else { + m_env->ExceptionClear(); // clear the exception, as we have extracted the status + return rocksdb::Status(*status); } - return; } - if (j_beginKey != nullptr) { - m_env->DeleteLocalRef(j_beginKey); - } + return rocksdb::Status::OK(); +} - if (j_endKey != nullptr) { - m_env->DeleteLocalRef(j_endKey); +rocksdb::Status WriteBatchHandlerJniCallback::MarkEndPrepare(const Slice& xid) { + auto markEndPrepare = [this] ( + jbyteArray j_xid) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jMarkEndPrepareMethodId, + j_xid); + }; + auto status = WriteBatchHandlerJniCallback::k_op(xid, markEndPrepare); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } } -void WriteBatchHandlerJniCallback::LogData(const Slice& blob) { - const jbyteArray j_blob = sliceToJArray(blob); - if(j_blob == nullptr) { +rocksdb::Status WriteBatchHandlerJniCallback::MarkNoop(bool empty_batch) { + m_env->CallVoidMethod(m_jcallback_obj, m_jMarkNoopMethodId, static_cast(empty_batch)); + + // check for Exception, in-particular RocksDBException + if (m_env->ExceptionCheck()) { // exception thrown - if(m_env->ExceptionCheck()) { + jthrowable exception = m_env->ExceptionOccurred(); + std::unique_ptr status = rocksdb::RocksDBExceptionJni::toCppStatus(m_env, exception); + if (status == nullptr) { + // unkown status or exception occurred extracting status m_env->ExceptionDescribe(); + return rocksdb::Status::OK(); // TODO(AR) probably need a better error code here + + } else { + m_env->ExceptionClear(); // clear the exception, as we have extracted the status + return rocksdb::Status(*status); } - return; } - m_env->CallVoidMethod( - m_jWriteBatchHandler, - m_jLogDataMethodId, - j_blob); - if(m_env->ExceptionCheck()) { - // exception thrown - m_env->ExceptionDescribe(); - if(j_blob != nullptr) { - m_env->DeleteLocalRef(j_blob); - } - return; + return rocksdb::Status::OK(); +} + +rocksdb::Status WriteBatchHandlerJniCallback::MarkRollback(const Slice& xid) { + auto markRollback = [this] ( + jbyteArray j_xid) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jMarkRollbackMethodId, + j_xid); + }; + auto status = WriteBatchHandlerJniCallback::k_op(xid, markRollback); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } +} - if(j_blob != nullptr) { - m_env->DeleteLocalRef(j_blob); +rocksdb::Status WriteBatchHandlerJniCallback::MarkCommit(const Slice& xid) { + auto markCommit = [this] ( + jbyteArray j_xid) { + m_env->CallVoidMethod( + m_jcallback_obj, + m_jMarkCommitMethodId, + j_xid); + }; + auto status = WriteBatchHandlerJniCallback::k_op(xid, markCommit); + if(status == nullptr) { + return rocksdb::Status::OK(); // TODO(AR) what to do if there is an Exception but we don't know the rocksdb::Status? + } else { + return rocksdb::Status(*status); } } bool WriteBatchHandlerJniCallback::Continue() { jboolean jContinue = m_env->CallBooleanMethod( - m_jWriteBatchHandler, + m_jcallback_obj, m_jContinueMethodId); if(m_env->ExceptionCheck()) { // exception thrown @@ -265,42 +414,101 @@ bool WriteBatchHandlerJniCallback::Continue() { return static_cast(jContinue == JNI_TRUE); } -/* - * Creates a Java Byte Array from the data in a Slice - * - * When calling this function - * you must remember to call env->DeleteLocalRef - * on the result after you have finished with it - * - * @param s A Slice to convery to a Java byte array - * - * @return A reference to a Java byte array, or a nullptr if an - * exception occurs - */ -jbyteArray WriteBatchHandlerJniCallback::sliceToJArray(const Slice& s) { - jbyteArray ja = m_env->NewByteArray(static_cast(s.size())); - if(ja == nullptr) { - // exception thrown: OutOfMemoryError +std::unique_ptr WriteBatchHandlerJniCallback::kv_op(const Slice& key, const Slice& value, std::function kvFn) { + const jbyteArray j_key = JniUtil::copyBytes(m_env, key); + if (j_key == nullptr) { + // exception thrown + if (m_env->ExceptionCheck()) { + m_env->ExceptionDescribe(); + } return nullptr; } - m_env->SetByteArrayRegion( - ja, 0, static_cast(s.size()), - const_cast(reinterpret_cast(s.data()))); - if(m_env->ExceptionCheck()) { - if(ja != nullptr) { - m_env->DeleteLocalRef(ja); + const jbyteArray j_value = JniUtil::copyBytes(m_env, value); + if (j_value == nullptr) { + // exception thrown + if (m_env->ExceptionCheck()) { + m_env->ExceptionDescribe(); + } + if (j_key != nullptr) { + m_env->DeleteLocalRef(j_key); } - // exception thrown: ArrayIndexOutOfBoundsException return nullptr; } - return ja; + kvFn(j_key, j_value); + + // check for Exception, in-particular RocksDBException + if (m_env->ExceptionCheck()) { + if (j_value != nullptr) { + m_env->DeleteLocalRef(j_value); + } + if (j_key != nullptr) { + m_env->DeleteLocalRef(j_key); + } + + // exception thrown + jthrowable exception = m_env->ExceptionOccurred(); + std::unique_ptr status = rocksdb::RocksDBExceptionJni::toCppStatus(m_env, exception); + if (status == nullptr) { + // unkown status or exception occurred extracting status + m_env->ExceptionDescribe(); + return nullptr; + + } else { + m_env->ExceptionClear(); // clear the exception, as we have extracted the status + return status; + } + } + + if (j_value != nullptr) { + m_env->DeleteLocalRef(j_value); + } + if (j_key != nullptr) { + m_env->DeleteLocalRef(j_key); + } + + // all OK + return std::unique_ptr(new rocksdb::Status(rocksdb::Status::OK())); } -WriteBatchHandlerJniCallback::~WriteBatchHandlerJniCallback() { - if(m_jWriteBatchHandler != nullptr) { - m_env->DeleteGlobalRef(m_jWriteBatchHandler); +std::unique_ptr WriteBatchHandlerJniCallback::k_op(const Slice& key, std::function kFn) { + const jbyteArray j_key = JniUtil::copyBytes(m_env, key); + if (j_key == nullptr) { + // exception thrown + if (m_env->ExceptionCheck()) { + m_env->ExceptionDescribe(); + } + return nullptr; + } + + kFn(j_key); + + // check for Exception, in-particular RocksDBException + if (m_env->ExceptionCheck()) { + if (j_key != nullptr) { + m_env->DeleteLocalRef(j_key); + } + + // exception thrown + jthrowable exception = m_env->ExceptionOccurred(); + std::unique_ptr status = rocksdb::RocksDBExceptionJni::toCppStatus(m_env, exception); + if (status == nullptr) { + // unkown status or exception occurred extracting status + m_env->ExceptionDescribe(); + return nullptr; + + } else { + m_env->ExceptionClear(); // clear the exception, as we have extracted the status + return status; + } + } + + if (j_key != nullptr) { + m_env->DeleteLocalRef(j_key); } + + // all OK + return std::unique_ptr(new rocksdb::Status(rocksdb::Status::OK())); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h b/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h index 5d3dee3b1a..720f1693cb 100644 --- a/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h +++ b/thirdparty/rocksdb/java/rocksjni/writebatchhandlerjnicallback.h @@ -9,7 +9,10 @@ #ifndef JAVA_ROCKSJNI_WRITEBATCHHANDLERJNICALLBACK_H_ #define JAVA_ROCKSJNI_WRITEBATCHHANDLERJNICALLBACK_H_ +#include #include +#include +#include "rocksjni/jnicallback.h" #include "rocksdb/write_batch.h" namespace rocksdb { @@ -20,28 +23,61 @@ namespace rocksdb { * which calls the appropriate Java method. * This enables Write Batch Handlers to be implemented in Java. */ -class WriteBatchHandlerJniCallback : public WriteBatch::Handler { +class WriteBatchHandlerJniCallback : public JniCallback, public WriteBatch::Handler { public: WriteBatchHandlerJniCallback( JNIEnv* env, jobject jWriteBackHandler); - ~WriteBatchHandlerJniCallback(); + Status PutCF(uint32_t column_family_id, const Slice& key, + const Slice& value); void Put(const Slice& key, const Slice& value); + Status MergeCF(uint32_t column_family_id, const Slice& key, + const Slice& value); void Merge(const Slice& key, const Slice& value); + Status DeleteCF(uint32_t column_family_id, const Slice& key); void Delete(const Slice& key); + Status SingleDeleteCF(uint32_t column_family_id, const Slice& key); + void SingleDelete(const Slice& key); + Status DeleteRangeCF(uint32_t column_family_id, const Slice& beginKey, + const Slice& endKey); void DeleteRange(const Slice& beginKey, const Slice& endKey); void LogData(const Slice& blob); + Status PutBlobIndexCF(uint32_t column_family_id, const Slice& key, + const Slice& value); + Status MarkBeginPrepare(bool); + Status MarkEndPrepare(const Slice& xid); + Status MarkNoop(bool empty_batch); + Status MarkRollback(const Slice& xid); + Status MarkCommit(const Slice& xid); bool Continue(); private: JNIEnv* m_env; - jobject m_jWriteBatchHandler; - jbyteArray sliceToJArray(const Slice& s); + jmethodID m_jPutCfMethodId; jmethodID m_jPutMethodId; + jmethodID m_jMergeCfMethodId; jmethodID m_jMergeMethodId; + jmethodID m_jDeleteCfMethodId; jmethodID m_jDeleteMethodId; + jmethodID m_jSingleDeleteCfMethodId; + jmethodID m_jSingleDeleteMethodId; + jmethodID m_jDeleteRangeCfMethodId; jmethodID m_jDeleteRangeMethodId; jmethodID m_jLogDataMethodId; + jmethodID m_jPutBlobIndexCfMethodId; + jmethodID m_jMarkBeginPrepareMethodId; + jmethodID m_jMarkEndPrepareMethodId; + jmethodID m_jMarkNoopMethodId; + jmethodID m_jMarkRollbackMethodId; + jmethodID m_jMarkCommitMethodId; jmethodID m_jContinueMethodId; + /** + * @return A pointer to a rocksdb::Status or nullptr if an unexpected exception occurred + */ + std::unique_ptr kv_op(const Slice& key, const Slice& value, std::function kvFn); + /** + * @return A pointer to a rocksdb::Status or nullptr if an unexpected exception occurred + */ + std::unique_ptr k_op(const Slice& key, std::function kFn); }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/java/samples/src/main/java/OptimisticTransactionSample.java b/thirdparty/rocksdb/java/samples/src/main/java/OptimisticTransactionSample.java new file mode 100644 index 0000000000..1633d1f2bd --- /dev/null +++ b/thirdparty/rocksdb/java/samples/src/main/java/OptimisticTransactionSample.java @@ -0,0 +1,184 @@ +// Copyright (c) 2011-present, Facebook, 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). + +import org.rocksdb.*; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Demonstrates using Transactions on an OptimisticTransactionDB with + * varying isolation guarantees + */ +public class OptimisticTransactionSample { + private static final String dbPath = "/tmp/rocksdb_optimistic_transaction_example"; + + public static final void main(final String args[]) throws RocksDBException { + + try(final Options options = new Options() + .setCreateIfMissing(true); + final OptimisticTransactionDB txnDb = + OptimisticTransactionDB.open(options, dbPath)) { + + try (final WriteOptions writeOptions = new WriteOptions(); + final ReadOptions readOptions = new ReadOptions()) { + + //////////////////////////////////////////////////////// + // + // Simple OptimisticTransaction Example ("Read Committed") + // + //////////////////////////////////////////////////////// + readCommitted(txnDb, writeOptions, readOptions); + + + //////////////////////////////////////////////////////// + // + // "Repeatable Read" (Snapshot Isolation) Example + // -- Using a single Snapshot + // + //////////////////////////////////////////////////////// + repeatableRead(txnDb, writeOptions, readOptions); + + + //////////////////////////////////////////////////////// + // + // "Read Committed" (Monotonic Atomic Views) Example + // --Using multiple Snapshots + // + //////////////////////////////////////////////////////// + readCommitted_monotonicAtomicViews(txnDb, writeOptions, readOptions); + } + } + } + + /** + * Demonstrates "Read Committed" isolation + */ + private static void readCommitted(final OptimisticTransactionDB txnDb, + final WriteOptions writeOptions, final ReadOptions readOptions) + throws RocksDBException { + final byte key1[] = "abc".getBytes(UTF_8); + final byte value1[] = "def".getBytes(UTF_8); + + final byte key2[] = "xyz".getBytes(UTF_8); + final byte value2[] = "zzz".getBytes(UTF_8); + + // Start a transaction + try(final Transaction txn = txnDb.beginTransaction(writeOptions)) { + // Read a key in this transaction + byte[] value = txn.get(readOptions, key1); + assert(value == null); + + // Write a key in this transaction + txn.put(key1, value1); + + // Read a key OUTSIDE this transaction. Does not affect txn. + value = txnDb.get(readOptions, key1); + assert(value == null); + + // 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. + txnDb.put(writeOptions, key2, value2); + + // Commit transaction + txn.commit(); + } + } + + /** + * Demonstrates "Repeatable Read" (Snapshot Isolation) isolation + */ + private static void repeatableRead(final OptimisticTransactionDB txnDb, + final WriteOptions writeOptions, final ReadOptions readOptions) + throws RocksDBException { + + final byte key1[] = "ghi".getBytes(UTF_8); + final byte value1[] = "jkl".getBytes(UTF_8); + + // Set a snapshot at start of transaction by setting setSnapshot(true) + try(final OptimisticTransactionOptions txnOptions = + new OptimisticTransactionOptions().setSetSnapshot(true); + final Transaction txn = + txnDb.beginTransaction(writeOptions, txnOptions)) { + + final Snapshot snapshot = txn.getSnapshot(); + + // Write a key OUTSIDE of transaction + txnDb.put(writeOptions, key1, value1); + + // Read a key using the snapshot. + readOptions.setSnapshot(snapshot); + final byte[] value = txn.getForUpdate(readOptions, key1, true); + assert(value == value1); + + try { + // Attempt to commit transaction + txn.commit(); + throw new IllegalStateException(); + } catch(final RocksDBException e) { + // Transaction could not commit since the write outside of the txn + // conflicted with the read! + assert(e.getStatus().getCode() == Status.Code.Busy); + } + + txn.rollback(); + } finally { + // Clear snapshot from read options since it is no longer valid + readOptions.setSnapshot(null); + } + } + + /** + * Demonstrates "Read Committed" (Monotonic Atomic Views) isolation + * + * In this example, we set the snapshot multiple times. This is probably + * only necessary if you have very strict isolation requirements to + * implement. + */ + private static void readCommitted_monotonicAtomicViews( + final OptimisticTransactionDB txnDb, final WriteOptions writeOptions, + final ReadOptions readOptions) throws RocksDBException { + + final byte keyX[] = "x".getBytes(UTF_8); + final byte valueX[] = "x".getBytes(UTF_8); + + final byte keyY[] = "y".getBytes(UTF_8); + final byte valueY[] = "y".getBytes(UTF_8); + + try (final OptimisticTransactionOptions txnOptions = + new OptimisticTransactionOptions().setSetSnapshot(true); + final Transaction txn = + txnDb.beginTransaction(writeOptions, txnOptions)) { + + // Do some reads and writes to key "x" + Snapshot snapshot = txnDb.getSnapshot(); + readOptions.setSnapshot(snapshot); + byte[] value = txn.get(readOptions, keyX); + txn.put(valueX, valueX); + + // Do a write outside of the transaction to key "y" + txnDb.put(writeOptions, keyY, valueY); + + // Set a new snapshot in the transaction + txn.setSnapshot(); + snapshot = txnDb.getSnapshot(); + readOptions.setSnapshot(snapshot); + + // Do some reads and writes to key "y" + // Since the snapshot was advanced, the write done outside of the + // transaction does not conflict. + value = txn.getForUpdate(readOptions, keyY, true); + txn.put(keyY, valueY); + + // Commit. Since the snapshot was advanced, the write done outside of the + // transaction does not prevent this transaction from Committing. + txn.commit(); + + } finally { + // Clear snapshot from read options since it is no longer valid + readOptions.setSnapshot(null); + } + } +} diff --git a/thirdparty/rocksdb/java/samples/src/main/java/TransactionSample.java b/thirdparty/rocksdb/java/samples/src/main/java/TransactionSample.java new file mode 100644 index 0000000000..b88a68f123 --- /dev/null +++ b/thirdparty/rocksdb/java/samples/src/main/java/TransactionSample.java @@ -0,0 +1,183 @@ +// Copyright (c) 2011-present, Facebook, 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). + +import org.rocksdb.*; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Demonstrates using Transactions on a TransactionDB with + * varying isolation guarantees + */ +public class TransactionSample { + private static final String dbPath = "/tmp/rocksdb_transaction_example"; + + public static final void main(final String args[]) throws RocksDBException { + + try(final Options options = new Options() + .setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB txnDb = + TransactionDB.open(options, txnDbOptions, dbPath)) { + + try (final WriteOptions writeOptions = new WriteOptions(); + final ReadOptions readOptions = new ReadOptions()) { + + //////////////////////////////////////////////////////// + // + // Simple Transaction Example ("Read Committed") + // + //////////////////////////////////////////////////////// + readCommitted(txnDb, writeOptions, readOptions); + + + //////////////////////////////////////////////////////// + // + // "Repeatable Read" (Snapshot Isolation) Example + // -- Using a single Snapshot + // + //////////////////////////////////////////////////////// + repeatableRead(txnDb, writeOptions, readOptions); + + + //////////////////////////////////////////////////////// + // + // "Read Committed" (Monotonic Atomic Views) Example + // --Using multiple Snapshots + // + //////////////////////////////////////////////////////// + readCommitted_monotonicAtomicViews(txnDb, writeOptions, readOptions); + } + } + } + + /** + * Demonstrates "Read Committed" isolation + */ + private static void readCommitted(final TransactionDB txnDb, + final WriteOptions writeOptions, final ReadOptions readOptions) + throws RocksDBException { + final byte key1[] = "abc".getBytes(UTF_8); + final byte value1[] = "def".getBytes(UTF_8); + + final byte key2[] = "xyz".getBytes(UTF_8); + final byte value2[] = "zzz".getBytes(UTF_8); + + // Start a transaction + try(final Transaction txn = txnDb.beginTransaction(writeOptions)) { + // Read a key in this transaction + byte[] value = txn.get(readOptions, key1); + assert(value == null); + + // Write a key in this transaction + txn.put(key1, value1); + + // Read a key OUTSIDE this transaction. Does not affect txn. + value = txnDb.get(readOptions, key1); + assert(value == null); + + // 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. + txnDb.put(writeOptions, key2, value2); + + // Commit transaction + txn.commit(); + } + } + + /** + * Demonstrates "Repeatable Read" (Snapshot Isolation) isolation + */ + private static void repeatableRead(final TransactionDB txnDb, + final WriteOptions writeOptions, final ReadOptions readOptions) + throws RocksDBException { + + final byte key1[] = "ghi".getBytes(UTF_8); + final byte value1[] = "jkl".getBytes(UTF_8); + + // Set a snapshot at start of transaction by setting setSnapshot(true) + try(final TransactionOptions txnOptions = new TransactionOptions() + .setSetSnapshot(true); + final Transaction txn = + txnDb.beginTransaction(writeOptions, txnOptions)) { + + final Snapshot snapshot = txn.getSnapshot(); + + // Write a key OUTSIDE of transaction + txnDb.put(writeOptions, key1, value1); + + // Attempt to read a key using the snapshot. This will fail since + // the previous write outside this txn conflicts with this read. + readOptions.setSnapshot(snapshot); + + try { + final byte[] value = txn.getForUpdate(readOptions, key1, true); + throw new IllegalStateException(); + } catch(final RocksDBException e) { + assert(e.getStatus().getCode() == Status.Code.Busy); + } + + txn.rollback(); + } finally { + // Clear snapshot from read options since it is no longer valid + readOptions.setSnapshot(null); + } + } + + /** + * Demonstrates "Read Committed" (Monotonic Atomic Views) isolation + * + * In this example, we set the snapshot multiple times. This is probably + * only necessary if you have very strict isolation requirements to + * implement. + */ + private static void readCommitted_monotonicAtomicViews( + final TransactionDB txnDb, final WriteOptions writeOptions, + final ReadOptions readOptions) throws RocksDBException { + + final byte keyX[] = "x".getBytes(UTF_8); + final byte valueX[] = "x".getBytes(UTF_8); + + final byte keyY[] = "y".getBytes(UTF_8); + final byte valueY[] = "y".getBytes(UTF_8); + + try (final TransactionOptions txnOptions = new TransactionOptions() + .setSetSnapshot(true); + final Transaction txn = + txnDb.beginTransaction(writeOptions, txnOptions)) { + + // Do some reads and writes to key "x" + Snapshot snapshot = txnDb.getSnapshot(); + readOptions.setSnapshot(snapshot); + byte[] value = txn.get(readOptions, keyX); + txn.put(valueX, valueX); + + // Do a write outside of the transaction to key "y" + txnDb.put(writeOptions, keyY, valueY); + + // Set a new snapshot in the transaction + txn.setSnapshot(); + txn.setSavePoint(); + snapshot = txnDb.getSnapshot(); + readOptions.setSnapshot(snapshot); + + // Do some reads and writes to key "y" + // Since the snapshot was advanced, the write done outside of the + // transaction does not conflict. + value = txn.getForUpdate(readOptions, keyY, true); + txn.put(keyY, valueY); + + // Decide we want to revert the last write from this transaction. + txn.rollbackToSavePoint(); + + // Commit. + txn.commit(); + } finally { + // Clear snapshot from read options since it is no longer valid + readOptions.setSnapshot(null); + } + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java index 976401fba0..2f0d4f3ca4 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilter.java @@ -14,6 +14,35 @@ public abstract class AbstractCompactionFilter> extends RocksObject { + public static class Context { + private final boolean fullCompaction; + private final boolean manualCompaction; + + public Context(final boolean fullCompaction, final boolean manualCompaction) { + this.fullCompaction = fullCompaction; + this.manualCompaction = manualCompaction; + } + + /** + * Does this compaction run include all data files + * + * @return true if this is a full compaction run + */ + public boolean isFullCompaction() { + return fullCompaction; + } + + /** + * Is this compaction requested by the client, + * or is it occurring as an automatic compaction process + * + * @return true if the compaction was initiated by the client + */ + public boolean isManualCompaction() { + return manualCompaction; + } + } + protected AbstractCompactionFilter(final long nativeHandle) { super(nativeHandle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java new file mode 100644 index 0000000000..380b4461d0 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java @@ -0,0 +1,77 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Each compaction will create a new {@link AbstractCompactionFilter} + * allowing the application to know about different compactions + * + * @param The concrete type of the compaction filter + */ +public abstract class AbstractCompactionFilterFactory> + extends RocksCallbackObject { + + public AbstractCompactionFilterFactory() { + super(null); + } + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewCompactionFilterFactory0(); + } + + /** + * Called from JNI, see compaction_filter_factory_jnicallback.cc + * + * @param fullCompaction {@link AbstractCompactionFilter.Context#fullCompaction} + * @param manualCompaction {@link AbstractCompactionFilter.Context#manualCompaction} + * + * @return native handle of the CompactionFilter + */ + private long createCompactionFilter(final boolean fullCompaction, + final boolean manualCompaction) { + final T filter = createCompactionFilter( + new AbstractCompactionFilter.Context(fullCompaction, manualCompaction)); + + // CompactionFilterFactory::CreateCompactionFilter returns a std::unique_ptr + // which therefore has ownership of the underlying native object + filter.disOwnNativeHandle(); + + return filter.nativeHandle_; + } + + /** + * Create a new compaction filter + * + * @param context The context describing the need for a new compaction filter + * + * @return A new instance of {@link AbstractCompactionFilter} + */ + public abstract T createCompactionFilter( + final AbstractCompactionFilter.Context context); + + /** + * A name which identifies this compaction filter + * + * The name will be printed to the LOG file on start up for diagnosis + * + * @return name which identifies this compaction filter. + */ + public abstract String name(); + + /** + * We override {@link RocksCallbackObject#disposeInternal()} + * as disposing of a rocksdb::AbstractCompactionFilterFactory requires + * a slightly different approach as it is a std::shared_ptr + */ + @Override + protected void disposeInternal() { + disposeInternal(nativeHandle_); + } + + private native long createNewCompactionFilterFactory0(); + private native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java index 0fc4a19dfb..9310397b0c 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractComparator.java @@ -15,12 +15,25 @@ * @see org.rocksdb.DirectComparator */ public abstract class AbstractComparator> - extends AbstractImmutableNativeReference { + extends RocksCallbackObject { protected AbstractComparator() { - super(true); + super(); } + protected AbstractComparator(final ComparatorOptions copt) { + super(copt.nativeHandle_); + } + + /** + * Get the type of this comparator. + * + * Used for determining the correct C++ cast in native code. + * + * @return The type of the comparator. + */ + abstract ComparatorType getComparatorType(); + /** * The name of the comparator. Used to check for comparator * mismatches (i.e., a DB created with one comparator is @@ -87,20 +100,4 @@ public String findShortestSeparator(final String start, final T limit) { public String findShortSuccessor(final String key) { return null; } - - /** - * Deletes underlying C++ comparator pointer. - * - * Note that this function should be called only after all - * RocksDB instances referencing the comparator are closed. - * Otherwise an undefined behavior will occur. - */ - @Override - protected void disposeInternal() { - disposeInternal(getNativeHandle()); - } - - protected abstract long getNativeHandle(); - - private native void disposeInternal(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java index b1dc1ef379..8532debf80 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractImmutableNativeReference.java @@ -12,6 +12,7 @@ * {@link AbstractNativeReference} which have an immutable reference to the * underlying native C++ object */ +//@ThreadSafe public abstract class AbstractImmutableNativeReference extends AbstractNativeReference { @@ -19,7 +20,7 @@ public abstract class AbstractImmutableNativeReference * A flag indicating whether the current {@code AbstractNativeReference} is * responsible to free the underlying C++ object */ - private final AtomicBoolean owningHandle_; + protected final AtomicBoolean owningHandle_; protected AbstractImmutableNativeReference(final boolean owningHandle) { this.owningHandle_ = new AtomicBoolean(owningHandle); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractMutableOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractMutableOptions.java new file mode 100644 index 0000000000..63015c39a9 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractMutableOptions.java @@ -0,0 +1,254 @@ +package org.rocksdb; + +import java.util.*; + +public abstract class AbstractMutableOptions { + + protected static final String KEY_VALUE_PAIR_SEPARATOR = ";"; + protected static final char KEY_VALUE_SEPARATOR = '='; + static final String INT_ARRAY_INT_SEPARATOR = ","; + + protected final String[] keys; + private final String[] values; + + /** + * User must use builder pattern, or parser. + * + * @param keys the keys + * @param values the values + */ + protected AbstractMutableOptions(final String[] keys, final String[] values) { + this.keys = keys; + this.values = values; + } + + String[] getKeys() { + return keys; + } + + String[] getValues() { + return values; + } + + /** + * Returns a string representation of MutableOptions which + * is suitable for consumption by {@code #parse(String)}. + * + * @return String representation of MutableOptions + */ + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + for(int i = 0; i < keys.length; i++) { + buffer + .append(keys[i]) + .append(KEY_VALUE_SEPARATOR) + .append(values[i]); + + if(i + 1 < keys.length) { + buffer.append(KEY_VALUE_PAIR_SEPARATOR); + } + } + return buffer.toString(); + } + + public static abstract class AbstractMutableOptionsBuilder< + T extends AbstractMutableOptions, + U extends AbstractMutableOptionsBuilder, + K extends MutableOptionKey> { + + private final Map> options = new LinkedHashMap<>(); + + protected abstract U self(); + + /** + * Get all of the possible keys + * + * @return A map of all keys, indexed by name. + */ + protected abstract Map allKeys(); + + /** + * Construct a sub-class instance of {@link AbstractMutableOptions}. + * + * @param keys the keys + * @param values the values + * + * @return an instance of the options. + */ + protected abstract T build(final String[] keys, final String[] values); + + public T build() { + final String keys[] = new String[options.size()]; + final String values[] = new String[options.size()]; + + int i = 0; + for (final Map.Entry> option : options.entrySet()) { + keys[i] = option.getKey().name(); + values[i] = option.getValue().asString(); + i++; + } + + return build(keys, values); + } + + protected U setDouble( + final K key, final double value) { + if (key.getValueType() != MutableOptionKey.ValueType.DOUBLE) { + throw new IllegalArgumentException( + key + " does not accept a double value"); + } + options.put(key, MutableOptionValue.fromDouble(value)); + return self(); + } + + protected double getDouble(final K key) + throws NoSuchElementException, NumberFormatException { + final MutableOptionValue value = options.get(key); + if(value == null) { + throw new NoSuchElementException(key.name() + " has not been set"); + } + return value.asDouble(); + } + + protected U setLong( + final K key, final long value) { + if(key.getValueType() != MutableOptionKey.ValueType.LONG) { + throw new IllegalArgumentException( + key + " does not accept a long value"); + } + options.put(key, MutableOptionValue.fromLong(value)); + return self(); + } + + protected long getLong(final K key) + throws NoSuchElementException, NumberFormatException { + final MutableOptionValue value = options.get(key); + if(value == null) { + throw new NoSuchElementException(key.name() + " has not been set"); + } + return value.asLong(); + } + + protected U setInt( + final K key, final int value) { + if(key.getValueType() != MutableOptionKey.ValueType.INT) { + throw new IllegalArgumentException( + key + " does not accept an integer value"); + } + options.put(key, MutableOptionValue.fromInt(value)); + return self(); + } + + protected int getInt(final K key) + throws NoSuchElementException, NumberFormatException { + final MutableOptionValue value = options.get(key); + if(value == null) { + throw new NoSuchElementException(key.name() + " has not been set"); + } + return value.asInt(); + } + + protected U setBoolean( + final K key, final boolean value) { + if(key.getValueType() != MutableOptionKey.ValueType.BOOLEAN) { + throw new IllegalArgumentException( + key + " does not accept a boolean value"); + } + options.put(key, MutableOptionValue.fromBoolean(value)); + return self(); + } + + protected boolean getBoolean(final K key) + throws NoSuchElementException, NumberFormatException { + final MutableOptionValue value = options.get(key); + if(value == null) { + throw new NoSuchElementException(key.name() + " has not been set"); + } + return value.asBoolean(); + } + + protected U setIntArray( + final K key, final int[] value) { + if(key.getValueType() != MutableOptionKey.ValueType.INT_ARRAY) { + throw new IllegalArgumentException( + key + " does not accept an int array value"); + } + options.put(key, MutableOptionValue.fromIntArray(value)); + return self(); + } + + protected int[] getIntArray(final K key) + throws NoSuchElementException, NumberFormatException { + final MutableOptionValue value = options.get(key); + if(value == null) { + throw new NoSuchElementException(key.name() + " has not been set"); + } + return value.asIntArray(); + } + + protected > U setEnum( + final K key, final N value) { + if(key.getValueType() != MutableOptionKey.ValueType.ENUM) { + throw new IllegalArgumentException( + key + " does not accept a Enum value"); + } + options.put(key, MutableOptionValue.fromEnum(value)); + return self(); + } + + protected > N getEnum(final K key) + throws NoSuchElementException, NumberFormatException { + final MutableOptionValue value = options.get(key); + if(value == null) { + throw new NoSuchElementException(key.name() + " has not been set"); + } + + if(!(value instanceof MutableOptionValue.MutableOptionEnumValue)) { + throw new NoSuchElementException(key.name() + " is not of Enum type"); + } + + return ((MutableOptionValue.MutableOptionEnumValue)value).asObject(); + } + + public U fromString( + final String keyStr, final String valueStr) + throws IllegalArgumentException { + Objects.requireNonNull(keyStr); + Objects.requireNonNull(valueStr); + + final K key = allKeys().get(keyStr); + switch(key.getValueType()) { + case DOUBLE: + return setDouble(key, Double.parseDouble(valueStr)); + + case LONG: + return setLong(key, Long.parseLong(valueStr)); + + case INT: + return setInt(key, Integer.parseInt(valueStr)); + + case BOOLEAN: + return setBoolean(key, Boolean.parseBoolean(valueStr)); + + case INT_ARRAY: + final String[] strInts = valueStr + .trim().split(INT_ARRAY_INT_SEPARATOR); + if(strInts == null || strInts.length == 0) { + throw new IllegalArgumentException( + "int array value is not correctly formatted"); + } + + final int value[] = new int[strInts.length]; + int i = 0; + for(final String strInt : strInts) { + value[i++] = Integer.parseInt(strInt); + } + return setIntArray(key, value); + } + + throw new IllegalStateException( + key + " has unknown value type: " + key.getValueType()); + } + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java index 52bd00f47c..2819b6c70c 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractRocksIterator.java @@ -58,6 +58,12 @@ public void seek(byte[] target) { seek0(nativeHandle_, target, target.length); } + @Override + public void seekForPrev(byte[] target) { + assert (isOwningHandle()); + seekForPrev0(nativeHandle_, target, target.length); + } + @Override public void next() { assert (isOwningHandle()); @@ -97,5 +103,6 @@ protected void disposeInternal() { abstract void next0(long handle); abstract void prev0(long handle); abstract void seek0(long handle, byte[] target, int targetLen); + abstract void seekForPrev0(long handle, byte[] target, int targetLen); abstract void status0(long handle) throws RocksDBException; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTableFilter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTableFilter.java new file mode 100644 index 0000000000..627e1ae1f7 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTableFilter.java @@ -0,0 +1,19 @@ +package org.rocksdb; + +/** + * Base class for Table Filters. + */ +public abstract class AbstractTableFilter + extends RocksCallbackObject implements TableFilter { + + protected AbstractTableFilter() { + super(); + } + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewTableFilter(); + } + + private native long createNewTableFilter(); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTraceWriter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTraceWriter.java new file mode 100644 index 0000000000..806709b1f7 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTraceWriter.java @@ -0,0 +1,70 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Base class for TraceWriters. + */ +public abstract class AbstractTraceWriter + extends RocksCallbackObject implements TraceWriter { + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewTraceWriter(); + } + + /** + * Called from JNI, proxy for {@link TraceWriter#write(Slice)}. + * + * @param sliceHandle the native handle of the slice (which we do not own) + * + * @return short (2 bytes) where the first byte is the + * {@link Status.Code#getValue()} and the second byte is the + * {@link Status.SubCode#getValue()}. + */ + private short writeProxy(final long sliceHandle) { + try { + write(new Slice(sliceHandle)); + return statusToShort(Status.Code.Ok, Status.SubCode.None); + } catch (final RocksDBException e) { + return statusToShort(e.getStatus()); + } + } + + /** + * Called from JNI, proxy for {@link TraceWriter#closeWriter()}. + * + * @return short (2 bytes) where the first byte is the + * {@link Status.Code#getValue()} and the second byte is the + * {@link Status.SubCode#getValue()}. + */ + private short closeWriterProxy() { + try { + closeWriter(); + return statusToShort(Status.Code.Ok, Status.SubCode.None); + } catch (final RocksDBException e) { + return statusToShort(e.getStatus()); + } + } + + private static short statusToShort(/*@Nullable*/ final Status status) { + final Status.Code code = status != null && status.getCode() != null + ? status.getCode() + : Status.Code.IOError; + final Status.SubCode subCode = status != null && status.getSubCode() != null + ? status.getSubCode() + : Status.SubCode.None; + return statusToShort(code, subCode); + } + + private static short statusToShort(final Status.Code code, + final Status.SubCode subCode) { + short result = (short)(code.getValue() << 8); + return (short)(result | subCode.getValue()); + } + + private native long createNewTraceWriter(); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java new file mode 100644 index 0000000000..cbb49836d1 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractTransactionNotifier.java @@ -0,0 +1,54 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Provides notification to the caller of SetSnapshotOnNextOperation when + * the actual snapshot gets created + */ +public abstract class AbstractTransactionNotifier + extends RocksCallbackObject { + + protected AbstractTransactionNotifier() { + super(); + } + + /** + * Implement this method to receive notification when a snapshot is + * requested via {@link Transaction#setSnapshotOnNextOperation()}. + * + * @param newSnapshot the snapshot that has been created. + */ + public abstract void snapshotCreated(final Snapshot newSnapshot); + + /** + * This is intentionally private as it is the callback hook + * from JNI + */ + private void snapshotCreated(final long snapshotHandle) { + snapshotCreated(new Snapshot(snapshotHandle)); + } + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewTransactionNotifier(); + } + + private native long createNewTransactionNotifier(); + + /** + * Deletes underlying C++ TransactionNotifier pointer. + * + * Note that this function should be called only after all + * Transactions referencing the comparator are closed. + * Otherwise an undefined behavior will occur. + */ + @Override + protected void disposeInternal() { + disposeInternal(nativeHandle_); + } + protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractWalFilter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractWalFilter.java new file mode 100644 index 0000000000..d525045c6b --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractWalFilter.java @@ -0,0 +1,49 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Base class for WAL Filters. + */ +public abstract class AbstractWalFilter + extends RocksCallbackObject implements WalFilter { + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewWalFilter(); + } + + /** + * Called from JNI, proxy for + * {@link WalFilter#logRecordFound(long, String, WriteBatch, WriteBatch)}. + * + * @param logNumber the log handle. + * @param logFileName the log file name + * @param batchHandle the native handle of a WriteBatch (which we do not own) + * @param newBatchHandle the native handle of a + * new WriteBatch (which we do not own) + * + * @return short (2 bytes) where the first byte is the + * {@link WalFilter.LogRecordFoundResult#walProcessingOption} + * {@link WalFilter.LogRecordFoundResult#batchChanged}. + */ + private short logRecordFoundProxy(final long logNumber, + final String logFileName, final long batchHandle, + final long newBatchHandle) { + final LogRecordFoundResult logRecordFoundResult = logRecordFound( + logNumber, logFileName, new WriteBatch(batchHandle), + new WriteBatch(newBatchHandle)); + return logRecordFoundResultToShort(logRecordFoundResult); + } + + private static short logRecordFoundResultToShort( + final LogRecordFoundResult logRecordFoundResult) { + short result = (short)(logRecordFoundResult.walProcessingOption.getValue() << 8); + return (short)(result | (logRecordFoundResult.batchChanged ? 1 : 0)); + } + + private native long createNewWalFilter(); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java index b2e5571809..9de0eb43c5 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AbstractWriteBatch.java @@ -18,52 +18,80 @@ public int count() { } @Override - public void put(byte[] key, byte[] value) { + public void put(byte[] key, byte[] value) throws RocksDBException { put(nativeHandle_, key, key.length, value, value.length); } @Override public void put(ColumnFamilyHandle columnFamilyHandle, byte[] key, - byte[] value) { + byte[] value) throws RocksDBException { put(nativeHandle_, key, key.length, value, value.length, columnFamilyHandle.nativeHandle_); } @Override - public void merge(byte[] key, byte[] value) { + public void merge(byte[] key, byte[] value) throws RocksDBException { merge(nativeHandle_, key, key.length, value, value.length); } @Override public void merge(ColumnFamilyHandle columnFamilyHandle, byte[] key, - byte[] value) { + byte[] value) throws RocksDBException { merge(nativeHandle_, key, key.length, value, value.length, columnFamilyHandle.nativeHandle_); } @Override - public void remove(byte[] key) { - remove(nativeHandle_, key, key.length); + @Deprecated + public void remove(byte[] key) throws RocksDBException { + delete(nativeHandle_, key, key.length); } @Override - public void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key) { - remove(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); + @Deprecated + public void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key) + throws RocksDBException { + delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); } @Override - public void deleteRange(byte[] beginKey, byte[] endKey) { + public void delete(byte[] key) throws RocksDBException { + delete(nativeHandle_, key, key.length); + } + + @Override + public void delete(ColumnFamilyHandle columnFamilyHandle, byte[] key) + throws RocksDBException { + delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); + } + + + @Override + public void singleDelete(byte[] key) throws RocksDBException { + singleDelete(nativeHandle_, key, key.length); + } + + @Override + public void singleDelete(ColumnFamilyHandle columnFamilyHandle, byte[] key) + throws RocksDBException { + singleDelete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); + } + + @Override + public void deleteRange(byte[] beginKey, byte[] endKey) + throws RocksDBException { deleteRange(nativeHandle_, beginKey, beginKey.length, endKey, endKey.length); } @Override - public void deleteRange(ColumnFamilyHandle columnFamilyHandle, byte[] beginKey, byte[] endKey) { + public void deleteRange(ColumnFamilyHandle columnFamilyHandle, + byte[] beginKey, byte[] endKey) throws RocksDBException { deleteRange(nativeHandle_, beginKey, beginKey.length, endKey, endKey.length, columnFamilyHandle.nativeHandle_); } @Override - public void putLogData(byte[] blob) { + public void putLogData(byte[] blob) throws RocksDBException { putLogData(nativeHandle_, blob, blob.length); } @@ -82,38 +110,67 @@ public void rollbackToSavePoint() throws RocksDBException { rollbackToSavePoint0(nativeHandle_); } + @Override + public void popSavePoint() throws RocksDBException { + popSavePoint(nativeHandle_); + } + + @Override + public void setMaxBytes(final long maxBytes) { + setMaxBytes(nativeHandle_, maxBytes); + } + + @Override + public WriteBatch getWriteBatch() { + return getWriteBatch(nativeHandle_); + } + abstract int count0(final long handle); abstract void put(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen); + final byte[] value, final int valueLen) throws RocksDBException; abstract void put(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen, final long cfHandle); + final byte[] value, final int valueLen, final long cfHandle) + throws RocksDBException; abstract void merge(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen); + final byte[] value, final int valueLen) throws RocksDBException; abstract void merge(final long handle, final byte[] key, final int keyLen, - final byte[] value, final int valueLen, final long cfHandle); + final byte[] value, final int valueLen, final long cfHandle) + throws RocksDBException; + + abstract void delete(final long handle, final byte[] key, + final int keyLen) throws RocksDBException; + + abstract void delete(final long handle, final byte[] key, + final int keyLen, final long cfHandle) throws RocksDBException; - abstract void remove(final long handle, final byte[] key, - final int keyLen); + abstract void singleDelete(final long handle, final byte[] key, + final int keyLen) throws RocksDBException; - abstract void remove(final long handle, final byte[] key, - final int keyLen, final long cfHandle); + abstract void singleDelete(final long handle, final byte[] key, + final int keyLen, final long cfHandle) throws RocksDBException; abstract void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen); + final byte[] endKey, final int endKeyLen) throws RocksDBException; abstract void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, - final byte[] endKey, final int endKeyLen, final long cfHandle); + final byte[] endKey, final int endKeyLen, final long cfHandle) throws RocksDBException; abstract void putLogData(final long handle, final byte[] blob, - final int blobLen); + final int blobLen) throws RocksDBException; abstract void clear0(final long handle); abstract void setSavePoint0(final long handle); abstract void rollbackToSavePoint0(final long handle); + + abstract void popSavePoint(final long handle) throws RocksDBException; + + abstract void setMaxBytes(final long handle, long maxBytes); + + abstract WriteBatch getWriteBatch(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java index d3908d1a37..ac8550f3ef 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java @@ -441,7 +441,7 @@ T setOptimizeFiltersForHits( boolean optimizeFiltersForHits(); /** - * In debug mode, RocksDB run consistency checks on the LSM everytime the LSM + * In debug mode, RocksDB run consistency checks on the LSM every time the LSM * change (Flush, Compaction, AddFile). These checks are disabled in release * mode, use this option to enable them in release mode as well. * @@ -455,7 +455,7 @@ T setForceConsistencyChecks( boolean forceConsistencyChecks); /** - * In debug mode, RocksDB run consistency checks on the LSM everytime the LSM + * In debug mode, RocksDB run consistency checks on the LSM every time the LSM * change (Flush, Compaction, AddFile). These checks are disabled in release * mode. * diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java index 092fe37843..3ec4671238 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/AdvancedMutableColumnFamilyOptionsInterface.java @@ -434,4 +434,32 @@ T setReportBgIoStats( * @return true if reporting is enabled */ boolean reportBgIoStats(); + + /** + * Non-bottom-level files older than TTL will go through the compaction + * process. This needs {@link MutableDBOptionsInterface#maxOpenFiles()} to be + * set to -1. + * + * Enabled only for level compaction for now. + * + * Default: 0 (disabled) + * + * Dynamically changeable through + * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)}. + * + * @param ttl the time-to-live. + * + * @return the reference to the current options. + */ + T setTtl(final long ttl); + + /** + * Get the TTL for Non-bottom-level files that will go through the compaction + * process. + * + * See {@link #setTtl(long)}. + * + * @return the time-to-live. + */ + long ttl(); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java index 763994575c..a028edea0a 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupEngine.java @@ -65,7 +65,10 @@ public void createNewBackup(final RocksDB db) throws RocksDBException { * When false, the Backup Engine will not issue a * flush before starting the backup. In that case, * the backup will also include log files - * corresponding to live memtables. The backup will + * corresponding to live memtables. If writes have + * been performed with the write ahead log disabled, + * set flushBeforeBackup to true to prevent those + * writes from being lost. Otherwise, the backup will * always be consistent with the current state of the * database regardless of the flushBeforeBackup * parameter. @@ -81,6 +84,38 @@ public void createNewBackup( createNewBackup(nativeHandle_, db.nativeHandle_, flushBeforeBackup); } + /** + * Captures the state of the database in the latest backup along with + * application specific metadata. + * + * @param db The database to backup + * @param metadata Application metadata + * @param flushBeforeBackup When true, the Backup Engine will first issue a + * memtable flush and only then copy the DB files to + * the backup directory. Doing so will prevent log + * files from being copied to the backup directory + * (since flush will delete them). + * When false, the Backup Engine will not issue a + * flush before starting the backup. In that case, + * the backup will also include log files + * corresponding to live memtables. If writes have + * been performed with the write ahead log disabled, + * set flushBeforeBackup to true to prevent those + * writes from being lost. Otherwise, the backup will + * always be consistent with the current state of the + * database regardless of the flushBeforeBackup + * parameter. + * + * Note - This method is not thread safe + * + * @throws RocksDBException thrown if a new backup could not be created + */ + public void createNewBackupWithMetadata(final RocksDB db, final String metadata, + final boolean flushBeforeBackup) throws RocksDBException { + assert (isOwningHandle()); + createNewBackupWithMetadata(nativeHandle_, db.nativeHandle_, metadata, flushBeforeBackup); + } + /** * Gets information about the available * backups @@ -197,6 +232,9 @@ private native static long open(final long env, private native void createNewBackup(final long handle, final long dbHandle, final boolean flushBeforeBackup) throws RocksDBException; + private native void createNewBackupWithMetadata(final long handle, final long dbHandle, + final String metadata, final boolean flushBeforeBackup) throws RocksDBException; + private native List getBackupInfo(final long handle); private native int[] getCorruptedBackups(final long handle); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java index 10f418629a..9244e4eb19 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BackupInfo.java @@ -19,12 +19,13 @@ public class BackupInfo { * @param size size of backup * @param numberFiles number of files related to this backup. */ - BackupInfo(final int backupId, final long timestamp, final long size, - final int numberFiles) { + BackupInfo(final int backupId, final long timestamp, final long size, final int numberFiles, + final String app_metadata) { backupId_ = backupId; timestamp_ = timestamp; size_ = size; numberFiles_ = numberFiles; + app_metadata_ = app_metadata; } /** @@ -59,8 +60,17 @@ public int numberFiles() { return numberFiles_; } + /** + * + * @return the associated application metadata, or null + */ + public String appMetadata() { + return app_metadata_; + } + private int backupId_; private long timestamp_; private long size_; private int numberFiles_; + private String app_metadata_; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java index 2d847de29d..7a4ff14bfe 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/BlockBasedTableConfig.java @@ -9,96 +9,326 @@ * * BlockBasedTable is a RocksDB's default SST file format. */ +//TODO(AR) should be renamed BlockBasedTableOptions public class BlockBasedTableConfig extends TableFormatConfig { public BlockBasedTableConfig() { - noBlockCache_ = false; - blockCacheSize_ = 8 * 1024 * 1024; - blockCacheNumShardBits_ = 0; - blockSize_ = 4 * 1024; - blockSizeDeviation_ = 10; - blockRestartInterval_ = 16; - wholeKeyFiltering_ = true; - filter_ = null; - cacheIndexAndFilterBlocks_ = false; - pinL0FilterAndIndexBlocksInCache_ = false; - hashIndexAllowCollision_ = true; - blockCacheCompressedSize_ = 0; - blockCacheCompressedNumShardBits_ = 0; - checksumType_ = ChecksumType.kCRC32c; - indexType_ = IndexType.kBinarySearch; - formatVersion_ = 0; + //TODO(AR) flushBlockPolicyFactory + cacheIndexAndFilterBlocks = false; + cacheIndexAndFilterBlocksWithHighPriority = false; + pinL0FilterAndIndexBlocksInCache = false; + pinTopLevelIndexAndFilter = true; + indexType = IndexType.kBinarySearch; + dataBlockIndexType = DataBlockIndexType.kDataBlockBinarySearch; + dataBlockHashTableUtilRatio = 0.75; + checksumType = ChecksumType.kCRC32c; + noBlockCache = false; + blockCache = null; + persistentCache = null; + blockCacheCompressed = null; + blockSize = 4 * 1024; + blockSizeDeviation = 10; + blockRestartInterval = 16; + indexBlockRestartInterval = 1; + metadataBlockSize = 4096; + partitionFilters = false; + useDeltaEncoding = true; + filterPolicy = null; + wholeKeyFiltering = true; + verifyCompression = true; + readAmpBytesPerBit = 0; + formatVersion = 2; + enableIndexCompression = true; + blockAlign = false; + + // NOTE: ONLY used if blockCache == null + blockCacheSize = 8 * 1024 * 1024; + blockCacheNumShardBits = 0; + + // NOTE: ONLY used if blockCacheCompressed == null + blockCacheCompressedSize = 0; + blockCacheCompressedNumShardBits = 0; } /** - * Disable block cache. If this is set to true, - * then no block cache should be used, and the block_cache should - * point to a {@code nullptr} object. - * Default: false + * Indicating if we'd put index/filter blocks to the block cache. + * If not specified, each "table reader" object will pre-load index/filter + * block during table initialization. * - * @param noBlockCache if use block cache + * @return if index and filter blocks should be put in block cache. + */ + public boolean cacheIndexAndFilterBlocks() { + return cacheIndexAndFilterBlocks; + } + + /** + * Indicating if we'd put index/filter blocks to the block cache. + * If not specified, each "table reader" object will pre-load index/filter + * block during table initialization. + * + * @param cacheIndexAndFilterBlocks and filter blocks should be put in block cache. * @return the reference to the current config. */ - public BlockBasedTableConfig setNoBlockCache(final boolean noBlockCache) { - noBlockCache_ = noBlockCache; + public BlockBasedTableConfig setCacheIndexAndFilterBlocks( + final boolean cacheIndexAndFilterBlocks) { + this.cacheIndexAndFilterBlocks = cacheIndexAndFilterBlocks; + return this; + } + + /** + * Indicates if index and filter blocks will be treated as high-priority in the block cache. + * See note below about applicability. If not specified, defaults to false. + * + * @return if index and filter blocks will be treated as high-priority. + */ + public boolean cacheIndexAndFilterBlocksWithHighPriority() { + return cacheIndexAndFilterBlocksWithHighPriority; + } + + /** + * If true, cache index and filter blocks with high priority. If set to true, + * depending on implementation of block cache, index and filter blocks may be + * less likely to be evicted than data blocks. + * + * @param cacheIndexAndFilterBlocksWithHighPriority if index and filter blocks + * will be treated as high-priority. + * @return the reference to the current config. + */ + public BlockBasedTableConfig setCacheIndexAndFilterBlocksWithHighPriority( + final boolean cacheIndexAndFilterBlocksWithHighPriority) { + this.cacheIndexAndFilterBlocksWithHighPriority = cacheIndexAndFilterBlocksWithHighPriority; + return this; + } + + /** + * Indicating if we'd like to pin L0 index/filter blocks to the block cache. + If not specified, defaults to false. + * + * @return if L0 index and filter blocks should be pinned to the block cache. + */ + public boolean pinL0FilterAndIndexBlocksInCache() { + return pinL0FilterAndIndexBlocksInCache; + } + + /** + * Indicating if we'd like to pin L0 index/filter blocks to the block cache. + If not specified, defaults to false. + * + * @param pinL0FilterAndIndexBlocksInCache pin blocks in block cache + * @return the reference to the current config. + */ + public BlockBasedTableConfig setPinL0FilterAndIndexBlocksInCache( + final boolean pinL0FilterAndIndexBlocksInCache) { + this.pinL0FilterAndIndexBlocksInCache = pinL0FilterAndIndexBlocksInCache; + return this; + } + + /** + * Indicates if top-level index and filter blocks should be pinned. + * + * @return if top-level index and filter blocks should be pinned. + */ + public boolean pinTopLevelIndexAndFilter() { + return pinTopLevelIndexAndFilter; + } + + /** + * If cacheIndexAndFilterBlocks is true and the below is true, then + * the top-level index of partitioned filter and index blocks are stored in + * the cache, but a reference is held in the "table reader" object so the + * blocks are pinned and only evicted from cache when the table reader is + * freed. This is not limited to l0 in LSM tree. + * + * @param pinTopLevelIndexAndFilter if top-level index and filter blocks should be pinned. + * @return the reference to the current config. + */ + public BlockBasedTableConfig setPinTopLevelIndexAndFilter(final boolean pinTopLevelIndexAndFilter) { + this.pinTopLevelIndexAndFilter = pinTopLevelIndexAndFilter; + return this; + } + + /** + * Get the index type. + * + * @return the currently set index type + */ + public IndexType indexType() { + return indexType; + } + + /** + * Sets the index type to used with this table. + * + * @param indexType {@link org.rocksdb.IndexType} value + * @return the reference to the current option. + */ + public BlockBasedTableConfig setIndexType( + final IndexType indexType) { + this.indexType = indexType; + return this; + } + + /** + * Get the data block index type. + * + * @return the currently set data block index type + */ + public DataBlockIndexType dataBlockIndexType() { + return dataBlockIndexType; + } + + /** + * Sets the data block index type to used with this table. + * + * @param dataBlockIndexType {@link org.rocksdb.DataBlockIndexType} value + * @return the reference to the current option. + */ + public BlockBasedTableConfig setDataBlockIndexType( + final DataBlockIndexType dataBlockIndexType) { + this.dataBlockIndexType = dataBlockIndexType; + return this; + } + + /** + * Get the #entries/#buckets. It is valid only when {@link #dataBlockIndexType()} is + * {@link DataBlockIndexType#kDataBlockBinaryAndHash}. + * + * @return the #entries/#buckets. + */ + public double dataBlockHashTableUtilRatio() { + return dataBlockHashTableUtilRatio; + } + + /** + * Set the #entries/#buckets. It is valid only when {@link #dataBlockIndexType()} is + * {@link DataBlockIndexType#kDataBlockBinaryAndHash}. + * + * @param dataBlockHashTableUtilRatio #entries/#buckets + * @return the reference to the current option. + */ + public BlockBasedTableConfig setDataBlockHashTableUtilRatio( + final double dataBlockHashTableUtilRatio) { + this.dataBlockHashTableUtilRatio = dataBlockHashTableUtilRatio; + return this; + } + + /** + * Get the checksum type to be used with this table. + * + * @return the currently set checksum type + */ + public ChecksumType checksumType() { + return checksumType; + } + + /** + * Sets + * + * @param checksumType {@link org.rocksdb.ChecksumType} value. + * @return the reference to the current option. + */ + public BlockBasedTableConfig setChecksumType( + final ChecksumType checksumType) { + this.checksumType = checksumType; return this; } /** + * Determine if the block cache is disabled. + * * @return if block cache is disabled */ public boolean noBlockCache() { - return noBlockCache_; + return noBlockCache; } /** - * Set the amount of cache in bytes that will be used by RocksDB. - * If cacheSize is non-positive, then cache will not be used. - * DEFAULT: 8M + * Disable block cache. If this is set to true, + * then no block cache should be used, and the {@link #setBlockCache(Cache)} + * should point to a {@code null} object. * - * @param blockCacheSize block cache size in bytes + * Default: false + * + * @param noBlockCache if use block cache * @return the reference to the current config. */ - public BlockBasedTableConfig setBlockCacheSize(final long blockCacheSize) { - blockCacheSize_ = blockCacheSize; + public BlockBasedTableConfig setNoBlockCache(final boolean noBlockCache) { + this.noBlockCache = noBlockCache; return this; } /** - * @return block cache size in bytes + * Use the specified cache for blocks. + * When not null this take precedence even if the user sets a block cache size. + * + * {@link org.rocksdb.Cache} should not be disposed before options instances + * using this cache is disposed. + * + * {@link org.rocksdb.Cache} instance can be re-used in multiple options + * instances. + * + * @param blockCache {@link org.rocksdb.Cache} Cache java instance + * (e.g. LRUCache). + * + * @return the reference to the current config. */ - public long blockCacheSize() { - return blockCacheSize_; + public BlockBasedTableConfig setBlockCache(final Cache blockCache) { + this.blockCache = blockCache; + return this; } /** - * Controls the number of shards for the block cache. - * This is applied only if cacheSize is set to non-negative. + * Use the specified persistent cache. * - * @param blockCacheNumShardBits the number of shard bits. The resulting - * number of shards would be 2 ^ numShardBits. Any negative - * number means use default settings." - * @return the reference to the current option. + * If {@code !null} use the specified cache for pages read from device, + * otherwise no page cache is used. + * + * @param persistentCache the persistent cache + * + * @return the reference to the current config. */ - public BlockBasedTableConfig setCacheNumShardBits( - final int blockCacheNumShardBits) { - blockCacheNumShardBits_ = blockCacheNumShardBits; + public BlockBasedTableConfig setPersistentCache( + final PersistentCache persistentCache) { + this.persistentCache = persistentCache; return this; } /** - * Returns the number of shard bits used in the block cache. - * The resulting number of shards would be 2 ^ (returned value). - * Any negative number means use default settings. + * Use the specified cache for compressed blocks. * - * @return the number of shard bits used in the block cache. + * If {@code null}, RocksDB will not use a compressed block cache. + * + * Note: though it looks similar to {@link #setBlockCache(Cache)}, RocksDB + * doesn't put the same type of object there. + * + * {@link org.rocksdb.Cache} should not be disposed before options instances + * using this cache is disposed. + * + * {@link org.rocksdb.Cache} instance can be re-used in multiple options + * instances. + * + * @param blockCacheCompressed {@link org.rocksdb.Cache} Cache java instance + * (e.g. LRUCache). + * + * @return the reference to the current config. */ - public int cacheNumShardBits() { - return blockCacheNumShardBits_; + public BlockBasedTableConfig setBlockCacheCompressed( + final Cache blockCacheCompressed) { + this.blockCacheCompressed = blockCacheCompressed; + return this; } /** - * Approximate size of user data packed per block. Note that the + * Get the approximate size of user data packed per block. + * + * @return block size in bytes + */ + public long blockSize() { + return blockSize; + } + + /** + * Approximate size of user data packed per block. Note that the * block size specified here corresponds to uncompressed data. The * actual size of the unit read from disk may be smaller if * compression is enabled. This parameter can be changed dynamically. @@ -108,23 +338,24 @@ public int cacheNumShardBits() { * @return the reference to the current config. */ public BlockBasedTableConfig setBlockSize(final long blockSize) { - blockSize_ = blockSize; + this.blockSize = blockSize; return this; } /** - * @return block size in bytes + * @return the hash table ratio. */ - public long blockSize() { - return blockSize_; + public int blockSizeDeviation() { + return blockSizeDeviation; } /** * This is used to close a block before it reaches the configured - * 'block_size'. If the percentage of free space in the current block is less - * than this specified number and adding a new record to the block will - * exceed the configured block size, then this block will be closed and the - * new record will be written to the next block. + * {@link #blockSize()}. If the percentage of free space in the current block + * is less than this specified number and adding a new record to the block + * will exceed the configured block size, then this block will be closed and + * the new record will be written to the next block. + * * Default is 10. * * @param blockSizeDeviation the deviation to block size allowed @@ -132,55 +363,120 @@ public long blockSize() { */ public BlockBasedTableConfig setBlockSizeDeviation( final int blockSizeDeviation) { - blockSizeDeviation_ = blockSizeDeviation; + this.blockSizeDeviation = blockSizeDeviation; return this; } /** - * @return the hash table ratio. + * Get the block restart interval. + * + * @return block restart interval */ - public int blockSizeDeviation() { - return blockSizeDeviation_; + public int blockRestartInterval() { + return blockRestartInterval; } /** - * Set block restart interval + * Set the block restart interval. * * @param restartInterval block restart interval. * @return the reference to the current config. */ public BlockBasedTableConfig setBlockRestartInterval( final int restartInterval) { - blockRestartInterval_ = restartInterval; + blockRestartInterval = restartInterval; return this; } /** - * @return block restart interval + * Get the index block restart interval. + * + * @return index block restart interval */ - public int blockRestartInterval() { - return blockRestartInterval_; + public int indexBlockRestartInterval() { + return indexBlockRestartInterval; } /** - * If true, place whole keys in the filter (not just prefixes). - * This must generally be true for gets to be efficient. - * Default: true + * Set the index block restart interval * - * @param wholeKeyFiltering if enable whole key filtering + * @param restartInterval index block restart interval. * @return the reference to the current config. */ - public BlockBasedTableConfig setWholeKeyFiltering( - final boolean wholeKeyFiltering) { - wholeKeyFiltering_ = wholeKeyFiltering; + public BlockBasedTableConfig setIndexBlockRestartInterval( + final int restartInterval) { + indexBlockRestartInterval = restartInterval; return this; } /** - * @return if whole key filtering is enabled + * Get the block size for partitioned metadata. + * + * @return block size for partitioned metadata. */ - public boolean wholeKeyFiltering() { - return wholeKeyFiltering_; + public long metadataBlockSize() { + return metadataBlockSize; + } + + /** + * Set block size for partitioned metadata. + * + * @param metadataBlockSize Partitioned metadata block size. + * @return the reference to the current config. + */ + public BlockBasedTableConfig setMetadataBlockSize( + final long metadataBlockSize) { + this.metadataBlockSize = metadataBlockSize; + return this; + } + + /** + * Indicates if we're using partitioned filters. + * + * @return if we're using partition filters. + */ + public boolean partitionFilters() { + return partitionFilters; + } + + /** + * Use partitioned full filters for each SST file. This option is incompatible + * with block-based filters. + * + * Defaults to false. + * + * @param partitionFilters use partition filters. + * @return the reference to the current config. + */ + public BlockBasedTableConfig setPartitionFilters(final boolean partitionFilters) { + this.partitionFilters = partitionFilters; + return this; + } + + /** + * Determine if delta encoding is being used to compress block keys. + * + * @return true if delta encoding is enabled, false otherwise. + */ + public boolean useDeltaEncoding() { + return useDeltaEncoding; + } + + /** + * Use delta encoding to compress keys in blocks. + * + * NOTE: {@link ReadOptions#pinData()} requires this option to be disabled. + * + * Default: true + * + * @param useDeltaEncoding true to enable delta encoding + * + * @return the reference to the current config. + */ + public BlockBasedTableConfig setUseDeltaEncoding( + final boolean useDeltaEncoding) { + this.useDeltaEncoding = useDeltaEncoding; + return this; } /** @@ -193,87 +489,274 @@ public boolean wholeKeyFiltering() { * {@link org.rocksdb.Filter} instance can be re-used in multiple options * instances. * - * @param filter {@link org.rocksdb.Filter} Filter Policy java instance. + * @param filterPolicy {@link org.rocksdb.Filter} Filter Policy java instance. * @return the reference to the current config. */ + public BlockBasedTableConfig setFilterPolicy( + final Filter filterPolicy) { + this.filterPolicy = filterPolicy; + return this; + } + + /* + * @deprecated Use {@link #setFilterPolicy(Filter)} + */ + @Deprecated public BlockBasedTableConfig setFilter( final Filter filter) { - filter_ = filter; + return setFilterPolicy(filter); + } + + /** + * Determine if whole keys as opposed to prefixes are placed in the filter. + * + * @return if whole key filtering is enabled + */ + public boolean wholeKeyFiltering() { + return wholeKeyFiltering; + } + + /** + * If true, place whole keys in the filter (not just prefixes). + * This must generally be true for gets to be efficient. + * Default: true + * + * @param wholeKeyFiltering if enable whole key filtering + * @return the reference to the current config. + */ + public BlockBasedTableConfig setWholeKeyFiltering( + final boolean wholeKeyFiltering) { + this.wholeKeyFiltering = wholeKeyFiltering; return this; } /** - * Indicating if we'd put index/filter blocks to the block cache. - If not specified, each "table reader" object will pre-load index/filter - block during table initialization. + * Returns true when compression verification is enabled. * - * @return if index and filter blocks should be put in block cache. + * See {@link #setVerifyCompression(boolean)}. + * + * @return true if compression verification is enabled. */ - public boolean cacheIndexAndFilterBlocks() { - return cacheIndexAndFilterBlocks_; + public boolean verifyCompression() { + return verifyCompression; } /** - * Indicating if we'd put index/filter blocks to the block cache. - If not specified, each "table reader" object will pre-load index/filter - block during table initialization. + * Verify that decompressing the compressed block gives back the input. This + * is a verification mode that we use to detect bugs in compression + * algorithms. + * + * @param verifyCompression true to enable compression verification. * - * @param cacheIndexAndFilterBlocks and filter blocks should be put in block cache. * @return the reference to the current config. */ - public BlockBasedTableConfig setCacheIndexAndFilterBlocks( - final boolean cacheIndexAndFilterBlocks) { - cacheIndexAndFilterBlocks_ = cacheIndexAndFilterBlocks; + public BlockBasedTableConfig setVerifyCompression( + final boolean verifyCompression) { + this.verifyCompression = verifyCompression; return this; } /** - * Indicating if we'd like to pin L0 index/filter blocks to the block cache. - If not specified, defaults to false. + * Get the Read amplification bytes per-bit. * - * @return if L0 index and filter blocks should be pinned to the block cache. + * See {@link #setReadAmpBytesPerBit(int)}. + * + * @return the bytes per-bit. */ - public boolean pinL0FilterAndIndexBlocksInCache() { - return pinL0FilterAndIndexBlocksInCache_; + public int readAmpBytesPerBit() { + return readAmpBytesPerBit; } /** - * Indicating if we'd like to pin L0 index/filter blocks to the block cache. - If not specified, defaults to false. + * Set the Read amplification bytes per-bit. + * + * If used, For every data block we load into memory, we will create a bitmap + * of size ((block_size / `read_amp_bytes_per_bit`) / 8) bytes. This bitmap + * will be used to figure out the percentage we actually read of the blocks. + * + * When this feature is used Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES and + * Tickers::READ_AMP_TOTAL_READ_BYTES can be used to calculate the + * read amplification using this formula + * (READ_AMP_TOTAL_READ_BYTES / READ_AMP_ESTIMATE_USEFUL_BYTES) + * + * value => memory usage (percentage of loaded blocks memory) + * 1 => 12.50 % + * 2 => 06.25 % + * 4 => 03.12 % + * 8 => 01.56 % + * 16 => 00.78 % + * + * Note: This number must be a power of 2, if not it will be sanitized + * to be the next lowest power of 2, for example a value of 7 will be + * treated as 4, a value of 19 will be treated as 16. + * + * Default: 0 (disabled) + * + * @param readAmpBytesPerBit the bytes per-bit * - * @param pinL0FilterAndIndexBlocksInCache pin blocks in block cache * @return the reference to the current config. */ - public BlockBasedTableConfig setPinL0FilterAndIndexBlocksInCache( - final boolean pinL0FilterAndIndexBlocksInCache) { - pinL0FilterAndIndexBlocksInCache_ = pinL0FilterAndIndexBlocksInCache; + public BlockBasedTableConfig setReadAmpBytesPerBit(final int readAmpBytesPerBit) { + this.readAmpBytesPerBit = readAmpBytesPerBit; return this; } /** - * Influence the behavior when kHashSearch is used. - if false, stores a precise prefix to block range mapping - if true, does not store prefix and allows prefix hash collision - (less memory consumption) + * Get the format version. + * See {@link #setFormatVersion(int)}. * - * @return if hash collisions should be allowed. + * @return the currently configured format version. */ - public boolean hashIndexAllowCollision() { - return hashIndexAllowCollision_; + public int formatVersion() { + return formatVersion; } /** - * Influence the behavior when kHashSearch is used. - if false, stores a precise prefix to block range mapping - if true, does not store prefix and allows prefix hash collision - (less memory consumption) + *

We currently have five versions:

* - * @param hashIndexAllowCollision points out if hash collisions should be allowed. + *
    + *
  • 0 - This version is currently written + * out by all RocksDB's versions by default. Can be read by really old + * RocksDB's. Doesn't support changing checksum (default is CRC32).
  • + *
  • 1 - Can be read by RocksDB's versions since 3.0. + * Supports non-default checksum, like xxHash. It is written by RocksDB when + * BlockBasedTableOptions::checksum is something other than kCRC32c. (version + * 0 is silently upconverted)
  • + *
  • 2 - Can be read by RocksDB's versions since 3.10. + * Changes the way we encode compressed blocks with LZ4, BZip2 and Zlib + * compression. If you don't plan to run RocksDB before version 3.10, + * you should probably use this.
  • + *
  • 3 - Can be read by RocksDB's versions since 5.15. Changes the way we + * encode the keys in index blocks. If you don't plan to run RocksDB before + * version 5.15, you should probably use this. + * This option only affects newly written tables. When reading existing + * tables, the information about version is read from the footer.
  • + *
  • 4 - Can be read by RocksDB's versions since 5.16. Changes the way we + * encode the values in index blocks. If you don't plan to run RocksDB before + * version 5.16 and you are using index_block_restart_interval > 1, you should + * probably use this as it would reduce the index size.
  • + *
+ *

This option only affects newly written tables. When reading existing + * tables, the information about version is read from the footer.

+ * + * @param formatVersion integer representing the version to be used. + * + * @return the reference to the current option. + */ + public BlockBasedTableConfig setFormatVersion( + final int formatVersion) { + assert(formatVersion >= 0 && formatVersion <= 4); + this.formatVersion = formatVersion; + return this; + } + + /** + * Determine if index compression is enabled. + * + * See {@link #setEnableIndexCompression(boolean)}. + * + * @return true if index compression is enabled, false otherwise + */ + public boolean enableIndexCompression() { + return enableIndexCompression; + } + + /** + * Store index blocks on disk in compressed format. + * + * Changing this option to false will avoid the overhead of decompression + * if index blocks are evicted and read back. + * + * @param enableIndexCompression true to enable index compression, + * false to disable + * + * @return the reference to the current option. + */ + public BlockBasedTableConfig setEnableIndexCompression( + final boolean enableIndexCompression) { + this.enableIndexCompression = enableIndexCompression; + return this; + } + + /** + * Determines whether data blocks are aligned on the lesser of page size + * and block size. + * + * @return true if data blocks are aligned on the lesser of page size + * and block size. + */ + public boolean blockAlign() { + return blockAlign; + } + + /** + * Set whether data blocks should be aligned on the lesser of page size + * and block size. + * + * @param blockAlign true to align data blocks on the lesser of page size + * and block size. + * + * @return the reference to the current option. + */ + public BlockBasedTableConfig setBlockAlign(final boolean blockAlign) { + this.blockAlign = blockAlign; + return this; + } + + + /** + * Get the size of the cache in bytes that will be used by RocksDB. + * + * @return block cache size in bytes + */ + @Deprecated + public long blockCacheSize() { + return blockCacheSize; + } + + /** + * Set the size of the cache in bytes that will be used by RocksDB. + * If cacheSize is non-positive, then cache will not be used. + * DEFAULT: 8M + * + * @param blockCacheSize block cache size in bytes * @return the reference to the current config. + * + * @deprecated Use {@link #setBlockCache(Cache)}. */ - public BlockBasedTableConfig setHashIndexAllowCollision( - final boolean hashIndexAllowCollision) { - hashIndexAllowCollision_ = hashIndexAllowCollision; + @Deprecated + public BlockBasedTableConfig setBlockCacheSize(final long blockCacheSize) { + this.blockCacheSize = blockCacheSize; + return this; + } + + /** + * Returns the number of shard bits used in the block cache. + * The resulting number of shards would be 2 ^ (returned value). + * Any negative number means use default settings. + * + * @return the number of shard bits used in the block cache. + */ + @Deprecated + public int cacheNumShardBits() { + return blockCacheNumShardBits; + } + + /** + * Controls the number of shards for the block cache. + * This is applied only if cacheSize is set to non-negative. + * + * @param blockCacheNumShardBits the number of shard bits. The resulting + * number of shards would be 2 ^ numShardBits. Any negative + * number means use default settings." + * @return the reference to the current option. + * + * @deprecated Use {@link #setBlockCache(Cache)}. + */ + @Deprecated + public BlockBasedTableConfig setCacheNumShardBits( + final int blockCacheNumShardBits) { + this.blockCacheNumShardBits = blockCacheNumShardBits; return this; } @@ -283,8 +766,9 @@ public BlockBasedTableConfig setHashIndexAllowCollision( * * @return size of compressed block cache. */ + @Deprecated public long blockCacheCompressedSize() { - return blockCacheCompressedSize_; + return blockCacheCompressedSize; } /** @@ -293,10 +777,13 @@ public long blockCacheCompressedSize() { * * @param blockCacheCompressedSize of compressed block cache. * @return the reference to the current config. + * + * @deprecated Use {@link #setBlockCacheCompressed(Cache)}. */ + @Deprecated public BlockBasedTableConfig setBlockCacheCompressedSize( final long blockCacheCompressedSize) { - blockCacheCompressedSize_ = blockCacheCompressedSize; + this.blockCacheCompressedSize = blockCacheCompressedSize; return this; } @@ -308,8 +795,9 @@ public BlockBasedTableConfig setBlockCacheCompressedSize( * number of shards would be 2 ^ numShardBits. Any negative * number means use default settings. */ + @Deprecated public int blockCacheCompressedNumShardBits() { - return blockCacheCompressedNumShardBits_; + return blockCacheCompressedNumShardBits; } /** @@ -320,133 +808,166 @@ public int blockCacheCompressedNumShardBits() { * number of shards would be 2 ^ numShardBits. Any negative * number means use default settings." * @return the reference to the current option. + * + * @deprecated Use {@link #setBlockCacheCompressed(Cache)}. */ + @Deprecated public BlockBasedTableConfig setBlockCacheCompressedNumShardBits( final int blockCacheCompressedNumShardBits) { - blockCacheCompressedNumShardBits_ = blockCacheCompressedNumShardBits; - return this; - } - - /** - * Sets the checksum type to be used with this table. - * - * @param checksumType {@link org.rocksdb.ChecksumType} value. - * @return the reference to the current option. - */ - public BlockBasedTableConfig setChecksumType( - final ChecksumType checksumType) { - checksumType_ = checksumType; + this.blockCacheCompressedNumShardBits = blockCacheCompressedNumShardBits; return this; } /** + * Influence the behavior when kHashSearch is used. + * if false, stores a precise prefix to block range mapping + * if true, does not store prefix and allows prefix hash collision + * (less memory consumption) * - * @return the currently set checksum type - */ - public ChecksumType checksumType() { - return checksumType_; - } - - /** - * Sets the index type to used with this table. + * @return if hash collisions should be allowed. * - * @param indexType {@link org.rocksdb.IndexType} value - * @return the reference to the current option. + * @deprecated This option is now deprecated. No matter what value it + * is set to, it will behave as + * if {@link #hashIndexAllowCollision()} == true. */ - public BlockBasedTableConfig setIndexType( - final IndexType indexType) { - indexType_ = indexType; - return this; + @Deprecated + public boolean hashIndexAllowCollision() { + return true; } /** + * Influence the behavior when kHashSearch is used. + * if false, stores a precise prefix to block range mapping + * if true, does not store prefix and allows prefix hash collision + * (less memory consumption) * - * @return the currently set index type - */ - public IndexType indexType() { - return indexType_; - } - - /** - *

We currently have three versions:

+ * @param hashIndexAllowCollision points out if hash collisions should be allowed. * - *
    - *
  • 0 - This version is currently written - * out by all RocksDB's versions by default. Can be read by really old - * RocksDB's. Doesn't support changing checksum (default is CRC32).
  • - *
  • 1 - Can be read by RocksDB's versions since 3.0. - * Supports non-default checksum, like xxHash. It is written by RocksDB when - * BlockBasedTableOptions::checksum is something other than kCRC32c. (version - * 0 is silently upconverted)
  • - *
  • 2 - Can be read by RocksDB's versions since 3.10. - * Changes the way we encode compressed blocks with LZ4, BZip2 and Zlib - * compression. If you don't plan to run RocksDB before version 3.10, - * you should probably use this.
  • - *
- *

This option only affects newly written tables. When reading existing - * tables, the information about version is read from the footer.

+ * @return the reference to the current config. * - * @param formatVersion integer representing the version to be used. - * @return the reference to the current option. + * @deprecated This option is now deprecated. No matter what value it + * is set to, it will behave as + * if {@link #hashIndexAllowCollision()} == true. */ - public BlockBasedTableConfig setFormatVersion( - final int formatVersion) { - assert(formatVersion >= 0 && formatVersion <= 2); - formatVersion_ = formatVersion; + @Deprecated + public BlockBasedTableConfig setHashIndexAllowCollision( + final boolean hashIndexAllowCollision) { + // no-op return this; } - /** - * - * @return the currently configured format version. - * See also: {@link #setFormatVersion(int)}. - */ - public int formatVersion() { - return formatVersion_; - } + @Override protected long newTableFactoryHandle() { + final long filterPolicyHandle; + if (filterPolicy != null) { + filterPolicyHandle = filterPolicy.nativeHandle_; + } else { + filterPolicyHandle = 0; + } + final long blockCacheHandle; + if (blockCache != null) { + blockCacheHandle = blockCache.nativeHandle_; + } else { + blockCacheHandle = 0; + } + final long persistentCacheHandle; + if (persistentCache != null) { + persistentCacheHandle = persistentCache.nativeHandle_; + } else { + persistentCacheHandle = 0; + } - @Override protected long newTableFactoryHandle() { - long filterHandle = 0; - if (filter_ != null) { - filterHandle = filter_.nativeHandle_; + final long blockCacheCompressedHandle; + if (blockCacheCompressed != null) { + blockCacheCompressedHandle = blockCacheCompressed.nativeHandle_; + } else { + blockCacheCompressedHandle = 0; } - return newTableFactoryHandle(noBlockCache_, blockCacheSize_, - blockCacheNumShardBits_, blockSize_, blockSizeDeviation_, - blockRestartInterval_, wholeKeyFiltering_, - filterHandle, cacheIndexAndFilterBlocks_, - pinL0FilterAndIndexBlocksInCache_, - hashIndexAllowCollision_, blockCacheCompressedSize_, - blockCacheCompressedNumShardBits_, - checksumType_.getValue(), indexType_.getValue(), - formatVersion_); + return newTableFactoryHandle(cacheIndexAndFilterBlocks, + cacheIndexAndFilterBlocksWithHighPriority, + pinL0FilterAndIndexBlocksInCache, pinTopLevelIndexAndFilter, + indexType.getValue(), dataBlockIndexType.getValue(), + dataBlockHashTableUtilRatio, checksumType.getValue(), noBlockCache, + blockCacheHandle, persistentCacheHandle, blockCacheCompressedHandle, + blockSize, blockSizeDeviation, blockRestartInterval, + indexBlockRestartInterval, metadataBlockSize, partitionFilters, + useDeltaEncoding, filterPolicyHandle, wholeKeyFiltering, + verifyCompression, readAmpBytesPerBit, formatVersion, + enableIndexCompression, blockAlign, + blockCacheSize, blockCacheNumShardBits, + blockCacheCompressedSize, blockCacheCompressedNumShardBits); } private native long newTableFactoryHandle( - boolean noBlockCache, long blockCacheSize, int blockCacheNumShardBits, - long blockSize, int blockSizeDeviation, int blockRestartInterval, - boolean wholeKeyFiltering, long filterPolicyHandle, - boolean cacheIndexAndFilterBlocks, boolean pinL0FilterAndIndexBlocksInCache, - boolean hashIndexAllowCollision, long blockCacheCompressedSize, - int blockCacheCompressedNumShardBits, byte checkSumType, - byte indexType, int formatVersion); - - private boolean cacheIndexAndFilterBlocks_; - private boolean pinL0FilterAndIndexBlocksInCache_; - private IndexType indexType_; - private boolean hashIndexAllowCollision_; - private ChecksumType checksumType_; - private boolean noBlockCache_; - private long blockSize_; - private long blockCacheSize_; - private int blockCacheNumShardBits_; - private long blockCacheCompressedSize_; - private int blockCacheCompressedNumShardBits_; - private int blockSizeDeviation_; - private int blockRestartInterval_; - private Filter filter_; - private boolean wholeKeyFiltering_; - private int formatVersion_; + final boolean cacheIndexAndFilterBlocks, + final boolean cacheIndexAndFilterBlocksWithHighPriority, + final boolean pinL0FilterAndIndexBlocksInCache, + final boolean pinTopLevelIndexAndFilter, + final byte indexTypeValue, + final byte dataBlockIndexTypeValue, + final double dataBlockHashTableUtilRatio, + final byte checksumTypeValue, + final boolean noBlockCache, + final long blockCacheHandle, + final long persistentCacheHandle, + final long blockCacheCompressedHandle, + final long blockSize, + final int blockSizeDeviation, + final int blockRestartInterval, + final int indexBlockRestartInterval, + final long metadataBlockSize, + final boolean partitionFilters, + final boolean useDeltaEncoding, + final long filterPolicyHandle, + final boolean wholeKeyFiltering, + final boolean verifyCompression, + final int readAmpBytesPerBit, + final int formatVersion, + final boolean enableIndexCompression, + final boolean blockAlign, + + @Deprecated final long blockCacheSize, + @Deprecated final int blockCacheNumShardBits, + + @Deprecated final long blockCacheCompressedSize, + @Deprecated final int blockCacheCompressedNumShardBits + ); + + //TODO(AR) flushBlockPolicyFactory + private boolean cacheIndexAndFilterBlocks; + private boolean cacheIndexAndFilterBlocksWithHighPriority; + private boolean pinL0FilterAndIndexBlocksInCache; + private boolean pinTopLevelIndexAndFilter; + private IndexType indexType; + private DataBlockIndexType dataBlockIndexType; + private double dataBlockHashTableUtilRatio; + private ChecksumType checksumType; + private boolean noBlockCache; + private Cache blockCache; + private PersistentCache persistentCache; + private Cache blockCacheCompressed; + private long blockSize; + private int blockSizeDeviation; + private int blockRestartInterval; + private int indexBlockRestartInterval; + private long metadataBlockSize; + private boolean partitionFilters; + private boolean useDeltaEncoding; + private Filter filterPolicy; + private boolean wholeKeyFiltering; + private boolean verifyCompression; + private int readAmpBytesPerBit; + private int formatVersion; + private boolean enableIndexCompression; + private boolean blockAlign; + + // NOTE: ONLY used if blockCache == null + @Deprecated private long blockCacheSize; + @Deprecated private int blockCacheNumShardBits; + + // NOTE: ONLY used if blockCacheCompressed == null + @Deprecated private long blockCacheCompressedSize; + @Deprecated private int blockCacheCompressedNumShardBits; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java index 26bf358835..6c87cc1884 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraCompactionFilter.java @@ -10,9 +10,10 @@ */ public class CassandraCompactionFilter extends AbstractCompactionFilter { - public CassandraCompactionFilter(boolean purgeTtlOnExpiration) { - super(createNewCassandraCompactionFilter0(purgeTtlOnExpiration)); + public CassandraCompactionFilter(boolean purgeTtlOnExpiration, int gcGracePeriodInSeconds) { + super(createNewCassandraCompactionFilter0(purgeTtlOnExpiration, gcGracePeriodInSeconds)); } - private native static long createNewCassandraCompactionFilter0(boolean purgeTtlOnExpiration); + private native static long createNewCassandraCompactionFilter0( + boolean purgeTtlOnExpiration, int gcGracePeriodInSeconds); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java index a09556a2b8..4b0c71ba5a 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CassandraValueMergeOperator.java @@ -10,11 +10,16 @@ * values. */ public class CassandraValueMergeOperator extends MergeOperator { - public CassandraValueMergeOperator() { - super(newSharedCassandraValueMergeOperator()); + public CassandraValueMergeOperator(int gcGracePeriodInSeconds) { + super(newSharedCassandraValueMergeOperator(gcGracePeriodInSeconds, 0)); } - private native static long newSharedCassandraValueMergeOperator(); + public CassandraValueMergeOperator(int gcGracePeriodInSeconds, int operandsLimit) { + super(newSharedCassandraValueMergeOperator(gcGracePeriodInSeconds, operandsLimit)); + } + + private native static long newSharedCassandraValueMergeOperator( + int gcGracePeriodInSeconds, int limit); @Override protected final native void disposeInternal(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java index d932fd9a92..8bb570e5d3 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyDescriptor.java @@ -5,6 +5,8 @@ package org.rocksdb; +import java.util.Arrays; + /** *

Describes a column family with a * name and respective Options.

@@ -32,7 +34,7 @@ public ColumnFamilyDescriptor(final byte[] columnFamilyName) { * @since 3.10.0 */ public ColumnFamilyDescriptor(final byte[] columnFamilyName, - final ColumnFamilyOptions columnFamilyOptions) { + final ColumnFamilyOptions columnFamilyOptions) { columnFamilyName_ = columnFamilyName; columnFamilyOptions_ = columnFamilyOptions; } @@ -43,19 +45,65 @@ public ColumnFamilyDescriptor(final byte[] columnFamilyName, * @return column family name. * @since 3.10.0 */ - public byte[] columnFamilyName() { + public byte[] getName() { return columnFamilyName_; } + /** + * Retrieve name of column family. + * + * @return column family name. + * @since 3.10.0 + * + * @deprecated Use {@link #getName()} instead. + */ + @Deprecated + public byte[] columnFamilyName() { + return getName(); + } + /** * Retrieve assigned options instance. * * @return Options instance assigned to this instance. */ - public ColumnFamilyOptions columnFamilyOptions() { + public ColumnFamilyOptions getOptions() { return columnFamilyOptions_; } + /** + * Retrieve assigned options instance. + * + * @return Options instance assigned to this instance. + * + * @deprecated Use {@link #getOptions()} instead. + */ + @Deprecated + public ColumnFamilyOptions columnFamilyOptions() { + return getOptions(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ColumnFamilyDescriptor that = (ColumnFamilyDescriptor) o; + return Arrays.equals(columnFamilyName_, that.columnFamilyName_) + && columnFamilyOptions_.nativeHandle_ == that.columnFamilyOptions_.nativeHandle_; + } + + @Override + public int hashCode() { + int result = (int) (columnFamilyOptions_.nativeHandle_ ^ (columnFamilyOptions_.nativeHandle_ >>> 32)); + result = 31 * result + Arrays.hashCode(columnFamilyName_); + return result; + } + private final byte[] columnFamilyName_; private final ColumnFamilyOptions columnFamilyOptions_; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java index 7726cc62d7..9cda136b79 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyHandle.java @@ -5,6 +5,9 @@ package org.rocksdb; +import java.util.Arrays; +import java.util.Objects; + /** * ColumnFamilyHandle class to hold handles to underlying rocksdb * ColumnFamily Pointers. @@ -21,6 +24,73 @@ public class ColumnFamilyHandle extends RocksObject { this.rocksDB_ = rocksDB; } + /** + * Gets the name of the Column Family. + * + * @return The name of the Column Family. + * + * @throws RocksDBException if an error occurs whilst retrieving the name. + */ + public byte[] getName() throws RocksDBException { + return getName(nativeHandle_); + } + + /** + * Gets the ID of the Column Family. + * + * @return the ID of the Column Family. + */ + public int getID() { + return getID(nativeHandle_); + } + + /** + * Gets the up-to-date descriptor of the column family + * associated with this handle. Since it fills "*desc" with the up-to-date + * information, this call might internally lock and release DB mutex to + * access the up-to-date CF options. In addition, all the pointer-typed + * options cannot be referenced any longer than the original options exist. + * + * Note that this function is not supported in RocksDBLite. + * + * @return the up-to-date descriptor. + * + * @throws RocksDBException if an error occurs whilst retrieving the + * descriptor. + */ + public ColumnFamilyDescriptor getDescriptor() throws RocksDBException { + assert(isOwningHandle()); + return getDescriptor(nativeHandle_); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ColumnFamilyHandle that = (ColumnFamilyHandle) o; + try { + return rocksDB_.nativeHandle_ == that.rocksDB_.nativeHandle_ && + getID() == that.getID() && + Arrays.equals(getName(), that.getName()); + } catch (RocksDBException e) { + throw new RuntimeException("Cannot compare column family handles", e); + } + } + + @Override + public int hashCode() { + try { + return Objects.hash(getName(), getID(), rocksDB_.nativeHandle_); + } catch (RocksDBException e) { + throw new RuntimeException("Cannot calculate hash code of column family handle", e); + } + } + /** *

Deletes underlying C++ iterator pointer.

* @@ -36,6 +106,9 @@ protected void disposeInternal() { } } + private native byte[] getName(final long handle) throws RocksDBException; + private native int getID(final long handle); + private native ColumnFamilyDescriptor getDescriptor(final long handle) throws RocksDBException; @Override protected final native void disposeInternal(final long handle); private final RocksDB rocksDB_; diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java new file mode 100644 index 0000000000..1919040172 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyMetaData.java @@ -0,0 +1,70 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.Arrays; +import java.util.List; + +/** + * The metadata that describes a column family. + */ +public class ColumnFamilyMetaData { + private final long size; + private final long fileCount; + private final byte[] name; + private final LevelMetaData[] levels; + + /** + * Called from JNI C++ + */ + private ColumnFamilyMetaData( + final long size, + final long fileCount, + final byte[] name, + final LevelMetaData[] levels) { + this.size = size; + this.fileCount = fileCount; + this.name = name; + this.levels = levels; + } + + /** + * The size of this column family in bytes, which is equal to the sum of + * the file size of its {@link #levels()}. + * + * @return the size of this column family + */ + public long size() { + return size; + } + + /** + * The number of files in this column family. + * + * @return the number of files + */ + public long fileCount() { + return fileCount; + } + + /** + * The name of the column family. + * + * @return the name + */ + public byte[] name() { + return name; + } + + /** + * The metadata of all levels in this column family. + * + * @return the levels metadata + */ + public List levels() { + return Arrays.asList(levels); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java index 647b92e16c..e577524637 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptions.java @@ -27,12 +27,54 @@ public class ColumnFamilyOptions extends RocksObject * Construct ColumnFamilyOptions. * * This constructor will create (by allocating a block of memory) - * an {@code rocksdb::DBOptions} in the c++ side. + * an {@code rocksdb::ColumnFamilyOptions} in the c++ side. */ public ColumnFamilyOptions() { super(newColumnFamilyOptions()); } + /** + * Copy constructor for ColumnFamilyOptions. + * + * NOTE: This does a shallow copy, which means comparator, merge_operator, compaction_filter, + * compaction_filter_factory and other pointers will be cloned! + * + * @param other The ColumnFamilyOptions to copy. + */ + public ColumnFamilyOptions(ColumnFamilyOptions other) { + super(copyColumnFamilyOptions(other.nativeHandle_)); + this.memTableConfig_ = other.memTableConfig_; + this.tableFormatConfig_ = other.tableFormatConfig_; + this.comparator_ = other.comparator_; + this.compactionFilter_ = other.compactionFilter_; + this.compactionFilterFactory_ = other.compactionFilterFactory_; + this.compactionOptionsUniversal_ = other.compactionOptionsUniversal_; + this.compactionOptionsFIFO_ = other.compactionOptionsFIFO_; + this.bottommostCompressionOptions_ = other.bottommostCompressionOptions_; + this.compressionOptions_ = other.compressionOptions_; + } + + /** + * Constructor from Options + * + * @param options The options. + */ + public ColumnFamilyOptions(final Options options) { + super(newColumnFamilyOptionsFromOptions(options.nativeHandle_)); + } + + /** + *

Constructor to be used by + * {@link #getColumnFamilyOptionsFromProps(java.util.Properties)}, + * {@link ColumnFamilyDescriptor#columnFamilyOptions()} + * and also called via JNI.

+ * + * @param handle native handle to ColumnFamilyOptions instance. + */ + ColumnFamilyOptions(final long handle) { + super(handle); + } + /** *

Method to get a options instance by using pre-configured * property values. If one or many values are undefined in @@ -130,7 +172,8 @@ public ColumnFamilyOptions setComparator( public ColumnFamilyOptions setComparator( final AbstractComparator> comparator) { assert (isOwningHandle()); - setComparatorHandle(nativeHandle_, comparator.getNativeHandle()); + setComparatorHandle(nativeHandle_, comparator.nativeHandle_, + comparator.getComparatorType().getValue()); comparator_ = comparator; return this; } @@ -153,6 +196,7 @@ public ColumnFamilyOptions setMergeOperator( return this; } + @Override public ColumnFamilyOptions setCompactionFilter( final AbstractCompactionFilter> compactionFilter) { @@ -161,6 +205,26 @@ public ColumnFamilyOptions setCompactionFilter( return this; } + @Override + public AbstractCompactionFilter> compactionFilter() { + assert (isOwningHandle()); + return compactionFilter_; + } + + @Override + public ColumnFamilyOptions setCompactionFilterFactory(final AbstractCompactionFilterFactory> compactionFilterFactory) { + assert (isOwningHandle()); + setCompactionFilterFactoryHandle(nativeHandle_, compactionFilterFactory.nativeHandle_); + compactionFilterFactory_ = compactionFilterFactory; + return this; + } + + @Override + public AbstractCompactionFilterFactory> compactionFilterFactory() { + assert (isOwningHandle()); + return compactionFilterFactory_; + } + @Override public ColumnFamilyOptions setWriteBufferSize(final long writeBufferSize) { assert(isOwningHandle()); @@ -264,6 +328,20 @@ public CompressionType bottommostCompressionType() { bottommostCompressionType(nativeHandle_)); } + @Override + public ColumnFamilyOptions setBottommostCompressionOptions( + final CompressionOptions bottommostCompressionOptions) { + setBottommostCompressionOptions(nativeHandle_, + bottommostCompressionOptions.nativeHandle_); + this.bottommostCompressionOptions_ = bottommostCompressionOptions; + return this; + } + + @Override + public CompressionOptions bottommostCompressionOptions() { + return this.bottommostCompressionOptions_; + } + @Override public ColumnFamilyOptions setCompressionOptions( final CompressionOptions compressionOptions) { @@ -428,7 +506,7 @@ public ColumnFamilyOptions setCompactionStyle( @Override public CompactionStyle compactionStyle() { - return CompactionStyle.values()[compactionStyle(nativeHandle_)]; + return CompactionStyle.fromValue(compactionStyle(nativeHandle_)); } @Override @@ -697,6 +775,17 @@ public boolean reportBgIoStats() { return reportBgIoStats(nativeHandle_); } + @Override + public ColumnFamilyOptions setTtl(final long ttl) { + setTtl(nativeHandle_, ttl); + return this; + } + + @Override + public long ttl() { + return ttl(nativeHandle_); + } + @Override public ColumnFamilyOptions setCompactionOptionsUniversal( final CompactionOptionsUniversal compactionOptionsUniversal) { @@ -735,20 +824,13 @@ public boolean forceConsistencyChecks() { return forceConsistencyChecks(nativeHandle_); } - /** - *

Private constructor to be used by - * {@link #getColumnFamilyOptionsFromProps(java.util.Properties)}

- * - * @param handle native handle to ColumnFamilyOptions instance. - */ - private ColumnFamilyOptions(final long handle) { - super(handle); - } - private static native long getColumnFamilyOptionsFromProps( String optString); private static native long newColumnFamilyOptions(); + private static native long copyColumnFamilyOptions(final long handle); + private static native long newColumnFamilyOptionsFromOptions( + final long optionsHandle); @Override protected final native void disposeInternal(final long handle); private native void optimizeForSmallDb(final long handle); @@ -760,11 +842,13 @@ private native void optimizeUniversalStyleCompaction(long handle, long memtableMemoryBudget); private native void setComparatorHandle(long handle, int builtinComparator); private native void setComparatorHandle(long optHandle, - long comparatorHandle); + long comparatorHandle, byte comparatorType); private native void setMergeOperatorName(long handle, String name); private native void setMergeOperator(long handle, long mergeOperatorHandle); private native void setCompactionFilterHandle(long handle, long compactionFilterHandle); + private native void setCompactionFilterFactoryHandle(long handle, + long compactionFilterFactoryHandle); private native void setWriteBufferSize(long handle, long writeBufferSize) throws IllegalArgumentException; private native long writeBufferSize(long handle); @@ -782,6 +866,8 @@ private native void setCompressionPerLevel(long handle, private native void setBottommostCompressionType(long handle, byte bottommostCompressionType); private native byte bottommostCompressionType(long handle); + private native void setBottommostCompressionOptions(final long handle, + final long bottommostCompressionOptionsHandle); private native void setCompressionOptions(long handle, long compressionOptionsHandle); private native void useFixedLengthPrefixExtractor( @@ -889,6 +975,8 @@ private native void setCompactionPriority(final long handle, private native void setReportBgIoStats(final long handle, final boolean reportBgIoStats); private native boolean reportBgIoStats(final long handle); + private native void setTtl(final long handle, final long ttl); + private native long ttl(final long handle); private native void setCompactionOptionsUniversal(final long handle, final long compactionOptionsUniversalHandle); private native void setCompactionOptionsFIFO(final long handle, @@ -898,12 +986,16 @@ private native void setForceConsistencyChecks(final long handle, private native boolean forceConsistencyChecks(final long handle); // instance variables + // NOTE: If you add new member variables, please update the copy constructor above! private MemTableConfig memTableConfig_; private TableFormatConfig tableFormatConfig_; private AbstractComparator> comparator_; private AbstractCompactionFilter> compactionFilter_; + private AbstractCompactionFilterFactory> + compactionFilterFactory_; private CompactionOptionsUniversal compactionOptionsUniversal_; private CompactionOptionsFIFO compactionOptionsFIFO_; + private CompressionOptions bottommostCompressionOptions_; private CompressionOptions compressionOptions_; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java index 5cb68b4614..f88a21af2b 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java @@ -151,6 +151,60 @@ T setComparator( */ T setMergeOperator(MergeOperator mergeOperator); + /** + * A single CompactionFilter instance to call into during compaction. + * Allows an application to modify/delete a key-value during background + * compaction. + * + * If the client requires a new compaction filter to be used for different + * compaction runs, it can specify call + * {@link #setCompactionFilterFactory(AbstractCompactionFilterFactory)} + * instead. + * + * The client should specify only set one of the two. + * {@link #setCompactionFilter(AbstractCompactionFilter)} takes precedence + * over {@link #setCompactionFilterFactory(AbstractCompactionFilterFactory)} + * if the client specifies both. + * + * If multithreaded compaction is being used, the supplied CompactionFilter + * instance may be used from different threads concurrently and so should be thread-safe. + * + * @param compactionFilter {@link AbstractCompactionFilter} instance. + * @return the instance of the current object. + */ + T setCompactionFilter( + final AbstractCompactionFilter> compactionFilter); + + /** + * Accessor for the CompactionFilter instance in use. + * + * @return Reference to the CompactionFilter, or null if one hasn't been set. + */ + AbstractCompactionFilter> compactionFilter(); + + /** + * This is a factory that provides {@link AbstractCompactionFilter} objects + * which allow an application to modify/delete a key-value during background + * compaction. + * + * A new filter will be created on each compaction run. If multithreaded + * compaction is being used, each created CompactionFilter will only be used + * from a single thread and so does not need to be thread-safe. + * + * @param compactionFilterFactory {@link AbstractCompactionFilterFactory} instance. + * @return the instance of the current object. + */ + T setCompactionFilterFactory( + final AbstractCompactionFilterFactory> + compactionFilterFactory); + + /** + * Accessor for the CompactionFilterFactory instance in use. + * + * @return Reference to the CompactionFilterFactory, or null if one hasn't been set. + */ + AbstractCompactionFilterFactory> compactionFilterFactory(); + /** * This prefix-extractor uses the first n bytes of a key as its prefix. * @@ -345,6 +399,28 @@ T setBottommostCompressionType( */ CompressionType bottommostCompressionType(); + /** + * Set the options for compression algorithms used by + * {@link #bottommostCompressionType()} if it is enabled. + * + * To enable it, please see the definition of + * {@link CompressionOptions}. + * + * @param compressionOptions the bottom most compression options. + * + * @return the reference of the current options. + */ + T setBottommostCompressionOptions( + final CompressionOptions compressionOptions); + + /** + * Get the bottom most compression options. + * + * See {@link #setBottommostCompressionOptions(CompressionOptions)}. + * + * @return the bottom most compression options. + */ + CompressionOptions bottommostCompressionOptions(); /** * Set the different options for compression algorithms diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactRangeOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactRangeOptions.java new file mode 100644 index 0000000000..c07bd96a55 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactRangeOptions.java @@ -0,0 +1,237 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * CompactRangeOptions is used by CompactRange() call. In the documentation of the methods "the compaction" refers to + * any compaction that is using this CompactRangeOptions. + */ +public class CompactRangeOptions extends RocksObject { + + private final static byte VALUE_kSkip = 0; + private final static byte VALUE_kIfHaveCompactionFilter = 1; + private final static byte VALUE_kForce = 2; + + // For level based compaction, we can configure if we want to skip/force bottommost level compaction. + // The order of this neum MUST follow the C++ layer. See BottommostLevelCompaction in db/options.h + public enum BottommostLevelCompaction { + /** + * Skip bottommost level compaction + */ + kSkip((byte)VALUE_kSkip), + /** + * Only compact bottommost level if there is a compaction filter. This is the default option + */ + kIfHaveCompactionFilter(VALUE_kIfHaveCompactionFilter), + /** + * Always compact bottommost level + */ + kForce(VALUE_kForce); + + private final byte value; + + BottommostLevelCompaction(final byte value) { + this.value = value; + } + + /** + *

Returns the byte value of the enumerations value.

+ * + * @return byte representation + */ + public byte getValue() { + return value; + } + + /** + * Returns the BottommostLevelCompaction for the given C++ rocks enum value. + * @param bottommostLevelCompaction The value of the BottommostLevelCompaction + * @return BottommostLevelCompaction instance, or null if none matches + */ + public static BottommostLevelCompaction fromRocksId(final int bottommostLevelCompaction) { + switch (bottommostLevelCompaction) { + case VALUE_kSkip: return kSkip; + case VALUE_kIfHaveCompactionFilter: return kIfHaveCompactionFilter; + case VALUE_kForce: return kForce; + default: return null; + } + } + } + + /** + * Construct CompactRangeOptions. + */ + public CompactRangeOptions() { + super(newCompactRangeOptions()); + } + + /** + * Returns whether the compaction is exclusive or other compactions may run concurrently at the same time. + * + * @return true if exclusive, false if concurrent + */ + public boolean exclusiveManualCompaction() { + return exclusiveManualCompaction(nativeHandle_); + } + + /** + * Sets whether the compaction is exclusive or other compaction are allowed run concurrently at the same time. + * + * @param exclusiveCompaction true if compaction should be exclusive + * @return This CompactRangeOptions + */ + public CompactRangeOptions setExclusiveManualCompaction(final boolean exclusiveCompaction) { + setExclusiveManualCompaction(nativeHandle_, exclusiveCompaction); + return this; + } + + /** + * Returns whether compacted files will be moved to the minimum level capable of holding the data or given level + * (specified non-negative target_level). + * @return true, if compacted files will be moved to the minimum level + */ + public boolean changeLevel() { + return changeLevel(nativeHandle_); + } + + /** + * Whether compacted files will be moved to the minimum level capable of holding the data or given level + * (specified non-negative target_level). + * + * @param changeLevel If true, compacted files will be moved to the minimum level + * @return This CompactRangeOptions + */ + public CompactRangeOptions setChangeLevel(final boolean changeLevel) { + setChangeLevel(nativeHandle_, changeLevel); + return this; + } + + /** + * If change_level is true and target_level have non-negative value, compacted files will be moved to target_level. + * @return The target level for the compacted files + */ + public int targetLevel() { + return targetLevel(nativeHandle_); + } + + + /** + * If change_level is true and target_level have non-negative value, compacted files will be moved to target_level. + * + * @param targetLevel target level for the compacted files + * @return This CompactRangeOptions + */ + public CompactRangeOptions setTargetLevel(final int targetLevel) { + setTargetLevel(nativeHandle_, targetLevel); + return this; + } + + /** + * target_path_id for compaction output. Compaction outputs will be placed in options.db_paths[target_path_id]. + * + * @return target_path_id + */ + public int targetPathId() { + return targetPathId(nativeHandle_); + } + + /** + * Compaction outputs will be placed in options.db_paths[target_path_id]. Behavior is undefined if target_path_id is + * out of range. + * + * @param targetPathId target path id + * @return This CompactRangeOptions + */ + public CompactRangeOptions setTargetPathId(final int targetPathId) { + setTargetPathId(nativeHandle_, targetPathId); + return this; + } + + /** + * Returns the policy for compacting the bottommost level + * @return The BottommostLevelCompaction policy + */ + public BottommostLevelCompaction bottommostLevelCompaction() { + return BottommostLevelCompaction.fromRocksId(bottommostLevelCompaction(nativeHandle_)); + } + + /** + * Sets the policy for compacting the bottommost level + * + * @param bottommostLevelCompaction The policy for compacting the bottommost level + * @return This CompactRangeOptions + */ + public CompactRangeOptions setBottommostLevelCompaction(final BottommostLevelCompaction bottommostLevelCompaction) { + setBottommostLevelCompaction(nativeHandle_, bottommostLevelCompaction.getValue()); + return this; + } + + /** + * If true, compaction will execute immediately even if doing so would cause the DB to + * enter write stall mode. Otherwise, it'll sleep until load is low enough. + * @return true if compaction will execute immediately + */ + public boolean allowWriteStall() { + return allowWriteStall(nativeHandle_); + } + + + /** + * If true, compaction will execute immediately even if doing so would cause the DB to + * enter write stall mode. Otherwise, it'll sleep until load is low enough. + * + * @return This CompactRangeOptions + * @param allowWriteStall true if compaction should execute immediately + */ + public CompactRangeOptions setAllowWriteStall(final boolean allowWriteStall) { + setAllowWriteStall(nativeHandle_, allowWriteStall); + return this; + } + + /** + * If > 0, it will replace the option in the DBOptions for this compaction + * @return number of subcompactions + */ + public int maxSubcompactions() { + return maxSubcompactions(nativeHandle_); + } + + /** + * If > 0, it will replace the option in the DBOptions for this compaction + * + * @param maxSubcompactions number of subcompactions + * @return This CompactRangeOptions + */ + public CompactRangeOptions setMaxSubcompactions(final int maxSubcompactions) { + setMaxSubcompactions(nativeHandle_, maxSubcompactions); + return this; + } + + private native static long newCompactRangeOptions(); + @Override protected final native void disposeInternal(final long handle); + + private native boolean exclusiveManualCompaction(final long handle); + private native void setExclusiveManualCompaction(final long handle, + final boolean exclusive_manual_compaction); + private native boolean changeLevel(final long handle); + private native void setChangeLevel(final long handle, + final boolean changeLevel); + private native int targetLevel(final long handle); + private native void setTargetLevel(final long handle, + final int targetLevel); + private native int targetPathId(final long handle); + private native void setTargetPathId(final long handle, + final int targetPathId); + private native int bottommostLevelCompaction(final long handle); + private native void setBottommostLevelCompaction(final long handle, + final int bottommostLevelCompaction); + private native boolean allowWriteStall(final long handle); + private native void setAllowWriteStall(final long handle, + final boolean allowWriteStall); + private native void setMaxSubcompactions(final long handle, + final int maxSubcompactions); + private native int maxSubcompactions(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionJobInfo.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionJobInfo.java new file mode 100644 index 0000000000..8b59edc91d --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionJobInfo.java @@ -0,0 +1,159 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class CompactionJobInfo extends RocksObject { + + public CompactionJobInfo() { + super(newCompactionJobInfo()); + } + + /** + * Private as called from JNI C++ + */ + private CompactionJobInfo(final long nativeHandle) { + super(nativeHandle); + } + + /** + * Get the name of the column family where the compaction happened. + * + * @return the name of the column family + */ + public byte[] columnFamilyName() { + return columnFamilyName(nativeHandle_); + } + + /** + * Get the status indicating whether the compaction was successful or not. + * + * @return the status + */ + public Status status() { + return status(nativeHandle_); + } + + /** + * Get the id of the thread that completed this compaction job. + * + * @return the id of the thread + */ + public long threadId() { + return threadId(nativeHandle_); + } + + /** + * Get the job id, which is unique in the same thread. + * + * @return the id of the thread + */ + public int jobId() { + return jobId(nativeHandle_); + } + + /** + * Get the smallest input level of the compaction. + * + * @return the input level + */ + public int baseInputLevel() { + return baseInputLevel(nativeHandle_); + } + + /** + * Get the output level of the compaction. + * + * @return the output level + */ + public int outputLevel() { + return outputLevel(nativeHandle_); + } + + /** + * Get the names of the compaction input files. + * + * @return the names of the input files. + */ + public List inputFiles() { + return Arrays.asList(inputFiles(nativeHandle_)); + } + + /** + * Get the names of the compaction output files. + * + * @return the names of the output files. + */ + public List outputFiles() { + return Arrays.asList(outputFiles(nativeHandle_)); + } + + /** + * Get the table properties for the input and output tables. + * + * The map is keyed by values from {@link #inputFiles()} and + * {@link #outputFiles()}. + * + * @return the table properties + */ + public Map tableProperties() { + return tableProperties(nativeHandle_); + } + + /** + * Get the Reason for running the compaction. + * + * @return the reason. + */ + public CompactionReason compactionReason() { + return CompactionReason.fromValue(compactionReason(nativeHandle_)); + } + + // + /** + * Get the compression algorithm used for output files. + * + * @return the compression algorithm + */ + public CompressionType compression() { + return CompressionType.getCompressionType(compression(nativeHandle_)); + } + + /** + * Get detailed information about this compaction. + * + * @return the detailed information, or null if not available. + */ + public /* @Nullable */ CompactionJobStats stats() { + final long statsHandle = stats(nativeHandle_); + if (statsHandle == 0) { + return null; + } + + return new CompactionJobStats(statsHandle); + } + + + private static native long newCompactionJobInfo(); + @Override protected native void disposeInternal(final long handle); + + private static native byte[] columnFamilyName(final long handle); + private static native Status status(final long handle); + private static native long threadId(final long handle); + private static native int jobId(final long handle); + private static native int baseInputLevel(final long handle); + private static native int outputLevel(final long handle); + private static native String[] inputFiles(final long handle); + private static native String[] outputFiles(final long handle); + private static native Map tableProperties( + final long handle); + private static native byte compactionReason(final long handle); + private static native byte compression(final long handle); + private static native long stats(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionJobStats.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionJobStats.java new file mode 100644 index 0000000000..3d53b5565e --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionJobStats.java @@ -0,0 +1,295 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public class CompactionJobStats extends RocksObject { + + public CompactionJobStats() { + super(newCompactionJobStats()); + } + + /** + * Private as called from JNI C++ + */ + CompactionJobStats(final long nativeHandle) { + super(nativeHandle); + } + + /** + * Reset the stats. + */ + public void reset() { + reset(nativeHandle_); + } + + /** + * Aggregate the CompactionJobStats from another instance with this one. + * + * @param compactionJobStats another instance of stats. + */ + public void add(final CompactionJobStats compactionJobStats) { + add(nativeHandle_, compactionJobStats.nativeHandle_); + } + + /** + * Get the elapsed time in micro of this compaction. + * + * @return the elapsed time in micro of this compaction. + */ + public long elapsedMicros() { + return elapsedMicros(nativeHandle_); + } + + /** + * Get the number of compaction input records. + * + * @return the number of compaction input records. + */ + public long numInputRecords() { + return numInputRecords(nativeHandle_); + } + + /** + * Get the number of compaction input files. + * + * @return the number of compaction input files. + */ + public long numInputFiles() { + return numInputFiles(nativeHandle_); + } + + /** + * Get the number of compaction input files at the output level. + * + * @return the number of compaction input files at the output level. + */ + public long numInputFilesAtOutputLevel() { + return numInputFilesAtOutputLevel(nativeHandle_); + } + + /** + * Get the number of compaction output records. + * + * @return the number of compaction output records. + */ + public long numOutputRecords() { + return numOutputRecords(nativeHandle_); + } + + /** + * Get the number of compaction output files. + * + * @return the number of compaction output files. + */ + public long numOutputFiles() { + return numOutputFiles(nativeHandle_); + } + + /** + * Determine if the compaction is a manual compaction. + * + * @return true if the compaction is a manual compaction, false otherwise. + */ + public boolean isManualCompaction() { + return isManualCompaction(nativeHandle_); + } + + /** + * Get the size of the compaction input in bytes. + * + * @return the size of the compaction input in bytes. + */ + public long totalInputBytes() { + return totalInputBytes(nativeHandle_); + } + + /** + * Get the size of the compaction output in bytes. + * + * @return the size of the compaction output in bytes. + */ + public long totalOutputBytes() { + return totalOutputBytes(nativeHandle_); + } + + /** + * Get the number of records being replaced by newer record associated + * with same key. + * + * This could be a new value or a deletion entry for that key so this field + * sums up all updated and deleted keys. + * + * @return the number of records being replaced by newer record associated + * with same key. + */ + public long numRecordsReplaced() { + return numRecordsReplaced(nativeHandle_); + } + + /** + * Get the sum of the uncompressed input keys in bytes. + * + * @return the sum of the uncompressed input keys in bytes. + */ + public long totalInputRawKeyBytes() { + return totalInputRawKeyBytes(nativeHandle_); + } + + /** + * Get the sum of the uncompressed input values in bytes. + * + * @return the sum of the uncompressed input values in bytes. + */ + public long totalInputRawValueBytes() { + return totalInputRawValueBytes(nativeHandle_); + } + + /** + * Get the number of deletion entries before compaction. + * + * Deletion entries can disappear after compaction because they expired. + * + * @return the number of deletion entries before compaction. + */ + public long numInputDeletionRecords() { + return numInputDeletionRecords(nativeHandle_); + } + + /** + * Get the number of deletion records that were found obsolete and discarded + * because it is not possible to delete any more keys with this entry. + * (i.e. all possible deletions resulting from it have been completed) + * + * @return the number of deletion records that were found obsolete and + * discarded. + */ + public long numExpiredDeletionRecords() { + return numExpiredDeletionRecords(nativeHandle_); + } + + /** + * Get the number of corrupt keys (ParseInternalKey returned false when + * applied to the key) encountered and written out. + * + * @return the number of corrupt keys. + */ + public long numCorruptKeys() { + return numCorruptKeys(nativeHandle_); + } + + /** + * Get the Time spent on file's Append() call. + * + * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. + * + * @return the Time spent on file's Append() call. + */ + public long fileWriteNanos() { + return fileWriteNanos(nativeHandle_); + } + + /** + * Get the Time spent on sync file range. + * + * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. + * + * @return the Time spent on sync file range. + */ + public long fileRangeSyncNanos() { + return fileRangeSyncNanos(nativeHandle_); + } + + /** + * Get the Time spent on file fsync. + * + * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. + * + * @return the Time spent on file fsync. + */ + public long fileFsyncNanos() { + return fileFsyncNanos(nativeHandle_); + } + + /** + * Get the Time spent on preparing file write (falocate, etc) + * + * Only populated if {@link ColumnFamilyOptions#reportBgIoStats()} is set. + * + * @return the Time spent on preparing file write (falocate, etc). + */ + public long filePrepareWriteNanos() { + return filePrepareWriteNanos(nativeHandle_); + } + + /** + * Get the smallest output key prefix. + * + * @return the smallest output key prefix. + */ + public byte[] smallestOutputKeyPrefix() { + return smallestOutputKeyPrefix(nativeHandle_); + } + + /** + * Get the largest output key prefix. + * + * @return the smallest output key prefix. + */ + public byte[] largestOutputKeyPrefix() { + return largestOutputKeyPrefix(nativeHandle_); + } + + /** + * Get the number of single-deletes which do not meet a put. + * + * @return number of single-deletes which do not meet a put. + */ + @Experimental("Performance optimization for a very specific workload") + public long numSingleDelFallthru() { + return numSingleDelFallthru(nativeHandle_); + } + + /** + * Get the number of single-deletes which meet something other than a put. + * + * @return the number of single-deletes which meet something other than a put. + */ + @Experimental("Performance optimization for a very specific workload") + public long numSingleDelMismatch() { + return numSingleDelMismatch(nativeHandle_); + } + + private static native long newCompactionJobStats(); + @Override protected native void disposeInternal(final long handle); + + + private static native void reset(final long handle); + private static native void add(final long handle, + final long compactionJobStatsHandle); + private static native long elapsedMicros(final long handle); + private static native long numInputRecords(final long handle); + private static native long numInputFiles(final long handle); + private static native long numInputFilesAtOutputLevel(final long handle); + private static native long numOutputRecords(final long handle); + private static native long numOutputFiles(final long handle); + private static native boolean isManualCompaction(final long handle); + private static native long totalInputBytes(final long handle); + private static native long totalOutputBytes(final long handle); + private static native long numRecordsReplaced(final long handle); + private static native long totalInputRawKeyBytes(final long handle); + private static native long totalInputRawValueBytes(final long handle); + private static native long numInputDeletionRecords(final long handle); + private static native long numExpiredDeletionRecords(final long handle); + private static native long numCorruptKeys(final long handle); + private static native long fileWriteNanos(final long handle); + private static native long fileRangeSyncNanos(final long handle); + private static native long fileFsyncNanos(final long handle); + private static native long filePrepareWriteNanos(final long handle); + private static native byte[] smallestOutputKeyPrefix(final long handle); + private static native byte[] largestOutputKeyPrefix(final long handle); + private static native long numSingleDelFallthru(final long handle); + private static native long numSingleDelMismatch(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionOptions.java new file mode 100644 index 0000000000..2c7e391fbf --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionOptions.java @@ -0,0 +1,121 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.List; + +/** + * CompactionOptions are used in + * {@link RocksDB#compactFiles(CompactionOptions, ColumnFamilyHandle, List, int, int, CompactionJobInfo)} + * calls. + */ +public class CompactionOptions extends RocksObject { + + public CompactionOptions() { + super(newCompactionOptions()); + } + + /** + * Get the compaction output compression type. + * + * See {@link #setCompression(CompressionType)}. + * + * @return the compression type. + */ + public CompressionType compression() { + return CompressionType.getCompressionType( + compression(nativeHandle_)); + } + + /** + * Set the compaction output compression type. + * + * Default: snappy + * + * If set to {@link CompressionType#DISABLE_COMPRESSION_OPTION}, + * RocksDB will choose compression type according to the + * {@link ColumnFamilyOptions#compressionType()}, taking into account + * the output level if {@link ColumnFamilyOptions#compressionPerLevel()} + * is specified. + * + * @param compression the compression type to use for compaction output. + * + * @return the instance of the current Options. + */ + public CompactionOptions setCompression(final CompressionType compression) { + setCompression(nativeHandle_, compression.getValue()); + return this; + } + + /** + * Get the compaction output file size limit. + * + * See {@link #setOutputFileSizeLimit(long)}. + * + * @return the file size limit. + */ + public long outputFileSizeLimit() { + return outputFileSizeLimit(nativeHandle_); + } + + /** + * Compaction will create files of size {@link #outputFileSizeLimit()}. + * + * Default: 2^64-1, which means that compaction will create a single file + * + * @param outputFileSizeLimit the size limit + * + * @return the instance of the current Options. + */ + public CompactionOptions setOutputFileSizeLimit( + final long outputFileSizeLimit) { + setOutputFileSizeLimit(nativeHandle_, outputFileSizeLimit); + return this; + } + + /** + * Get the maximum number of threads that will concurrently perform a + * compaction job. + * + * @return the maximum number of threads. + */ + public int maxSubcompactions() { + return maxSubcompactions(nativeHandle_); + } + + /** + * This value represents the maximum number of threads that will + * concurrently perform a compaction job by breaking it into multiple, + * smaller ones that are run simultaneously. + * + * Default: 0 (i.e. no subcompactions) + * + * If > 0, it will replace the option in + * {@link DBOptions#maxSubcompactions()} for this compaction. + * + * @param maxSubcompactions The maximum number of threads that will + * concurrently perform a compaction job + * + * @return the instance of the current Options. + */ + public CompactionOptions setMaxSubcompactions(final int maxSubcompactions) { + setMaxSubcompactions(nativeHandle_, maxSubcompactions); + return this; + } + + private static native long newCompactionOptions(); + @Override protected final native void disposeInternal(final long handle); + + private static native byte compression(final long handle); + private static native void setCompression(final long handle, + final byte compressionTypeValue); + private static native long outputFileSizeLimit(final long handle); + private static native void setOutputFileSizeLimit(final long handle, + final long outputFileSizeLimit); + private static native int maxSubcompactions(final long handle); + private static native void setMaxSubcompactions(final long handle, + final int maxSubcompactions); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java index f795807804..4c8d6545cb 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionOptionsFIFO.java @@ -42,9 +42,48 @@ public long maxTableFilesSize() { return maxTableFilesSize(nativeHandle_); } - private native void setMaxTableFilesSize(long handle, long maxTableFilesSize); - private native long maxTableFilesSize(long handle); + /** + * If true, try to do compaction to compact smaller files into larger ones. + * Minimum files to compact follows options.level0_file_num_compaction_trigger + * and compaction won't trigger if average compact bytes per del file is + * larger than options.write_buffer_size. This is to protect large files + * from being compacted again. + * + * Default: false + * + * @param allowCompaction true to allow intra-L0 compaction + * + * @return the reference to the current options. + */ + public CompactionOptionsFIFO setAllowCompaction( + final boolean allowCompaction) { + setAllowCompaction(nativeHandle_, allowCompaction); + return this; + } + + + /** + * Check if intra-L0 compaction is enabled. + * When enabled, we try to compact smaller files into larger ones. + * + * See {@link #setAllowCompaction(boolean)}. + * + * Default: false + * + * @return true if intra-L0 compaction is enabled, false otherwise. + */ + public boolean allowCompaction() { + return allowCompaction(nativeHandle_); + } + private native static long newCompactionOptionsFIFO(); @Override protected final native void disposeInternal(final long handle); + + private native void setMaxTableFilesSize(final long handle, + final long maxTableFilesSize); + private native long maxTableFilesSize(final long handle); + private native void setAllowCompaction(final long handle, + final boolean allowCompaction); + private native boolean allowCompaction(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionReason.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionReason.java new file mode 100644 index 0000000000..f18c481222 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionReason.java @@ -0,0 +1,115 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public enum CompactionReason { + kUnknown((byte)0x0), + + /** + * [Level] number of L0 files > level0_file_num_compaction_trigger + */ + kLevelL0FilesNum((byte)0x1), + + /** + * [Level] total size of level > MaxBytesForLevel() + */ + kLevelMaxLevelSize((byte)0x2), + + /** + * [Universal] Compacting for size amplification + */ + kUniversalSizeAmplification((byte)0x3), + + /** + * [Universal] Compacting for size ratio + */ + kUniversalSizeRatio((byte)0x4), + + /** + * [Universal] number of sorted runs > level0_file_num_compaction_trigger + */ + kUniversalSortedRunNum((byte)0x5), + + /** + * [FIFO] total size > max_table_files_size + */ + kFIFOMaxSize((byte)0x6), + + /** + * [FIFO] reduce number of files. + */ + kFIFOReduceNumFiles((byte)0x7), + + /** + * [FIFO] files with creation time < (current_time - interval) + */ + kFIFOTtl((byte)0x8), + + /** + * Manual compaction + */ + kManualCompaction((byte)0x9), + + /** + * DB::SuggestCompactRange() marked files for compaction + */ + kFilesMarkedForCompaction((byte)0x10), + + /** + * [Level] Automatic compaction within bottommost level to cleanup duplicate + * versions of same user key, usually due to a released snapshot. + */ + kBottommostFiles((byte)0x0A), + + /** + * Compaction based on TTL + */ + kTtl((byte)0x0B), + + /** + * According to the comments in flush_job.cc, RocksDB treats flush as + * a level 0 compaction in internal stats. + */ + kFlush((byte)0x0C), + + /** + * Compaction caused by external sst file ingestion + */ + kExternalSstIngestion((byte)0x0D); + + private final byte value; + + CompactionReason(final byte value) { + this.value = value; + } + + /** + * Get the internal representation value. + * + * @return the internal representation value + */ + byte getValue() { + return value; + } + + /** + * Get the CompactionReason from the internal representation value. + * + * @return the compaction reason. + * + * @throws IllegalArgumentException if the value is unknown. + */ + static CompactionReason fromValue(final byte value) { + for (final CompactionReason compactionReason : CompactionReason.values()) { + if(compactionReason.value == value) { + return compactionReason; + } + } + + throw new IllegalArgumentException( + "Illegal value provided for CompactionReason: " + value); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java index 5e13363c44..b24bbf8509 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompactionStyle.java @@ -5,6 +5,8 @@ package org.rocksdb; +import java.util.List; + /** * Enum CompactionStyle * @@ -21,6 +23,9 @@ * compaction strategy. It is suited for keeping event log data with * very low overhead (query log for example). It periodically deletes * the old data, so it's basically a TTL compaction style. + *
  • NONE - Disable background compaction. + * Compaction jobs are submitted + * {@link RocksDB#compactFiles(CompactionOptions, ColumnFamilyHandle, List, int, int, CompactionJobInfo)} ()}.
  • * * * @see */ public enum CompactionStyle { - LEVEL((byte) 0), - UNIVERSAL((byte) 1), - FIFO((byte) 2); + LEVEL((byte) 0x0), + UNIVERSAL((byte) 0x1), + FIFO((byte) 0x2), + NONE((byte) 0x3); - private final byte value_; + private final byte value; - private CompactionStyle(byte value) { - value_ = value; + CompactionStyle(final byte value) { + this.value = value; } /** - * Returns the byte value of the enumerations value + * Get the internal representation value. * - * @return byte representation + * @return the internal representation value. */ + //TODO(AR) should be made package-private public byte getValue() { - return value_; + return value; + } + + /** + * Get the Compaction style from the internal representation value. + * + * @param value the internal representation value. + * + * @return the Compaction style + * + * @throws IllegalArgumentException if the value does not match a + * CompactionStyle + */ + static CompactionStyle fromValue(final byte value) + throws IllegalArgumentException { + for (final CompactionStyle compactionStyle : CompactionStyle.values()) { + if (compactionStyle.value == value) { + return compactionStyle; + } + } + throw new IllegalArgumentException("Unknown value for CompactionStyle: " + + value); } } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Comparator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Comparator.java index 817e00fd27..4d06073f26 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Comparator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Comparator.java @@ -16,16 +16,18 @@ */ public abstract class Comparator extends AbstractComparator { - private final long nativeHandle_; - public Comparator(final ComparatorOptions copt) { - super(); - this.nativeHandle_ = createNewComparator0(copt.nativeHandle_); + super(copt); + } + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewComparator0(nativeParameterHandles[0]); } @Override - protected final long getNativeHandle() { - return nativeHandle_; + final ComparatorType getComparatorType() { + return ComparatorType.JAVA_COMPARATOR; } private native long createNewComparator0(final long comparatorOptionsHandle); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ComparatorType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ComparatorType.java new file mode 100644 index 0000000000..df8b475907 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ComparatorType.java @@ -0,0 +1,49 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +enum ComparatorType { + JAVA_COMPARATOR((byte)0x0), + JAVA_DIRECT_COMPARATOR((byte)0x1), + JAVA_NATIVE_COMPARATOR_WRAPPER((byte)0x2); + + private final byte value; + + ComparatorType(final byte value) { + this.value = value; + } + + /** + *

    Returns the byte value of the enumerations value.

    + * + * @return byte representation + */ + byte getValue() { + return value; + } + + /** + *

    Get the ComparatorType enumeration value by + * passing the byte identifier to this method.

    + * + * @param byteIdentifier of ComparatorType. + * + * @return ComparatorType instance. + * + * @throws IllegalArgumentException if the comparator type for the byteIdentifier + * cannot be found + */ + static ComparatorType getComparatorType(final byte byteIdentifier) { + for (final ComparatorType comparatorType : ComparatorType.values()) { + if (comparatorType.getValue() == byteIdentifier) { + return comparatorType; + } + } + + throw new IllegalArgumentException( + "Illegal value provided for ComparatorType."); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java index 4927770e52..a9072bbb97 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/CompressionOptions.java @@ -71,6 +71,67 @@ public int maxDictBytes() { return maxDictBytes(nativeHandle_); } + /** + * Maximum size of training data passed to zstd's dictionary trainer. Using + * zstd's dictionary trainer can achieve even better compression ratio + * improvements than using {@link #setMaxDictBytes(int)} alone. + * + * The training data will be used to generate a dictionary + * of {@link #maxDictBytes()}. + * + * Default: 0. + * + * @param zstdMaxTrainBytes Maximum bytes to use for training ZStd. + * + * @return the reference to the current options + */ + public CompressionOptions setZStdMaxTrainBytes(final int zstdMaxTrainBytes) { + setZstdMaxTrainBytes(nativeHandle_, zstdMaxTrainBytes); + return this; + } + + /** + * Maximum size of training data passed to zstd's dictionary trainer. + * + * @return Maximum bytes to use for training ZStd + */ + public int zstdMaxTrainBytes() { + return zstdMaxTrainBytes(nativeHandle_); + } + + /** + * When the compression options are set by the user, it will be set to "true". + * For bottommost_compression_opts, to enable it, user must set enabled=true. + * Otherwise, bottommost compression will use compression_opts as default + * compression options. + * + * For compression_opts, if compression_opts.enabled=false, it is still + * used as compression options for compression process. + * + * Default: false. + * + * @param enabled true to use these compression options + * for the bottommost_compression_opts, false otherwise + * + * @return the reference to the current options + */ + public CompressionOptions setEnabled(final boolean enabled) { + setEnabled(nativeHandle_, enabled); + return this; + } + + /** + * Determine whether these compression options + * are used for the bottommost_compression_opts. + * + * @return true if these compression options are used + * for the bottommost_compression_opts, false otherwise + */ + public boolean enabled() { + return enabled(nativeHandle_); + } + + private native static long newCompressionOptions(); @Override protected final native void disposeInternal(final long handle); @@ -82,4 +143,9 @@ public int maxDictBytes() { private native int strategy(final long handle); private native void setMaxDictBytes(final long handle, final int maxDictBytes); private native int maxDictBytes(final long handle); + private native void setZstdMaxTrainBytes(final long handle, + final int zstdMaxTrainBytes); + private native int zstdMaxTrainBytes(final long handle); + private native void setEnabled(final long handle, final boolean enabled); + private native boolean enabled(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java index 14f0c6c7c9..e2c4c02b32 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptions.java @@ -15,8 +15,9 @@ * If {@link #dispose()} function is not called, then it will be GC'd * automatically and native resources will be released as part of the process. */ -public class DBOptions - extends RocksObject implements DBOptionsInterface { +public class DBOptions extends RocksObject + implements DBOptionsInterface, + MutableDBOptionsInterface { static { RocksDB.loadLibrary(); } @@ -32,6 +33,33 @@ public DBOptions() { numShardBits_ = DEFAULT_NUM_SHARD_BITS; } + /** + * Copy constructor for DBOptions. + * + * NOTE: This does a shallow copy, which means env, rate_limiter, sst_file_manager, + * info_log and other pointers will be cloned! + * + * @param other The DBOptions to copy. + */ + public DBOptions(DBOptions other) { + super(copyDBOptions(other.nativeHandle_)); + this.env_ = other.env_; + this.numShardBits_ = other.numShardBits_; + this.rateLimiter_ = other.rateLimiter_; + this.rowCache_ = other.rowCache_; + this.walFilter_ = other.walFilter_; + this.writeBufferManager_ = other.writeBufferManager_; + } + + /** + * Constructor from Options + * + * @param options The options. + */ + public DBOptions(final Options options) { + super(newDBOptionsFromOptions(options.nativeHandle_)); + } + /** *

    Method to get a options instance by using pre-configured * property values. If one or many values are undefined in @@ -114,18 +142,6 @@ public boolean createMissingColumnFamilies() { return createMissingColumnFamilies(nativeHandle_); } - @Override - public DBOptions setEnv(final Env env) { - setEnv(nativeHandle_, env.nativeHandle_); - this.env_ = env; - return this; - } - - @Override - public Env getEnv() { - return env_; - } - @Override public DBOptions setErrorIfExists( final boolean errorIfExists) { @@ -154,6 +170,18 @@ public boolean paranoidChecks() { return paranoidChecks(nativeHandle_); } + @Override + public DBOptions setEnv(final Env env) { + setEnv(nativeHandle_, env.nativeHandle_); + this.env_ = env; + return this; + } + + @Override + public Env getEnv() { + return env_; + } + @Override public DBOptions setRateLimiter(final RateLimiter rateLimiter) { assert(isOwningHandle()); @@ -162,6 +190,13 @@ public DBOptions setRateLimiter(final RateLimiter rateLimiter) { return this; } + @Override + public DBOptions setSstFileManager(final SstFileManager sstFileManager) { + assert(isOwningHandle()); + setSstFileManager(nativeHandle_, sstFileManager.nativeHandle_); + return this; + } + @Override public DBOptions setLogger(final Logger logger) { assert(isOwningHandle()); @@ -262,8 +297,8 @@ public DBOptions setDbPaths(final Collection dbPaths) { assert(isOwningHandle()); final int len = dbPaths.size(); - final String paths[] = new String[len]; - final long targetSizes[] = new long[len]; + final String[] paths = new String[len]; + final long[] targetSizes = new long[len]; int i = 0; for(final DbPath dbPath : dbPaths) { @@ -281,8 +316,8 @@ public List dbPaths() { if(len == 0) { return Collections.emptyList(); } else { - final String paths[] = new String[len]; - final long targetSizes[] = new long[len]; + final String[] paths = new String[len]; + final long[] targetSizes = new long[len]; dbPaths(nativeHandle_, paths, targetSizes); @@ -336,6 +371,19 @@ public long deleteObsoleteFilesPeriodMicros() { return deleteObsoleteFilesPeriodMicros(nativeHandle_); } + @Override + public DBOptions setMaxBackgroundJobs(final int maxBackgroundJobs) { + assert(isOwningHandle()); + setMaxBackgroundJobs(nativeHandle_, maxBackgroundJobs); + return this; + } + + @Override + public int maxBackgroundJobs() { + assert(isOwningHandle()); + return maxBackgroundJobs(nativeHandle_); + } + @Override public void setBaseBackgroundCompactions( final int baseBackgroundCompactions) { @@ -364,9 +412,10 @@ public int maxBackgroundCompactions() { } @Override - public void setMaxSubcompactions(final int maxSubcompactions) { + public DBOptions setMaxSubcompactions(final int maxSubcompactions) { assert(isOwningHandle()); setMaxSubcompactions(nativeHandle_, maxSubcompactions); + return this; } @Override @@ -390,8 +439,7 @@ public int maxBackgroundFlushes() { } @Override - public DBOptions setMaxLogFileSize( - final long maxLogFileSize) { + public DBOptions setMaxLogFileSize(final long maxLogFileSize) { assert(isOwningHandle()); setMaxLogFileSize(nativeHandle_, maxLogFileSize); return this; @@ -515,73 +563,73 @@ public long manifestPreallocationSize() { } @Override - public DBOptions setUseDirectReads( - final boolean useDirectReads) { + public DBOptions setAllowMmapReads( + final boolean allowMmapReads) { assert(isOwningHandle()); - setUseDirectReads(nativeHandle_, useDirectReads); + setAllowMmapReads(nativeHandle_, allowMmapReads); return this; } @Override - public boolean useDirectReads() { + public boolean allowMmapReads() { assert(isOwningHandle()); - return useDirectReads(nativeHandle_); + return allowMmapReads(nativeHandle_); } @Override - public DBOptions setUseDirectIoForFlushAndCompaction( - final boolean useDirectIoForFlushAndCompaction) { + public DBOptions setAllowMmapWrites( + final boolean allowMmapWrites) { assert(isOwningHandle()); - setUseDirectIoForFlushAndCompaction(nativeHandle_, - useDirectIoForFlushAndCompaction); + setAllowMmapWrites(nativeHandle_, allowMmapWrites); return this; } @Override - public boolean useDirectIoForFlushAndCompaction() { + public boolean allowMmapWrites() { assert(isOwningHandle()); - return useDirectIoForFlushAndCompaction(nativeHandle_); + return allowMmapWrites(nativeHandle_); } @Override - public DBOptions setAllowFAllocate(final boolean allowFAllocate) { + public DBOptions setUseDirectReads( + final boolean useDirectReads) { assert(isOwningHandle()); - setAllowFAllocate(nativeHandle_, allowFAllocate); + setUseDirectReads(nativeHandle_, useDirectReads); return this; } @Override - public boolean allowFAllocate() { + public boolean useDirectReads() { assert(isOwningHandle()); - return allowFAllocate(nativeHandle_); + return useDirectReads(nativeHandle_); } @Override - public DBOptions setAllowMmapReads( - final boolean allowMmapReads) { + public DBOptions setUseDirectIoForFlushAndCompaction( + final boolean useDirectIoForFlushAndCompaction) { assert(isOwningHandle()); - setAllowMmapReads(nativeHandle_, allowMmapReads); + setUseDirectIoForFlushAndCompaction(nativeHandle_, + useDirectIoForFlushAndCompaction); return this; } @Override - public boolean allowMmapReads() { + public boolean useDirectIoForFlushAndCompaction() { assert(isOwningHandle()); - return allowMmapReads(nativeHandle_); + return useDirectIoForFlushAndCompaction(nativeHandle_); } @Override - public DBOptions setAllowMmapWrites( - final boolean allowMmapWrites) { + public DBOptions setAllowFAllocate(final boolean allowFAllocate) { assert(isOwningHandle()); - setAllowMmapWrites(nativeHandle_, allowMmapWrites); + setAllowFAllocate(nativeHandle_, allowFAllocate); return this; } @Override - public boolean allowMmapWrites() { + public boolean allowFAllocate() { assert(isOwningHandle()); - return allowMmapWrites(nativeHandle_); + return allowFAllocate(nativeHandle_); } @Override @@ -632,6 +680,20 @@ public DBOptions setDbWriteBufferSize(final long dbWriteBufferSize) { return this; } + @Override + public DBOptions setWriteBufferManager(final WriteBufferManager writeBufferManager) { + assert(isOwningHandle()); + setWriteBufferManager(nativeHandle_, writeBufferManager.nativeHandle_); + this.writeBufferManager_ = writeBufferManager; + return this; + } + + @Override + public WriteBufferManager writeBufferManager() { + assert(isOwningHandle()); + return this.writeBufferManager_; + } + @Override public long dbWriteBufferSize() { assert(isOwningHandle()); @@ -745,6 +807,33 @@ public long walBytesPerSync() { return walBytesPerSync(nativeHandle_); } + //TODO(AR) NOW +// @Override +// public DBOptions setListeners(final List listeners) { +// assert(isOwningHandle()); +// final long[] eventListenerHandlers = new long[listeners.size()]; +// for (int i = 0; i < eventListenerHandlers.length; i++) { +// eventListenerHandlers[i] = listeners.get(i).nativeHandle_; +// } +// setEventListeners(nativeHandle_, eventListenerHandlers); +// return this; +// } +// +// @Override +// public Collection listeners() { +// assert(isOwningHandle()); +// final long[] eventListenerHandlers = listeners(nativeHandle_); +// if (eventListenerHandlers == null || eventListenerHandlers.length == 0) { +// return Collections.emptyList(); +// } +// +// final List eventListeners = new ArrayList<>(); +// for (final long eventListenerHandle : eventListenerHandlers) { +// eventListeners.add(new EventListener(eventListenerHandle)); //TODO(AR) check ownership is set to false! +// } +// return eventListeners; +// } + @Override public DBOptions setEnableThreadTracking(final boolean enableThreadTracking) { assert(isOwningHandle()); @@ -770,6 +859,19 @@ public long delayedWriteRate(){ return delayedWriteRate(nativeHandle_); } + @Override + public DBOptions setEnablePipelinedWrite(final boolean enablePipelinedWrite) { + assert(isOwningHandle()); + setEnablePipelinedWrite(nativeHandle_, enablePipelinedWrite); + return this; + } + + @Override + public boolean enablePipelinedWrite() { + assert(isOwningHandle()); + return enablePipelinedWrite(nativeHandle_); + } + @Override public DBOptions setAllowConcurrentMemtableWrite( final boolean allowConcurrentMemtableWrite) { @@ -871,6 +973,20 @@ public Cache rowCache() { return this.rowCache_; } + @Override + public DBOptions setWalFilter(final AbstractWalFilter walFilter) { + assert(isOwningHandle()); + setWalFilter(nativeHandle_, walFilter.nativeHandle_); + this.walFilter_ = walFilter; + return this; + } + + @Override + public WalFilter walFilter() { + assert(isOwningHandle()); + return this.walFilter_; + } + @Override public DBOptions setFailIfOptionsFileError(final boolean failIfOptionsFileError) { assert(isOwningHandle()); @@ -923,6 +1039,69 @@ public boolean avoidFlushDuringShutdown() { return avoidFlushDuringShutdown(nativeHandle_); } + @Override + public DBOptions setAllowIngestBehind(final boolean allowIngestBehind) { + assert(isOwningHandle()); + setAllowIngestBehind(nativeHandle_, allowIngestBehind); + return this; + } + + @Override + public boolean allowIngestBehind() { + assert(isOwningHandle()); + return allowIngestBehind(nativeHandle_); + } + + @Override + public DBOptions setPreserveDeletes(final boolean preserveDeletes) { + assert(isOwningHandle()); + setPreserveDeletes(nativeHandle_, preserveDeletes); + return this; + } + + @Override + public boolean preserveDeletes() { + assert(isOwningHandle()); + return preserveDeletes(nativeHandle_); + } + + @Override + public DBOptions setTwoWriteQueues(final boolean twoWriteQueues) { + assert(isOwningHandle()); + setTwoWriteQueues(nativeHandle_, twoWriteQueues); + return this; + } + + @Override + public boolean twoWriteQueues() { + assert(isOwningHandle()); + return twoWriteQueues(nativeHandle_); + } + + @Override + public DBOptions setManualWalFlush(final boolean manualWalFlush) { + assert(isOwningHandle()); + setManualWalFlush(nativeHandle_, manualWalFlush); + return this; + } + + @Override + public boolean manualWalFlush() { + assert(isOwningHandle()); + return manualWalFlush(nativeHandle_); + } + + @Override + public DBOptions setAtomicFlush(final boolean atomicFlush) { + setAtomicFlush(nativeHandle_, atomicFlush); + return this; + } + + @Override + public boolean atomicFlush() { + return atomicFlush(nativeHandle_); + } + static final int DEFAULT_NUM_SHARD_BITS = -1; @@ -941,7 +1120,9 @@ private DBOptions(final long nativeHandle) { private static native long getDBOptionsFromProps( String optString); - private native static long newDBOptions(); + private static native long newDBOptions(); + private static native long copyDBOptions(final long handle); + private static native long newDBOptionsFromOptions(final long optionsHandle); @Override protected final native void disposeInternal(final long handle); private native void optimizeForSmallDb(final long handle); @@ -959,6 +1140,8 @@ private native void setParanoidChecks( private native boolean paranoidChecks(long handle); private native void setRateLimiter(long handle, long rateLimiterHandle); + private native void setSstFileManager(final long handle, + final long sstFileManagerHandle); private native void setLogger(long handle, long loggerHandle); private native void setInfoLogLevel(long handle, byte logLevel); @@ -998,6 +1181,8 @@ private native void setMaxBackgroundCompactions( private native void setMaxBackgroundFlushes( long handle, int maxBackgroundFlushes); private native int maxBackgroundFlushes(long handle); + private native void setMaxBackgroundJobs(long handle, int maxBackgroundJobs); + private native int maxBackgroundJobs(long handle); private native void setMaxLogFileSize(long handle, long maxLogFileSize) throws IllegalArgumentException; private native long maxLogFileSize(long handle); @@ -1047,6 +1232,8 @@ private native void setAdviseRandomOnOpen( private native boolean adviseRandomOnOpen(long handle); private native void setDbWriteBufferSize(final long handle, final long dbWriteBufferSize); + private native void setWriteBufferManager(final long dbOptionsHandle, + final long writeBufferManagerHandle); private native long dbWriteBufferSize(final long handle); private native void setAccessHintOnCompactionStart(final long handle, final byte accessHintOnCompactionStart); @@ -1076,6 +1263,9 @@ private native void setEnableThreadTracking(long handle, private native boolean enableThreadTracking(long handle); private native void setDelayedWriteRate(long handle, long delayedWriteRate); private native long delayedWriteRate(long handle); + private native void setEnablePipelinedWrite(final long handle, + final boolean enablePipelinedWrite); + private native boolean enablePipelinedWrite(final long handle); private native void setAllowConcurrentMemtableWrite(long handle, boolean allowConcurrentMemtableWrite); private native boolean allowConcurrentMemtableWrite(long handle); @@ -1098,7 +1288,9 @@ private native void setAllow2pc(final long handle, final boolean allow2pc); private native boolean allow2pc(final long handle); private native void setRowCache(final long handle, - final long row_cache_handle); + final long rowCacheHandle); + private native void setWalFilter(final long handle, + final long walFilterHandle); private native void setFailIfOptionsFileError(final long handle, final boolean failIfOptionsFileError); private native boolean failIfOptionsFileError(final long handle); @@ -1111,10 +1303,28 @@ private native void setAvoidFlushDuringRecovery(final long handle, private native void setAvoidFlushDuringShutdown(final long handle, final boolean avoidFlushDuringShutdown); private native boolean avoidFlushDuringShutdown(final long handle); + private native void setAllowIngestBehind(final long handle, + final boolean allowIngestBehind); + private native boolean allowIngestBehind(final long handle); + private native void setPreserveDeletes(final long handle, + final boolean preserveDeletes); + private native boolean preserveDeletes(final long handle); + private native void setTwoWriteQueues(final long handle, + final boolean twoWriteQueues); + private native boolean twoWriteQueues(final long handle); + private native void setManualWalFlush(final long handle, + final boolean manualWalFlush); + private native boolean manualWalFlush(final long handle); + private native void setAtomicFlush(final long handle, + final boolean atomicFlush); + private native boolean atomicFlush(final long handle); // instance variables + // NOTE: If you add new member variables, please update the copy constructor above! private Env env_; private int numShardBits_; private RateLimiter rateLimiter_; private Cache rowCache_; + private WalFilter walFilter_; + private WriteBufferManager writeBufferManager_; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java index 50ca083d37..af9aa179bf 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DBOptionsInterface.java @@ -158,6 +158,26 @@ public interface DBOptionsInterface { */ T setRateLimiter(RateLimiter rateLimiter); + /** + * Use to track SST files and control their file deletion rate. + * + * Features: + * - Throttle the deletion rate of the SST files. + * - Keep track the total size of all SST files. + * - Set a maximum allowed space limit for SST files that when reached + * the DB wont do any further flushes or compactions and will set the + * background error. + * - Can be shared between multiple dbs. + * + * Limitations: + * - Only track and throttle deletes of SST files in + * first db_path (db_name if db_paths is empty). + * + * @param sstFileManager The SST File Manager for the db. + * @return the instance of the current object. + */ + T setSstFileManager(SstFileManager sstFileManager); + /** *

    Any internal progress/error information generated by * the db will be written to the Logger if it is non-nullptr, @@ -186,35 +206,9 @@ public interface DBOptionsInterface { InfoLogLevel infoLogLevel(); /** - * Number of open files that can be used by the DB. You may need to - * increase this if your database has a large working set. Value -1 means - * files opened are always kept open. You can estimate number of files based - * on {@code target_file_size_base} and {@code target_file_size_multiplier} - * for level-based compaction. For universal-style compaction, you can usually - * set it to -1. - * Default: 5000 - * - * @param maxOpenFiles the maximum number of open files. - * @return the instance of the current object. - */ - T setMaxOpenFiles(int maxOpenFiles); - - /** - * Number of open files that can be used by the DB. You may need to - * increase this if your database has a large working set. Value -1 means - * files opened are always kept open. You can estimate number of files based - * on {@code target_file_size_base} and {@code target_file_size_multiplier} - * for level-based compaction. For universal-style compaction, you can usually - * set it to -1. - * - * @return the maximum number of open files. - */ - int maxOpenFiles(); - - /** - * If {@link #maxOpenFiles()} is -1, DB will open all files on DB::Open(). You - * can use this option to increase the number of threads used to open the - * files. + * If {@link MutableDBOptionsInterface#maxOpenFiles()} is -1, DB will open + * all files on DB::Open(). You can use this option to increase the number + * of threads used to open the files. * * Default: 16 * @@ -226,9 +220,9 @@ public interface DBOptionsInterface { T setMaxFileOpeningThreads(int maxFileOpeningThreads); /** - * If {@link #maxOpenFiles()} is -1, DB will open all files on DB::Open(). You - * can use this option to increase the number of threads used to open the - * files. + * If {@link MutableDBOptionsInterface#maxOpenFiles()} is -1, DB will open all + * files on DB::Open(). You can use this option to increase the number of + * threads used to open the files. * * Default: 16 * @@ -236,40 +230,15 @@ public interface DBOptionsInterface { */ int maxFileOpeningThreads(); - /** - *

    Once write-ahead logs exceed this size, we will start forcing the - * flush of column families whose memtables are backed by the oldest live - * WAL file (i.e. the ones that are causing all the space amplification). - *

    - *

    If set to 0 (default), we will dynamically choose the WAL size limit to - * be [sum of all write_buffer_size * max_write_buffer_number] * 2

    - *

    Default: 0

    - * - * @param maxTotalWalSize max total wal size. - * @return the instance of the current object. - */ - T setMaxTotalWalSize(long maxTotalWalSize); - - /** - *

    Returns the max total wal size. Once write-ahead logs exceed this size, - * we will start forcing the flush of column families whose memtables are - * backed by the oldest live WAL file (i.e. the ones that are causing all - * the space amplification).

    - * - *

    If set to 0 (default), we will dynamically choose the WAL size limit - * to be [sum of all write_buffer_size * max_write_buffer_number] * 2 - *

    - * - * @return max total wal size - */ - long maxTotalWalSize(); - /** *

    Sets the statistics object which collects metrics about database operations. * Statistics objects should not be shared between DB instances as * it does not use any locks to prevent concurrent updates.

    * + * @param statistics The statistics to set + * * @return the instance of the current object. + * * @see RocksDB#open(org.rocksdb.Options, String) */ T setStatistics(final Statistics statistics); @@ -277,7 +246,9 @@ public interface DBOptionsInterface { /** *

    Returns statistics object.

    * - * @return the instance of the statistics object or null if there is no statistics object. + * @return the instance of the statistics object or null if there is no + * statistics object. + * * @see #setStatistics(Statistics) */ Statistics statistics(); @@ -439,55 +410,6 @@ public interface DBOptionsInterface { */ long deleteObsoleteFilesPeriodMicros(); - /** - * Suggested number of concurrent background compaction jobs, submitted to - * the default LOW priority thread pool. - * Default: 1 - * - * @param baseBackgroundCompactions Suggested number of background compaction - * jobs - */ - void setBaseBackgroundCompactions(int baseBackgroundCompactions); - - /** - * Suggested number of concurrent background compaction jobs, submitted to - * the default LOW priority thread pool. - * Default: 1 - * - * @return Suggested number of background compaction jobs - */ - int baseBackgroundCompactions(); - - /** - * Specifies the maximum number of concurrent background compaction jobs, - * submitted to the default LOW priority thread pool. - * If you're increasing this, also consider increasing number of threads in - * LOW priority thread pool. For more information, see - * Default: 1 - * - * @param maxBackgroundCompactions the maximum number of background - * compaction jobs. - * @return the instance of the current object. - * - * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, int) - * @see #maxBackgroundFlushes() - */ - T setMaxBackgroundCompactions(int maxBackgroundCompactions); - - /** - * Returns the maximum number of concurrent background compaction jobs, - * submitted to the default LOW priority thread pool. - * When increasing this number, we may also want to consider increasing - * number of threads in LOW priority thread pool. - * Default: 1 - * - * @return the maximum number of concurrent background compaction jobs. - * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, int) - */ - int maxBackgroundCompactions(); - /** * This value represents the maximum number of threads that will * concurrently perform a compaction job by breaking it into multiple, @@ -496,8 +418,10 @@ public interface DBOptionsInterface { * * @param maxSubcompactions The maximum number of threads that will * concurrently perform a compaction job + * + * @return the instance of the current object. */ - void setMaxSubcompactions(int maxSubcompactions); + T setMaxSubcompactions(int maxSubcompactions); /** * This value represents the maximum number of threads that will @@ -520,9 +444,12 @@ public interface DBOptionsInterface { * @return the instance of the current object. * * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, int) - * @see #maxBackgroundCompactions() + * @see RocksEnv#setBackgroundThreads(int, Priority) + * @see MutableDBOptionsInterface#maxBackgroundCompactions() + * + * @deprecated Use {@link MutableDBOptionsInterface#setMaxBackgroundJobs(int)} */ + @Deprecated T setMaxBackgroundFlushes(int maxBackgroundFlushes); /** @@ -533,8 +460,9 @@ public interface DBOptionsInterface { * * @return the maximum number of concurrent background flush jobs. * @see RocksEnv#setBackgroundThreads(int) - * @see RocksEnv#setBackgroundThreads(int, int) + * @see RocksEnv#setBackgroundThreads(int, Priority) */ + @Deprecated int maxBackgroundFlushes(); /** @@ -886,23 +814,6 @@ public interface DBOptionsInterface { */ boolean isFdCloseOnExec(); - /** - * if not zero, dump rocksdb.stats to LOG every stats_dump_period_sec - * Default: 600 (10 minutes) - * - * @param statsDumpPeriodSec time interval in seconds. - * @return the instance of the current object. - */ - T setStatsDumpPeriodSec(int statsDumpPeriodSec); - - /** - * If not zero, dump rocksdb.stats to LOG every stats_dump_period_sec - * Default: 600 (10 minutes) - * - * @return time interval in seconds. - */ - int statsDumpPeriodSec(); - /** * If set true, will hint the underlying file system that the file * access pattern is random, when a sst file is opened. @@ -940,6 +851,28 @@ public interface DBOptionsInterface { */ T setDbWriteBufferSize(long dbWriteBufferSize); + /** + * Use passed {@link WriteBufferManager} to control memory usage across + * multiple column families and/or DB instances. + * + * Check
    + * https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager + * for more details on when to use it + * + * @param writeBufferManager The WriteBufferManager to use + * @return the reference of the current options. + */ + T setWriteBufferManager(final WriteBufferManager writeBufferManager); + + /** + * Reference to {@link WriteBufferManager} used by it.
    + * + * Default: null (Disabled) + * + * @return a reference to WriteBufferManager + */ + WriteBufferManager writeBufferManager(); + /** * Amount of data to build up in memtables across all column * families before writing to disk. @@ -1015,36 +948,6 @@ T setNewTableReaderForCompactionInputs( */ boolean newTableReaderForCompactionInputs(); - /** - * If non-zero, we perform bigger reads when doing compaction. If you're - * running RocksDB on spinning disks, you should set this to at least 2MB. - * - * That way RocksDB's compaction is doing sequential instead of random reads. - * When non-zero, we also force {@link #newTableReaderForCompactionInputs()} - * to true. - * - * Default: 0 - * - * @param compactionReadaheadSize The compaction read-ahead size - * - * @return the reference to the current options. - */ - T setCompactionReadaheadSize(final long compactionReadaheadSize); - - /** - * If non-zero, we perform bigger reads when doing compaction. If you're - * running RocksDB on spinning disks, you should set this to at least 2MB. - * - * That way RocksDB's compaction is doing sequential instead of random reads. - * When non-zero, we also force {@link #newTableReaderForCompactionInputs()} - * to true. - * - * Default: 0 - * - * @return The compaction read-ahead size - */ - long compactionReadaheadSize(); - /** * This is a maximum buffer size that is used by WinMmapReadableFile in * unbuffered disk I/O mode. We need to maintain an aligned buffer for @@ -1052,7 +955,8 @@ T setNewTableReaderForCompactionInputs( * for bigger requests allocate one shot buffers. In unbuffered mode we * always bypass read-ahead buffer at ReadaheadRandomAccessFile * When read-ahead is required we then make use of - * {@link #compactionReadaheadSize()} value and always try to read ahead. + * {@link MutableDBOptionsInterface#compactionReadaheadSize()} value and + * always try to read ahead. * With read-ahead we always pre-allocate buffer to the size instead of * growing it up to a limit. * @@ -1077,9 +981,9 @@ T setNewTableReaderForCompactionInputs( * for bigger requests allocate one shot buffers. In unbuffered mode we * always bypass read-ahead buffer at ReadaheadRandomAccessFile * When read-ahead is required we then make use of - * {@link #compactionReadaheadSize()} value and always try to read ahead. - * With read-ahead we always pre-allocate buffer to the size instead of - * growing it up to a limit. + * {@link MutableDBOptionsInterface#compactionReadaheadSize()} value and + * always try to read ahead. With read-ahead we always pre-allocate buffer + * to the size instead of growing it up to a limit. * * This option is currently honored only on Windows * @@ -1092,30 +996,6 @@ T setNewTableReaderForCompactionInputs( */ long randomAccessMaxBufferSize(); - /** - * This is the maximum buffer size that is used by WritableFileWriter. - * On Windows, we need to maintain an aligned buffer for writes. - * We allow the buffer to grow until it's size hits the limit. - * - * Default: 1024 * 1024 (1 MB) - * - * @param writableFileMaxBufferSize the maximum buffer size - * - * @return the reference to the current options. - */ - T setWritableFileMaxBufferSize(long writableFileMaxBufferSize); - - /** - * This is the maximum buffer size that is used by WritableFileWriter. - * On Windows, we need to maintain an aligned buffer for writes. - * We allow the buffer to grow until it's size hits the limit. - * - * Default: 1024 * 1024 (1 MB) - * - * @return the maximum buffer size - */ - long writableFileMaxBufferSize(); - /** * Use adaptive mutex, which spins in the user space before resorting * to kernel. This could reduce context switch when the mutex is not @@ -1139,45 +1019,24 @@ T setNewTableReaderForCompactionInputs( */ boolean useAdaptiveMutex(); - /** - * Allows OS to incrementally sync files to disk while they are being - * written, asynchronously, in the background. - * Issue one request for every bytes_per_sync written. 0 turns it off. - * Default: 0 - * - * @param bytesPerSync size in bytes - * @return the instance of the current object. - */ - T setBytesPerSync(long bytesPerSync); - - /** - * Allows OS to incrementally sync files to disk while they are being - * written, asynchronously, in the background. - * Issue one request for every bytes_per_sync written. 0 turns it off. - * Default: 0 - * - * @return size in bytes - */ - long bytesPerSync(); - - /** - * Same as {@link #setBytesPerSync(long)} , but applies to WAL files - * - * Default: 0, turned off - * - * @param walBytesPerSync size in bytes - * @return the instance of the current object. - */ - T setWalBytesPerSync(long walBytesPerSync); - - /** - * Same as {@link #bytesPerSync()} , but applies to WAL files - * - * Default: 0, turned off - * - * @return size in bytes - */ - long walBytesPerSync(); + //TODO(AR) NOW +// /** +// * Sets the {@link EventListener}s whose callback functions +// * will be called when specific RocksDB event happens. +// * +// * @param listeners the listeners who should be notified on various events. +// * +// * @return the instance of the current object. +// */ +// T setListeners(final List listeners); +// +// /** +// * Gets the {@link EventListener}s whose callback functions +// * will be called when specific RocksDB event happens. +// * +// * @return a collection of Event listeners. +// */ +// Collection listeners(); /** * If true, then the status of the threads involved in this DB will @@ -1202,40 +1061,33 @@ T setNewTableReaderForCompactionInputs( boolean enableThreadTracking(); /** - * The limited write rate to DB if - * {@link ColumnFamilyOptions#softPendingCompactionBytesLimit()} or - * {@link ColumnFamilyOptions#level0SlowdownWritesTrigger()} is triggered, - * or we are writing to the last mem table allowed and we allow more than 3 - * mem tables. It is calculated using size of user write requests before - * compression. RocksDB may decide to slow down more if the compaction still - * gets behind further. + * By default, a single write thread queue is maintained. The thread gets + * to the head of the queue becomes write batch group leader and responsible + * for writing to WAL and memtable for the batch group. * - * Unit: bytes per second. + * If {@link #enablePipelinedWrite()} is true, separate write thread queue is + * maintained for WAL write and memtable write. A write thread first enter WAL + * writer queue and then memtable writer queue. Pending thread on the WAL + * writer queue thus only have to wait for previous writers to finish their + * WAL writing but not the memtable writing. Enabling the feature may improve + * write throughput and reduce latency of the prepare phase of two-phase + * commit. * - * Default: 16MB/s + * Default: false * - * @param delayedWriteRate the rate in bytes per second + * @param enablePipelinedWrite true to enabled pipelined writes * * @return the reference to the current options. */ - T setDelayedWriteRate(long delayedWriteRate); + T setEnablePipelinedWrite(final boolean enablePipelinedWrite); /** - * The limited write rate to DB if - * {@link ColumnFamilyOptions#softPendingCompactionBytesLimit()} or - * {@link ColumnFamilyOptions#level0SlowdownWritesTrigger()} is triggered, - * or we are writing to the last mem table allowed and we allow more than 3 - * mem tables. It is calculated using size of user write requests before - * compression. RocksDB may decide to slow down more if the compaction still - * gets behind further. - * - * Unit: bytes per second. - * - * Default: 16MB/s + * Returns true if pipelined writes are enabled. + * See {@link #setEnablePipelinedWrite(boolean)}. * - * @return the rate in bytes per second + * @return true if pipelined writes are enabled, false otherwise. */ - long delayedWriteRate(); + boolean enablePipelinedWrite(); /** * If true, allow multi-writers to update mem tables in parallel. @@ -1437,6 +1289,27 @@ T setEnableWriteThreadAdaptiveYield( */ Cache rowCache(); + /** + * A filter object supplied to be invoked while processing write-ahead-logs + * (WALs) during recovery. The filter provides a way to inspect log + * records, ignoring a particular record or skipping replay. + * The filter is invoked at startup and is invoked from a single-thread + * currently. + * + * @param walFilter the filter for processing WALs during recovery. + * + * @return the reference to the current options. + */ + T setWalFilter(final AbstractWalFilter walFilter); + + /** + * Get's the filter for processing WALs during recovery. + * See {@link #setWalFilter(AbstractWalFilter)}. + * + * @return the filter used for processing WALs during recovery. + */ + WalFilter walFilter(); + /** * If true, then DB::Open / CreateColumnFamily / DropColumnFamily * / SetOptions will fail if options file is not detected or properly @@ -1515,35 +1388,126 @@ T setEnableWriteThreadAdaptiveYield( boolean avoidFlushDuringRecovery(); /** - * By default RocksDB will flush all memtables on DB close if there are - * unpersisted data (i.e. with WAL disabled) The flush can be skip to speedup - * DB close. Unpersisted data WILL BE LOST. + * Set this option to true during creation of database if you want + * to be able to ingest behind (call IngestExternalFile() skipping keys + * that already exist, rather than overwriting matching keys). + * Setting this option to true will affect 2 things: + * 1) Disable some internal optimizations around SST file compression + * 2) Reserve bottom-most level for ingested files only. + * 3) Note that num_levels should be >= 3 if this option is turned on. * * DEFAULT: false * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} - * API. + * @param allowIngestBehind true to allow ingest behind, false to disallow. + * + * @return the reference to the current options. + */ + T setAllowIngestBehind(final boolean allowIngestBehind); + + /** + * Returns true if ingest behind is allowed. + * See {@link #setAllowIngestBehind(boolean)}. + * + * @return true if ingest behind is allowed, false otherwise. + */ + boolean allowIngestBehind(); + + /** + * Needed to support differential snapshots. + * If set to true then DB will only process deletes with sequence number + * less than what was set by SetPreserveDeletesSequenceNumber(uint64_t ts). + * Clients are responsible to periodically call this method to advance + * the cutoff time. If this method is never called and preserve_deletes + * is set to true NO deletes will ever be processed. + * At the moment this only keeps normal deletes, SingleDeletes will + * not be preserved. + * + * DEFAULT: false + * + * @param preserveDeletes true to preserve deletes. + * + * @return the reference to the current options. + */ + T setPreserveDeletes(final boolean preserveDeletes); + + /** + * Returns true if deletes are preserved. + * See {@link #setPreserveDeletes(boolean)}. + * + * @return true if deletes are preserved, false otherwise. + */ + boolean preserveDeletes(); + + /** + * If enabled it uses two queues for writes, one for the ones with + * disable_memtable and one for the ones that also write to memtable. This + * allows the memtable writes not to lag behind other writes. It can be used + * to optimize MySQL 2PC in which only the commits, which are serial, write to + * memtable. + * + * DEFAULT: false * - * @param avoidFlushDuringShutdown true if we should avoid flush during - * shutdown + * @param twoWriteQueues true to enable two write queues, false otherwise. * * @return the reference to the current options. */ - T setAvoidFlushDuringShutdown(boolean avoidFlushDuringShutdown); + T setTwoWriteQueues(final boolean twoWriteQueues); + + /** + * Returns true if two write queues are enabled. + * + * @return true if two write queues are enabled, false otherwise. + */ + boolean twoWriteQueues(); /** - * By default RocksDB will flush all memtables on DB close if there are - * unpersisted data (i.e. with WAL disabled) The flush can be skip to speedup - * DB close. Unpersisted data WILL BE LOST. + * If true WAL is not flushed automatically after each write. Instead it + * relies on manual invocation of FlushWAL to write the WAL buffer to its + * file. * * DEFAULT: false * - * Dynamically changeable through - * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} - * API. + * @param manualWalFlush true to set disable automatic WAL flushing, + * false otherwise. + * + * @return the reference to the current options. + */ + T setManualWalFlush(final boolean manualWalFlush); + + /** + * Returns true if automatic WAL flushing is disabled. + * See {@link #setManualWalFlush(boolean)}. + * + * @return true if automatic WAL flushing is disabled, false otherwise. + */ + boolean manualWalFlush(); + + /** + * If true, RocksDB supports flushing multiple column families and committing + * their results atomically to MANIFEST. Note that it is not + * necessary to set atomic_flush to true if WAL is always enabled since WAL + * allows the database to be restored to the last persistent state in WAL. + * This option is useful when there are column families with writes NOT + * protected by WAL. + * For manual flush, application has to specify which column families to + * flush atomically in {@link RocksDB#flush(FlushOptions, List)}. + * For auto-triggered flush, RocksDB atomically flushes ALL column families. + * + * Currently, any WAL-enabled writes after atomic flush may be replayed + * independently if the process crashes later and tries to recover. + * + * @param atomicFlush true to enable atomic flush of multiple column families. + * + * @return the reference to the current options. + */ + T setAtomicFlush(final boolean atomicFlush); + + /** + * Determine if atomic flush of multiple column families is enabled. + * + * See {@link #setAtomicFlush(boolean)}. * - * @return true if we should avoid flush during shutdown + * @return true if atomic flush is enabled. */ - boolean avoidFlushDuringShutdown(); + boolean atomicFlush(); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DataBlockIndexType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DataBlockIndexType.java new file mode 100644 index 0000000000..513e5b4294 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DataBlockIndexType.java @@ -0,0 +1,32 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + + +/** + * DataBlockIndexType used in conjunction with BlockBasedTable. + */ +public enum DataBlockIndexType { + /** + * traditional block type + */ + kDataBlockBinarySearch((byte)0x0), + + /** + * additional hash index + */ + kDataBlockBinaryAndHash((byte)0x1); + + private final byte value; + + DataBlockIndexType(final byte value) { + this.value = value; + } + + byte getValue() { + return value; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DirectComparator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DirectComparator.java index 4c37dfd56b..e33004f5d8 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DirectComparator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/DirectComparator.java @@ -16,16 +16,18 @@ */ public abstract class DirectComparator extends AbstractComparator { - private final long nativeHandle_; - public DirectComparator(final ComparatorOptions copt) { - super(); - this.nativeHandle_ = createNewDirectComparator0(copt.nativeHandle_); + super(copt); + } + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewDirectComparator0(nativeParameterHandles[0]); } @Override - protected final long getNativeHandle() { - return nativeHandle_; + final ComparatorType getComparatorType() { + return ComparatorType.JAVA_DIRECT_COMPARATOR; } private native long createNewDirectComparator0( diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Env.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Env.java index a46f06178d..d7658f2394 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Env.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Env.java @@ -5,12 +5,23 @@ package org.rocksdb; +import java.util.Arrays; +import java.util.List; + /** * Base class for all Env implementations in RocksDB. */ public abstract class Env extends RocksObject { - public static final int FLUSH_POOL = 0; - public static final int COMPACTION_POOL = 1; + + private static final Env DEFAULT_ENV = new RocksEnv(getDefaultEnvInternal()); + static { + /** + * The Ownership of the Default Env belongs to C++ + * and so we disown the native handle here so that + * we cannot accidentally free it from Java. + */ + DEFAULT_ENV.disOwnNativeHandle(); + } /** *

    Returns the default environment suitable for the current operating @@ -18,13 +29,13 @@ public abstract class Env extends RocksObject { * *

    The result of {@code getDefault()} is a singleton whose ownership * belongs to rocksdb c++. As a result, the returned RocksEnv will not - * have the ownership of its c++ resource, and calling its dispose() + * have the ownership of its c++ resource, and calling its dispose()/close() * will be no-op.

    * * @return the default {@link org.rocksdb.RocksEnv} instance. */ public static Env getDefault() { - return default_env_; + return DEFAULT_ENV; } /** @@ -32,27 +43,36 @@ public static Env getDefault() { * for this environment.

    *

    Default number: 1

    * - * @param num the number of threads + * @param number the number of threads * * @return current {@link RocksEnv} instance. */ - public Env setBackgroundThreads(final int num) { - return setBackgroundThreads(num, FLUSH_POOL); + public Env setBackgroundThreads(final int number) { + return setBackgroundThreads(number, Priority.LOW); + } + + /** + *

    Gets the number of background worker threads of the pool + * for this environment.

    + * + * @return the number of threads. + */ + public int getBackgroundThreads(final Priority priority) { + return getBackgroundThreads(nativeHandle_, priority.getValue()); } /** *

    Sets the number of background worker threads of the specified thread * pool for this environment.

    * - * @param num the number of threads - * @param poolID the id to specified a thread pool. Should be either - * FLUSH_POOL or COMPACTION_POOL. + * @param number the number of threads + * @param priority the priority id of a specified thread pool. * *

    Default number: 1

    * @return current {@link RocksEnv} instance. */ - public Env setBackgroundThreads(final int num, final int poolID) { - setBackgroundThreads(nativeHandle_, num, poolID); + public Env setBackgroundThreads(final int number, final Priority priority) { + setBackgroundThreads(nativeHandle_, number, priority.getValue()); return this; } @@ -60,33 +80,75 @@ public Env setBackgroundThreads(final int num, final int poolID) { *

    Returns the length of the queue associated with the specified * thread pool.

    * - * @param poolID the id to specified a thread pool. Should be either - * FLUSH_POOL or COMPACTION_POOL. + * @param priority the priority id of a specified thread pool. * * @return the thread pool queue length. */ - public int getThreadPoolQueueLen(final int poolID) { - return getThreadPoolQueueLen(nativeHandle_, poolID); + public int getThreadPoolQueueLen(final Priority priority) { + return getThreadPoolQueueLen(nativeHandle_, priority.getValue()); } + /** + * Enlarge number of background worker threads of a specific thread pool + * for this environment if it is smaller than specified. 'LOW' is the default + * pool. + * + * @param number the number of threads. + * + * @return current {@link RocksEnv} instance. + */ + public Env incBackgroundThreadsIfNeeded(final int number, + final Priority priority) { + incBackgroundThreadsIfNeeded(nativeHandle_, number, priority.getValue()); + return this; + } - protected Env(final long nativeHandle) { - super(nativeHandle); + /** + * Lower IO priority for threads from the specified pool. + * + * @param priority the priority id of a specified thread pool. + */ + public Env lowerThreadPoolIOPriority(final Priority priority) { + lowerThreadPoolIOPriority(nativeHandle_, priority.getValue()); + return this; } - static { - default_env_ = new RocksEnv(getDefaultEnvInternal()); + /** + * Lower CPU priority for threads from the specified pool. + * + * @param priority the priority id of a specified thread pool. + */ + public Env lowerThreadPoolCPUPriority(final Priority priority) { + lowerThreadPoolCPUPriority(nativeHandle_, priority.getValue()); + return this; } /** - *

    The static default Env. The ownership of its native handle - * belongs to rocksdb c++ and is not able to be released on the Java - * side.

    + * Returns the status of all threads that belong to the current Env. + * + * @return the status of all threads belong to this env. */ - static Env default_env_; + public List getThreadList() throws RocksDBException { + return Arrays.asList(getThreadList(nativeHandle_)); + } + + Env(final long nativeHandle) { + super(nativeHandle); + } private static native long getDefaultEnvInternal(); private native void setBackgroundThreads( - long handle, int num, int priority); - private native int getThreadPoolQueueLen(long handle, int poolID); + final long handle, final int number, final byte priority); + private native int getBackgroundThreads(final long handle, + final byte priority); + private native int getThreadPoolQueueLen(final long handle, + final byte priority); + private native void incBackgroundThreadsIfNeeded(final long handle, + final int number, final byte priority); + private native void lowerThreadPoolIOPriority(final long handle, + final byte priority); + private native void lowerThreadPoolCPUPriority(final long handle, + final byte priority); + private native ThreadStatus[] getThreadList(final long handle) + throws RocksDBException; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java index 2bca0355e4..6baddb3102 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/EnvOptions.java @@ -5,203 +5,362 @@ package org.rocksdb; +/** + * Options while opening a file to read/write + */ public class EnvOptions extends RocksObject { static { RocksDB.loadLibrary(); } + /** + * Construct with default Options + */ public EnvOptions() { super(newEnvOptions()); } - public EnvOptions setUseOsBuffer(final boolean useOsBuffer) { - setUseOsBuffer(nativeHandle_, useOsBuffer); - return this; - } - - public boolean useOsBuffer() { - assert(isOwningHandle()); - return useOsBuffer(nativeHandle_); + /** + * Construct from {@link DBOptions}. + * + * @param dbOptions the database options. + */ + public EnvOptions(final DBOptions dbOptions) { + super(newEnvOptions(dbOptions.nativeHandle_)); } + /** + * Enable/Disable memory mapped reads. + * + * Default: false + * + * @param useMmapReads true to enable memory mapped reads, false to disable. + * + * @return the reference to these options. + */ public EnvOptions setUseMmapReads(final boolean useMmapReads) { setUseMmapReads(nativeHandle_, useMmapReads); return this; } + /** + * Determine if memory mapped reads are in-use. + * + * @return true if memory mapped reads are in-use, false otherwise. + */ public boolean useMmapReads() { assert(isOwningHandle()); return useMmapReads(nativeHandle_); } + /** + * Enable/Disable memory mapped Writes. + * + * Default: true + * + * @param useMmapWrites true to enable memory mapped writes, false to disable. + * + * @return the reference to these options. + */ public EnvOptions setUseMmapWrites(final boolean useMmapWrites) { setUseMmapWrites(nativeHandle_, useMmapWrites); return this; } + /** + * Determine if memory mapped writes are in-use. + * + * @return true if memory mapped writes are in-use, false otherwise. + */ public boolean useMmapWrites() { assert(isOwningHandle()); return useMmapWrites(nativeHandle_); } + /** + * Enable/Disable direct reads, i.e. {@code O_DIRECT}. + * + * Default: false + * + * @param useDirectReads true to enable direct reads, false to disable. + * + * @return the reference to these options. + */ public EnvOptions setUseDirectReads(final boolean useDirectReads) { setUseDirectReads(nativeHandle_, useDirectReads); return this; } + /** + * Determine if direct reads are in-use. + * + * @return true if direct reads are in-use, false otherwise. + */ public boolean useDirectReads() { assert(isOwningHandle()); return useDirectReads(nativeHandle_); } + /** + * Enable/Disable direct writes, i.e. {@code O_DIRECT}. + * + * Default: false + * + * @param useDirectWrites true to enable direct writes, false to disable. + * + * @return the reference to these options. + */ public EnvOptions setUseDirectWrites(final boolean useDirectWrites) { setUseDirectWrites(nativeHandle_, useDirectWrites); return this; } + /** + * Determine if direct writes are in-use. + * + * @return true if direct writes are in-use, false otherwise. + */ public boolean useDirectWrites() { assert(isOwningHandle()); return useDirectWrites(nativeHandle_); } + /** + * Enable/Disable fallocate calls. + * + * Default: true + * + * If false, {@code fallocate()} calls are bypassed. + * + * @param allowFallocate true to enable fallocate calls, false to disable. + * + * @return the reference to these options. + */ public EnvOptions setAllowFallocate(final boolean allowFallocate) { setAllowFallocate(nativeHandle_, allowFallocate); return this; } + /** + * Determine if fallocate calls are used. + * + * @return true if fallocate calls are used, false otherwise. + */ public boolean allowFallocate() { assert(isOwningHandle()); return allowFallocate(nativeHandle_); } + /** + * Enable/Disable the {@code FD_CLOEXEC} bit when opening file descriptors. + * + * Default: true + * + * @param setFdCloexec true to enable the {@code FB_CLOEXEC} bit, + * false to disable. + * + * @return the reference to these options. + */ public EnvOptions setSetFdCloexec(final boolean setFdCloexec) { setSetFdCloexec(nativeHandle_, setFdCloexec); return this; } + /** + * Determine i fthe {@code FD_CLOEXEC} bit is set when opening file + * descriptors. + * + * @return true if the {@code FB_CLOEXEC} bit is enabled, false otherwise. + */ public boolean setFdCloexec() { assert(isOwningHandle()); return setFdCloexec(nativeHandle_); } + /** + * Allows OS to incrementally sync files to disk while they are being + * written, in the background. Issue one request for every + * {@code bytesPerSync} written. + * + * Default: 0 + * + * @param bytesPerSync 0 to disable, otherwise the number of bytes. + * + * @return the reference to these options. + */ public EnvOptions setBytesPerSync(final long bytesPerSync) { setBytesPerSync(nativeHandle_, bytesPerSync); return this; } + /** + * Get the number of incremental bytes per sync written in the background. + * + * @return 0 if disabled, otherwise the number of bytes. + */ public long bytesPerSync() { assert(isOwningHandle()); return bytesPerSync(nativeHandle_); } - public EnvOptions setFallocateWithKeepSize(final boolean fallocateWithKeepSize) { + /** + * If true, we will preallocate the file with {@code FALLOC_FL_KEEP_SIZE} + * flag, which means that file size won't change as part of preallocation. + * If false, preallocation will also change the file size. This option will + * improve the performance in workloads where you sync the data on every + * write. By default, we set it to true for MANIFEST writes and false for + * WAL writes + * + * @param fallocateWithKeepSize true to preallocate, false otherwise. + * + * @return the reference to these options. + */ + public EnvOptions setFallocateWithKeepSize( + final boolean fallocateWithKeepSize) { setFallocateWithKeepSize(nativeHandle_, fallocateWithKeepSize); return this; } + /** + * Determine if file is preallocated. + * + * @return true if the file is preallocated, false otherwise. + */ public boolean fallocateWithKeepSize() { assert(isOwningHandle()); return fallocateWithKeepSize(nativeHandle_); } - public EnvOptions setCompactionReadaheadSize(final long compactionReadaheadSize) { + /** + * See {@link DBOptions#setCompactionReadaheadSize(long)}. + * + * @param compactionReadaheadSize the compaction read-ahead size. + * + * @return the reference to these options. + */ + public EnvOptions setCompactionReadaheadSize( + final long compactionReadaheadSize) { setCompactionReadaheadSize(nativeHandle_, compactionReadaheadSize); return this; } + /** + * See {@link DBOptions#compactionReadaheadSize()}. + * + * @return the compaction read-ahead size. + */ public long compactionReadaheadSize() { assert(isOwningHandle()); return compactionReadaheadSize(nativeHandle_); } - public EnvOptions setRandomAccessMaxBufferSize(final long randomAccessMaxBufferSize) { + /** + * See {@link DBOptions#setRandomAccessMaxBufferSize(long)}. + * + * @param randomAccessMaxBufferSize the max buffer size for random access. + * + * @return the reference to these options. + */ + public EnvOptions setRandomAccessMaxBufferSize( + final long randomAccessMaxBufferSize) { setRandomAccessMaxBufferSize(nativeHandle_, randomAccessMaxBufferSize); return this; } + /** + * See {@link DBOptions#randomAccessMaxBufferSize()}. + * + * @return the max buffer size for random access. + */ public long randomAccessMaxBufferSize() { assert(isOwningHandle()); return randomAccessMaxBufferSize(nativeHandle_); } - public EnvOptions setWritableFileMaxBufferSize(final long writableFileMaxBufferSize) { + /** + * See {@link DBOptions#setWritableFileMaxBufferSize(long)}. + * + * @param writableFileMaxBufferSize the max buffer size. + * + * @return the reference to these options. + */ + public EnvOptions setWritableFileMaxBufferSize( + final long writableFileMaxBufferSize) { setWritableFileMaxBufferSize(nativeHandle_, writableFileMaxBufferSize); return this; } + /** + * See {@link DBOptions#writableFileMaxBufferSize()}. + * + * @return the max buffer size. + */ public long writableFileMaxBufferSize() { assert(isOwningHandle()); return writableFileMaxBufferSize(nativeHandle_); } + /** + * Set the write rate limiter for flush and compaction. + * + * @param rateLimiter the rate limiter. + * + * @return the reference to these options. + */ public EnvOptions setRateLimiter(final RateLimiter rateLimiter) { this.rateLimiter = rateLimiter; setRateLimiter(nativeHandle_, rateLimiter.nativeHandle_); return this; } + /** + * Get the write rate limiter for flush and compaction. + * + * @return the rate limiter. + */ public RateLimiter rateLimiter() { assert(isOwningHandle()); return rateLimiter; } private native static long newEnvOptions(); - + private native static long newEnvOptions(final long dboptions_handle); @Override protected final native void disposeInternal(final long handle); - private native void setUseOsBuffer(final long handle, final boolean useOsBuffer); - - private native boolean useOsBuffer(final long handle); - - private native void setUseMmapReads(final long handle, final boolean useMmapReads); - + private native void setUseMmapReads(final long handle, + final boolean useMmapReads); private native boolean useMmapReads(final long handle); - - private native void setUseMmapWrites(final long handle, final boolean useMmapWrites); - + private native void setUseMmapWrites(final long handle, + final boolean useMmapWrites); private native boolean useMmapWrites(final long handle); - - private native void setUseDirectReads(final long handle, final boolean useDirectReads); - + private native void setUseDirectReads(final long handle, + final boolean useDirectReads); private native boolean useDirectReads(final long handle); - - private native void setUseDirectWrites(final long handle, final boolean useDirectWrites); - + private native void setUseDirectWrites(final long handle, + final boolean useDirectWrites); private native boolean useDirectWrites(final long handle); - - private native void setAllowFallocate(final long handle, final boolean allowFallocate); - + private native void setAllowFallocate(final long handle, + final boolean allowFallocate); private native boolean allowFallocate(final long handle); - - private native void setSetFdCloexec(final long handle, final boolean setFdCloexec); - + private native void setSetFdCloexec(final long handle, + final boolean setFdCloexec); private native boolean setFdCloexec(final long handle); - - private native void setBytesPerSync(final long handle, final long bytesPerSync); - + private native void setBytesPerSync(final long handle, + final long bytesPerSync); private native long bytesPerSync(final long handle); - private native void setFallocateWithKeepSize( final long handle, final boolean fallocateWithKeepSize); - private native boolean fallocateWithKeepSize(final long handle); - private native void setCompactionReadaheadSize( final long handle, final long compactionReadaheadSize); - private native long compactionReadaheadSize(final long handle); - private native void setRandomAccessMaxBufferSize( final long handle, final long randomAccessMaxBufferSize); - private native long randomAccessMaxBufferSize(final long handle); - private native void setWritableFileMaxBufferSize( final long handle, final long writableFileMaxBufferSize); - private native long writableFileMaxBufferSize(final long handle); - - private native void setRateLimiter(final long handle, final long rateLimiterHandle); - + private native void setRateLimiter(final long handle, + final long rateLimiterHandle); private RateLimiter rateLimiter; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Filter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Filter.java index 011be20856..7f490cf594 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Filter.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Filter.java @@ -12,6 +12,7 @@ * number of disk seeks form a handful to a single disk seek per * DB::Get() call. */ +//TODO(AR) should be renamed FilterPolicy public abstract class Filter extends RocksObject { protected Filter(final long nativeHandle) { diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java index ce54a528bf..760b515fdf 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/FlushOptions.java @@ -1,3 +1,8 @@ +// Copyright (c) 2011-present, Facebook, 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). + package org.rocksdb; /** @@ -41,9 +46,45 @@ public boolean waitForFlush() { return waitForFlush(nativeHandle_); } + /** + * Set to true so that flush would proceeds immediately even it it means + * writes will stall for the duration of the flush. + * + * Set to false so that the operation will wait until it's possible to do + * the flush without causing stall or until required flush is performed by + * someone else (foreground call or background thread). + * + * Default: false + * + * @param allowWriteStall true to allow writes to stall for flush, false + * otherwise. + * + * @return instance of current FlushOptions. + */ + public FlushOptions setAllowWriteStall(final boolean allowWriteStall) { + assert(isOwningHandle()); + setAllowWriteStall(nativeHandle_, allowWriteStall); + return this; + } + + /** + * Returns true if writes are allowed to stall for flushes to complete, false + * otherwise. + * + * @return true if writes are allowed to stall for flushes + */ + public boolean allowWriteStall() { + assert(isOwningHandle()); + return allowWriteStall(nativeHandle_); + } + private native static long newFlushOptions(); @Override protected final native void disposeInternal(final long handle); - private native void setWaitForFlush(long handle, - boolean wait); - private native boolean waitForFlush(long handle); + + private native void setWaitForFlush(final long handle, + final boolean wait); + private native boolean waitForFlush(final long handle); + private native void setAllowWriteStall(final long handle, + final boolean allowWriteStall); + private native boolean allowWriteStall(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HdfsEnv.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HdfsEnv.java new file mode 100644 index 0000000000..4d8d3bff6f --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HdfsEnv.java @@ -0,0 +1,27 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * HDFS environment. + */ +public class HdfsEnv extends Env { + + /** +

    Creates a new environment that is used for HDFS environment.

    + * + *

    The caller must delete the result when it is + * no longer needed.

    + * + * @param fsName the HDFS as a string in the form "hdfs://hostname:port/" + */ + public HdfsEnv(final String fsName) { + super(createHdfsEnv(fsName)); + } + + private static native long createHdfsEnv(final String fsName); + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java index 11798eb59f..81d8908834 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramData.java @@ -11,15 +11,30 @@ public class HistogramData { private final double percentile99_; private final double average_; private final double standardDeviation_; + private final double max_; + private final long count_; + private final long sum_; + private final double min_; + + public HistogramData(final double median, final double percentile95, + final double percentile99, final double average, + final double standardDeviation) { + this(median, percentile95, percentile99, average, standardDeviation, 0.0, 0, 0, 0.0); + } public HistogramData(final double median, final double percentile95, final double percentile99, final double average, - final double standardDeviation) { + final double standardDeviation, final double max, final long count, + final long sum, final double min) { median_ = median; percentile95_ = percentile95; percentile99_ = percentile99; average_ = average; standardDeviation_ = standardDeviation; + min_ = min; + max_ = max; + count_ = count; + sum_ = sum; } public double getMedian() { @@ -41,4 +56,20 @@ public double getAverage() { public double getStandardDeviation() { return standardDeviation_; } + + public double getMax() { + return max_; + } + + public long getCount() { + return count_; + } + + public long getSum() { + return sum_; + } + + public double getMin() { + return min_; + } } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java index 2d95f5149f..ab97a4d257 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/HistogramType.java @@ -84,6 +84,82 @@ public enum HistogramType { READ_NUM_MERGE_OPERANDS((byte) 0x1E), + /** + * Time spent flushing memtable to disk. + */ + FLUSH_TIME((byte) 0x20), + + /** + * Size of keys written to BlobDB. + */ + BLOB_DB_KEY_SIZE((byte) 0x21), + + /** + * Size of values written to BlobDB. + */ + BLOB_DB_VALUE_SIZE((byte) 0x22), + + /** + * BlobDB Put/PutWithTTL/PutUntil/Write latency. + */ + BLOB_DB_WRITE_MICROS((byte) 0x23), + + /** + * BlobDB Get lagency. + */ + BLOB_DB_GET_MICROS((byte) 0x24), + + /** + * BlobDB MultiGet latency. + */ + BLOB_DB_MULTIGET_MICROS((byte) 0x25), + + /** + * BlobDB Seek/SeekToFirst/SeekToLast/SeekForPrev latency. + */ + BLOB_DB_SEEK_MICROS((byte) 0x26), + + /** + * BlobDB Next latency. + */ + BLOB_DB_NEXT_MICROS((byte) 0x27), + + /** + * BlobDB Prev latency. + */ + BLOB_DB_PREV_MICROS((byte) 0x28), + + /** + * Blob file write latency. + */ + BLOB_DB_BLOB_FILE_WRITE_MICROS((byte) 0x29), + + /** + * Blob file read latency. + */ + BLOB_DB_BLOB_FILE_READ_MICROS((byte) 0x2A), + + /** + * Blob file sync latency. + */ + BLOB_DB_BLOB_FILE_SYNC_MICROS((byte) 0x2B), + + /** + * BlobDB garbage collection time. + */ + BLOB_DB_GC_MICROS((byte) 0x2C), + + /** + * BlobDB compression time. + */ + BLOB_DB_COMPRESSION_MICROS((byte) 0x2D), + + /** + * BlobDB decompression time. + */ + BLOB_DB_DECOMPRESSION_MICROS((byte) 0x2E), + + // 0x1F for backwards compatibility on current minor version. HISTOGRAM_ENUM_MAX((byte) 0x1F); private final byte value; @@ -92,6 +168,12 @@ public enum HistogramType { this.value = value; } + /** + * @deprecated + * Exposes internal value of native enum mappings. This method will be marked private in the + * next major release. + */ + @Deprecated public byte getValue() { return value; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IndexType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IndexType.java index e0c113d39a..04e4814658 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IndexType.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IndexType.java @@ -33,7 +33,7 @@ public byte getValue() { return value_; } - private IndexType(byte value) { + IndexType(byte value) { value_ = value; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java index 7343691817..a6a308daa3 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/IngestExternalFileOptions.java @@ -7,7 +7,8 @@ import java.util.List; /** - * IngestExternalFileOptions is used by {@link RocksDB#ingestExternalFile(ColumnFamilyHandle, List, IngestExternalFileOptions)} + * IngestExternalFileOptions is used by + * {@link RocksDB#ingestExternalFile(ColumnFamilyHandle, List, IngestExternalFileOptions)}. */ public class IngestExternalFileOptions extends RocksObject { @@ -41,9 +42,12 @@ public boolean moveFiles() { * Can be set to true to move the files instead of copying them. * * @param moveFiles true if files should be moved instead of copied + * + * @return the reference to the current IngestExternalFileOptions. */ - public void setMoveFiles(final boolean moveFiles) { + public IngestExternalFileOptions setMoveFiles(final boolean moveFiles) { setMoveFiles(nativeHandle_, moveFiles); + return this; } /** @@ -61,9 +65,13 @@ public boolean snapshotConsistency() { * that where created before the file was ingested. * * @param snapshotConsistency true if snapshot consistency is required + * + * @return the reference to the current IngestExternalFileOptions. */ - public void setSnapshotConsistency(final boolean snapshotConsistency) { + public IngestExternalFileOptions setSnapshotConsistency( + final boolean snapshotConsistency) { setSnapshotConsistency(nativeHandle_, snapshotConsistency); + return this; } /** @@ -81,9 +89,13 @@ public boolean allowGlobalSeqNo() { * will fail if the file key range overlaps with existing keys or tombstones in the DB. * * @param allowGlobalSeqNo true if global seq numbers are required + * + * @return the reference to the current IngestExternalFileOptions. */ - public void setAllowGlobalSeqNo(final boolean allowGlobalSeqNo) { + public IngestExternalFileOptions setAllowGlobalSeqNo( + final boolean allowGlobalSeqNo) { setAllowGlobalSeqNo(nativeHandle_, allowGlobalSeqNo); + return this; } /** @@ -101,15 +113,100 @@ public boolean allowBlockingFlush() { * (memtable flush required), IngestExternalFile will fail. * * @param allowBlockingFlush true if blocking flushes are allowed + * + * @return the reference to the current IngestExternalFileOptions. */ - public void setAllowBlockingFlush(final boolean allowBlockingFlush) { + public IngestExternalFileOptions setAllowBlockingFlush( + final boolean allowBlockingFlush) { setAllowBlockingFlush(nativeHandle_, allowBlockingFlush); + return this; + } + + /** + * Returns true if duplicate keys in the file being ingested are + * to be skipped rather than overwriting existing data under that key. + * + * @return true if duplicate keys in the file being ingested are to be + * skipped, false otherwise. + */ + public boolean ingestBehind() { + return ingestBehind(nativeHandle_); + } + + /** + * Set to true if you would like duplicate keys in the file being ingested + * to be skipped rather than overwriting existing data under that key. + * + * Usecase: back-fill of some historical data in the database without + * over-writing existing newer version of data. + * + * This option could only be used if the DB has been running + * with DBOptions#allowIngestBehind() == true since the dawn of time. + * + * All files will be ingested at the bottommost level with seqno=0. + * + * Default: false + * + * @param ingestBehind true if you would like duplicate keys in the file being + * ingested to be skipped. + * + * @return the reference to the current IngestExternalFileOptions. + */ + public IngestExternalFileOptions setIngestBehind(final boolean ingestBehind) { + setIngestBehind(nativeHandle_, ingestBehind); + return this; + } + + /** + * Returns true write if the global_seqno is written to a given offset + * in the external SST file for backward compatibility. + * + * See {@link #setWriteGlobalSeqno(boolean)}. + * + * @return true if the global_seqno is written to a given offset, + * false otherwise. + */ + public boolean writeGlobalSeqno() { + return writeGlobalSeqno(nativeHandle_); + } + + /** + * Set to true if you would like to write the global_seqno to a given offset + * in the external SST file for backward compatibility. + * + * Older versions of RocksDB write the global_seqno to a given offset within + * the ingested SST files, and new versions of RocksDB do not. + * + * If you ingest an external SST using new version of RocksDB and would like + * to be able to downgrade to an older version of RocksDB, you should set + * {@link #writeGlobalSeqno()} to true. + * + * If your service is just starting to use the new RocksDB, we recommend that + * you set this option to false, which brings two benefits: + * 1. No extra random write for global_seqno during ingestion. + * 2. Without writing external SST file, it's possible to do checksum. + * + * We have a plan to set this option to false by default in the future. + * + * Default: true + * + * @param writeGlobalSeqno true to write the gloal_seqno to a given offset, + * false otherwise + * + * @return the reference to the current IngestExternalFileOptions. + */ + public IngestExternalFileOptions setWriteGlobalSeqno( + final boolean writeGlobalSeqno) { + setWriteGlobalSeqno(nativeHandle_, writeGlobalSeqno); + return this; } private native static long newIngestExternalFileOptions(); private native static long newIngestExternalFileOptions( final boolean moveFiles, final boolean snapshotConsistency, final boolean allowGlobalSeqNo, final boolean allowBlockingFlush); + @Override protected final native void disposeInternal(final long handle); + private native boolean moveFiles(final long handle); private native void setMoveFiles(final long handle, final boolean move_files); private native boolean snapshotConsistency(final long handle); @@ -121,5 +218,10 @@ private native void setAllowGlobalSeqNo(final long handle, private native boolean allowBlockingFlush(final long handle); private native void setAllowBlockingFlush(final long handle, final boolean allowBlockingFlush); - @Override protected final native void disposeInternal(final long handle); + private native boolean ingestBehind(final long handle); + private native void setIngestBehind(final long handle, + final boolean ingestBehind); + private native boolean writeGlobalSeqno(final long handle); + private native void setWriteGlobalSeqno(final long handle, + final boolean writeGlobalSeqNo); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LevelMetaData.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LevelMetaData.java new file mode 100644 index 0000000000..c5685098be --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LevelMetaData.java @@ -0,0 +1,56 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.Arrays; +import java.util.List; + +/** + * The metadata that describes a level. + */ +public class LevelMetaData { + private final int level; + private final long size; + private final SstFileMetaData[] files; + + /** + * Called from JNI C++ + */ + private LevelMetaData(final int level, final long size, + final SstFileMetaData[] files) { + this.level = level; + this.size = size; + this.files = files; + } + + /** + * The level which this meta data describes. + * + * @return the level + */ + public int level() { + return level; + } + + /** + * The size of this level in bytes, which is equal to the sum of + * the file size of its {@link #files()}. + * + * @return the size + */ + public long size() { + return size; + } + + /** + * The metadata of all sst files in this level. + * + * @return the metadata of the files + */ + public List files() { + return Arrays.asList(files); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LiveFileMetaData.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LiveFileMetaData.java new file mode 100644 index 0000000000..35d883e180 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LiveFileMetaData.java @@ -0,0 +1,55 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * The full set of metadata associated with each SST file. + */ +public class LiveFileMetaData extends SstFileMetaData { + private final byte[] columnFamilyName; + private final int level; + + /** + * Called from JNI C++ + */ + private LiveFileMetaData( + final byte[] columnFamilyName, + final int level, + final String fileName, + final String path, + final long size, + final long smallestSeqno, + final long largestSeqno, + final byte[] smallestKey, + final byte[] largestKey, + final long numReadsSampled, + final boolean beingCompacted, + final long numEntries, + final long numDeletions) { + super(fileName, path, size, smallestSeqno, largestSeqno, smallestKey, + largestKey, numReadsSampled, beingCompacted, numEntries, numDeletions); + this.columnFamilyName = columnFamilyName; + this.level = level; + } + + /** + * Get the name of the column family. + * + * @return the name of the column family + */ + public byte[] columnFamilyName() { + return columnFamilyName; + } + + /** + * Get the level at which this file resides. + * + * @return the level at which the file resides. + */ + public int level() { + return level; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LogFile.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LogFile.java new file mode 100644 index 0000000000..ef24a6427c --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/LogFile.java @@ -0,0 +1,75 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public class LogFile { + private final String pathName; + private final long logNumber; + private final WalFileType type; + private final long startSequence; + private final long sizeFileBytes; + + /** + * Called from JNI C++ + */ + private LogFile(final String pathName, final long logNumber, + final byte walFileTypeValue, final long startSequence, + final long sizeFileBytes) { + this.pathName = pathName; + this.logNumber = logNumber; + this.type = WalFileType.fromValue(walFileTypeValue); + this.startSequence = startSequence; + this.sizeFileBytes = sizeFileBytes; + } + + /** + * Returns log file's pathname relative to the main db dir + * Eg. For a live-log-file = /000003.log + * For an archived-log-file = /archive/000003.log + * + * @return log file's pathname + */ + public String pathName() { + return pathName; + } + + /** + * Primary identifier for log file. + * This is directly proportional to creation time of the log file + * + * @return the log number + */ + public long logNumber() { + return logNumber; + } + + /** + * Log file can be either alive or archived. + * + * @return the type of the log file. + */ + public WalFileType type() { + return type; + } + + /** + * Starting sequence number of writebatch written in this log file. + * + * @return the stating sequence number + */ + public long startSequence() { + return startSequence; + } + + /** + * Size of log file on disk in Bytes. + * + * @return size of log file + */ + public long sizeFileBytes() { + return sizeFileBytes; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Logger.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Logger.java index 9021259290..00a5d56745 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Logger.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Logger.java @@ -35,9 +35,10 @@ * {@link org.rocksdb.InfoLogLevel#FATAL_LEVEL}. *

    */ -public abstract class Logger extends AbstractImmutableNativeReference { +public abstract class Logger extends RocksCallbackObject { - final long nativeHandle_; + private final static long WITH_OPTIONS = 0; + private final static long WITH_DBOPTIONS = 1; /** *

    AbstractLogger constructor.

    @@ -49,8 +50,8 @@ public abstract class Logger extends AbstractImmutableNativeReference { * @param options {@link org.rocksdb.Options} instance. */ public Logger(final Options options) { - super(true); - this.nativeHandle_ = createNewLoggerOptions(options.nativeHandle_); + super(options.nativeHandle_, WITH_OPTIONS); + } /** @@ -63,8 +64,18 @@ public Logger(final Options options) { * @param dboptions {@link org.rocksdb.DBOptions} instance. */ public Logger(final DBOptions dboptions) { - super(true); - this.nativeHandle_ = createNewLoggerDbOptions(dboptions.nativeHandle_); + super(dboptions.nativeHandle_, WITH_DBOPTIONS); + } + + @Override + protected long initializeNative(long... nativeParameterHandles) { + if(nativeParameterHandles[1] == WITH_OPTIONS) { + return createNewLoggerOptions(nativeParameterHandles[0]); + } else if(nativeParameterHandles[1] == WITH_DBOPTIONS) { + return createNewLoggerDbOptions(nativeParameterHandles[0]); + } else { + throw new IllegalArgumentException(); + } } /** @@ -89,17 +100,6 @@ public InfoLogLevel infoLogLevel() { protected abstract void log(InfoLogLevel infoLogLevel, String logMsg); - /** - * Deletes underlying C++ slice pointer. - * Note that this function should be called only after all - * RocksDB instances referencing the slice are closed. - * Otherwise an undefined behavior will occur. - */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); - } - protected native long createNewLoggerOptions( long options); protected native long createNewLoggerDbOptions( @@ -107,5 +107,16 @@ protected native long createNewLoggerDbOptions( protected native void setInfoLogLevel(long handle, byte infoLogLevel); protected native byte infoLogLevel(long handle); + + /** + * We override {@link RocksCallbackObject#disposeInternal()} + * as disposing of a rocksdb::LoggerJniCallback requires + * a slightly different approach as it is a std::shared_ptr + */ + @Override + protected void disposeInternal() { + disposeInternal(nativeHandle_); + } + private native void disposeInternal(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MemoryUsageType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MemoryUsageType.java new file mode 100644 index 0000000000..6010ce7af5 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MemoryUsageType.java @@ -0,0 +1,72 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * MemoryUsageType + * + *

    The value will be used as a key to indicate the type of memory usage + * described

    + */ +public enum MemoryUsageType { + /** + * Memory usage of all the mem-tables. + */ + kMemTableTotal((byte) 0), + /** + * Memory usage of those un-flushed mem-tables. + */ + kMemTableUnFlushed((byte) 1), + /** + * Memory usage of all the table readers. + */ + kTableReadersTotal((byte) 2), + /** + * Memory usage by Cache. + */ + kCacheTotal((byte) 3), + /** + * Max usage types - copied to keep 1:1 with native. + */ + kNumUsageTypes((byte) 4); + + /** + * Returns the byte value of the enumerations value + * + * @return byte representation + */ + public byte getValue() { + return value_; + } + + /** + *

    Get the MemoryUsageType enumeration value by + * passing the byte identifier to this method.

    + * + * @param byteIdentifier of MemoryUsageType. + * + * @return MemoryUsageType instance. + * + * @throws IllegalArgumentException if the usage type for the byteIdentifier + * cannot be found + */ + public static MemoryUsageType getMemoryUsageType(final byte byteIdentifier) { + for (final MemoryUsageType memoryUsageType : MemoryUsageType.values()) { + if (memoryUsageType.getValue() == byteIdentifier) { + return memoryUsageType; + } + } + + throw new IllegalArgumentException( + "Illegal value provided for MemoryUsageType."); + } + + MemoryUsageType(byte value) { + value_ = value; + } + + private final byte value_; +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MemoryUtil.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MemoryUtil.java new file mode 100644 index 0000000000..52b2175e6b --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MemoryUtil.java @@ -0,0 +1,60 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.*; + +/** + * JNI passthrough for MemoryUtil. + */ +public class MemoryUtil { + + /** + *

    Returns the approximate memory usage of different types in the input + * list of DBs and Cache set. For instance, in the output map the key + * kMemTableTotal will be associated with the memory + * usage of all the mem-tables from all the input rocksdb instances.

    + * + *

    Note that for memory usage inside Cache class, we will + * only report the usage of the input "cache_set" without + * including those Cache usage inside the input list "dbs" + * of DBs.

    + * + * @param dbs List of dbs to collect memory usage for. + * @param caches Set of caches to collect memory usage for. + * @return Map from {@link MemoryUsageType} to memory usage as a {@link Long}. + */ + public static Map getApproximateMemoryUsageByType(final List dbs, final Set caches) { + int dbCount = (dbs == null) ? 0 : dbs.size(); + int cacheCount = (caches == null) ? 0 : caches.size(); + long[] dbHandles = new long[dbCount]; + long[] cacheHandles = new long[cacheCount]; + if (dbCount > 0) { + ListIterator dbIter = dbs.listIterator(); + while (dbIter.hasNext()) { + dbHandles[dbIter.nextIndex()] = dbIter.next().nativeHandle_; + } + } + if (cacheCount > 0) { + // NOTE: This index handling is super ugly but I couldn't get a clean way to track both the + // index and the iterator simultaneously within a Set. + int i = 0; + for (Cache cache : caches) { + cacheHandles[i] = cache.nativeHandle_; + i++; + } + } + Map byteOutput = getApproximateMemoryUsageByType(dbHandles, cacheHandles); + Map output = new HashMap<>(); + for(Map.Entry longEntry : byteOutput.entrySet()) { + output.put(MemoryUsageType.getMemoryUsageType(longEntry.getKey()), longEntry.getValue()); + } + return output; + } + + private native static Map getApproximateMemoryUsageByType(final long[] dbHandles, + final long[] cacheHandles); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java index 3585318dbd..1d9ca08174 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableColumnFamilyOptions.java @@ -7,27 +7,20 @@ import java.util.*; -public class MutableColumnFamilyOptions { - private final static String KEY_VALUE_PAIR_SEPARATOR = ";"; - private final static char KEY_VALUE_SEPARATOR = '='; - private final static String INT_ARRAY_INT_SEPARATOR = ","; - - private final String[] keys; - private final String[] values; - - // user must use builder pattern, or parser - private MutableColumnFamilyOptions(final String keys[], - final String values[]) { - this.keys = keys; - this.values = values; - } - - String[] getKeys() { - return keys; - } +public class MutableColumnFamilyOptions + extends AbstractMutableOptions { - String[] getValues() { - return values; + /** + * User must use builder pattern, or parser. + * + * @param keys the keys + * @param values the values + * + * See {@link #builder()} and {@link #parse(String)}. + */ + private MutableColumnFamilyOptions(final String[] keys, + final String[] values) { + super(keys, values); } /** @@ -60,7 +53,7 @@ public static MutableColumnFamilyOptionsBuilder parse(final String str) { final MutableColumnFamilyOptionsBuilder builder = new MutableColumnFamilyOptionsBuilder(); - final String options[] = str.trim().split(KEY_VALUE_PAIR_SEPARATOR); + final String[] options = str.trim().split(KEY_VALUE_PAIR_SEPARATOR); for(final String option : options) { final int equalsOffset = option.indexOf(KEY_VALUE_SEPARATOR); if(equalsOffset <= 0) { @@ -69,12 +62,12 @@ public static MutableColumnFamilyOptionsBuilder parse(final String str) { } final String key = option.substring(0, equalsOffset); - if(key == null || key.isEmpty()) { + if(key.isEmpty()) { throw new IllegalArgumentException("options string is invalid"); } final String value = option.substring(equalsOffset + 1); - if(value == null || value.isEmpty()) { + if(value.isEmpty()) { throw new IllegalArgumentException("options string is invalid"); } @@ -84,37 +77,7 @@ public static MutableColumnFamilyOptionsBuilder parse(final String str) { return builder; } - /** - * Returns a string representation - * of MutableColumnFamilyOptions which is - * suitable for consumption by {@link #parse(String)} - * - * @return String representation of MutableColumnFamilyOptions - */ - @Override - public String toString() { - final StringBuilder buffer = new StringBuilder(); - for(int i = 0; i < keys.length; i++) { - buffer - .append(keys[i]) - .append(KEY_VALUE_SEPARATOR) - .append(values[i]); - - if(i + 1 < keys.length) { - buffer.append(KEY_VALUE_PAIR_SEPARATOR); - } - } - return buffer.toString(); - } - - public enum ValueType { - DOUBLE, - LONG, - INT, - BOOLEAN, - INT_ARRAY, - ENUM - } + private interface MutableColumnFamilyOptionKey extends MutableOptionKey {} public enum MemtableOption implements MutableColumnFamilyOptionKey { write_buffer_size(ValueType.LONG), @@ -153,7 +116,8 @@ public enum CompactionOption implements MutableColumnFamilyOptionKey { target_file_size_multiplier(ValueType.INT), max_bytes_for_level_base(ValueType.LONG), max_bytes_for_level_multiplier(ValueType.INT), - max_bytes_for_level_multiplier_additional(ValueType.INT_ARRAY); + max_bytes_for_level_multiplier_additional(ValueType.INT_ARRAY), + ttl(ValueType.LONG); private final ValueType valueType; CompactionOption(final ValueType valueType) { @@ -183,356 +147,9 @@ public ValueType getValueType() { } } - private interface MutableColumnFamilyOptionKey { - String name(); - ValueType getValueType(); - } - - private static abstract class MutableColumnFamilyOptionValue { - protected final T value; - - MutableColumnFamilyOptionValue(final T value) { - this.value = value; - } - - abstract double asDouble() throws NumberFormatException; - abstract long asLong() throws NumberFormatException; - abstract int asInt() throws NumberFormatException; - abstract boolean asBoolean() throws IllegalStateException; - abstract int[] asIntArray() throws IllegalStateException; - abstract String asString(); - abstract T asObject(); - } - - private static class MutableColumnFamilyOptionStringValue - extends MutableColumnFamilyOptionValue { - MutableColumnFamilyOptionStringValue(final String value) { - super(value); - } - - @Override - double asDouble() throws NumberFormatException { - return Double.parseDouble(value); - } - - @Override - long asLong() throws NumberFormatException { - return Long.parseLong(value); - } - - @Override - int asInt() throws NumberFormatException { - return Integer.parseInt(value); - } - - @Override - boolean asBoolean() throws IllegalStateException { - return Boolean.parseBoolean(value); - } - - @Override - int[] asIntArray() throws IllegalStateException { - throw new IllegalStateException("String is not applicable as int[]"); - } - - @Override - String asString() { - return value; - } - - @Override - String asObject() { - return value; - } - } - - private static class MutableColumnFamilyOptionDoubleValue - extends MutableColumnFamilyOptionValue { - MutableColumnFamilyOptionDoubleValue(final double value) { - super(value); - } - - @Override - double asDouble() { - return value; - } - - @Override - long asLong() throws NumberFormatException { - return value.longValue(); - } - - @Override - int asInt() throws NumberFormatException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "double value lies outside the bounds of int"); - } - return value.intValue(); - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new IllegalStateException( - "double is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "double value lies outside the bounds of int"); - } - return new int[] { value.intValue() }; - } - - @Override - String asString() { - return Double.toString(value); - } - - @Override - Double asObject() { - return value; - } - } - - private static class MutableColumnFamilyOptionLongValue - extends MutableColumnFamilyOptionValue { - MutableColumnFamilyOptionLongValue(final long value) { - super(value); - } - - @Override - double asDouble() { - if(value > Double.MAX_VALUE || value < Double.MIN_VALUE) { - throw new NumberFormatException( - "long value lies outside the bounds of int"); - } - return value.doubleValue(); - } - - @Override - long asLong() throws NumberFormatException { - return value; - } - - @Override - int asInt() throws NumberFormatException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "long value lies outside the bounds of int"); - } - return value.intValue(); - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new IllegalStateException( - "long is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { - throw new NumberFormatException( - "long value lies outside the bounds of int"); - } - return new int[] { value.intValue() }; - } - - @Override - String asString() { - return Long.toString(value); - } - - @Override - Long asObject() { - return value; - } - } - - private static class MutableColumnFamilyOptionIntValue - extends MutableColumnFamilyOptionValue { - MutableColumnFamilyOptionIntValue(final int value) { - super(value); - } - - @Override - double asDouble() { - if(value > Double.MAX_VALUE || value < Double.MIN_VALUE) { - throw new NumberFormatException("int value lies outside the bounds of int"); - } - return value.doubleValue(); - } - - @Override - long asLong() throws NumberFormatException { - return value; - } - - @Override - int asInt() throws NumberFormatException { - return value; - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new IllegalStateException("int is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - return new int[] { value }; - } - - @Override - String asString() { - return Integer.toString(value); - } - - @Override - Integer asObject() { - return value; - } - } - - private static class MutableColumnFamilyOptionBooleanValue - extends MutableColumnFamilyOptionValue { - MutableColumnFamilyOptionBooleanValue(final boolean value) { - super(value); - } - - @Override - double asDouble() { - throw new NumberFormatException("boolean is not applicable as double"); - } - - @Override - long asLong() throws NumberFormatException { - throw new NumberFormatException("boolean is not applicable as Long"); - } - - @Override - int asInt() throws NumberFormatException { - throw new NumberFormatException("boolean is not applicable as int"); - } - - @Override - boolean asBoolean() { - return value; - } - - @Override - int[] asIntArray() throws IllegalStateException { - throw new IllegalStateException("boolean is not applicable as int[]"); - } - - @Override - String asString() { - return Boolean.toString(value); - } - - @Override - Boolean asObject() { - return value; - } - } - - private static class MutableColumnFamilyOptionIntArrayValue - extends MutableColumnFamilyOptionValue { - MutableColumnFamilyOptionIntArrayValue(final int[] value) { - super(value); - } - - @Override - double asDouble() { - throw new NumberFormatException("int[] is not applicable as double"); - } - - @Override - long asLong() throws NumberFormatException { - throw new NumberFormatException("int[] is not applicable as Long"); - } - - @Override - int asInt() throws NumberFormatException { - throw new NumberFormatException("int[] is not applicable as int"); - } - - @Override - boolean asBoolean() { - throw new NumberFormatException("int[] is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - return value; - } - - @Override - String asString() { - final StringBuilder builder = new StringBuilder(); - for(int i = 0; i < value.length; i++) { - builder.append(Integer.toString(i)); - if(i + 1 < value.length) { - builder.append(INT_ARRAY_INT_SEPARATOR); - } - } - return builder.toString(); - } - - @Override - int[] asObject() { - return value; - } - } - - private static class MutableColumnFamilyOptionEnumValue> - extends MutableColumnFamilyOptionValue { - - MutableColumnFamilyOptionEnumValue(final T value) { - super(value); - } - - @Override - double asDouble() throws NumberFormatException { - throw new NumberFormatException("Enum is not applicable as double"); - } - - @Override - long asLong() throws NumberFormatException { - throw new NumberFormatException("Enum is not applicable as long"); - } - - @Override - int asInt() throws NumberFormatException { - throw new NumberFormatException("Enum is not applicable as int"); - } - - @Override - boolean asBoolean() throws IllegalStateException { - throw new NumberFormatException("Enum is not applicable as boolean"); - } - - @Override - int[] asIntArray() throws IllegalStateException { - throw new NumberFormatException("Enum is not applicable as int[]"); - } - - @Override - String asString() { - return value.name(); - } - - @Override - T asObject() { - return value; - } - } - public static class MutableColumnFamilyOptionsBuilder - implements MutableColumnFamilyOptionsInterface { + extends AbstractMutableOptionsBuilder + implements MutableColumnFamilyOptionsInterface { private final static Map ALL_KEYS_LOOKUP = new HashMap<>(); static { @@ -549,179 +166,24 @@ public static class MutableColumnFamilyOptionsBuilder } } - private final Map> options = new LinkedHashMap<>(); - - public MutableColumnFamilyOptions build() { - final String keys[] = new String[options.size()]; - final String values[] = new String[options.size()]; - - int i = 0; - for(final Map.Entry> option : options.entrySet()) { - keys[i] = option.getKey().name(); - values[i] = option.getValue().asString(); - i++; - } - - return new MutableColumnFamilyOptions(keys, values); - } - - private MutableColumnFamilyOptionsBuilder setDouble( - final MutableColumnFamilyOptionKey key, final double value) { - if(key.getValueType() != ValueType.DOUBLE) { - throw new IllegalArgumentException( - key + " does not accept a double value"); - } - options.put(key, new MutableColumnFamilyOptionDoubleValue(value)); - return this; - } - - private double getDouble(final MutableColumnFamilyOptionKey key) - throws NoSuchElementException, NumberFormatException { - final MutableColumnFamilyOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asDouble(); - } - - private MutableColumnFamilyOptionsBuilder setLong( - final MutableColumnFamilyOptionKey key, final long value) { - if(key.getValueType() != ValueType.LONG) { - throw new IllegalArgumentException( - key + " does not accept a long value"); - } - options.put(key, new MutableColumnFamilyOptionLongValue(value)); - return this; - } - - private long getLong(final MutableColumnFamilyOptionKey key) - throws NoSuchElementException, NumberFormatException { - final MutableColumnFamilyOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asLong(); - } - - private MutableColumnFamilyOptionsBuilder setInt( - final MutableColumnFamilyOptionKey key, final int value) { - if(key.getValueType() != ValueType.INT) { - throw new IllegalArgumentException( - key + " does not accept an integer value"); - } - options.put(key, new MutableColumnFamilyOptionIntValue(value)); - return this; - } - - private int getInt(final MutableColumnFamilyOptionKey key) - throws NoSuchElementException, NumberFormatException { - final MutableColumnFamilyOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asInt(); - } - - private MutableColumnFamilyOptionsBuilder setBoolean( - final MutableColumnFamilyOptionKey key, final boolean value) { - if(key.getValueType() != ValueType.BOOLEAN) { - throw new IllegalArgumentException( - key + " does not accept a boolean value"); - } - options.put(key, new MutableColumnFamilyOptionBooleanValue(value)); - return this; - } - - private boolean getBoolean(final MutableColumnFamilyOptionKey key) - throws NoSuchElementException, NumberFormatException { - final MutableColumnFamilyOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asBoolean(); + private MutableColumnFamilyOptionsBuilder() { + super(); } - private MutableColumnFamilyOptionsBuilder setIntArray( - final MutableColumnFamilyOptionKey key, final int[] value) { - if(key.getValueType() != ValueType.INT_ARRAY) { - throw new IllegalArgumentException( - key + " does not accept an int array value"); - } - options.put(key, new MutableColumnFamilyOptionIntArrayValue(value)); - return this; - } - - private int[] getIntArray(final MutableColumnFamilyOptionKey key) - throws NoSuchElementException, NumberFormatException { - final MutableColumnFamilyOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - return value.asIntArray(); - } - - private > MutableColumnFamilyOptionsBuilder setEnum( - final MutableColumnFamilyOptionKey key, final T value) { - if(key.getValueType() != ValueType.ENUM) { - throw new IllegalArgumentException( - key + " does not accept a Enum value"); - } - options.put(key, new MutableColumnFamilyOptionEnumValue(value)); + @Override + protected MutableColumnFamilyOptionsBuilder self() { return this; - } - private > T getEnum(final MutableColumnFamilyOptionKey key) - throws NoSuchElementException, NumberFormatException { - final MutableColumnFamilyOptionValue value = options.get(key); - if(value == null) { - throw new NoSuchElementException(key.name() + " has not been set"); - } - - if(!(value instanceof MutableColumnFamilyOptionEnumValue)) { - throw new NoSuchElementException(key.name() + " is not of Enum type"); - } - - return ((MutableColumnFamilyOptionEnumValue)value).asObject(); + @Override + protected Map allKeys() { + return ALL_KEYS_LOOKUP; } - public MutableColumnFamilyOptionsBuilder fromString(final String keyStr, - final String valueStr) throws IllegalArgumentException { - Objects.requireNonNull(keyStr); - Objects.requireNonNull(valueStr); - - final MutableColumnFamilyOptionKey key = ALL_KEYS_LOOKUP.get(keyStr); - switch(key.getValueType()) { - case DOUBLE: - return setDouble(key, Double.parseDouble(valueStr)); - - case LONG: - return setLong(key, Long.parseLong(valueStr)); - - case INT: - return setInt(key, Integer.parseInt(valueStr)); - - case BOOLEAN: - return setBoolean(key, Boolean.parseBoolean(valueStr)); - - case INT_ARRAY: - final String[] strInts = valueStr - .trim().split(INT_ARRAY_INT_SEPARATOR); - if(strInts == null || strInts.length == 0) { - throw new IllegalArgumentException( - "int array value is not correctly formatted"); - } - - final int value[] = new int[strInts.length]; - int i = 0; - for(final String strInt : strInts) { - value[i++] = Integer.parseInt(strInt); - } - return setIntArray(key, value); - } - - throw new IllegalStateException( - key + " has unknown value type: " + key.getValueType()); + @Override + protected MutableColumnFamilyOptions build(final String[] keys, + final String[] values) { + return new MutableColumnFamilyOptions(keys, values); } @Override @@ -993,5 +455,15 @@ public MutableColumnFamilyOptionsBuilder setReportBgIoStats( public boolean reportBgIoStats() { return getBoolean(MiscOption.report_bg_io_stats); } + + @Override + public MutableColumnFamilyOptionsBuilder setTtl(final long ttl) { + return setLong(CompactionOption.ttl, ttl); + } + + @Override + public long ttl() { + return getLong(CompactionOption.ttl); + } } } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptions.java new file mode 100644 index 0000000000..328f7f9795 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptions.java @@ -0,0 +1,286 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class MutableDBOptions extends AbstractMutableOptions { + + /** + * User must use builder pattern, or parser. + * + * @param keys the keys + * @param values the values + * + * See {@link #builder()} and {@link #parse(String)}. + */ + private MutableDBOptions(final String[] keys, final String[] values) { + super(keys, values); + } + + /** + * Creates a builder which allows you + * to set MutableDBOptions in a fluent + * manner + * + * @return A builder for MutableDBOptions + */ + public static MutableDBOptionsBuilder builder() { + return new MutableDBOptionsBuilder(); + } + + /** + * Parses a String representation of MutableDBOptions + * + * The format is: key1=value1;key2=value2;key3=value3 etc + * + * For int[] values, each int should be separated by a comma, e.g. + * + * key1=value1;intArrayKey1=1,2,3 + * + * @param str The string representation of the mutable db options + * + * @return A builder for the mutable db options + */ + public static MutableDBOptionsBuilder parse(final String str) { + Objects.requireNonNull(str); + + final MutableDBOptionsBuilder builder = + new MutableDBOptionsBuilder(); + + final String[] options = str.trim().split(KEY_VALUE_PAIR_SEPARATOR); + for(final String option : options) { + final int equalsOffset = option.indexOf(KEY_VALUE_SEPARATOR); + if(equalsOffset <= 0) { + throw new IllegalArgumentException( + "options string has an invalid key=value pair"); + } + + final String key = option.substring(0, equalsOffset); + if(key.isEmpty()) { + throw new IllegalArgumentException("options string is invalid"); + } + + final String value = option.substring(equalsOffset + 1); + if(value.isEmpty()) { + throw new IllegalArgumentException("options string is invalid"); + } + + builder.fromString(key, value); + } + + return builder; + } + + private interface MutableDBOptionKey extends MutableOptionKey {} + + public enum DBOption implements MutableDBOptionKey { + max_background_jobs(ValueType.INT), + base_background_compactions(ValueType.INT), + max_background_compactions(ValueType.INT), + avoid_flush_during_shutdown(ValueType.BOOLEAN), + writable_file_max_buffer_size(ValueType.LONG), + delayed_write_rate(ValueType.LONG), + max_total_wal_size(ValueType.LONG), + delete_obsolete_files_period_micros(ValueType.LONG), + stats_dump_period_sec(ValueType.INT), + max_open_files(ValueType.INT), + bytes_per_sync(ValueType.LONG), + wal_bytes_per_sync(ValueType.LONG), + compaction_readahead_size(ValueType.LONG); + + private final ValueType valueType; + DBOption(final ValueType valueType) { + this.valueType = valueType; + } + + @Override + public ValueType getValueType() { + return valueType; + } + } + + public static class MutableDBOptionsBuilder + extends AbstractMutableOptionsBuilder + implements MutableDBOptionsInterface { + + private final static Map ALL_KEYS_LOOKUP = new HashMap<>(); + static { + for(final MutableDBOptionKey key : DBOption.values()) { + ALL_KEYS_LOOKUP.put(key.name(), key); + } + } + + private MutableDBOptionsBuilder() { + super(); + } + + @Override + protected MutableDBOptionsBuilder self() { + return this; + } + + @Override + protected Map allKeys() { + return ALL_KEYS_LOOKUP; + } + + @Override + protected MutableDBOptions build(final String[] keys, + final String[] values) { + return new MutableDBOptions(keys, values); + } + + @Override + public MutableDBOptionsBuilder setMaxBackgroundJobs( + final int maxBackgroundJobs) { + return setInt(DBOption.max_background_jobs, maxBackgroundJobs); + } + + @Override + public int maxBackgroundJobs() { + return getInt(DBOption.max_background_jobs); + } + + @Override + public void setBaseBackgroundCompactions( + final int baseBackgroundCompactions) { + setInt(DBOption.base_background_compactions, + baseBackgroundCompactions); + } + + @Override + public int baseBackgroundCompactions() { + return getInt(DBOption.base_background_compactions); + } + + @Override + public MutableDBOptionsBuilder setMaxBackgroundCompactions( + final int maxBackgroundCompactions) { + return setInt(DBOption.max_background_compactions, + maxBackgroundCompactions); + } + + @Override + public int maxBackgroundCompactions() { + return getInt(DBOption.max_background_compactions); + } + + @Override + public MutableDBOptionsBuilder setAvoidFlushDuringShutdown( + final boolean avoidFlushDuringShutdown) { + return setBoolean(DBOption.avoid_flush_during_shutdown, + avoidFlushDuringShutdown); + } + + @Override + public boolean avoidFlushDuringShutdown() { + return getBoolean(DBOption.avoid_flush_during_shutdown); + } + + @Override + public MutableDBOptionsBuilder setWritableFileMaxBufferSize( + final long writableFileMaxBufferSize) { + return setLong(DBOption.writable_file_max_buffer_size, + writableFileMaxBufferSize); + } + + @Override + public long writableFileMaxBufferSize() { + return getLong(DBOption.writable_file_max_buffer_size); + } + + @Override + public MutableDBOptionsBuilder setDelayedWriteRate( + final long delayedWriteRate) { + return setLong(DBOption.delayed_write_rate, + delayedWriteRate); + } + + @Override + public long delayedWriteRate() { + return getLong(DBOption.delayed_write_rate); + } + + @Override + public MutableDBOptionsBuilder setMaxTotalWalSize( + final long maxTotalWalSize) { + return setLong(DBOption.max_total_wal_size, maxTotalWalSize); + } + + @Override + public long maxTotalWalSize() { + return getLong(DBOption.max_total_wal_size); + } + + @Override + public MutableDBOptionsBuilder setDeleteObsoleteFilesPeriodMicros( + final long micros) { + return setLong(DBOption.delete_obsolete_files_period_micros, micros); + } + + @Override + public long deleteObsoleteFilesPeriodMicros() { + return getLong(DBOption.delete_obsolete_files_period_micros); + } + + @Override + public MutableDBOptionsBuilder setStatsDumpPeriodSec( + final int statsDumpPeriodSec) { + return setInt(DBOption.stats_dump_period_sec, statsDumpPeriodSec); + } + + @Override + public int statsDumpPeriodSec() { + return getInt(DBOption.stats_dump_period_sec); + } + + @Override + public MutableDBOptionsBuilder setMaxOpenFiles(final int maxOpenFiles) { + return setInt(DBOption.max_open_files, maxOpenFiles); + } + + @Override + public int maxOpenFiles() { + return getInt(DBOption.max_open_files); + } + + @Override + public MutableDBOptionsBuilder setBytesPerSync(final long bytesPerSync) { + return setLong(DBOption.bytes_per_sync, bytesPerSync); + } + + @Override + public long bytesPerSync() { + return getLong(DBOption.bytes_per_sync); + } + + @Override + public MutableDBOptionsBuilder setWalBytesPerSync( + final long walBytesPerSync) { + return setLong(DBOption.wal_bytes_per_sync, walBytesPerSync); + } + + @Override + public long walBytesPerSync() { + return getLong(DBOption.wal_bytes_per_sync); + } + + @Override + public MutableDBOptionsBuilder setCompactionReadaheadSize( + final long compactionReadaheadSize) { + return setLong(DBOption.compaction_readahead_size, + compactionReadaheadSize); + } + + @Override + public long compactionReadaheadSize() { + return getLong(DBOption.compaction_readahead_size); + } + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java new file mode 100644 index 0000000000..5fe3215b39 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableDBOptionsInterface.java @@ -0,0 +1,336 @@ +package org.rocksdb; + +public interface MutableDBOptionsInterface { + + /** + * Specifies the maximum number of concurrent background jobs (both flushes + * and compactions combined). + * Default: 2 + * + * @param maxBackgroundJobs number of max concurrent background jobs + * @return the instance of the current object. + */ + T setMaxBackgroundJobs(int maxBackgroundJobs); + + /** + * Returns the maximum number of concurrent background jobs (both flushes + * and compactions combined). + * Default: 2 + * + * @return the maximum number of concurrent background jobs. + */ + int maxBackgroundJobs(); + + /** + * Suggested number of concurrent background compaction jobs, submitted to + * the default LOW priority thread pool. + * Default: 1 + * + * @param baseBackgroundCompactions Suggested number of background compaction + * jobs + * + * @deprecated Use {@link #setMaxBackgroundJobs(int)} + */ + @Deprecated + void setBaseBackgroundCompactions(int baseBackgroundCompactions); + + /** + * Suggested number of concurrent background compaction jobs, submitted to + * the default LOW priority thread pool. + * Default: 1 + * + * @return Suggested number of background compaction jobs + */ + int baseBackgroundCompactions(); + + /** + * Specifies the maximum number of concurrent background compaction jobs, + * submitted to the default LOW priority thread pool. + * If you're increasing this, also consider increasing number of threads in + * LOW priority thread pool. For more information, see + * Default: 1 + * + * @param maxBackgroundCompactions the maximum number of background + * compaction jobs. + * @return the instance of the current object. + * + * @see RocksEnv#setBackgroundThreads(int) + * @see RocksEnv#setBackgroundThreads(int, Priority) + * @see DBOptionsInterface#maxBackgroundFlushes() + */ + T setMaxBackgroundCompactions(int maxBackgroundCompactions); + + /** + * Returns the maximum number of concurrent background compaction jobs, + * submitted to the default LOW priority thread pool. + * When increasing this number, we may also want to consider increasing + * number of threads in LOW priority thread pool. + * Default: 1 + * + * @return the maximum number of concurrent background compaction jobs. + * @see RocksEnv#setBackgroundThreads(int) + * @see RocksEnv#setBackgroundThreads(int, Priority) + * + * @deprecated Use {@link #setMaxBackgroundJobs(int)} + */ + @Deprecated + int maxBackgroundCompactions(); + + /** + * By default RocksDB will flush all memtables on DB close if there are + * unpersisted data (i.e. with WAL disabled) The flush can be skip to speedup + * DB close. Unpersisted data WILL BE LOST. + * + * DEFAULT: false + * + * Dynamically changeable through + * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} + * API. + * + * @param avoidFlushDuringShutdown true if we should avoid flush during + * shutdown + * + * @return the reference to the current options. + */ + T setAvoidFlushDuringShutdown(boolean avoidFlushDuringShutdown); + + /** + * By default RocksDB will flush all memtables on DB close if there are + * unpersisted data (i.e. with WAL disabled) The flush can be skip to speedup + * DB close. Unpersisted data WILL BE LOST. + * + * DEFAULT: false + * + * Dynamically changeable through + * {@link RocksDB#setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} + * API. + * + * @return true if we should avoid flush during shutdown + */ + boolean avoidFlushDuringShutdown(); + + /** + * This is the maximum buffer size that is used by WritableFileWriter. + * On Windows, we need to maintain an aligned buffer for writes. + * We allow the buffer to grow until it's size hits the limit. + * + * Default: 1024 * 1024 (1 MB) + * + * @param writableFileMaxBufferSize the maximum buffer size + * + * @return the reference to the current options. + */ + T setWritableFileMaxBufferSize(long writableFileMaxBufferSize); + + /** + * This is the maximum buffer size that is used by WritableFileWriter. + * On Windows, we need to maintain an aligned buffer for writes. + * We allow the buffer to grow until it's size hits the limit. + * + * Default: 1024 * 1024 (1 MB) + * + * @return the maximum buffer size + */ + long writableFileMaxBufferSize(); + + /** + * The limited write rate to DB if + * {@link ColumnFamilyOptions#softPendingCompactionBytesLimit()} or + * {@link ColumnFamilyOptions#level0SlowdownWritesTrigger()} is triggered, + * or we are writing to the last mem table allowed and we allow more than 3 + * mem tables. It is calculated using size of user write requests before + * compression. RocksDB may decide to slow down more if the compaction still + * gets behind further. + * + * Unit: bytes per second. + * + * Default: 16MB/s + * + * @param delayedWriteRate the rate in bytes per second + * + * @return the reference to the current options. + */ + T setDelayedWriteRate(long delayedWriteRate); + + /** + * The limited write rate to DB if + * {@link ColumnFamilyOptions#softPendingCompactionBytesLimit()} or + * {@link ColumnFamilyOptions#level0SlowdownWritesTrigger()} is triggered, + * or we are writing to the last mem table allowed and we allow more than 3 + * mem tables. It is calculated using size of user write requests before + * compression. RocksDB may decide to slow down more if the compaction still + * gets behind further. + * + * Unit: bytes per second. + * + * Default: 16MB/s + * + * @return the rate in bytes per second + */ + long delayedWriteRate(); + + /** + *

    Once write-ahead logs exceed this size, we will start forcing the + * flush of column families whose memtables are backed by the oldest live + * WAL file (i.e. the ones that are causing all the space amplification). + *

    + *

    If set to 0 (default), we will dynamically choose the WAL size limit to + * be [sum of all write_buffer_size * max_write_buffer_number] * 2

    + *

    This option takes effect only when there are more than one column family as + * otherwise the wal size is dictated by the write_buffer_size.

    + *

    Default: 0

    + * + * @param maxTotalWalSize max total wal size. + * @return the instance of the current object. + */ + T setMaxTotalWalSize(long maxTotalWalSize); + + /** + *

    Returns the max total wal size. Once write-ahead logs exceed this size, + * we will start forcing the flush of column families whose memtables are + * backed by the oldest live WAL file (i.e. the ones that are causing all + * the space amplification).

    + * + *

    If set to 0 (default), we will dynamically choose the WAL size limit + * to be [sum of all write_buffer_size * max_write_buffer_number] * 2 + *

    + * + * @return max total wal size + */ + long maxTotalWalSize(); + + /** + * The periodicity when obsolete files get deleted. The default + * value is 6 hours. The files that get out of scope by compaction + * process will still get automatically delete on every compaction, + * regardless of this setting + * + * @param micros the time interval in micros + * @return the instance of the current object. + */ + T setDeleteObsoleteFilesPeriodMicros(long micros); + + /** + * The periodicity when obsolete files get deleted. The default + * value is 6 hours. The files that get out of scope by compaction + * process will still get automatically delete on every compaction, + * regardless of this setting + * + * @return the time interval in micros when obsolete files will be deleted. + */ + long deleteObsoleteFilesPeriodMicros(); + + /** + * if not zero, dump rocksdb.stats to LOG every stats_dump_period_sec + * Default: 600 (10 minutes) + * + * @param statsDumpPeriodSec time interval in seconds. + * @return the instance of the current object. + */ + T setStatsDumpPeriodSec(int statsDumpPeriodSec); + + /** + * If not zero, dump rocksdb.stats to LOG every stats_dump_period_sec + * Default: 600 (10 minutes) + * + * @return time interval in seconds. + */ + int statsDumpPeriodSec(); + + /** + * Number of open files that can be used by the DB. You may need to + * increase this if your database has a large working set. Value -1 means + * files opened are always kept open. You can estimate number of files based + * on {@code target_file_size_base} and {@code target_file_size_multiplier} + * for level-based compaction. For universal-style compaction, you can usually + * set it to -1. + * Default: 5000 + * + * @param maxOpenFiles the maximum number of open files. + * @return the instance of the current object. + */ + T setMaxOpenFiles(int maxOpenFiles); + + /** + * Number of open files that can be used by the DB. You may need to + * increase this if your database has a large working set. Value -1 means + * files opened are always kept open. You can estimate number of files based + * on {@code target_file_size_base} and {@code target_file_size_multiplier} + * for level-based compaction. For universal-style compaction, you can usually + * set it to -1. + * + * @return the maximum number of open files. + */ + int maxOpenFiles(); + + /** + * Allows OS to incrementally sync files to disk while they are being + * written, asynchronously, in the background. + * Issue one request for every bytes_per_sync written. 0 turns it off. + * Default: 0 + * + * @param bytesPerSync size in bytes + * @return the instance of the current object. + */ + T setBytesPerSync(long bytesPerSync); + + /** + * Allows OS to incrementally sync files to disk while they are being + * written, asynchronously, in the background. + * Issue one request for every bytes_per_sync written. 0 turns it off. + * Default: 0 + * + * @return size in bytes + */ + long bytesPerSync(); + + /** + * Same as {@link #setBytesPerSync(long)} , but applies to WAL files + * + * Default: 0, turned off + * + * @param walBytesPerSync size in bytes + * @return the instance of the current object. + */ + T setWalBytesPerSync(long walBytesPerSync); + + /** + * Same as {@link #bytesPerSync()} , but applies to WAL files + * + * Default: 0, turned off + * + * @return size in bytes + */ + long walBytesPerSync(); + + + /** + * If non-zero, we perform bigger reads when doing compaction. If you're + * running RocksDB on spinning disks, you should set this to at least 2MB. + * + * That way RocksDB's compaction is doing sequential instead of random reads. + * When non-zero, we also force + * {@link DBOptionsInterface#newTableReaderForCompactionInputs()} to true. + * + * Default: 0 + * + * @param compactionReadaheadSize The compaction read-ahead size + * + * @return the reference to the current options. + */ + T setCompactionReadaheadSize(final long compactionReadaheadSize); + + /** + * If non-zero, we perform bigger reads when doing compaction. If you're + * running RocksDB on spinning disks, you should set this to at least 2MB. + * + * That way RocksDB's compaction is doing sequential instead of random reads. + * When non-zero, we also force + * {@link DBOptionsInterface#newTableReaderForCompactionInputs()} to true. + * + * Default: 0 + * + * @return The compaction read-ahead size + */ + long compactionReadaheadSize(); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableOptionKey.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableOptionKey.java new file mode 100644 index 0000000000..7402471ff2 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableOptionKey.java @@ -0,0 +1,15 @@ +package org.rocksdb; + +public interface MutableOptionKey { + enum ValueType { + DOUBLE, + LONG, + INT, + BOOLEAN, + INT_ARRAY, + ENUM + } + + String name(); + ValueType getValueType(); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableOptionValue.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableOptionValue.java new file mode 100644 index 0000000000..3727f7c1f2 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/MutableOptionValue.java @@ -0,0 +1,375 @@ +package org.rocksdb; + +import static org.rocksdb.AbstractMutableOptions.INT_ARRAY_INT_SEPARATOR; + +public abstract class MutableOptionValue { + + abstract double asDouble() throws NumberFormatException; + abstract long asLong() throws NumberFormatException; + abstract int asInt() throws NumberFormatException; + abstract boolean asBoolean() throws IllegalStateException; + abstract int[] asIntArray() throws IllegalStateException; + abstract String asString(); + abstract T asObject(); + + private static abstract class MutableOptionValueObject + extends MutableOptionValue { + protected final T value; + + private MutableOptionValueObject(final T value) { + this.value = value; + } + + @Override T asObject() { + return value; + } + } + + static MutableOptionValue fromString(final String s) { + return new MutableOptionStringValue(s); + } + + static MutableOptionValue fromDouble(final double d) { + return new MutableOptionDoubleValue(d); + } + + static MutableOptionValue fromLong(final long d) { + return new MutableOptionLongValue(d); + } + + static MutableOptionValue fromInt(final int i) { + return new MutableOptionIntValue(i); + } + + static MutableOptionValue fromBoolean(final boolean b) { + return new MutableOptionBooleanValue(b); + } + + static MutableOptionValue fromIntArray(final int[] ix) { + return new MutableOptionIntArrayValue(ix); + } + + static > MutableOptionValue fromEnum(final N value) { + return new MutableOptionEnumValue<>(value); + } + + static class MutableOptionStringValue + extends MutableOptionValueObject { + MutableOptionStringValue(final String value) { + super(value); + } + + @Override + double asDouble() throws NumberFormatException { + return Double.parseDouble(value); + } + + @Override + long asLong() throws NumberFormatException { + return Long.parseLong(value); + } + + @Override + int asInt() throws NumberFormatException { + return Integer.parseInt(value); + } + + @Override + boolean asBoolean() throws IllegalStateException { + return Boolean.parseBoolean(value); + } + + @Override + int[] asIntArray() throws IllegalStateException { + throw new IllegalStateException("String is not applicable as int[]"); + } + + @Override + String asString() { + return value; + } + } + + static class MutableOptionDoubleValue + extends MutableOptionValue { + private final double value; + MutableOptionDoubleValue(final double value) { + this.value = value; + } + + @Override + double asDouble() { + return value; + } + + @Override + long asLong() throws NumberFormatException { + return Double.valueOf(value).longValue(); + } + + @Override + int asInt() throws NumberFormatException { + if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new NumberFormatException( + "double value lies outside the bounds of int"); + } + return Double.valueOf(value).intValue(); + } + + @Override + boolean asBoolean() throws IllegalStateException { + throw new IllegalStateException( + "double is not applicable as boolean"); + } + + @Override + int[] asIntArray() throws IllegalStateException { + if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new NumberFormatException( + "double value lies outside the bounds of int"); + } + return new int[] { Double.valueOf(value).intValue() }; + } + + @Override + String asString() { + return String.valueOf(value); + } + + @Override + Double asObject() { + return value; + } + } + + static class MutableOptionLongValue + extends MutableOptionValue { + private final long value; + + MutableOptionLongValue(final long value) { + this.value = value; + } + + @Override + double asDouble() { + if(value > Double.MAX_VALUE || value < Double.MIN_VALUE) { + throw new NumberFormatException( + "long value lies outside the bounds of int"); + } + return Long.valueOf(value).doubleValue(); + } + + @Override + long asLong() throws NumberFormatException { + return value; + } + + @Override + int asInt() throws NumberFormatException { + if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new NumberFormatException( + "long value lies outside the bounds of int"); + } + return Long.valueOf(value).intValue(); + } + + @Override + boolean asBoolean() throws IllegalStateException { + throw new IllegalStateException( + "long is not applicable as boolean"); + } + + @Override + int[] asIntArray() throws IllegalStateException { + if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new NumberFormatException( + "long value lies outside the bounds of int"); + } + return new int[] { Long.valueOf(value).intValue() }; + } + + @Override + String asString() { + return String.valueOf(value); + } + + @Override + Long asObject() { + return value; + } + } + + static class MutableOptionIntValue + extends MutableOptionValue { + private final int value; + + MutableOptionIntValue(final int value) { + this.value = value; + } + + @Override + double asDouble() { + if(value > Double.MAX_VALUE || value < Double.MIN_VALUE) { + throw new NumberFormatException("int value lies outside the bounds of int"); + } + return Integer.valueOf(value).doubleValue(); + } + + @Override + long asLong() throws NumberFormatException { + return value; + } + + @Override + int asInt() throws NumberFormatException { + return value; + } + + @Override + boolean asBoolean() throws IllegalStateException { + throw new IllegalStateException("int is not applicable as boolean"); + } + + @Override + int[] asIntArray() throws IllegalStateException { + return new int[] { value }; + } + + @Override + String asString() { + return String.valueOf(value); + } + + @Override + Integer asObject() { + return value; + } + } + + static class MutableOptionBooleanValue + extends MutableOptionValue { + private final boolean value; + + MutableOptionBooleanValue(final boolean value) { + this.value = value; + } + + @Override + double asDouble() { + throw new NumberFormatException("boolean is not applicable as double"); + } + + @Override + long asLong() throws NumberFormatException { + throw new NumberFormatException("boolean is not applicable as Long"); + } + + @Override + int asInt() throws NumberFormatException { + throw new NumberFormatException("boolean is not applicable as int"); + } + + @Override + boolean asBoolean() { + return value; + } + + @Override + int[] asIntArray() throws IllegalStateException { + throw new IllegalStateException("boolean is not applicable as int[]"); + } + + @Override + String asString() { + return String.valueOf(value); + } + + @Override + Boolean asObject() { + return value; + } + } + + static class MutableOptionIntArrayValue + extends MutableOptionValueObject { + MutableOptionIntArrayValue(final int[] value) { + super(value); + } + + @Override + double asDouble() { + throw new NumberFormatException("int[] is not applicable as double"); + } + + @Override + long asLong() throws NumberFormatException { + throw new NumberFormatException("int[] is not applicable as Long"); + } + + @Override + int asInt() throws NumberFormatException { + throw new NumberFormatException("int[] is not applicable as int"); + } + + @Override + boolean asBoolean() { + throw new NumberFormatException("int[] is not applicable as boolean"); + } + + @Override + int[] asIntArray() throws IllegalStateException { + return value; + } + + @Override + String asString() { + final StringBuilder builder = new StringBuilder(); + for(int i = 0; i < value.length; i++) { + builder.append(i); + if(i + 1 < value.length) { + builder.append(INT_ARRAY_INT_SEPARATOR); + } + } + return builder.toString(); + } + } + + static class MutableOptionEnumValue> + extends MutableOptionValueObject { + + MutableOptionEnumValue(final T value) { + super(value); + } + + @Override + double asDouble() throws NumberFormatException { + throw new NumberFormatException("Enum is not applicable as double"); + } + + @Override + long asLong() throws NumberFormatException { + throw new NumberFormatException("Enum is not applicable as long"); + } + + @Override + int asInt() throws NumberFormatException { + throw new NumberFormatException("Enum is not applicable as int"); + } + + @Override + boolean asBoolean() throws IllegalStateException { + throw new NumberFormatException("Enum is not applicable as boolean"); + } + + @Override + int[] asIntArray() throws IllegalStateException { + throw new NumberFormatException("Enum is not applicable as int[]"); + } + + @Override + String asString() { + return value.name(); + } + } + +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java new file mode 100644 index 0000000000..28a427aaa7 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/NativeComparatorWrapper.java @@ -0,0 +1,57 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * A simple abstraction to allow a Java class to wrap a custom comparator + * implemented in C++. + * + * The native comparator must directly extend rocksdb::Comparator. + */ +public abstract class NativeComparatorWrapper + extends AbstractComparator { + + @Override + final ComparatorType getComparatorType() { + return ComparatorType.JAVA_NATIVE_COMPARATOR_WRAPPER; + } + + @Override + public final String name() { + throw new IllegalStateException("This should not be called. " + + "Implementation is in Native code"); + } + + @Override + public final int compare(final Slice s1, final Slice s2) { + throw new IllegalStateException("This should not be called. " + + "Implementation is in Native code"); + } + + @Override + public final String findShortestSeparator(final String start, final Slice limit) { + throw new IllegalStateException("This should not be called. " + + "Implementation is in Native code"); + } + + @Override + public final String findShortSuccessor(final String key) { + throw new IllegalStateException("This should not be called. " + + "Implementation is in Native code"); + } + + /** + * We override {@link RocksCallbackObject#disposeInternal()} + * as disposing of a native rocksd::Comparator extension requires + * a slightly different approach as it is not really a RocksCallbackObject + */ + @Override + protected void disposeInternal() { + disposeInternal(nativeHandle_); + } + + private native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OperationStage.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OperationStage.java new file mode 100644 index 0000000000..6ac0a15a24 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OperationStage.java @@ -0,0 +1,59 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * The operation stage. + */ +public enum OperationStage { + STAGE_UNKNOWN((byte)0x0), + STAGE_FLUSH_RUN((byte)0x1), + STAGE_FLUSH_WRITE_L0((byte)0x2), + STAGE_COMPACTION_PREPARE((byte)0x3), + STAGE_COMPACTION_RUN((byte)0x4), + STAGE_COMPACTION_PROCESS_KV((byte)0x5), + STAGE_COMPACTION_INSTALL((byte)0x6), + STAGE_COMPACTION_SYNC_FILE((byte)0x7), + STAGE_PICK_MEMTABLES_TO_FLUSH((byte)0x8), + STAGE_MEMTABLE_ROLLBACK((byte)0x9), + STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS((byte)0xA); + + private final byte value; + + OperationStage(final byte value) { + this.value = value; + } + + /** + * Get the internal representation value. + * + * @return the internal representation value. + */ + byte getValue() { + return value; + } + + /** + * Get the Operation stage from the internal representation value. + * + * @param value the internal representation value. + * + * @return the operation stage + * + * @throws IllegalArgumentException if the value does not match + * an OperationStage + */ + static OperationStage fromValue(final byte value) + throws IllegalArgumentException { + for (final OperationStage threadType : OperationStage.values()) { + if (threadType.value == value) { + return threadType; + } + } + throw new IllegalArgumentException( + "Unknown value for OperationStage: " + value); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OperationType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OperationType.java new file mode 100644 index 0000000000..7cc9b65cdf --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OperationType.java @@ -0,0 +1,54 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * The type used to refer to a thread operation. + * + * A thread operation describes high-level action of a thread, + * examples include compaction and flush. + */ +public enum OperationType { + OP_UNKNOWN((byte)0x0), + OP_COMPACTION((byte)0x1), + OP_FLUSH((byte)0x2); + + private final byte value; + + OperationType(final byte value) { + this.value = value; + } + + /** + * Get the internal representation value. + * + * @return the internal representation value. + */ + byte getValue() { + return value; + } + + /** + * Get the Operation type from the internal representation value. + * + * @param value the internal representation value. + * + * @return the operation type + * + * @throws IllegalArgumentException if the value does not match + * an OperationType + */ + static OperationType fromValue(final byte value) + throws IllegalArgumentException { + for (final OperationType threadType : OperationType.values()) { + if (threadType.value == value) { + return threadType; + } + } + throw new IllegalArgumentException( + "Unknown value for OperationType: " + value); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java new file mode 100644 index 0000000000..267cab1dec --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionDB.java @@ -0,0 +1,226 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.List; + +/** + * Database with Transaction support. + */ +public class OptimisticTransactionDB extends RocksDB + implements TransactionalDB { + + /** + * Private constructor. + * + * @param nativeHandle The native handle of the C++ OptimisticTransactionDB + * object + */ + private OptimisticTransactionDB(final long nativeHandle) { + super(nativeHandle); + } + + /** + * Open an OptimisticTransactionDB similar to + * {@link RocksDB#open(Options, String)}. + * + * @param options {@link org.rocksdb.Options} instance. + * @param path the path to the rocksdb. + * + * @return a {@link OptimisticTransactionDB} instance on success, null if the + * specified {@link OptimisticTransactionDB} can not be opened. + * + * @throws RocksDBException if an error occurs whilst opening the database. + */ + public static OptimisticTransactionDB open(final Options options, + final String path) throws RocksDBException { + final OptimisticTransactionDB otdb = new OptimisticTransactionDB(open( + options.nativeHandle_, path)); + + // when non-default Options is used, keeping an Options reference + // in RocksDB can prevent Java to GC during the life-time of + // the currently-created RocksDB. + otdb.storeOptionsInstance(options); + + return otdb; + } + + /** + * Open an OptimisticTransactionDB similar to + * {@link RocksDB#open(DBOptions, String, List, List)}. + * + * @param dbOptions {@link org.rocksdb.DBOptions} instance. + * @param path the path to the rocksdb. + * @param columnFamilyDescriptors list of column family descriptors + * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances + * + * @return a {@link OptimisticTransactionDB} instance on success, null if the + * specified {@link OptimisticTransactionDB} can not be opened. + * + * @throws RocksDBException if an error occurs whilst opening the database. + */ + public static OptimisticTransactionDB open(final DBOptions dbOptions, + final String path, + final List columnFamilyDescriptors, + final List columnFamilyHandles) + throws RocksDBException { + + final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; + final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; + for (int i = 0; i < columnFamilyDescriptors.size(); i++) { + final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors + .get(i); + cfNames[i] = cfDescriptor.columnFamilyName(); + cfOptionHandles[i] = cfDescriptor.columnFamilyOptions().nativeHandle_; + } + + final long[] handles = open(dbOptions.nativeHandle_, path, cfNames, + cfOptionHandles); + final OptimisticTransactionDB otdb = + new OptimisticTransactionDB(handles[0]); + + // when non-default Options is used, keeping an Options reference + // in RocksDB can prevent Java to GC during the life-time of + // the currently-created RocksDB. + otdb.storeOptionsInstance(dbOptions); + + for (int i = 1; i < handles.length; i++) { + columnFamilyHandles.add(new ColumnFamilyHandle(otdb, handles[i])); + } + + return otdb; + } + + + /** + * This is similar to {@link #close()} except that it + * throws an exception if any error occurs. + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + * + * @throws RocksDBException if an error occurs whilst closing. + */ + public void closeE() throws RocksDBException { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } finally { + disposeInternal(); + } + } + } + + /** + * This is similar to {@link #closeE()} except that it + * silently ignores any errors. + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + */ + @Override + public void close() { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } catch (final RocksDBException e) { + // silently ignore the error report + } finally { + disposeInternal(); + } + } + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions) { + return new Transaction(this, beginTransaction(nativeHandle_, + writeOptions.nativeHandle_)); + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions, + final OptimisticTransactionOptions optimisticTransactionOptions) { + return new Transaction(this, beginTransaction(nativeHandle_, + writeOptions.nativeHandle_, + optimisticTransactionOptions.nativeHandle_)); + } + + // TODO(AR) consider having beingTransaction(... oldTransaction) set a + // reference count inside Transaction, so that we can always call + // Transaction#close but the object is only disposed when there are as many + // closes as beginTransaction. Makes the try-with-resources paradigm easier for + // java developers + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions, + final Transaction oldTransaction) { + final long jtxn_handle = beginTransaction_withOld(nativeHandle_, + writeOptions.nativeHandle_, oldTransaction.nativeHandle_); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_txn + assert(jtxn_handle == oldTransaction.nativeHandle_); + + return oldTransaction; + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions, + final OptimisticTransactionOptions optimisticTransactionOptions, + final Transaction oldTransaction) { + final long jtxn_handle = beginTransaction_withOld(nativeHandle_, + writeOptions.nativeHandle_, optimisticTransactionOptions.nativeHandle_, + oldTransaction.nativeHandle_); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_txn + assert(jtxn_handle == oldTransaction.nativeHandle_); + + return oldTransaction; + } + + /** + * Get the underlying database that was opened. + * + * @return The underlying database that was opened. + */ + public RocksDB getBaseDB() { + final RocksDB db = new RocksDB(getBaseDB(nativeHandle_)); + db.disOwnNativeHandle(); + return db; + } + + @Override protected final native void disposeInternal(final long handle); + + protected static native long open(final long optionsHandle, + final String path) throws RocksDBException; + protected static native long[] open(final long handle, final String path, + final byte[][] columnFamilyNames, final long[] columnFamilyOptions); + private native static void closeDatabase(final long handle) + throws RocksDBException; + private native long beginTransaction(final long handle, + final long writeOptionsHandle); + private native long beginTransaction(final long handle, + final long writeOptionsHandle, + final long optimisticTransactionOptionsHandle); + private native long beginTransaction_withOld(final long handle, + final long writeOptionsHandle, final long oldTransactionHandle); + private native long beginTransaction_withOld(final long handle, + final long writeOptionsHandle, + final long optimisticTransactionOptionsHandle, + final long oldTransactionHandle); + private native long getBaseDB(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java new file mode 100644 index 0000000000..650ee22550 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptimisticTransactionOptions.java @@ -0,0 +1,53 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public class OptimisticTransactionOptions extends RocksObject + implements TransactionalOptions { + + public OptimisticTransactionOptions() { + super(newOptimisticTransactionOptions()); + } + + @Override + public boolean isSetSnapshot() { + assert(isOwningHandle()); + return isSetSnapshot(nativeHandle_); + } + + @Override + public OptimisticTransactionOptions setSetSnapshot( + final boolean setSnapshot) { + assert(isOwningHandle()); + setSetSnapshot(nativeHandle_, setSnapshot); + return this; + } + + /** + * Should be set if the DB has a non-default comparator. + * See comment in + * {@link WriteBatchWithIndex#WriteBatchWithIndex(AbstractComparator, int, boolean)} + * constructor. + * + * @param comparator The comparator to use for the transaction. + * + * @return this OptimisticTransactionOptions instance + */ + public OptimisticTransactionOptions setComparator( + final AbstractComparator> comparator) { + assert(isOwningHandle()); + setComparator(nativeHandle_, comparator.nativeHandle_); + return this; + } + + private native static long newOptimisticTransactionOptions(); + private native boolean isSetSnapshot(final long handle); + private native void setSetSnapshot(final long handle, + final boolean setSnapshot); + private native void setComparator(final long handle, + final long comparatorHandle); + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Options.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Options.java index dcd1138a8a..5831b1e298 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Options.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Options.java @@ -19,7 +19,9 @@ * automaticallyand native resources will be released as part of the process. */ public class Options extends RocksObject - implements DBOptionsInterface, ColumnFamilyOptionsInterface, + implements DBOptionsInterface, + MutableDBOptionsInterface, + ColumnFamilyOptionsInterface, MutableColumnFamilyOptionsInterface { static { RocksDB.loadLibrary(); @@ -51,6 +53,30 @@ public Options(final DBOptions dbOptions, env_ = Env.getDefault(); } + /** + * Copy constructor for ColumnFamilyOptions. + * + * NOTE: This does a shallow copy, which means comparator, merge_operator + * and other pointers will be cloned! + * + * @param other The Options to copy. + */ + public Options(Options other) { + super(copyOptions(other.nativeHandle_)); + this.env_ = other.env_; + this.memTableConfig_ = other.memTableConfig_; + this.tableFormatConfig_ = other.tableFormatConfig_; + this.rateLimiter_ = other.rateLimiter_; + this.comparator_ = other.comparator_; + this.compactionFilter_ = other.compactionFilter_; + this.compactionFilterFactory_ = other.compactionFilterFactory_; + this.compactionOptionsUniversal_ = other.compactionOptionsUniversal_; + this.compactionOptionsFIFO_ = other.compactionOptionsFIFO_; + this.compressionOptions_ = other.compressionOptions_; + this.rowCache_ = other.rowCache_; + this.writeBufferManager_ = other.writeBufferManager_; + } + @Override public Options setIncreaseParallelism(final int totalThreads) { assert(isOwningHandle()); @@ -169,7 +195,8 @@ public Options setComparator(final BuiltinComparator builtinComparator) { public Options setComparator( final AbstractComparator> comparator) { assert(isOwningHandle()); - setComparatorHandle(nativeHandle_, comparator.getNativeHandle()); + setComparatorHandle(nativeHandle_, comparator.nativeHandle_, + comparator.getComparatorType().getValue()); comparator_ = comparator; return this; } @@ -191,6 +218,35 @@ public Options setMergeOperator(final MergeOperator mergeOperator) { return this; } + @Override + public Options setCompactionFilter( + final AbstractCompactionFilter> + compactionFilter) { + setCompactionFilterHandle(nativeHandle_, compactionFilter.nativeHandle_); + compactionFilter_ = compactionFilter; + return this; + } + + @Override + public AbstractCompactionFilter> compactionFilter() { + assert (isOwningHandle()); + return compactionFilter_; + } + + @Override + public Options setCompactionFilterFactory(final AbstractCompactionFilterFactory> compactionFilterFactory) { + assert (isOwningHandle()); + setCompactionFilterFactoryHandle(nativeHandle_, compactionFilterFactory.nativeHandle_); + compactionFilterFactory_ = compactionFilterFactory; + return this; + } + + @Override + public AbstractCompactionFilterFactory> compactionFilterFactory() { + assert (isOwningHandle()); + return compactionFilterFactory_; + } + @Override public Options setWriteBufferSize(final long writeBufferSize) { assert(isOwningHandle()); @@ -418,9 +474,10 @@ public Options setMaxBackgroundCompactions( } @Override - public void setMaxSubcompactions(final int maxSubcompactions) { + public Options setMaxSubcompactions(final int maxSubcompactions) { assert(isOwningHandle()); setMaxSubcompactions(nativeHandle_, maxSubcompactions); + return this; } @Override @@ -443,6 +500,19 @@ public Options setMaxBackgroundFlushes( return this; } + @Override + public int maxBackgroundJobs() { + assert(isOwningHandle()); + return maxBackgroundJobs(nativeHandle_); + } + + @Override + public Options setMaxBackgroundJobs(final int maxBackgroundJobs) { + assert(isOwningHandle()); + setMaxBackgroundJobs(nativeHandle_, maxBackgroundJobs); + return this; + } + @Override public long maxLogFileSize() { assert(isOwningHandle()); @@ -689,6 +759,20 @@ public Options setDbWriteBufferSize(final long dbWriteBufferSize) { } @Override + public Options setWriteBufferManager(final WriteBufferManager writeBufferManager) { + assert(isOwningHandle()); + setWriteBufferManager(nativeHandle_, writeBufferManager.nativeHandle_); + this.writeBufferManager_ = writeBufferManager; + return this; + } + + @Override + public WriteBufferManager writeBufferManager() { + assert(isOwningHandle()); + return this.writeBufferManager_; + } + + @Override public long dbWriteBufferSize() { assert(isOwningHandle()); return dbWriteBufferSize(nativeHandle_); @@ -824,6 +908,17 @@ public long delayedWriteRate(){ return delayedWriteRate(nativeHandle_); } + @Override + public Options setEnablePipelinedWrite(final boolean enablePipelinedWrite) { + setEnablePipelinedWrite(nativeHandle_, enablePipelinedWrite); + return this; + } + + @Override + public boolean enablePipelinedWrite() { + return enablePipelinedWrite(nativeHandle_); + } + @Override public Options setAllowConcurrentMemtableWrite( final boolean allowConcurrentMemtableWrite) { @@ -925,6 +1020,20 @@ public Cache rowCache() { return this.rowCache_; } + @Override + public Options setWalFilter(final AbstractWalFilter walFilter) { + assert(isOwningHandle()); + setWalFilter(nativeHandle_, walFilter.nativeHandle_); + this.walFilter_ = walFilter; + return this; + } + + @Override + public WalFilter walFilter() { + assert(isOwningHandle()); + return this.walFilter_; + } + @Override public Options setFailIfOptionsFileError(final boolean failIfOptionsFileError) { assert(isOwningHandle()); @@ -977,6 +1086,58 @@ public boolean avoidFlushDuringShutdown() { return avoidFlushDuringShutdown(nativeHandle_); } + @Override + public Options setAllowIngestBehind(final boolean allowIngestBehind) { + assert(isOwningHandle()); + setAllowIngestBehind(nativeHandle_, allowIngestBehind); + return this; + } + + @Override + public boolean allowIngestBehind() { + assert(isOwningHandle()); + return allowIngestBehind(nativeHandle_); + } + + @Override + public Options setPreserveDeletes(final boolean preserveDeletes) { + assert(isOwningHandle()); + setPreserveDeletes(nativeHandle_, preserveDeletes); + return this; + } + + @Override + public boolean preserveDeletes() { + assert(isOwningHandle()); + return preserveDeletes(nativeHandle_); + } + + @Override + public Options setTwoWriteQueues(final boolean twoWriteQueues) { + assert(isOwningHandle()); + setTwoWriteQueues(nativeHandle_, twoWriteQueues); + return this; + } + + @Override + public boolean twoWriteQueues() { + assert(isOwningHandle()); + return twoWriteQueues(nativeHandle_); + } + + @Override + public Options setManualWalFlush(final boolean manualWalFlush) { + assert(isOwningHandle()); + setManualWalFlush(nativeHandle_, manualWalFlush); + return this; + } + + @Override + public boolean manualWalFlush() { + assert(isOwningHandle()); + return manualWalFlush(nativeHandle_); + } + @Override public MemTableConfig memTableConfig() { return this.memTableConfig_; @@ -997,6 +1158,13 @@ public Options setRateLimiter(final RateLimiter rateLimiter) { return this; } + @Override + public Options setSstFileManager(final SstFileManager sstFileManager) { + assert(isOwningHandle()); + setSstFileManager(nativeHandle_, sstFileManager.nativeHandle_); + return this; + } + @Override public Options setLogger(final Logger logger) { assert(isOwningHandle()); @@ -1106,6 +1274,20 @@ public CompressionType bottommostCompressionType() { bottommostCompressionType(nativeHandle_)); } + @Override + public Options setBottommostCompressionOptions( + final CompressionOptions bottommostCompressionOptions) { + setBottommostCompressionOptions(nativeHandle_, + bottommostCompressionOptions.nativeHandle_); + this.bottommostCompressionOptions_ = bottommostCompressionOptions; + return this; + } + + @Override + public CompressionOptions bottommostCompressionOptions() { + return this.bottommostCompressionOptions_; + } + @Override public Options setCompressionOptions( final CompressionOptions compressionOptions) { @@ -1121,7 +1303,7 @@ public CompressionOptions compressionOptions() { @Override public CompactionStyle compactionStyle() { - return CompactionStyle.values()[compactionStyle(nativeHandle_)]; + return CompactionStyle.fromValue(compactionStyle(nativeHandle_)); } @Override @@ -1493,6 +1675,17 @@ public boolean reportBgIoStats() { return reportBgIoStats(nativeHandle_); } + @Override + public Options setTtl(final long ttl) { + setTtl(nativeHandle_, ttl); + return this; + } + + @Override + public long ttl() { + return ttl(nativeHandle_); + } + @Override public Options setCompactionOptionsUniversal( final CompactionOptionsUniversal compactionOptionsUniversal) { @@ -1531,9 +1724,21 @@ public boolean forceConsistencyChecks() { return forceConsistencyChecks(nativeHandle_); } + @Override + public Options setAtomicFlush(final boolean atomicFlush) { + setAtomicFlush(nativeHandle_, atomicFlush); + return this; + } + + @Override + public boolean atomicFlush() { + return atomicFlush(nativeHandle_); + } + private native static long newOptions(); private native static long newOptions(long dbOptHandle, long cfOptHandle); + private native static long copyOptions(long handle); @Override protected final native void disposeInternal(final long handle); private native void setEnv(long optHandle, long envHandle); private native void prepareForBulkLoad(long handle); @@ -1552,6 +1757,8 @@ private native void setParanoidChecks( private native boolean paranoidChecks(long handle); private native void setRateLimiter(long handle, long rateLimiterHandle); + private native void setSstFileManager(final long handle, + final long sstFileManagerHandle); private native void setLogger(long handle, long loggerHandle); private native void setInfoLogLevel(long handle, byte logLevel); @@ -1591,6 +1798,8 @@ private native void setMaxBackgroundCompactions( private native void setMaxBackgroundFlushes( long handle, int maxBackgroundFlushes); private native int maxBackgroundFlushes(long handle); + private native void setMaxBackgroundJobs(long handle, int maxMaxBackgroundJobs); + private native int maxBackgroundJobs(long handle); private native void setMaxLogFileSize(long handle, long maxLogFileSize) throws IllegalArgumentException; private native long maxLogFileSize(long handle); @@ -1643,6 +1852,8 @@ private native void setAdviseRandomOnOpen( private native boolean adviseRandomOnOpen(long handle); private native void setDbWriteBufferSize(final long handle, final long dbWriteBufferSize); + private native void setWriteBufferManager(final long handle, + final long writeBufferManagerHandle); private native long dbWriteBufferSize(final long handle); private native void setAccessHintOnCompactionStart(final long handle, final byte accessHintOnCompactionStart); @@ -1672,6 +1883,9 @@ private native void setEnableThreadTracking(long handle, private native boolean enableThreadTracking(long handle); private native void setDelayedWriteRate(long handle, long delayedWriteRate); private native long delayedWriteRate(long handle); + private native void setEnablePipelinedWrite(final long handle, + final boolean pipelinedWrite); + private native boolean enablePipelinedWrite(final long handle); private native void setAllowConcurrentMemtableWrite(long handle, boolean allowConcurrentMemtableWrite); private native boolean allowConcurrentMemtableWrite(long handle); @@ -1694,7 +1908,9 @@ private native void setAllow2pc(final long handle, final boolean allow2pc); private native boolean allow2pc(final long handle); private native void setRowCache(final long handle, - final long row_cache_handle); + final long rowCacheHandle); + private native void setWalFilter(final long handle, + final long walFilterHandle); private native void setFailIfOptionsFileError(final long handle, final boolean failIfOptionsFileError); private native boolean failIfOptionsFileError(final long handle); @@ -1707,6 +1923,19 @@ private native void setAvoidFlushDuringRecovery(final long handle, private native void setAvoidFlushDuringShutdown(final long handle, final boolean avoidFlushDuringShutdown); private native boolean avoidFlushDuringShutdown(final long handle); + private native void setAllowIngestBehind(final long handle, + final boolean allowIngestBehind); + private native boolean allowIngestBehind(final long handle); + private native void setPreserveDeletes(final long handle, + final boolean preserveDeletes); + private native boolean preserveDeletes(final long handle); + private native void setTwoWriteQueues(final long handle, + final boolean twoWriteQueues); + private native boolean twoWriteQueues(final long handle); + private native void setManualWalFlush(final long handle, + final boolean manualWalFlush); + private native boolean manualWalFlush(final long handle); + // CF native handles private native void optimizeForSmallDb(final long handle); @@ -1718,11 +1947,15 @@ private native void optimizeUniversalStyleCompaction(long handle, long memtableMemoryBudget); private native void setComparatorHandle(long handle, int builtinComparator); private native void setComparatorHandle(long optHandle, - long comparatorHandle); + long comparatorHandle, byte comparatorType); private native void setMergeOperatorName( long handle, String name); private native void setMergeOperator( long handle, long mergeOperatorHandle); + private native void setCompactionFilterHandle( + long handle, long compactionFilterHandle); + private native void setCompactionFilterFactoryHandle( + long handle, long compactionFilterFactoryHandle); private native void setWriteBufferSize(long handle, long writeBufferSize) throws IllegalArgumentException; private native long writeBufferSize(long handle); @@ -1740,6 +1973,8 @@ private native void setCompressionPerLevel(long handle, private native void setBottommostCompressionType(long handle, byte bottommostCompressionType); private native byte bottommostCompressionType(long handle); + private native void setBottommostCompressionOptions(final long handle, + final long bottommostCompressionOptionsHandle); private native void setCompressionOptions(long handle, long compressionOptionsHandle); private native void useFixedLengthPrefixExtractor( @@ -1843,6 +2078,8 @@ private native void setCompactionPriority(final long handle, private native void setReportBgIoStats(final long handle, final boolean reportBgIoStats); private native boolean reportBgIoStats(final long handle); + private native void setTtl(final long handle, final long ttl); + private native long ttl(final long handle); private native void setCompactionOptionsUniversal(final long handle, final long compactionOptionsUniversalHandle); private native void setCompactionOptionsFIFO(final long handle, @@ -1850,15 +2087,25 @@ private native void setCompactionOptionsFIFO(final long handle, private native void setForceConsistencyChecks(final long handle, final boolean forceConsistencyChecks); private native boolean forceConsistencyChecks(final long handle); + private native void setAtomicFlush(final long handle, + final boolean atomicFlush); + private native boolean atomicFlush(final long handle); // instance variables + // NOTE: If you add new member variables, please update the copy constructor above! private Env env_; private MemTableConfig memTableConfig_; private TableFormatConfig tableFormatConfig_; private RateLimiter rateLimiter_; private AbstractComparator> comparator_; + private AbstractCompactionFilter> compactionFilter_; + private AbstractCompactionFilterFactory> + compactionFilterFactory_; private CompactionOptionsUniversal compactionOptionsUniversal_; private CompactionOptionsFIFO compactionOptionsFIFO_; + private CompressionOptions bottommostCompressionOptions_; private CompressionOptions compressionOptions_; private Cache rowCache_; + private WalFilter walFilter_; + private WriteBufferManager writeBufferManager_; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptionsUtil.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptionsUtil.java new file mode 100644 index 0000000000..f153556ba3 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/OptionsUtil.java @@ -0,0 +1,142 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.ArrayList; +import java.util.List; + +public class OptionsUtil { + /** + * A static method to construct the DBOptions and ColumnFamilyDescriptors by + * loading the latest RocksDB options file stored in the specified rocksdb + * database. + * + * Note that the all the pointer options (except table_factory, which will + * be described in more details below) will be initialized with the default + * values. Developers can further initialize them after this function call. + * Below is an example list of pointer options which will be initialized. + * + * - env + * - memtable_factory + * - compaction_filter_factory + * - prefix_extractor + * - comparator + * - merge_operator + * - compaction_filter + * + * For table_factory, this function further supports deserializing + * BlockBasedTableFactory and its BlockBasedTableOptions except the + * pointer options of BlockBasedTableOptions (flush_block_policy_factory, + * block_cache, and block_cache_compressed), which will be initialized with + * default values. Developers can further specify these three options by + * casting the return value of TableFactoroy::GetOptions() to + * BlockBasedTableOptions and making necessary changes. + * + * @param dbPath the path to the RocksDB. + * @param env {@link org.rocksdb.Env} instance. + * @param dbOptions {@link org.rocksdb.DBOptions} instance. This will be + * filled and returned. + * @param cfDescs A list of {@link org.rocksdb.ColumnFamilyDescriptor}'s be + * returned. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + + public static void loadLatestOptions(String dbPath, Env env, DBOptions dbOptions, + List cfDescs) throws RocksDBException { + loadLatestOptions(dbPath, env, dbOptions, cfDescs, false); + } + + /** + * @param dbPath the path to the RocksDB. + * @param env {@link org.rocksdb.Env} instance. + * @param dbOptions {@link org.rocksdb.DBOptions} instance. This will be + * filled and returned. + * @param cfDescs A list of {@link org.rocksdb.ColumnFamilyDescriptor}'s be + * returned. + * @param ignoreUnknownOptions this flag can be set to true if you want to + * ignore options that are from a newer version of the db, esentially for + * forward compatibility. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public static void loadLatestOptions(String dbPath, Env env, DBOptions dbOptions, + List cfDescs, boolean ignoreUnknownOptions) throws RocksDBException { + loadLatestOptions( + dbPath, env.nativeHandle_, dbOptions.nativeHandle_, cfDescs, ignoreUnknownOptions); + } + + /** + * Similar to LoadLatestOptions, this function constructs the DBOptions + * and ColumnFamilyDescriptors based on the specified RocksDB Options file. + * See LoadLatestOptions above. + * + * @param optionsFileName the RocksDB options file path. + * @param env {@link org.rocksdb.Env} instance. + * @param dbOptions {@link org.rocksdb.DBOptions} instance. This will be + * filled and returned. + * @param cfDescs A list of {@link org.rocksdb.ColumnFamilyDescriptor}'s be + * returned. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public static void loadOptionsFromFile(String optionsFileName, Env env, DBOptions dbOptions, + List cfDescs) throws RocksDBException { + loadOptionsFromFile(optionsFileName, env, dbOptions, cfDescs, false); + } + + /** + * @param optionsFileName the RocksDB options file path. + * @param env {@link org.rocksdb.Env} instance. + * @param dbOptions {@link org.rocksdb.DBOptions} instance. This will be + * filled and returned. + * @param cfDescs A list of {@link org.rocksdb.ColumnFamilyDescriptor}'s be + * returned. + * @param ignoreUnknownOptions this flag can be set to true if you want to + * ignore options that are from a newer version of the db, esentially for + * forward compatibility. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public static void loadOptionsFromFile(String optionsFileName, Env env, DBOptions dbOptions, + List cfDescs, boolean ignoreUnknownOptions) throws RocksDBException { + loadOptionsFromFile( + optionsFileName, env.nativeHandle_, dbOptions.nativeHandle_, cfDescs, ignoreUnknownOptions); + } + + /** + * Returns the latest options file name under the specified RocksDB path. + * + * @param dbPath the path to the RocksDB. + * @param env {@link org.rocksdb.Env} instance. + * @return the latest options file name under the db path. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public static String getLatestOptionsFileName(String dbPath, Env env) throws RocksDBException { + return getLatestOptionsFileName(dbPath, env.nativeHandle_); + } + + /** + * Private constructor. + * This class has only static methods and shouldn't be instantiated. + */ + private OptionsUtil() {} + + // native methods + private native static void loadLatestOptions(String dbPath, long envHandle, long dbOptionsHandle, + List cfDescs, boolean ignoreUnknownOptions) throws RocksDBException; + private native static void loadOptionsFromFile(String optionsFileName, long envHandle, + long dbOptionsHandle, List cfDescs, boolean ignoreUnknownOptions) + throws RocksDBException; + private native static String getLatestOptionsFileName(String dbPath, long envHandle) + throws RocksDBException; +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/PersistentCache.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/PersistentCache.java new file mode 100644 index 0000000000..aed5652973 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/PersistentCache.java @@ -0,0 +1,26 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Persistent cache for caching IO pages on a persistent medium. The + * cache is specifically designed for persistent read cache. + */ +public class PersistentCache extends RocksObject { + + public PersistentCache(final Env env, final String path, final long size, + final Logger logger, final boolean optimizedForNvm) + throws RocksDBException { + super(newPersistentCache(env.nativeHandle_, path, size, + logger.nativeHandle_, optimizedForNvm)); + } + + private native static long newPersistentCache(final long envHandle, + final String path, final long size, final long loggerHandle, + final boolean optimizedForNvm) throws RocksDBException; + + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Priority.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Priority.java new file mode 100644 index 0000000000..34a56edcbc --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Priority.java @@ -0,0 +1,49 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * The Thread Pool priority. + */ +public enum Priority { + BOTTOM((byte) 0x0), + LOW((byte) 0x1), + HIGH((byte)0x2), + TOTAL((byte)0x3); + + private final byte value; + + Priority(final byte value) { + this.value = value; + } + + /** + *

    Returns the byte value of the enumerations value.

    + * + * @return byte representation + */ + byte getValue() { + return value; + } + + /** + * Get Priority by byte value. + * + * @param value byte representation of Priority. + * + * @return {@link org.rocksdb.Priority} instance. + * @throws java.lang.IllegalArgumentException if an invalid + * value is provided. + */ + static Priority getPriority(final byte value) { + for (final Priority priority : Priority.values()) { + if (priority.getValue() == value){ + return priority; + } + } + throw new IllegalArgumentException("Illegal value provided for Priority."); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Range.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Range.java new file mode 100644 index 0000000000..74c85e5f04 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Range.java @@ -0,0 +1,19 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Range from start to limit. + */ +public class Range { + final Slice start; + final Slice limit; + + public Range(final Slice start, final Slice limit) { + this.start = start; + this.limit = limit; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java index fc2388777e..c2b8a0fd92 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RateLimiter.java @@ -12,8 +12,11 @@ * @since 3.10.0 */ public class RateLimiter extends RocksObject { - private static final long DEFAULT_REFILL_PERIOD_MICROS = (100 * 1000); - private static final int DEFAULT_FAIRNESS = 10; + public static final long DEFAULT_REFILL_PERIOD_MICROS = 100 * 1000; + public static final int DEFAULT_FAIRNESS = 10; + public static final RateLimiterMode DEFAULT_MODE = + RateLimiterMode.WRITES_ONLY; + public static final boolean DEFAULT_AUTOTUNE = false; /** * RateLimiter constructor @@ -21,24 +24,62 @@ public class RateLimiter extends RocksObject { * @param rateBytesPerSecond this is the only parameter you want to set * most of the time. It controls the total write rate of compaction * and flush in bytes per second. Currently, RocksDB does not enforce - * rate limit for anything other than flush and compaction, e.g. write to WAL. - * @param refillPeriodMicros this controls how often tokens are refilled. For example, + * rate limit for anything other than flush and compaction, e.g. write to + * WAL. + */ + public RateLimiter(final long rateBytesPerSecond) { + this(rateBytesPerSecond, DEFAULT_REFILL_PERIOD_MICROS, DEFAULT_FAIRNESS, + DEFAULT_MODE, DEFAULT_AUTOTUNE); + } + + /** + * RateLimiter constructor + * + * @param rateBytesPerSecond this is the only parameter you want to set + * most of the time. It controls the total write rate of compaction + * and flush in bytes per second. Currently, RocksDB does not enforce + * rate limit for anything other than flush and compaction, e.g. write to + * WAL. + * @param refillPeriodMicros this controls how often tokens are refilled. For + * example, + * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to + * 100ms, then 1MB is refilled every 100ms internally. Larger value can + * lead to burstier writes while smaller value introduces more CPU + * overhead. The default of 100,000ms should work for most cases. + */ + public RateLimiter(final long rateBytesPerSecond, + final long refillPeriodMicros) { + this(rateBytesPerSecond, refillPeriodMicros, DEFAULT_FAIRNESS, DEFAULT_MODE, + DEFAULT_AUTOTUNE); + } + + /** + * RateLimiter constructor + * + * @param rateBytesPerSecond this is the only parameter you want to set + * most of the time. It controls the total write rate of compaction + * and flush in bytes per second. Currently, RocksDB does not enforce + * rate limit for anything other than flush and compaction, e.g. write to + * WAL. + * @param refillPeriodMicros this controls how often tokens are refilled. For + * example, * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to - * 100ms, then 1MB is refilled every 100ms internally. Larger value can lead to - * burstier writes while smaller value introduces more CPU overhead. - * The default should work for most cases. + * 100ms, then 1MB is refilled every 100ms internally. Larger value can + * lead to burstier writes while smaller value introduces more CPU + * overhead. The default of 100,000ms should work for most cases. * @param fairness RateLimiter accepts high-pri requests and low-pri requests. - * A low-pri request is usually blocked in favor of hi-pri request. Currently, - * RocksDB assigns low-pri to request from compaction and high-pri to request - * from flush. Low-pri requests can get blocked if flush requests come in - * continuously. This fairness parameter grants low-pri requests permission by - * fairness chance even though high-pri requests exist to avoid starvation. + * A low-pri request is usually blocked in favor of hi-pri request. + * Currently, RocksDB assigns low-pri to request from compaction and + * high-pri to request from flush. Low-pri requests can get blocked if + * flush requests come in continuously. This fairness parameter grants + * low-pri requests permission by fairness chance even though high-pri + * requests exist to avoid starvation. * You should be good by leaving it at default 10. */ public RateLimiter(final long rateBytesPerSecond, final long refillPeriodMicros, final int fairness) { - super(newRateLimiterHandle(rateBytesPerSecond, - refillPeriodMicros, fairness)); + this(rateBytesPerSecond, refillPeriodMicros, fairness, DEFAULT_MODE, + DEFAULT_AUTOTUNE); } /** @@ -47,10 +88,65 @@ public RateLimiter(final long rateBytesPerSecond, * @param rateBytesPerSecond this is the only parameter you want to set * most of the time. It controls the total write rate of compaction * and flush in bytes per second. Currently, RocksDB does not enforce - * rate limit for anything other than flush and compaction, e.g. write to WAL. + * rate limit for anything other than flush and compaction, e.g. write to + * WAL. + * @param refillPeriodMicros this controls how often tokens are refilled. For + * example, + * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to + * 100ms, then 1MB is refilled every 100ms internally. Larger value can + * lead to burstier writes while smaller value introduces more CPU + * overhead. The default of 100,000ms should work for most cases. + * @param fairness RateLimiter accepts high-pri requests and low-pri requests. + * A low-pri request is usually blocked in favor of hi-pri request. + * Currently, RocksDB assigns low-pri to request from compaction and + * high-pri to request from flush. Low-pri requests can get blocked if + * flush requests come in continuously. This fairness parameter grants + * low-pri requests permission by fairness chance even though high-pri + * requests exist to avoid starvation. + * You should be good by leaving it at default 10. + * @param rateLimiterMode indicates which types of operations count against + * the limit. */ - public RateLimiter(final long rateBytesPerSecond) { - this(rateBytesPerSecond, DEFAULT_REFILL_PERIOD_MICROS, DEFAULT_FAIRNESS); + public RateLimiter(final long rateBytesPerSecond, + final long refillPeriodMicros, final int fairness, + final RateLimiterMode rateLimiterMode) { + this(rateBytesPerSecond, refillPeriodMicros, fairness, rateLimiterMode, + DEFAULT_AUTOTUNE); + } + + /** + * RateLimiter constructor + * + * @param rateBytesPerSecond this is the only parameter you want to set + * most of the time. It controls the total write rate of compaction + * and flush in bytes per second. Currently, RocksDB does not enforce + * rate limit for anything other than flush and compaction, e.g. write to + * WAL. + * @param refillPeriodMicros this controls how often tokens are refilled. For + * example, + * when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to + * 100ms, then 1MB is refilled every 100ms internally. Larger value can + * lead to burstier writes while smaller value introduces more CPU + * overhead. The default of 100,000ms should work for most cases. + * @param fairness RateLimiter accepts high-pri requests and low-pri requests. + * A low-pri request is usually blocked in favor of hi-pri request. + * Currently, RocksDB assigns low-pri to request from compaction and + * high-pri to request from flush. Low-pri requests can get blocked if + * flush requests come in continuously. This fairness parameter grants + * low-pri requests permission by fairness chance even though high-pri + * requests exist to avoid starvation. + * You should be good by leaving it at default 10. + * @param rateLimiterMode indicates which types of operations count against + * the limit. + * @param autoTune Enables dynamic adjustment of rate limit within the range + * {@code [rate_bytes_per_sec / 20, rate_bytes_per_sec]}, according to + * the recent demand for background I/O. + */ + public RateLimiter(final long rateBytesPerSecond, + final long refillPeriodMicros, final int fairness, + final RateLimiterMode rateLimiterMode, final boolean autoTune) { + super(newRateLimiterHandle(rateBytesPerSecond, + refillPeriodMicros, fairness, rateLimiterMode.getValue(), autoTune)); } /** @@ -64,6 +160,16 @@ public void setBytesPerSecond(final long bytesPerSecond) { setBytesPerSecond(nativeHandle_, bytesPerSecond); } + /** + * Returns the bytes per second. + * + * @return bytes per second. + */ + public long getBytesPerSecond() { + assert(isOwningHandle()); + return getBytesPerSecond(nativeHandle_); + } + /** *

    Request for token to write bytes. If this request can not be satisfied, * the call is blocked. Caller is responsible to make sure @@ -87,9 +193,9 @@ public long getSingleBurstBytes() { } /** - *

    Total bytes that go though rate limiter.

    + *

    Total bytes that go through rate limiter.

    * - * @return total bytes that go though rate limiter. + * @return total bytes that go through rate limiter. */ public long getTotalBytesThrough() { assert(isOwningHandle()); @@ -97,9 +203,9 @@ public long getTotalBytesThrough() { } /** - *

    Total # of requests that go though rate limiter.

    + *

    Total # of requests that go through rate limiter.

    * - * @return total # of requests that go though rate limiter. + * @return total # of requests that go through rate limiter. */ public long getTotalRequests() { assert(isOwningHandle()); @@ -107,11 +213,13 @@ public long getTotalRequests() { } private static native long newRateLimiterHandle(final long rateBytesPerSecond, - final long refillPeriodMicros, final int fairness); + final long refillPeriodMicros, final int fairness, + final byte rateLimiterMode, final boolean autoTune); @Override protected final native void disposeInternal(final long handle); private native void setBytesPerSecond(final long handle, final long bytesPerSecond); + private native long getBytesPerSecond(final long handle); private native void request(final long handle, final long bytes); private native long getSingleBurstBytes(final long handle); private native long getTotalBytesThrough(final long handle); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RateLimiterMode.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RateLimiterMode.java new file mode 100644 index 0000000000..4b029d8165 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RateLimiterMode.java @@ -0,0 +1,52 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Mode for {@link RateLimiter#RateLimiter(long, long, int, RateLimiterMode)}. + */ +public enum RateLimiterMode { + READS_ONLY((byte)0x0), + WRITES_ONLY((byte)0x1), + ALL_IO((byte)0x2); + + private final byte value; + + RateLimiterMode(final byte value) { + this.value = value; + } + + /** + *

    Returns the byte value of the enumerations value.

    + * + * @return byte representation + */ + public byte getValue() { + return value; + } + + /** + *

    Get the RateLimiterMode enumeration value by + * passing the byte identifier to this method.

    + * + * @param byteIdentifier of RateLimiterMode. + * + * @return AccessHint instance. + * + * @throws IllegalArgumentException if the access hint for the byteIdentifier + * cannot be found + */ + public static RateLimiterMode getRateLimiterMode(final byte byteIdentifier) { + for (final RateLimiterMode rateLimiterMode : RateLimiterMode.values()) { + if (rateLimiterMode.getValue() == byteIdentifier) { + return rateLimiterMode; + } + } + + throw new IllegalArgumentException( + "Illegal value provided for RateLimiterMode."); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java index 9d7b999561..8353e0fe83 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadOptions.java @@ -16,6 +16,29 @@ public ReadOptions() { super(newReadOptions()); } + /** + * @param verifyChecksums verification will be performed on every read + * when set to true + * @param fillCache if true, then fill-cache behavior will be performed. + */ + public ReadOptions(final boolean verifyChecksums, final boolean fillCache) { + super(newReadOptions(verifyChecksums, fillCache)); + } + + /** + * Copy constructor. + * + * NOTE: This does a shallow copy, which means snapshot, iterate_upper_bound + * and other pointers will be cloned! + * + * @param other The ReadOptions to copy. + */ + public ReadOptions(ReadOptions other) { + super(copyReadOptions(other.nativeHandle_)); + this.iterateLowerBoundSlice_ = other.iterateLowerBoundSlice_; + this.iterateUpperBoundSlice_ = other.iterateUpperBoundSlice_; + } + /** * If true, all data read from underlying storage will be * verified against corresponding checksums. @@ -168,8 +191,12 @@ public ReadOptions setTailing(final boolean tailing) { /** * Returns whether managed iterators will be used. * - * @return the setting of whether managed iterators will be used, by default false + * @return the setting of whether managed iterators will be used, + * by default false + * + * @deprecated This options is not used anymore. */ + @Deprecated public boolean managed() { assert(isOwningHandle()); return managed(nativeHandle_); @@ -182,7 +209,10 @@ public boolean managed() { * * @param managed if true, then managed iterators will be enabled. * @return the reference to the current ReadOptions. + * + * @deprecated This options is not used anymore. */ + @Deprecated public ReadOptions setManaged(final boolean managed) { assert(isOwningHandle()); setManaged(nativeHandle_, managed); @@ -224,7 +254,6 @@ public boolean prefixSameAsStart() { return prefixSameAsStart(nativeHandle_); } - /** * Enforce that the iterator only iterates over the same prefix as the seek. * This option is effective only for prefix seeks, i.e. prefix_extractor is @@ -332,6 +361,37 @@ public ReadOptions setReadaheadSize(final long readaheadSize) { return this; } + /** + * A threshold for the number of keys that can be skipped before failing an + * iterator seek as incomplete. + * + * @return the number of keys that can be skipped + * before failing an iterator seek as incomplete. + */ + public long maxSkippableInternalKeys() { + assert(isOwningHandle()); + return maxSkippableInternalKeys(nativeHandle_); + } + + /** + * A threshold for the number of keys that can be skipped before failing an + * iterator seek as incomplete. The default value of 0 should be used to + * never fail a request as incomplete, even on skipping too many keys. + * + * Default: 0 + * + * @param maxSkippableInternalKeys the number of keys that can be skipped + * before failing an iterator seek as incomplete. + * + * @return the reference to the current ReadOptions. + */ + public ReadOptions setMaxSkippableInternalKeys( + final long maxSkippableInternalKeys) { + assert(isOwningHandle()); + setMaxSkippableInternalKeys(nativeHandle_, maxSkippableInternalKeys); + return this; + } + /** * If true, keys deleted using the DeleteRange() API will be visible to * readers until they are naturally deleted during compaction. This improves @@ -363,7 +423,162 @@ public ReadOptions setIgnoreRangeDeletions(final boolean ignoreRangeDeletions) { return this; } + /** + * Defines the smallest key at which the backward + * iterator can return an entry. Once the bound is passed, + * {@link RocksIterator#isValid()} will be false. + * + * The lower bound is inclusive i.e. the bound value is a valid + * entry. + * + * If prefix_extractor is not null, the Seek target and `iterate_lower_bound` + * need to have the same prefix. This is because ordering is not guaranteed + * outside of prefix domain. + * + * Default: null + * + * @param iterateLowerBound Slice representing the upper bound + * @return the reference to the current ReadOptions. + */ + public ReadOptions setIterateLowerBound(final Slice iterateLowerBound) { + assert(isOwningHandle()); + if (iterateLowerBound != null) { + // Hold onto a reference so it doesn't get garbage collected out from under us. + iterateLowerBoundSlice_ = iterateLowerBound; + setIterateLowerBound(nativeHandle_, iterateLowerBoundSlice_.getNativeHandle()); + } + return this; + } + + /** + * Returns the smallest key at which the backward + * iterator can return an entry. + * + * The lower bound is inclusive i.e. the bound value is a valid entry. + * + * @return the smallest key, or null if there is no lower bound defined. + */ + public Slice iterateLowerBound() { + assert(isOwningHandle()); + final long lowerBoundSliceHandle = iterateLowerBound(nativeHandle_); + if (lowerBoundSliceHandle != 0) { + // Disown the new slice - it's owned by the C++ side of the JNI boundary + // from the perspective of this method. + return new Slice(lowerBoundSliceHandle, false); + } + return null; + } + + /** + * Defines the extent up to which the forward iterator + * can returns entries. Once the bound is reached, + * {@link RocksIterator#isValid()} will be false. + * + * The upper bound is exclusive i.e. the bound value is not a valid entry. + * + * If iterator_extractor is not null, the Seek target and iterate_upper_bound + * need to have the same prefix. This is because ordering is not guaranteed + * outside of prefix domain. + * + * Default: null + * + * @param iterateUpperBound Slice representing the upper bound + * @return the reference to the current ReadOptions. + */ + public ReadOptions setIterateUpperBound(final Slice iterateUpperBound) { + assert(isOwningHandle()); + if (iterateUpperBound != null) { + // Hold onto a reference so it doesn't get garbage collected out from under us. + iterateUpperBoundSlice_ = iterateUpperBound; + setIterateUpperBound(nativeHandle_, iterateUpperBoundSlice_.getNativeHandle()); + } + return this; + } + + /** + * Returns the largest key at which the forward + * iterator can return an entry. + * + * The upper bound is exclusive i.e. the bound value is not a valid entry. + * + * @return the largest key, or null if there is no upper bound defined. + */ + public Slice iterateUpperBound() { + assert(isOwningHandle()); + final long upperBoundSliceHandle = iterateUpperBound(nativeHandle_); + if (upperBoundSliceHandle != 0) { + // Disown the new slice - it's owned by the C++ side of the JNI boundary + // from the perspective of this method. + return new Slice(upperBoundSliceHandle, false); + } + return null; + } + + /** + * A callback to determine whether relevant keys for this scan exist in a + * given table based on the table's properties. The callback is passed the + * properties of each table during iteration. If the callback returns false, + * the table will not be scanned. This option only affects Iterators and has + * no impact on point lookups. + * + * Default: null (every table will be scanned) + * + * @param tableFilter the table filter for the callback. + * + * @return the reference to the current ReadOptions. + */ + public ReadOptions setTableFilter(final AbstractTableFilter tableFilter) { + assert(isOwningHandle()); + setTableFilter(nativeHandle_, tableFilter.nativeHandle_); + return this; + } + + /** + * Needed to support differential snapshots. Has 2 effects: + * 1) Iterator will skip all internal keys with seqnum < iter_start_seqnum + * 2) if this param > 0 iterator will return INTERNAL keys instead of user + * keys; e.g. return tombstones as well. + * + * Default: 0 (don't filter by seqnum, return user keys) + * + * @param startSeqnum the starting sequence number. + * + * @return the reference to the current ReadOptions. + */ + public ReadOptions setIterStartSeqnum(final long startSeqnum) { + assert(isOwningHandle()); + setIterStartSeqnum(nativeHandle_, startSeqnum); + return this; + } + + /** + * Returns the starting Sequence Number of any iterator. + * See {@link #setIterStartSeqnum(long)}. + * + * @return the starting sequence number of any iterator. + */ + public long iterStartSeqnum() { + assert(isOwningHandle()); + return iterStartSeqnum(nativeHandle_); + } + + // instance variables + // NOTE: If you add new member variables, please update the copy constructor above! + // + // Hold a reference to any iterate lower or upper bound that was set on this + // object until we're destroyed or it's overwritten. That way the caller can + // freely leave scope without us losing the Java Slice object, which during + // close() would also reap its associated rocksdb::Slice native object since + // it's possibly (likely) to be an owning handle. + private Slice iterateLowerBoundSlice_; + private Slice iterateUpperBoundSlice_; + private native static long newReadOptions(); + private native static long newReadOptions(final boolean verifyChecksums, + final boolean fillCache); + private native static long copyReadOptions(long handle); + @Override protected final native void disposeInternal(final long handle); + private native boolean verifyChecksums(long handle); private native void setVerifyChecksums(long handle, boolean verifyChecksums); private native boolean fillCache(long handle); @@ -388,10 +603,20 @@ private native void setBackgroundPurgeOnIteratorCleanup(final long handle, private native long readaheadSize(final long handle); private native void setReadaheadSize(final long handle, final long readaheadSize); + private native long maxSkippableInternalKeys(final long handle); + private native void setMaxSkippableInternalKeys(final long handle, + final long maxSkippableInternalKeys); private native boolean ignoreRangeDeletions(final long handle); private native void setIgnoreRangeDeletions(final long handle, final boolean ignoreRangeDeletions); - - @Override protected final native void disposeInternal(final long handle); - + private native void setIterateUpperBound(final long handle, + final long upperBoundSliceHandle); + private native long iterateUpperBound(final long handle); + private native void setIterateLowerBound(final long handle, + final long lowerBoundSliceHandle); + private native long iterateLowerBound(final long handle); + private native void setTableFilter(final long handle, + final long tableFilterHandle); + private native void setIterStartSeqnum(final long handle, final long seqNum); + private native long iterStartSeqnum(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java index 6dc76c52e5..78f83f6ad6 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ReadTier.java @@ -11,7 +11,8 @@ public enum ReadTier { READ_ALL_TIER((byte)0), BLOCK_CACHE_TIER((byte)1), - PERSISTED_TIER((byte)2); + PERSISTED_TIER((byte)2), + MEMTABLE_TIER((byte)3); private final byte value; diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksCallbackObject.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksCallbackObject.java new file mode 100644 index 0000000000..a662f78fd7 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksCallbackObject.java @@ -0,0 +1,50 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * RocksCallbackObject is similar to {@link RocksObject} but varies + * in its construction as it is designed for Java objects which have functions + * which are called from C++ via JNI. + * + * RocksCallbackObject is the base-class any RocksDB classes that acts as a + * callback from some underlying underlying native C++ {@code rocksdb} object. + * + * The use of {@code RocksObject} should always be preferred over + * {@link RocksCallbackObject} if callbacks are not required. + */ +public abstract class RocksCallbackObject extends + AbstractImmutableNativeReference { + + protected final long nativeHandle_; + + protected RocksCallbackObject(final long... nativeParameterHandles) { + super(true); + this.nativeHandle_ = initializeNative(nativeParameterHandles); + } + + /** + * Construct the Native C++ object which will callback + * to our object methods + * + * @param nativeParameterHandles An array of native handles for any parameter + * objects that are needed during construction + * + * @return The native handle of the C++ object which will callback to us + */ + protected abstract long initializeNative( + final long... nativeParameterHandles); + + /** + * Deletes underlying C++ native callback object pointer + */ + @Override + protected void disposeInternal() { + disposeInternal(nativeHandle_); + } + + private native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java index eda0950990..b93a51e28a 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksDB.java @@ -7,7 +7,6 @@ import java.util.*; import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.rocksdb.util.Environment; @@ -64,8 +63,8 @@ public static void loadLibrary() { NativeLibraryLoader.getInstance().loadLibrary(tmpDir); } catch (IOException e) { libraryLoaded.set(LibraryState.NOT_LOADED); - throw new RuntimeException("Unable to load the RocksDB shared library" - + e); + throw new RuntimeException("Unable to load the RocksDB shared library", + e); } libraryLoaded.set(LibraryState.LOADED); @@ -139,6 +138,15 @@ public static void loadLibrary(final List paths) { } } + /** + * Private constructor. + * + * @param nativeHandle The native handle of the C++ RocksDB object + */ + protected RocksDB(final long nativeHandle) { + super(nativeHandle); + } + /** * The factory constructor of RocksDB that opens a RocksDB instance given * the path to the database using the default options w/ createIfMissing @@ -153,9 +161,7 @@ public static void loadLibrary(final List paths) { * @see Options#setCreateIfMissing(boolean) */ public static RocksDB open(final String path) throws RocksDBException { - // This allows to use the rocksjni default Options instead of - // the c++ one. - Options options = new Options(); + final Options options = new Options(); options.setCreateIfMissing(true); return open(options, path); } @@ -193,9 +199,7 @@ public static RocksDB open(final String path, final List columnFamilyDescriptors, final List columnFamilyHandles) throws RocksDBException { - // This allows to use the rocksjni default Options instead of - // the c++ one. - DBOptions options = new DBOptions(); + final DBOptions options = new DBOptions(); return open(options, path, columnFamilyDescriptors, columnFamilyHandles); } @@ -418,6 +422,54 @@ public static RocksDB openReadOnly(final DBOptions options, final String path, return db; } + + /** + * This is similar to {@link #close()} except that it + * throws an exception if any error occurs. + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + * + * @throws RocksDBException if an error occurs whilst closing. + */ + public void closeE() throws RocksDBException { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } finally { + disposeInternal(); + } + } + } + + /** + * This is similar to {@link #closeE()} except that it + * silently ignores any errors. + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + */ + @Override + public void close() { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } catch (final RocksDBException e) { + // silently ignore the error report + } finally { + disposeInternal(); + } + } + } + /** * Static method to determine all available column families for a * rocksdb database identified by path @@ -435,10 +487,108 @@ public static List listColumnFamilies(final Options options, path)); } - private void storeOptionsInstance(DBOptionsInterface options) { - options_ = options; + /** + * Creates a new column family with the name columnFamilyName and + * allocates a ColumnFamilyHandle within an internal structure. + * The ColumnFamilyHandle is automatically disposed with DB disposal. + * + * @param columnFamilyDescriptor column family to be created. + * @return {@link org.rocksdb.ColumnFamilyHandle} instance. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public ColumnFamilyHandle createColumnFamily( + final ColumnFamilyDescriptor columnFamilyDescriptor) + throws RocksDBException { + return new ColumnFamilyHandle(this, createColumnFamily(nativeHandle_, + columnFamilyDescriptor.getName(), + columnFamilyDescriptor.getName().length, + columnFamilyDescriptor.getOptions().nativeHandle_)); + } + + /** + * Bulk create column families with the same column family options. + * + * @param columnFamilyOptions the options for the column families. + * @param columnFamilyNames the names of the column families. + * + * @return the handles to the newly created column families. + */ + public List createColumnFamilies( + final ColumnFamilyOptions columnFamilyOptions, + final List columnFamilyNames) throws RocksDBException { + final byte[][] cfNames = columnFamilyNames.toArray( + new byte[0][]); + final long[] cfHandles = createColumnFamilies(nativeHandle_, + columnFamilyOptions.nativeHandle_, cfNames); + final List columnFamilyHandles = + new ArrayList<>(cfHandles.length); + for (int i = 0; i < cfHandles.length; i++) { + columnFamilyHandles.add(new ColumnFamilyHandle(this, cfHandles[i])); + } + return columnFamilyHandles; + } + + /** + * Bulk create column families with the same column family options. + * + * @param columnFamilyDescriptors the descriptions of the column families. + * + * @return the handles to the newly created column families. + */ + public List createColumnFamilies( + final List columnFamilyDescriptors) + throws RocksDBException { + final long[] cfOptsHandles = new long[columnFamilyDescriptors.size()]; + final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; + for (int i = 0; i < columnFamilyDescriptors.size(); i++) { + final ColumnFamilyDescriptor columnFamilyDescriptor + = columnFamilyDescriptors.get(i); + cfOptsHandles[i] = columnFamilyDescriptor.getOptions().nativeHandle_; + cfNames[i] = columnFamilyDescriptor.getName(); + } + final long[] cfHandles = createColumnFamilies(nativeHandle_, + cfOptsHandles, cfNames); + final List columnFamilyHandles = + new ArrayList<>(cfHandles.length); + for (int i = 0; i < cfHandles.length; i++) { + columnFamilyHandles.add(new ColumnFamilyHandle(this, cfHandles[i])); + } + return columnFamilyHandles; + } + + /** + * Drops the column family specified by {@code columnFamilyHandle}. This call + * only records a drop record in the manifest and prevents the column + * family from flushing and compacting. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public void dropColumnFamily(final ColumnFamilyHandle columnFamilyHandle) + throws RocksDBException { + dropColumnFamily(nativeHandle_, columnFamilyHandle.nativeHandle_); + } + + // Bulk drop column families. This call only records drop records in the + // manifest and prevents the column families from flushing and compacting. + // In case of error, the request may succeed partially. User may call + // ListColumnFamilies to check the result. + public void dropColumnFamilies( + final List columnFamilies) throws RocksDBException { + final long[] cfHandles = new long[columnFamilies.size()]; + for (int i = 0; i < columnFamilies.size(); i++) { + cfHandles[i] = columnFamilies.get(i).nativeHandle_; + } + dropColumnFamilies(nativeHandle_, cfHandles); } + //TODO(AR) what about DestroyColumnFamilyHandle + /** * Set the database entry for "key" to "value". * @@ -453,6 +603,32 @@ public void put(final byte[] key, final byte[] value) put(nativeHandle_, key, 0, key.length, value, 0, value.length); } + /** + * Set the database entry for "key" to "value". + * + * @param key The specified key to be inserted + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the value associated with the specified key + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) + * + * @throws RocksDBException thrown if errors happens in underlying native + * library. + * @throws IndexOutOfBoundsException if an offset or length is out of bounds + */ + public void put(final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) + throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + put(nativeHandle_, key, offset, len, value, vOffset, vLen); + } + /** * Set the database entry for "key" to "value" in the specified * column family. @@ -473,6 +649,37 @@ public void put(final ColumnFamilyHandle columnFamilyHandle, columnFamilyHandle.nativeHandle_); } + /** + * Set the database entry for "key" to "value" in the specified + * column family. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key The specified key to be inserted + * @param offset the offset of the "key" array to be used, must + * be non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the value associated with the specified key + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) + * + * @throws RocksDBException thrown if errors happens in underlying native + * library. + * @throws IndexOutOfBoundsException if an offset or length is out of bounds + */ + public void put(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) + throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + put(nativeHandle_, key, offset, len, value, vOffset, vLen, + columnFamilyHandle.nativeHandle_); + } + /** * Set the database entry for "key" to "value". * @@ -489,6 +696,35 @@ public void put(final WriteOptions writeOpts, final byte[] key, key, 0, key.length, value, 0, value.length); } + /** + * Set the database entry for "key" to "value". + * + * @param writeOpts {@link org.rocksdb.WriteOptions} instance. + * @param key The specified key to be inserted + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the value associated with the specified key + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + * @throws IndexOutOfBoundsException if an offset or length is out of bounds + */ + public void put(final WriteOptions writeOpts, + final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) + throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + put(nativeHandle_, writeOpts.nativeHandle_, + key, offset, len, value, vOffset, vLen); + } + /** * Set the database entry for "key" to "value" for the specified * column family. @@ -513,1009 +749,1611 @@ public void put(final ColumnFamilyHandle columnFamilyHandle, } /** - * If the key definitely does not exist in the database, then this method - * returns false, else true. + * Set the database entry for "key" to "value" for the specified + * column family. * - * This check is potentially lighter-weight than invoking DB::Get(). One way - * to make this lighter weight is to avoid doing any IOs. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param writeOpts {@link org.rocksdb.WriteOptions} instance. + * @param key The specified key to be inserted + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the value associated with the specified key + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) * - * @param key byte array of a key to search for - * @param value StringBuilder instance which is a out parameter if a value is - * found in block-cache. - * @return boolean value indicating if key does not exist or might exist. + * @throws RocksDBException thrown if error happens in underlying + * native library. + * @throws IndexOutOfBoundsException if an offset or length is out of bounds */ - public boolean keyMayExist(final byte[] key, final StringBuilder value) { - return keyMayExist(nativeHandle_, key, 0, key.length, value); + public void put(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpts, + final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) + throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + put(nativeHandle_, writeOpts.nativeHandle_, key, offset, len, value, + vOffset, vLen, columnFamilyHandle.nativeHandle_); } /** - * If the key definitely does not exist in the database, then this method - * returns false, else true. + * Remove the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * This check is potentially lighter-weight than invoking DB::Get(). One way - * to make this lighter weight is to avoid doing any IOs. + * @param key Key to delete within database * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key byte array of a key to search for - * @param value StringBuilder instance which is a out parameter if a value is - * found in block-cache. - * @return boolean value indicating if key does not exist or might exist. + * @throws RocksDBException thrown if error happens in underlying + * native library. + * + * @deprecated Use {@link #delete(byte[])} */ - public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final StringBuilder value) { - return keyMayExist(nativeHandle_, key, 0, key.length, - columnFamilyHandle.nativeHandle_, value); + @Deprecated + public void remove(final byte[] key) throws RocksDBException { + delete(key); } /** - * If the key definitely does not exist in the database, then this method - * returns false, else true. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * This check is potentially lighter-weight than invoking DB::Get(). One way - * to make this lighter weight is to avoid doing any IOs. + * @param key Key to delete within database * - * @param readOptions {@link ReadOptions} instance - * @param key byte array of a key to search for - * @param value StringBuilder instance which is a out parameter if a value is - * found in block-cache. - * @return boolean value indicating if key does not exist or might exist. + * @throws RocksDBException thrown if error happens in underlying + * native library. */ - public boolean keyMayExist(final ReadOptions readOptions, - final byte[] key, final StringBuilder value) { - return keyMayExist(nativeHandle_, readOptions.nativeHandle_, - key, 0, key.length, value); + public void delete(final byte[] key) throws RocksDBException { + delete(nativeHandle_, key, 0, key.length); } /** - * If the key definitely does not exist in the database, then this method - * returns false, else true. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * This check is potentially lighter-weight than invoking DB::Get(). One way - * to make this lighter weight is to avoid doing any IOs. + * @param key Key to delete within database + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be + * non-negative and no larger than ("key".length - offset) * - * @param readOptions {@link ReadOptions} instance - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key byte array of a key to search for - * @param value StringBuilder instance which is a out parameter if a value is - * found in block-cache. - * @return boolean value indicating if key does not exist or might exist. + * @throws RocksDBException thrown if error happens in underlying + * native library. */ - public boolean keyMayExist(final ReadOptions readOptions, - final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - final StringBuilder value) { - return keyMayExist(nativeHandle_, readOptions.nativeHandle_, - key, 0, key.length, columnFamilyHandle.nativeHandle_, - value); + public void delete(final byte[] key, final int offset, final int len) + throws RocksDBException { + delete(nativeHandle_, key, offset, len); } /** - * Apply the specified updates to the database. + * Remove the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * @param writeOpts WriteOptions instance - * @param updates WriteBatch instance + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying * native library. + * + * @deprecated Use {@link #delete(ColumnFamilyHandle, byte[])} */ - public void write(final WriteOptions writeOpts, final WriteBatch updates) - throws RocksDBException { - write0(nativeHandle_, writeOpts.nativeHandle_, updates.nativeHandle_); + @Deprecated + public void remove(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException { + delete(columnFamilyHandle, key); } /** - * Apply the specified updates to the database. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * @param writeOpts WriteOptions instance - * @param updates WriteBatchWithIndex instance + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public void write(final WriteOptions writeOpts, - final WriteBatchWithIndex updates) throws RocksDBException { - write1(nativeHandle_, writeOpts.nativeHandle_, updates.nativeHandle_); + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException { + delete(nativeHandle_, key, 0, key.length, columnFamilyHandle.nativeHandle_); } /** - * Add merge operand for key/value pair. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key Key to delete within database + * @param offset the offset of the "key" array to be used, + * must be non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("value".length - offset) * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public void merge(final byte[] key, final byte[] value) + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final int offset, final int len) throws RocksDBException { - merge(nativeHandle_, key, 0, key.length, value, 0, value.length); + delete(nativeHandle_, key, offset, len, columnFamilyHandle.nativeHandle_); } /** - * Add merge operand for key/value pair in a ColumnFamily. + * Remove the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying * native library. + * + * @deprecated Use {@link #delete(WriteOptions, byte[])} */ - public void merge(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key, final byte[] value) throws RocksDBException { - merge(nativeHandle_, key, 0, key.length, value, 0, value.length, - columnFamilyHandle.nativeHandle_); + @Deprecated + public void remove(final WriteOptions writeOpt, final byte[] key) + throws RocksDBException { + delete(writeOpt, key); } /** - * Add merge operand for key/value pair. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * @param writeOpts {@link WriteOptions} for this write. - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public void merge(final WriteOptions writeOpts, final byte[] key, - final byte[] value) throws RocksDBException { - merge(nativeHandle_, writeOpts.nativeHandle_, - key, 0, key.length, value, 0, value.length); + public void delete(final WriteOptions writeOpt, final byte[] key) + throws RocksDBException { + delete(nativeHandle_, writeOpt.nativeHandle_, key, 0, key.length); } /** - * Add merge operand for key/value pair. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param writeOpts {@link WriteOptions} for this write. - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be + * non-negative and no larger than ("key".length - offset) * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public void merge(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpts, final byte[] key, - final byte[] value) throws RocksDBException { - merge(nativeHandle_, writeOpts.nativeHandle_, - key, 0, key.length, value, 0, value.length, - columnFamilyHandle.nativeHandle_); + public void delete(final WriteOptions writeOpt, final byte[] key, + final int offset, final int len) throws RocksDBException { + delete(nativeHandle_, writeOpt.nativeHandle_, key, offset, len); } - // TODO(AR) we should improve the #get() API, returning -1 (RocksDB.NOT_FOUND) is not very nice - // when we could communicate better status into, also the C++ code show that -2 could be returned - /** - * Get the value associated with the specified key within column family* - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. + * Remove the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying * native library. + * + * @deprecated Use {@link #delete(ColumnFamilyHandle, WriteOptions, byte[])} */ - public int get(final byte[] key, final byte[] value) throws RocksDBException { - return get(nativeHandle_, key, 0, key.length, value, 0, value.length); + @Deprecated + public void remove(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpt, final byte[] key) throws RocksDBException { + delete(columnFamilyHandle, writeOpt, key); } /** - * Get the value associated with the specified key within column family. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} * instance - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public int get(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, - final byte[] value) throws RocksDBException, IllegalArgumentException { - return get(nativeHandle_, key, 0, key.length, value, 0, value.length, + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpt, final byte[] key) + throws RocksDBException { + delete(nativeHandle_, writeOpt.nativeHandle_, key, 0, key.length, columnFamilyHandle.nativeHandle_); } /** - * Get the value associated with the specified key. - * - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public int get(final ReadOptions opt, final byte[] key, - final byte[] value) throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, - key, 0, key.length, value, 0, value.length); - } - /** - * Get the value associated with the specified key within column family. + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. * * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} * instance - * @param opt {@link org.rocksdb.ReadOptions} instance. - * @param key the key to retrieve the value. - * @param value the out-value to receive the retrieved value. - * @return The size of the actual value that matches the specified - * {@code key} in byte. If the return value is greater than the - * length of {@code value}, then it indicates that the size of the - * input buffer {@code value} is insufficient and partial result will - * be returned. RocksDB.NOT_FOUND will be returned if the value not - * found. + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be + * non-negative and no larger than ("key".length - offset) * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public int get(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions opt, final byte[] key, final byte[] value) - throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length, value, - 0, value.length, columnFamilyHandle.nativeHandle_); + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpt, final byte[] key, final int offset, + final int len) throws RocksDBException { + delete(nativeHandle_, writeOpt.nativeHandle_, key, offset, len, + columnFamilyHandle.nativeHandle_); } /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. * - * @param key the key retrieve the value. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - public byte[] get(final byte[] key) throws RocksDBException { - return get(nativeHandle_, key, 0, key.length); + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final byte[] key) throws RocksDBException { + singleDelete(nativeHandle_, key, key.length); } /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key retrieve the value. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * @param columnFamilyHandle The column family to delete the key from + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - public byte[] get(final ColumnFamilyHandle columnFamilyHandle, + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, final byte[] key) throws RocksDBException { - return get(nativeHandle_, key, 0, key.length, + singleDelete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); } /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. * - * @param key the key retrieve the value. - * @param opt Read options. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public byte[] get(final ReadOptions opt, final byte[] key) - throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length); - } - - /** - * The simplified version of get which returns a new byte array storing - * the value associated with the specified input key if any. null will be - * returned if the specified key is not found. + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the key retrieve the value. - * @param opt Read options. - * @return a byte array storing the value associated with the input key if - * any. null if it does not find the specified key. + * Note: consider setting {@link WriteOptions#setSync(boolean)} true. + * + * @param writeOpt Write options for the delete + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - public byte[] get(final ColumnFamilyHandle columnFamilyHandle, - final ReadOptions opt, final byte[] key) throws RocksDBException { - return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length, - columnFamilyHandle.nativeHandle_); + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final WriteOptions writeOpt, final byte[] key) + throws RocksDBException { + singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length); } /** - * Returns a map of keys for which values were found in DB. + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. * - * @param keys List of keys for which values need to be retrieved. - * @return Map where key of map is the key passed by user and value for map - * entry is the corresponding value in DB. + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * Note: consider setting {@link WriteOptions#setSync(boolean)} true. + * + * @param columnFamilyHandle The column family to delete the key from + * @param writeOpt Write options for the delete + * @param key Key to delete within database * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - public Map multiGet(final List keys) - throws RocksDBException { - assert(keys.size() != 0); - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - final byte[][] values = multiGet(nativeHandle_, keysArray, keyOffsets, - keyLengths); - - final Map keyValueMap = - new HashMap<>(computeCapacityHint(values.length)); - for(int i = 0; i < values.length; i++) { - if(values[i] == null) { - continue; - } - - keyValueMap.put(keys.get(i), values[i]); - } - - return keyValueMap; + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpt, final byte[] key) throws RocksDBException { + singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length, + columnFamilyHandle.nativeHandle_); } - private static int computeCapacityHint(final int estimatedNumberOfItems) { - // Default load factor for HashMap is 0.75, so N * 1.5 will be at the load - // limit. We add +1 for a buffer. - return (int)Math.ceil(estimatedNumberOfItems * 1.5 + 1.0); - } /** - * Returns a map of keys for which values were found in DB. - *

    - * Note: Every key needs to have a related column family name in - * {@code columnFamilyHandleList}. - *

    + * Removes the database entries in the range ["beginKey", "endKey"), i.e., + * including "beginKey" and excluding "endKey". a non-OK status on error. It + * is not an error if no keys exist in the range ["beginKey", "endKey"). * - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys List of keys for which values need to be retrieved. - * @return Map where key of map is the key passed by user and value for map - * entry is the corresponding value in DB. + * Delete the database entry (if any) for "key". Returns OK on success, and a + * non-OK status on error. It is not an error if "key" did not exist in the + * database. * - * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IllegalArgumentException thrown if the size of passed keys is not - * equal to the amount of passed column family handles. + * @param beginKey First key to delete within database (inclusive) + * @param endKey Last key to delete within database (exclusive) + * + * @throws RocksDBException thrown if error happens in underlying native + * library. */ - public Map multiGet( - final List columnFamilyHandleList, - final List keys) throws RocksDBException, - IllegalArgumentException { - assert(keys.size() != 0); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.size() != columnFamilyHandleList.size()) { - throw new IllegalArgumentException( - "For each key there must be a ColumnFamilyHandle."); - } - final long[] cfHandles = new long[columnFamilyHandleList.size()]; - for (int i = 0; i < columnFamilyHandleList.size(); i++) { - cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; - } - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - final byte[][] values = multiGet(nativeHandle_, keysArray, keyOffsets, - keyLengths, cfHandles); - - final Map keyValueMap = - new HashMap<>(computeCapacityHint(values.length)); - for(int i = 0; i < values.length; i++) { - if (values[i] == null) { - continue; - } - keyValueMap.put(keys.get(i), values[i]); - } - return keyValueMap; + public void deleteRange(final byte[] beginKey, final byte[] endKey) + throws RocksDBException { + deleteRange(nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, + endKey.length); } /** - * Returns a map of keys for which values were found in DB. + * Removes the database entries in the range ["beginKey", "endKey"), i.e., + * including "beginKey" and excluding "endKey". a non-OK status on error. It + * is not an error if no keys exist in the range ["beginKey", "endKey"). * - * @param opt Read options. - * @param keys of keys for which values need to be retrieved. - * @return Map where key of map is the key passed by user and value for map - * entry is the corresponding value in DB. + * Delete the database entry (if any) for "key". Returns OK on success, and a + * non-OK status on error. It is not an error if "key" did not exist in the + * database. * - * @throws RocksDBException thrown if error happens in underlying - * native library. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance + * @param beginKey First key to delete within database (inclusive) + * @param endKey Last key to delete within database (exclusive) + * + * @throws RocksDBException thrown if error happens in underlying native + * library. */ - public Map multiGet(final ReadOptions opt, - final List keys) throws RocksDBException { - assert(keys.size() != 0); - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - final byte[][] values = multiGet(nativeHandle_, opt.nativeHandle_, - keysArray, keyOffsets, keyLengths); - - final Map keyValueMap = - new HashMap<>(computeCapacityHint(values.length)); - for(int i = 0; i < values.length; i++) { - if(values[i] == null) { - continue; - } - - keyValueMap.put(keys.get(i), values[i]); - } - - return keyValueMap; + public void deleteRange(final ColumnFamilyHandle columnFamilyHandle, + final byte[] beginKey, final byte[] endKey) throws RocksDBException { + deleteRange(nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, + endKey.length, columnFamilyHandle.nativeHandle_); } /** - * Returns a map of keys for which values were found in DB. - *

    - * Note: Every key needs to have a related column family name in - * {@code columnFamilyHandleList}. - *

    + * Removes the database entries in the range ["beginKey", "endKey"), i.e., + * including "beginKey" and excluding "endKey". a non-OK status on error. It + * is not an error if no keys exist in the range ["beginKey", "endKey"). * - * @param opt Read options. - * @param columnFamilyHandleList {@link java.util.List} containing - * {@link org.rocksdb.ColumnFamilyHandle} instances. - * @param keys of keys for which values need to be retrieved. - * @return Map where key of map is the key passed by user and value for map - * entry is the corresponding value in DB. + * Delete the database entry (if any) for "key". Returns OK on success, and a + * non-OK status on error. It is not an error if "key" did not exist in the + * database. + * + * @param writeOpt WriteOptions to be used with delete operation + * @param beginKey First key to delete within database (inclusive) + * @param endKey Last key to delete within database (exclusive) * * @throws RocksDBException thrown if error happens in underlying - * native library. - * @throws IllegalArgumentException thrown if the size of passed keys is not - * equal to the amount of passed column family handles. + * native library. */ - public Map multiGet(final ReadOptions opt, - final List columnFamilyHandleList, - final List keys) throws RocksDBException { - assert(keys.size() != 0); - // Check if key size equals cfList size. If not a exception must be - // thrown. If not a Segmentation fault happens. - if (keys.size()!=columnFamilyHandleList.size()){ - throw new IllegalArgumentException( - "For each key there must be a ColumnFamilyHandle."); - } - final long[] cfHandles = new long[columnFamilyHandleList.size()]; - for (int i = 0; i < columnFamilyHandleList.size(); i++) { - cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; - } - - final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; - for(int i = 0; i < keyLengths.length; i++) { - keyLengths[i] = keysArray[i].length; - } - - final byte[][] values = multiGet(nativeHandle_, opt.nativeHandle_, - keysArray, keyOffsets, keyLengths, cfHandles); - - final Map keyValueMap - = new HashMap<>(computeCapacityHint(values.length)); - for(int i = 0; i < values.length; i++) { - if(values[i] == null) { - continue; - } - keyValueMap.put(keys.get(i), values[i]); - } - - return keyValueMap; + public void deleteRange(final WriteOptions writeOpt, final byte[] beginKey, + final byte[] endKey) throws RocksDBException { + deleteRange(nativeHandle_, writeOpt.nativeHandle_, beginKey, 0, + beginKey.length, endKey, 0, endKey.length); } /** - * Remove the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Removes the database entries in the range ["beginKey", "endKey"), i.e., + * including "beginKey" and excluding "endKey". a non-OK status on error. It + * is not an error if no keys exist in the range ["beginKey", "endKey"). * - * @param key Key to delete within database + * Delete the database entry (if any) for "key". Returns OK on success, and a + * non-OK status on error. It is not an error if "key" did not exist in the + * database. * - * @throws RocksDBException thrown if error happens in underlying - * native library. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance + * @param writeOpt WriteOptions to be used with delete operation + * @param beginKey First key to delete within database (included) + * @param endKey Last key to delete within database (excluded) * - * @deprecated Use {@link #delete(byte[])} + * @throws RocksDBException thrown if error happens in underlying native + * library. */ - @Deprecated - public void remove(final byte[] key) throws RocksDBException { - delete(key); + public void deleteRange(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpt, final byte[] beginKey, final byte[] endKey) + throws RocksDBException { + deleteRange(nativeHandle_, writeOpt.nativeHandle_, beginKey, 0, + beginKey.length, endKey, 0, endKey.length, + columnFamilyHandle.nativeHandle_); } + /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Add merge operand for key/value pair. * - * @param key Key to delete within database + * @param key the specified key to be merged. + * @param value the value to be merged with the current value for the + * specified key. * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public void delete(final byte[] key) throws RocksDBException { - delete(nativeHandle_, key, 0, key.length); + public void merge(final byte[] key, final byte[] value) + throws RocksDBException { + merge(nativeHandle_, key, 0, key.length, value, 0, value.length); } /** - * Remove the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Add merge operand for key/value pair. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key Key to delete within database + * @param key the specified key to be merged. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the value to be merged with the current value for the + * specified key. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and must be non-negative and no larger than + * ("value".length - offset) * * @throws RocksDBException thrown if error happens in underlying * native library. - * - * @deprecated Use {@link #delete(ColumnFamilyHandle, byte[])} + * @throws IndexOutOfBoundsException if an offset or length is out of bounds */ - @Deprecated - public void remove(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - delete(columnFamilyHandle, key); + public void merge(final byte[] key, int offset, int len, final byte[] value, + final int vOffset, final int vLen) throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + merge(nativeHandle_, key, offset, len, value, vOffset, vLen); } /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Add merge operand for key/value pair in a ColumnFamily. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key Key to delete within database + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key the specified key to be merged. + * @param value the value to be merged with the current value for + * the specified key. * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - delete(nativeHandle_, key, 0, key.length, columnFamilyHandle.nativeHandle_); + public void merge(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final byte[] value) throws RocksDBException { + merge(nativeHandle_, key, 0, key.length, value, 0, value.length, + columnFamilyHandle.nativeHandle_); } /** - * Remove the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Add merge operand for key/value pair in a ColumnFamily. * - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key the specified key to be merged. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the value to be merged with the current value for + * the specified key. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * must be non-negative and no larger than ("value".length - offset) * * @throws RocksDBException thrown if error happens in underlying * native library. + * @throws IndexOutOfBoundsException if an offset or length is out of bounds + */ + public void merge(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final int offset, final int len, final byte[] value, + final int vOffset, final int vLen) throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + merge(nativeHandle_, key, offset, len, value, vOffset, vLen, + columnFamilyHandle.nativeHandle_); + } + + /** + * Add merge operand for key/value pair. * - * @deprecated Use {@link #delete(WriteOptions, byte[])} + * @param writeOpts {@link WriteOptions} for this write. + * @param key the specified key to be merged. + * @param value the value to be merged with the current value for + * the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. */ - @Deprecated - public void remove(final WriteOptions writeOpt, final byte[] key) - throws RocksDBException { - delete(writeOpt, key); + public void merge(final WriteOptions writeOpts, final byte[] key, + final byte[] value) throws RocksDBException { + merge(nativeHandle_, writeOpts.nativeHandle_, + key, 0, key.length, value, 0, value.length); } /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Add merge operand for key/value pair. * - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database + * @param writeOpts {@link WriteOptions} for this write. + * @param key the specified key to be merged. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("value".length - offset) + * @param value the value to be merged with the current value for + * the specified key. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) * * @throws RocksDBException thrown if error happens in underlying * native library. + * @throws IndexOutOfBoundsException if an offset or length is out of bounds */ - public void delete(final WriteOptions writeOpt, final byte[] key) + public void merge(final WriteOptions writeOpts, + final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) throws RocksDBException { - delete(nativeHandle_, writeOpt.nativeHandle_, key, 0, key.length); + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + merge(nativeHandle_, writeOpts.nativeHandle_, + key, offset, len, value, vOffset, vLen); } /** - * Remove the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Add merge operand for key/value pair. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param writeOpts {@link WriteOptions} for this write. + * @param key the specified key to be merged. + * @param value the value to be merged with the current value for the + * specified key. * * @throws RocksDBException thrown if error happens in underlying * native library. - * - * @deprecated Use {@link #delete(ColumnFamilyHandle, WriteOptions, byte[])} */ - @Deprecated - public void remove(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpt, final byte[] key) + public void merge(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpts, final byte[] key, final byte[] value) throws RocksDBException { - delete(columnFamilyHandle, writeOpt, key); + merge(nativeHandle_, writeOpts.nativeHandle_, + key, 0, key.length, value, 0, value.length, + columnFamilyHandle.nativeHandle_); } /** - * Delete the database entry (if any) for "key". Returns OK on - * success, and a non-OK status on error. It is not an error if "key" - * did not exist in the database. + * Add merge operand for key/value pair. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpt WriteOptions to be used with delete operation - * @param key Key to delete within database + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param writeOpts {@link WriteOptions} for this write. + * @param key the specified key to be merged. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the value to be merged with the current value for + * the specified key. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) * * @throws RocksDBException thrown if error happens in underlying * native library. + * @throws IndexOutOfBoundsException if an offset or length is out of bounds */ - public void delete(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpt, final byte[] key) + public void merge( + final ColumnFamilyHandle columnFamilyHandle, final WriteOptions writeOpts, + final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) throws RocksDBException { - delete(nativeHandle_, writeOpt.nativeHandle_, key, 0, key.length, + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + merge(nativeHandle_, writeOpts.nativeHandle_, + key, offset, len, value, vOffset, vLen, columnFamilyHandle.nativeHandle_); } /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. + * Apply the specified updates to the database. * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. + * @param writeOpts WriteOptions instance + * @param updates WriteBatch instance * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public void write(final WriteOptions writeOpts, final WriteBatch updates) + throws RocksDBException { + write0(nativeHandle_, writeOpts.nativeHandle_, updates.nativeHandle_); + } + + /** + * Apply the specified updates to the database. * - * @param key Key to delete within database + * @param writeOpts WriteOptions instance + * @param updates WriteBatchWithIndex instance * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final byte[] key) throws RocksDBException { - singleDelete(nativeHandle_, key, key.length); + public void write(final WriteOptions writeOpts, + final WriteBatchWithIndex updates) throws RocksDBException { + write1(nativeHandle_, writeOpts.nativeHandle_, updates.nativeHandle_); } + // TODO(AR) we should improve the #get() API, returning -1 (RocksDB.NOT_FOUND) is not very nice + // when we could communicate better status into, also the C++ code show that -2 could be returned + /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. - * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. + * Get the value associated with the specified key within column family* * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. + * @param key the key to retrieve the value. + * @param value the out-value to receive the retrieved value. * - * @param columnFamilyHandle The column family to delete the key from - * @param key Key to delete within database + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final byte[] key) throws RocksDBException { - singleDelete(nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_); + public int get(final byte[] key, final byte[] value) throws RocksDBException { + return get(nativeHandle_, key, 0, key.length, value, 0, value.length); } /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. + * Get the value associated with the specified key within column family* * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. + * @param key the key to retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the out-value to receive the retrieved value. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "value".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and and no larger than ("value".length - offset) * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. * - * Note: consider setting {@link WriteOptions#setSync(boolean)} true. + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public int get(final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) + throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + return get(nativeHandle_, key, offset, len, value, vOffset, vLen); + } + + /** + * Get the value associated with the specified key within column family. * - * @param writeOpt Write options for the delete - * @param key Key to delete within database + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key to retrieve the value. + * @param value the out-value to receive the retrieved value. + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final WriteOptions writeOpt, final byte[] key) - throws RocksDBException { - singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length); + public int get(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final byte[] value) throws RocksDBException, IllegalArgumentException { + return get(nativeHandle_, key, 0, key.length, value, 0, value.length, + columnFamilyHandle.nativeHandle_); } /** - * Remove the database entry for {@code key}. Requires that the key exists - * and was not overwritten. It is not an error if the key did not exist - * in the database. + * Get the value associated with the specified key within column family. * - * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple - * times), then the result of calling SingleDelete() on this key is undefined. - * SingleDelete() only behaves correctly if there has been only one Put() - * for this key since the previous call to SingleDelete() for this key. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key to retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * an no larger than ("key".length - offset) + * @param value the out-value to receive the retrieved value. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) * - * This feature is currently an experimental performance optimization - * for a very specific workload. It is up to the caller to ensure that - * SingleDelete is only used for a key that is not deleted using Delete() or - * written using Merge(). Mixing SingleDelete operations with Deletes and - * Merges can result in undefined behavior. + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. * - * Note: consider setting {@link WriteOptions#setSync(boolean)} true. + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public int get(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final int offset, final int len, final byte[] value, final int vOffset, + final int vLen) throws RocksDBException, IllegalArgumentException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + return get(nativeHandle_, key, offset, len, value, vOffset, vLen, + columnFamilyHandle.nativeHandle_); + } + + /** + * Get the value associated with the specified key. * - * @param columnFamilyHandle The column family to delete the key from - * @param writeOpt Write options for the delete - * @param key Key to delete within database + * @param opt {@link org.rocksdb.ReadOptions} instance. + * @param key the key to retrieve the value. + * @param value the out-value to receive the retrieved value. + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public int get(final ReadOptions opt, final byte[] key, + final byte[] value) throws RocksDBException { + return get(nativeHandle_, opt.nativeHandle_, + key, 0, key.length, value, 0, value.length); + } + + /** + * Get the value associated with the specified key. + * + * @param opt {@link org.rocksdb.ReadOptions} instance. + * @param key the key to retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param value the out-value to receive the retrieved value. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, must be + * non-negative and no larger than ("value".length - offset) + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public int get(final ReadOptions opt, final byte[] key, final int offset, + final int len, final byte[] value, final int vOffset, final int vLen) + throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + return get(nativeHandle_, opt.nativeHandle_, + key, offset, len, value, vOffset, vLen); + } + + /** + * Get the value associated with the specified key within column family. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param opt {@link org.rocksdb.ReadOptions} instance. + * @param key the key to retrieve the value. + * @param value the out-value to receive the retrieved value. + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public int get(final ColumnFamilyHandle columnFamilyHandle, + final ReadOptions opt, final byte[] key, final byte[] value) + throws RocksDBException { + return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length, value, + 0, value.length, columnFamilyHandle.nativeHandle_); + } + + /** + * Get the value associated with the specified key within column family. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param opt {@link org.rocksdb.ReadOptions} instance. + * @param key the key to retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be + * non-negative and and no larger than ("key".length - offset) + * @param value the out-value to receive the retrieved value. + * @param vOffset the offset of the "value" array to be used, must be + * non-negative and no longer than "key".length + * @param vLen the length of the "value" array to be used, and must be + * non-negative and no larger than ("value".length - offset) + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public int get(final ColumnFamilyHandle columnFamilyHandle, + final ReadOptions opt, final byte[] key, final int offset, final int len, + final byte[] value, final int vOffset, final int vLen) + throws RocksDBException { + checkBounds(offset, len, key.length); + checkBounds(vOffset, vLen, value.length); + return get(nativeHandle_, opt.nativeHandle_, key, offset, len, value, + vOffset, vLen, columnFamilyHandle.nativeHandle_); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param key the key retrieve the value. + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final byte[] key) throws RocksDBException { + return get(nativeHandle_, key, 0, key.length); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param key the key retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final byte[] key, final int offset, + final int len) throws RocksDBException { + checkBounds(offset, len, key.length); + return get(nativeHandle_, key, offset, len); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key retrieve the value. + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException { + return get(nativeHandle_, key, 0, key.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final int offset, final int len) + throws RocksDBException { + checkBounds(offset, len, key.length); + return get(nativeHandle_, key, offset, len, + columnFamilyHandle.nativeHandle_); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param key the key retrieve the value. + * @param opt Read options. + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final ReadOptions opt, final byte[] key) + throws RocksDBException { + return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param key the key retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param opt Read options. + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final ReadOptions opt, final byte[] key, final int offset, + final int len) throws RocksDBException { + checkBounds(offset, len, key.length); + return get(nativeHandle_, opt.nativeHandle_, key, offset, len); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key retrieve the value. + * @param opt Read options. + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final ColumnFamilyHandle columnFamilyHandle, + final ReadOptions opt, final byte[] key) throws RocksDBException { + return get(nativeHandle_, opt.nativeHandle_, key, 0, key.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key retrieve the value. + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than ("key".length - offset) + * @param opt Read options. + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] get(final ColumnFamilyHandle columnFamilyHandle, + final ReadOptions opt, final byte[] key, final int offset, final int len) + throws RocksDBException { + checkBounds(offset, len, key.length); + return get(nativeHandle_, opt.nativeHandle_, key, offset, len, + columnFamilyHandle.nativeHandle_); + } + + /** + * Returns a map of keys for which values were found in DB. + * + * @param keys List of keys for which values need to be retrieved. + * @return Map where key of map is the key passed by user and value for map + * entry is the corresponding value in DB. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + * + * @deprecated Consider {@link #multiGetAsList(List)} instead. + */ + @Deprecated + public Map multiGet(final List keys) + throws RocksDBException { + assert(keys.size() != 0); + + final byte[][] keysArray = keys.toArray(new byte[0][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + final byte[][] values = multiGet(nativeHandle_, keysArray, keyOffsets, + keyLengths); + + final Map keyValueMap = + new HashMap<>(computeCapacityHint(values.length)); + for(int i = 0; i < values.length; i++) { + if(values[i] == null) { + continue; + } + + keyValueMap.put(keys.get(i), values[i]); + } + + return keyValueMap; + } + + /** + * Returns a map of keys for which values were found in DB. + *

    + * Note: Every key needs to have a related column family name in + * {@code columnFamilyHandleList}. + *

    + * + * @param columnFamilyHandleList {@link java.util.List} containing + * {@link org.rocksdb.ColumnFamilyHandle} instances. + * @param keys List of keys for which values need to be retrieved. + * @return Map where key of map is the key passed by user and value for map + * entry is the corresponding value in DB. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + * @throws IllegalArgumentException thrown if the size of passed keys is not + * equal to the amount of passed column family handles. + * + * @deprecated Consider {@link #multiGetAsList(List, List)} instead. + */ + @Deprecated + public Map multiGet( + final List columnFamilyHandleList, + final List keys) throws RocksDBException, + IllegalArgumentException { + assert(keys.size() != 0); + // Check if key size equals cfList size. If not a exception must be + // thrown. If not a Segmentation fault happens. + if (keys.size() != columnFamilyHandleList.size()) { + throw new IllegalArgumentException( + "For each key there must be a ColumnFamilyHandle."); + } + final long[] cfHandles = new long[columnFamilyHandleList.size()]; + for (int i = 0; i < columnFamilyHandleList.size(); i++) { + cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; + } + + final byte[][] keysArray = keys.toArray(new byte[0][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + final byte[][] values = multiGet(nativeHandle_, keysArray, keyOffsets, + keyLengths, cfHandles); + + final Map keyValueMap = + new HashMap<>(computeCapacityHint(values.length)); + for(int i = 0; i < values.length; i++) { + if (values[i] == null) { + continue; + } + keyValueMap.put(keys.get(i), values[i]); + } + return keyValueMap; + } + + /** + * Returns a map of keys for which values were found in DB. + * + * @param opt Read options. + * @param keys of keys for which values need to be retrieved. + * @return Map where key of map is the key passed by user and value for map + * entry is the corresponding value in DB. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + * + * @deprecated Consider {@link #multiGetAsList(ReadOptions, List)} instead. + */ + @Deprecated + public Map multiGet(final ReadOptions opt, + final List keys) throws RocksDBException { + assert(keys.size() != 0); + + final byte[][] keysArray = keys.toArray(new byte[0][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + final byte[][] values = multiGet(nativeHandle_, opt.nativeHandle_, + keysArray, keyOffsets, keyLengths); + + final Map keyValueMap = + new HashMap<>(computeCapacityHint(values.length)); + for(int i = 0; i < values.length; i++) { + if(values[i] == null) { + continue; + } + + keyValueMap.put(keys.get(i), values[i]); + } + + return keyValueMap; + } + + /** + * Returns a map of keys for which values were found in DB. + *

    + * Note: Every key needs to have a related column family name in + * {@code columnFamilyHandleList}. + *

    + * + * @param opt Read options. + * @param columnFamilyHandleList {@link java.util.List} containing + * {@link org.rocksdb.ColumnFamilyHandle} instances. + * @param keys of keys for which values need to be retrieved. + * @return Map where key of map is the key passed by user and value for map + * entry is the corresponding value in DB. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + * @throws IllegalArgumentException thrown if the size of passed keys is not + * equal to the amount of passed column family handles. + * + * @deprecated Consider {@link #multiGetAsList(ReadOptions, List, List)} + * instead. + */ + @Deprecated + public Map multiGet(final ReadOptions opt, + final List columnFamilyHandleList, + final List keys) throws RocksDBException { + assert(keys.size() != 0); + // Check if key size equals cfList size. If not a exception must be + // thrown. If not a Segmentation fault happens. + if (keys.size()!=columnFamilyHandleList.size()){ + throw new IllegalArgumentException( + "For each key there must be a ColumnFamilyHandle."); + } + final long[] cfHandles = new long[columnFamilyHandleList.size()]; + for (int i = 0; i < columnFamilyHandleList.size(); i++) { + cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; + } + + final byte[][] keysArray = keys.toArray(new byte[0][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + final byte[][] values = multiGet(nativeHandle_, opt.nativeHandle_, + keysArray, keyOffsets, keyLengths, cfHandles); + + final Map keyValueMap + = new HashMap<>(computeCapacityHint(values.length)); + for(int i = 0; i < values.length; i++) { + if(values[i] == null) { + continue; + } + keyValueMap.put(keys.get(i), values[i]); + } + + return keyValueMap; + } + + /** + * Takes a list of keys, and returns a list of values for the given list of + * keys. List will contain null for keys which could not be found. + * + * @param keys List of keys for which values need to be retrieved. + * @return List of values for the given list of keys. List will contain + * null for keys which could not be found. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public List multiGetAsList(final List keys) + throws RocksDBException { + assert(keys.size() != 0); + + final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + return Arrays.asList(multiGet(nativeHandle_, keysArray, keyOffsets, + keyLengths)); + } + + /** + * Returns a list of values for the given list of keys. List will contain + * null for keys which could not be found. + *

    + * Note: Every key needs to have a related column family name in + * {@code columnFamilyHandleList}. + *

    + * + * @param columnFamilyHandleList {@link java.util.List} containing + * {@link org.rocksdb.ColumnFamilyHandle} instances. + * @param keys List of keys for which values need to be retrieved. + * @return List of values for the given list of keys. List will contain + * null for keys which could not be found. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + * @throws IllegalArgumentException thrown if the size of passed keys is not + * equal to the amount of passed column family handles. + */ + public List multiGetAsList( + final List columnFamilyHandleList, + final List keys) throws RocksDBException, + IllegalArgumentException { + assert(keys.size() != 0); + // Check if key size equals cfList size. If not a exception must be + // thrown. If not a Segmentation fault happens. + if (keys.size() != columnFamilyHandleList.size()) { + throw new IllegalArgumentException( + "For each key there must be a ColumnFamilyHandle."); + } + final long[] cfHandles = new long[columnFamilyHandleList.size()]; + for (int i = 0; i < columnFamilyHandleList.size(); i++) { + cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; + } + + final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + return Arrays.asList(multiGet(nativeHandle_, keysArray, keyOffsets, + keyLengths, cfHandles)); + } + + /** + * Returns a list of values for the given list of keys. List will contain + * null for keys which could not be found. + * + * @param opt Read options. + * @param keys of keys for which values need to be retrieved. + * @return List of values for the given list of keys. List will contain + * null for keys which could not be found. * * @throws RocksDBException thrown if error happens in underlying - * native library. + * native library. */ - @Experimental("Performance optimization for a very specific workload") - public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, - final WriteOptions writeOpt, final byte[] key) throws RocksDBException { - singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length, - columnFamilyHandle.nativeHandle_); + public List multiGetAsList(final ReadOptions opt, + final List keys) throws RocksDBException { + assert(keys.size() != 0); + + final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + return Arrays.asList(multiGet(nativeHandle_, opt.nativeHandle_, + keysArray, keyOffsets, keyLengths)); } /** - * DB implements can export properties about their state - * via this method on a per column family level. - * - *

    If {@code property} is a valid property understood by this DB - * implementation, fills {@code value} with its current value and - * returns true. Otherwise returns false.

    - * - *

    Valid property names include: - *

      - *
    • "rocksdb.num-files-at-level<N>" - return the number of files at - * level <N>, where <N> is an ASCII representation of a level - * number (e.g. "0").
    • - *
    • "rocksdb.stats" - returns a multi-line string that describes statistics - * about the internal operation of the DB.
    • - *
    • "rocksdb.sstables" - returns a multi-line string that describes all - * of the sstables that make up the db contents.
    • - *
    + * Returns a list of values for the given list of keys. List will contain + * null for keys which could not be found. + *

    + * Note: Every key needs to have a related column family name in + * {@code columnFamilyHandleList}. + *

    * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param property to be fetched. See above for examples - * @return property value + * @param opt Read options. + * @param columnFamilyHandleList {@link java.util.List} containing + * {@link org.rocksdb.ColumnFamilyHandle} instances. + * @param keys of keys for which values need to be retrieved. + * @return List of values for the given list of keys. List will contain + * null for keys which could not be found. * * @throws RocksDBException thrown if error happens in underlying * native library. + * @throws IllegalArgumentException thrown if the size of passed keys is not + * equal to the amount of passed column family handles. */ - public String getProperty(final ColumnFamilyHandle columnFamilyHandle, - final String property) throws RocksDBException { - return getProperty0(nativeHandle_, columnFamilyHandle.nativeHandle_, - property, property.length()); + public List multiGetAsList(final ReadOptions opt, + final List columnFamilyHandleList, + final List keys) throws RocksDBException { + assert(keys.size() != 0); + // Check if key size equals cfList size. If not a exception must be + // thrown. If not a Segmentation fault happens. + if (keys.size()!=columnFamilyHandleList.size()){ + throw new IllegalArgumentException( + "For each key there must be a ColumnFamilyHandle."); + } + final long[] cfHandles = new long[columnFamilyHandleList.size()]; + for (int i = 0; i < columnFamilyHandleList.size(); i++) { + cfHandles[i] = columnFamilyHandleList.get(i).nativeHandle_; + } + + final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); + final int keyOffsets[] = new int[keysArray.length]; + final int keyLengths[] = new int[keysArray.length]; + for(int i = 0; i < keyLengths.length; i++) { + keyLengths[i] = keysArray[i].length; + } + + return Arrays.asList(multiGet(nativeHandle_, opt.nativeHandle_, + keysArray, keyOffsets, keyLengths, cfHandles)); } /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - * @param beginKey - * First key to delete within database (included) - * @param endKey - * Last key to delete within database (excluded) + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @throws RocksDBException - * thrown if error happens in underlying native library. + * @param key byte array of a key to search for + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. + * @return boolean value indicating if key does not exist or might exist. */ - public void deleteRange(final byte[] beginKey, final byte[] endKey) throws RocksDBException { - deleteRange(nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, endKey.length); + public boolean keyMayExist(final byte[] key, final StringBuilder value) { + return keyMayExist(nativeHandle_, key, 0, key.length, value); } /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @param columnFamilyHandle - * {@link org.rocksdb.ColumnFamilyHandle} instance - * @param beginKey - * First key to delete within database (included) - * @param endKey - * Last key to delete within database (excluded) + * @param key byte array of a key to search for + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than "key".length + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. * - * @throws RocksDBException - * thrown if error happens in underlying native library. + * @return boolean value indicating if key does not exist or might exist. */ - public void deleteRange(final ColumnFamilyHandle columnFamilyHandle, final byte[] beginKey, - final byte[] endKey) throws RocksDBException { - deleteRange(nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, endKey.length, - columnFamilyHandle.nativeHandle_); + public boolean keyMayExist(final byte[] key, final int offset, final int len, + final StringBuilder value) { + checkBounds(offset, len, key.length); + return keyMayExist(nativeHandle_, key, offset, len, value); } /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - * @param writeOpt - * WriteOptions to be used with delete operation - * @param beginKey - * First key to delete within database (included) - * @param endKey - * Last key to delete within database (excluded) + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @throws RocksDBException - * thrown if error happens in underlying native library. + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key byte array of a key to search for + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. + * @return boolean value indicating if key does not exist or might exist. */ - public void deleteRange(final WriteOptions writeOpt, final byte[] beginKey, final byte[] endKey) - throws RocksDBException { - deleteRange(nativeHandle_, writeOpt.nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, - endKey.length); + public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final StringBuilder value) { + return keyMayExist(nativeHandle_, key, 0, key.length, + columnFamilyHandle.nativeHandle_, value); } /** - * Removes the database entries in the range ["beginKey", "endKey"), i.e., - * including "beginKey" and excluding "endKey". a non-OK status on error. It - * is not an error if no keys exist in the range ["beginKey", "endKey"). - * - * Delete the database entry (if any) for "key". Returns OK on success, and a - * non-OK status on error. It is not an error if "key" did not exist in the - * database. + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param writeOpt - * WriteOptions to be used with delete operation - * @param beginKey - * First key to delete within database (included) - * @param endKey - * Last key to delete within database (excluded) + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @throws RocksDBException - * thrown if error happens in underlying native library. + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key byte array of a key to search for + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than "key".length + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. + * @return boolean value indicating if key does not exist or might exist. */ - public void deleteRange(final ColumnFamilyHandle columnFamilyHandle, final WriteOptions writeOpt, - final byte[] beginKey, final byte[] endKey) throws RocksDBException { - deleteRange(nativeHandle_, writeOpt.nativeHandle_, beginKey, 0, beginKey.length, endKey, 0, - endKey.length, columnFamilyHandle.nativeHandle_); + public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, int offset, int len, final StringBuilder value) { + checkBounds(offset, len, key.length); + return keyMayExist(nativeHandle_, key, offset, len, + columnFamilyHandle.nativeHandle_, value); } /** - * DB implementations can export properties about their state - * via this method. If "property" is a valid property understood by this - * DB implementation, fills "*value" with its current value and returns - * true. Otherwise returns false. - * - *

    Valid property names include: - *

      - *
    • "rocksdb.num-files-at-level<N>" - return the number of files at - * level <N>, where <N> is an ASCII representation of a level - * number (e.g. "0").
    • - *
    • "rocksdb.stats" - returns a multi-line string that describes statistics - * about the internal operation of the DB.
    • - *
    • "rocksdb.sstables" - returns a multi-line string that describes all - * of the sstables that make up the db contents.
    • - *
    + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - * @param property to be fetched. See above for examples - * @return property value + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @throws RocksDBException thrown if error happens in underlying - * native library. + * @param readOptions {@link ReadOptions} instance + * @param key byte array of a key to search for + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. + * @return boolean value indicating if key does not exist or might exist. */ - public String getProperty(final String property) throws RocksDBException { - return getProperty0(nativeHandle_, property, property.length()); + public boolean keyMayExist(final ReadOptions readOptions, + final byte[] key, final StringBuilder value) { + return keyMayExist(nativeHandle_, readOptions.nativeHandle_, + key, 0, key.length, value); } /** - *

    Similar to GetProperty(), but only works for a subset of properties - * whose return value is a numerical value. Return the value as long.

    - * - *

    Note: As the returned property is of type - * {@code uint64_t} on C++ side the returning value can be negative - * because Java supports in Java 7 only signed long values.

    - * - *

    Java 7: To mitigate the problem of the non - * existent unsigned long tpye, values should be encapsulated using - * {@link java.math.BigInteger} to reflect the correct value. The correct - * behavior is guaranteed if {@code 2^64} is added to negative values.

    - * - *

    Java 8: In Java 8 the value should be treated as - * unsigned long using provided methods of type {@link Long}.

    - * - * @param property to be fetched. + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - * @return numerical property value. + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @throws RocksDBException if an error happens in the underlying native code. + * @param readOptions {@link ReadOptions} instance + * @param key byte array of a key to search for + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than "key".length + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. + * @return boolean value indicating if key does not exist or might exist. */ - public long getLongProperty(final String property) throws RocksDBException { - return getLongProperty(nativeHandle_, property, property.length()); + public boolean keyMayExist(final ReadOptions readOptions, + final byte[] key, final int offset, final int len, + final StringBuilder value) { + checkBounds(offset, len, key.length); + return keyMayExist(nativeHandle_, readOptions.nativeHandle_, + key, offset, len, value); } /** - *

    Similar to GetProperty(), but only works for a subset of properties - * whose return value is a numerical value. Return the value as long.

    - * - *

    Note: As the returned property is of type - * {@code uint64_t} on C++ side the returning value can be negative - * because Java supports in Java 7 only signed long values.

    - * - *

    Java 7: To mitigate the problem of the non - * existent unsigned long tpye, values should be encapsulated using - * {@link java.math.BigInteger} to reflect the correct value. The correct - * behavior is guaranteed if {@code 2^64} is added to negative values.

    + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - *

    Java 8: In Java 8 the value should be treated as - * unsigned long using provided methods of type {@link Long}.

    + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param property to be fetched. + * @param readOptions {@link ReadOptions} instance + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key byte array of a key to search for + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. + * @return boolean value indicating if key does not exist or might exist. + */ + public boolean keyMayExist(final ReadOptions readOptions, + final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final StringBuilder value) { + return keyMayExist(nativeHandle_, readOptions.nativeHandle_, + key, 0, key.length, columnFamilyHandle.nativeHandle_, + value); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns false, else true. * - * @return numerical property value + * This check is potentially lighter-weight than invoking DB::Get(). One way + * to make this lighter weight is to avoid doing any IOs. * - * @throws RocksDBException if an error happens in the underlying native code. + * @param readOptions {@link ReadOptions} instance + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key byte array of a key to search for + * @param offset the offset of the "key" array to be used, must be + * non-negative and no larger than "key".length + * @param len the length of the "key" array to be used, must be non-negative + * and no larger than "key".length + * @param value StringBuilder instance which is a out parameter if a value is + * found in block-cache. + * @return boolean value indicating if key does not exist or might exist. */ - public long getLongProperty(final ColumnFamilyHandle columnFamilyHandle, - final String property) throws RocksDBException { - return getLongProperty(nativeHandle_, columnFamilyHandle.nativeHandle_, - property, property.length()); + public boolean keyMayExist(final ReadOptions readOptions, + final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final int offset, final int len, final StringBuilder value) { + checkBounds(offset, len, key.length); + return keyMayExist(nativeHandle_, readOptions.nativeHandle_, + key, offset, len, columnFamilyHandle.nativeHandle_, + value); } /** @@ -1552,37 +2390,6 @@ public RocksIterator newIterator(final ReadOptions readOptions) { readOptions.nativeHandle_)); } - /** - *

    Return a handle to the current DB state. Iterators created with - * this handle will all observe a stable snapshot of the current DB - * state. The caller must call ReleaseSnapshot(result) when the - * snapshot is no longer needed.

    - * - *

    nullptr will be returned if the DB fails to take a snapshot or does - * not support snapshot.

    - * - * @return Snapshot {@link Snapshot} instance - */ - public Snapshot getSnapshot() { - long snapshotHandle = getSnapshot(nativeHandle_); - if (snapshotHandle != 0) { - return new Snapshot(snapshotHandle); - } - return null; - } - - /** - * Release a previously acquired snapshot. The caller must not - * use "snapshot" after this call. - * - * @param snapshot {@link Snapshot} instance - */ - public void releaseSnapshot(final Snapshot snapshot) { - if (snapshot != null) { - releaseSnapshot(nativeHandle_, snapshot.nativeHandle_); - } - } - /** *

    Return a heap-allocated iterator over the contents of the * database. The result of newIterator() is initially invalid @@ -1677,88 +2484,331 @@ public List newIterators( return iterators; } + /** - * Gets the handle for the default column family + *

    Return a handle to the current DB state. Iterators created with + * this handle will all observe a stable snapshot of the current DB + * state. The caller must call ReleaseSnapshot(result) when the + * snapshot is no longer needed.

    * - * @return The handle of the default column family + *

    nullptr will be returned if the DB fails to take a snapshot or does + * not support snapshot.

    + * + * @return Snapshot {@link Snapshot} instance */ - public ColumnFamilyHandle getDefaultColumnFamily() { - ColumnFamilyHandle cfHandle = new ColumnFamilyHandle(this, - getDefaultColumnFamily(nativeHandle_)); - cfHandle.disOwnNativeHandle(); - return cfHandle; + public Snapshot getSnapshot() { + long snapshotHandle = getSnapshot(nativeHandle_); + if (snapshotHandle != 0) { + return new Snapshot(snapshotHandle); + } + return null; } /** - * Creates a new column family with the name columnFamilyName and - * allocates a ColumnFamilyHandle within an internal structure. - * The ColumnFamilyHandle is automatically disposed with DB disposal. + * Release a previously acquired snapshot. * - * @param columnFamilyDescriptor column family to be created. - * @return {@link org.rocksdb.ColumnFamilyHandle} instance. + * The caller must not use "snapshot" after this call. + * + * @param snapshot {@link Snapshot} instance + */ + public void releaseSnapshot(final Snapshot snapshot) { + if (snapshot != null) { + releaseSnapshot(nativeHandle_, snapshot.nativeHandle_); + } + } + + /** + * DB implements can export properties about their state + * via this method on a per column family level. + * + *

    If {@code property} is a valid property understood by this DB + * implementation, fills {@code value} with its current value and + * returns true. Otherwise returns false.

    + * + *

    Valid property names include: + *

      + *
    • "rocksdb.num-files-at-level<N>" - return the number of files at + * level <N>, where <N> is an ASCII representation of a level + * number (e.g. "0").
    • + *
    • "rocksdb.stats" - returns a multi-line string that describes statistics + * about the internal operation of the DB.
    • + *
    • "rocksdb.sstables" - returns a multi-line string that describes all + * of the sstables that make up the db contents.
    • + *
    + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family. + * @param property to be fetched. See above for examples + * @return property value * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public ColumnFamilyHandle createColumnFamily( - final ColumnFamilyDescriptor columnFamilyDescriptor) - throws RocksDBException { - return new ColumnFamilyHandle(this, createColumnFamily(nativeHandle_, - columnFamilyDescriptor.columnFamilyName(), - columnFamilyDescriptor.columnFamilyOptions().nativeHandle_)); + public String getProperty( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, + final String property) throws RocksDBException { + return getProperty(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + property, property.length()); } /** - * Drops the column family identified by columnFamilyName. Internal - * handles to this column family will be disposed. If the column family - * is not known removal will fail. + * DB implementations can export properties about their state + * via this method. If "property" is a valid property understood by this + * DB implementation, fills "*value" with its current value and returns + * true. Otherwise returns false. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance + *

    Valid property names include: + *

      + *
    • "rocksdb.num-files-at-level<N>" - return the number of files at + * level <N>, where <N> is an ASCII representation of a level + * number (e.g. "0").
    • + *
    • "rocksdb.stats" - returns a multi-line string that describes statistics + * about the internal operation of the DB.
    • + *
    • "rocksdb.sstables" - returns a multi-line string that describes all + * of the sstables that make up the db contents.
    • + *
    + * + * @param property to be fetched. See above for examples + * @return property value * * @throws RocksDBException thrown if error happens in underlying * native library. */ - public void dropColumnFamily(final ColumnFamilyHandle columnFamilyHandle) - throws RocksDBException, IllegalArgumentException { - // throws RocksDBException if something goes wrong - dropColumnFamily(nativeHandle_, columnFamilyHandle.nativeHandle_); - // After the drop the native handle is not valid anymore - columnFamilyHandle.disOwnNativeHandle(); + public String getProperty(final String property) throws RocksDBException { + return getProperty(null, property); } + /** - *

    Flush all memory table data.

    + * Gets a property map. * - *

    Note: it must be ensured that the FlushOptions instance - * is not GC'ed before this method finishes. If the wait parameter is - * set to false, flush processing is asynchronous.

    + * @param property to be fetched. * - * @param flushOptions {@link org.rocksdb.FlushOptions} instance. - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. + * @return the property map + * + * @throws RocksDBException if an error happens in the underlying native code. + */ + public Map getMapProperty(final String property) + throws RocksDBException { + return getMapProperty(null, property); + } + + /** + * Gets a property map. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family. + * @param property to be fetched. + * + * @return the property map + * + * @throws RocksDBException if an error happens in the underlying native code. + */ + public Map getMapProperty( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, + final String property) throws RocksDBException { + return getMapProperty(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + property, property.length()); + } + + /** + *

    Similar to GetProperty(), but only works for a subset of properties + * whose return value is a numerical value. Return the value as long.

    + * + *

    Note: As the returned property is of type + * {@code uint64_t} on C++ side the returning value can be negative + * because Java supports in Java 7 only signed long values.

    + * + *

    Java 7: To mitigate the problem of the non + * existent unsigned long tpye, values should be encapsulated using + * {@link java.math.BigInteger} to reflect the correct value. The correct + * behavior is guaranteed if {@code 2^64} is added to negative values.

    + * + *

    Java 8: In Java 8 the value should be treated as + * unsigned long using provided methods of type {@link Long}.

    + * + * @param property to be fetched. + * + * @return numerical property value. + * + * @throws RocksDBException if an error happens in the underlying native code. + */ + public long getLongProperty(final String property) throws RocksDBException { + return getLongProperty(null, property); + } + + /** + *

    Similar to GetProperty(), but only works for a subset of properties + * whose return value is a numerical value. Return the value as long.

    + * + *

    Note: As the returned property is of type + * {@code uint64_t} on C++ side the returning value can be negative + * because Java supports in Java 7 only signed long values.

    + * + *

    Java 7: To mitigate the problem of the non + * existent unsigned long tpye, values should be encapsulated using + * {@link java.math.BigInteger} to reflect the correct value. The correct + * behavior is guaranteed if {@code 2^64} is added to negative values.

    + * + *

    Java 8: In Java 8 the value should be treated as + * unsigned long using provided methods of type {@link Long}.

    + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family + * @param property to be fetched. + * + * @return numerical property value + * + * @throws RocksDBException if an error happens in the underlying native code. + */ + public long getLongProperty( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, + final String property) throws RocksDBException { + return getLongProperty(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + property, property.length()); + } + + /** + * Reset internal stats for DB and all column families. + * + * Note this doesn't reset {@link Options#statistics()} as it is not + * owned by DB. + */ + public void resetStats() throws RocksDBException { + resetStats(nativeHandle_); + } + + /** + *

    Return sum of the getLongProperty of all the column families

    + * + *

    Note: As the returned property is of type + * {@code uint64_t} on C++ side the returning value can be negative + * because Java supports in Java 7 only signed long values.

    + * + *

    Java 7: To mitigate the problem of the non + * existent unsigned long tpye, values should be encapsulated using + * {@link java.math.BigInteger} to reflect the correct value. The correct + * behavior is guaranteed if {@code 2^64} is added to negative values.

    + * + *

    Java 8: In Java 8 the value should be treated as + * unsigned long using provided methods of type {@link Long}.

    + * + * @param property to be fetched. + * + * @return numerical property value + * + * @throws RocksDBException if an error happens in the underlying native code. + */ + public long getAggregatedLongProperty(final String property) + throws RocksDBException { + return getAggregatedLongProperty(nativeHandle_, property, + property.length()); + } + + /** + * Get the approximate file system space used by keys in each range. + * + * Note that the returned sizes measure file system space usage, so + * if the user data compresses by a factor of ten, the returned + * sizes will be one-tenth the size of the corresponding user data size. + * + * If {@code sizeApproximationFlags} defines whether the returned size + * should include the recently written data in the mem-tables (if + * the mem-table type supports it), data serialized to disk, or both. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family + * @param ranges the ranges over which to approximate sizes + * @param sizeApproximationFlags flags to determine what to include in the + * approximation. + * + * @return the sizes + */ + public long[] getApproximateSizes( + /*@Nullable*/ final ColumnFamilyHandle columnFamilyHandle, + final List ranges, + final SizeApproximationFlag... sizeApproximationFlags) { + + byte flags = 0x0; + for (final SizeApproximationFlag sizeApproximationFlag + : sizeApproximationFlags) { + flags |= sizeApproximationFlag.getValue(); + } + + return getApproximateSizes(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + toRangeSliceHandles(ranges), flags); + } + + /** + * Get the approximate file system space used by keys in each range for + * the default column family. + * + * Note that the returned sizes measure file system space usage, so + * if the user data compresses by a factor of ten, the returned + * sizes will be one-tenth the size of the corresponding user data size. + * + * If {@code sizeApproximationFlags} defines whether the returned size + * should include the recently written data in the mem-tables (if + * the mem-table type supports it), data serialized to disk, or both. + * + * @param ranges the ranges over which to approximate sizes + * @param sizeApproximationFlags flags to determine what to include in the + * approximation. + * + * @return the sizes. + */ + public long[] getApproximateSizes(final List ranges, + final SizeApproximationFlag... sizeApproximationFlags) { + return getApproximateSizes(null, ranges, sizeApproximationFlags); + } + + public static class CountAndSize { + public final long count; + public final long size; + + public CountAndSize(final long count, final long size) { + this.count = count; + this.size = size; + } + } + + /** + * This method is similar to + * {@link #getApproximateSizes(ColumnFamilyHandle, List, SizeApproximationFlag...)}, + * except that it returns approximate number of records and size in memtables. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family + * @param range the ranges over which to get the memtable stats + * + * @return the count and size for the range */ - public void flush(final FlushOptions flushOptions) - throws RocksDBException { - flush(nativeHandle_, flushOptions.nativeHandle_); + public CountAndSize getApproximateMemTableStats( + /*@Nullable*/ final ColumnFamilyHandle columnFamilyHandle, + final Range range) { + final long[] result = getApproximateMemTableStats(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + range.start.getNativeHandle(), + range.limit.getNativeHandle()); + return new CountAndSize(result[0], result[1]); } /** - *

    Flush all memory table data.

    + * This method is similar to + * {@link #getApproximateSizes(ColumnFamilyHandle, List, SizeApproximationFlag...)}, + * except that it returns approximate number of records and size in memtables. * - *

    Note: it must be ensured that the FlushOptions instance - * is not GC'ed before this method finishes. If the wait parameter is - * set to false, flush processing is asynchronous.

    + * @param range the ranges over which to get the memtable stats * - * @param flushOptions {@link org.rocksdb.FlushOptions} instance. - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance. - * @throws RocksDBException thrown if an error occurs within the native - * part of the library. + * @return the count and size for the range */ - public void flush(final FlushOptions flushOptions, - final ColumnFamilyHandle columnFamilyHandle) throws RocksDBException { - flush(nativeHandle_, flushOptions.nativeHandle_, - columnFamilyHandle.nativeHandle_); + public CountAndSize getApproximateMemTableStats( + final Range range) { + return getApproximateMemTableStats(null, range); } /** @@ -1778,7 +2828,40 @@ public void flush(final FlushOptions flushOptions, * part of the library. */ public void compactRange() throws RocksDBException { - compactRange0(nativeHandle_, false, -1, 0); + compactRange(null); + } + + /** + *

    Range compaction of column family.

    + *

    Note: After the database has been compacted, + * all data will have been pushed down to the last level containing + * any data.

    + * + *

    See also

    + *
      + *
    • + * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)} + *
    • + *
    • + * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} + *
    • + *
    • + * {@link #compactRange(ColumnFamilyHandle, byte[], byte[], + * boolean, int, int)} + *
    • + *
    + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family. + * + * @throws RocksDBException thrown if an error occurs within the native + * part of the library. + */ + public void compactRange( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) + throws RocksDBException { + compactRange(nativeHandle_, null, -1, null, -1, 0, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); } /** @@ -1802,42 +2885,44 @@ public void compactRange() throws RocksDBException { */ public void compactRange(final byte[] begin, final byte[] end) throws RocksDBException { - compactRange0(nativeHandle_, begin, begin.length, end, - end.length, false, -1, 0); + compactRange(null, begin, end); } /** - *

    Range compaction of database.

    + *

    Range compaction of column family.

    *

    Note: After the database has been compacted, * all data will have been pushed down to the last level containing * any data.

    * - *

    Compaction outputs should be placed in options.db_paths - * [target_path_id]. Behavior is undefined if target_path_id is - * out of range.

    - * *

    See also

    *
      - *
    • {@link #compactRange()}
    • - *
    • {@link #compactRange(byte[], byte[])}
    • - *
    • {@link #compactRange(byte[], byte[], boolean, int, int)}
    • + *
    • {@link #compactRange(ColumnFamilyHandle)}
    • + *
    • + * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)} + *
    • + *
    • + * {@link #compactRange(ColumnFamilyHandle, byte[], byte[], + * boolean, int, int)} + *
    • *
    * - * @param reduce_level reduce level after compaction - * @param target_level target level to compact to - * @param target_path_id the target path id of output path + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family. + * @param begin start of key range (included in range) + * @param end end of key range (excluded from range) * * @throws RocksDBException thrown if an error occurs within the native * part of the library. */ - public void compactRange(final boolean reduce_level, - final int target_level, final int target_path_id) - throws RocksDBException { - compactRange0(nativeHandle_, reduce_level, - target_level, target_path_id); + public void compactRange( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, + final byte[] begin, final byte[] end) throws RocksDBException { + compactRange(nativeHandle_, + begin, begin == null ? -1 : begin.length, + end, end == null ? -1 : end.length, + 0, columnFamilyHandle == null ? 0: columnFamilyHandle.nativeHandle_); } - /** *

    Range compaction of database.

    *

    Note: After the database has been compacted, @@ -1851,24 +2936,23 @@ public void compactRange(final boolean reduce_level, *

    See also

    *
      *
    • {@link #compactRange()}
    • - *
    • {@link #compactRange(boolean, int, int)}
    • *
    • {@link #compactRange(byte[], byte[])}
    • + *
    • {@link #compactRange(byte[], byte[], boolean, int, int)}
    • *
    * - * @param begin start of key range (included in range) - * @param end end of key range (excluded from range) - * @param reduce_level reduce level after compaction - * @param target_level target level to compact to - * @param target_path_id the target path id of output path + * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)} instead + * + * @param changeLevel reduce level after compaction + * @param targetLevel target level to compact to + * @param targetPathId the target path id of output path * * @throws RocksDBException thrown if an error occurs within the native * part of the library. */ - public void compactRange(final byte[] begin, final byte[] end, - final boolean reduce_level, final int target_level, - final int target_path_id) throws RocksDBException { - compactRange0(nativeHandle_, begin, begin.length, end, end.length, - reduce_level, target_level, target_path_id); + @Deprecated + public void compactRange(final boolean changeLevel, final int targetLevel, + final int targetPathId) throws RocksDBException { + compactRange(null, changeLevel, targetLevel, targetPathId); } /** @@ -1877,11 +2961,13 @@ public void compactRange(final byte[] begin, final byte[] end, * all data will have been pushed down to the last level containing * any data.

    * + *

    Compaction outputs should be placed in options.db_paths + * [target_path_id]. Behavior is undefined if target_path_id is + * out of range.

    + * *

    See also

    *
      - *
    • - * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)} - *
    • + *
    • {@link #compactRange(ColumnFamilyHandle)}
    • *
    • * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} *
    • @@ -1891,48 +2977,67 @@ public void compactRange(final byte[] begin, final byte[] end, * *
    * + * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)} instead + * * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance. + * instance, or null for the default column family. + * @param changeLevel reduce level after compaction + * @param targetLevel target level to compact to + * @param targetPathId the target path id of output path * * @throws RocksDBException thrown if an error occurs within the native * part of the library. */ - public void compactRange(final ColumnFamilyHandle columnFamilyHandle) + @Deprecated + public void compactRange( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, + final boolean changeLevel, final int targetLevel, final int targetPathId) throws RocksDBException { - compactRange(nativeHandle_, false, -1, 0, - columnFamilyHandle.nativeHandle_); + final CompactRangeOptions options = new CompactRangeOptions(); + options.setChangeLevel(changeLevel); + options.setTargetLevel(targetLevel); + options.setTargetPathId(targetPathId); + compactRange(nativeHandle_, + null, -1, + null, -1, + options.nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); } /** - *

    Range compaction of column family.

    + *

    Range compaction of database.

    *

    Note: After the database has been compacted, * all data will have been pushed down to the last level containing * any data.

    * + *

    Compaction outputs should be placed in options.db_paths + * [target_path_id]. Behavior is undefined if target_path_id is + * out of range.

    + * *

    See also

    *
      - *
    • {@link #compactRange(ColumnFamilyHandle)}
    • - *
    • - * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)} - *
    • - *
    • - * {@link #compactRange(ColumnFamilyHandle, byte[], byte[], - * boolean, int, int)} - *
    • + *
    • {@link #compactRange()}
    • + *
    • {@link #compactRange(boolean, int, int)}
    • + *
    • {@link #compactRange(byte[], byte[])}
    • *
    * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance. + * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)} + * instead + * * @param begin start of key range (included in range) * @param end end of key range (excluded from range) + * @param changeLevel reduce level after compaction + * @param targetLevel target level to compact to + * @param targetPathId the target path id of output path * * @throws RocksDBException thrown if an error occurs within the native * part of the library. */ - public void compactRange(final ColumnFamilyHandle columnFamilyHandle, - final byte[] begin, final byte[] end) throws RocksDBException { - compactRange(nativeHandle_, begin, begin.length, end, end.length, - false, -1, 0, columnFamilyHandle.nativeHandle_); + @Deprecated + public void compactRange(final byte[] begin, final byte[] end, + final boolean changeLevel, final int targetLevel, + final int targetPathId) throws RocksDBException { + compactRange(null, begin, end, changeLevel, targetLevel, targetPathId); } /** @@ -1949,90 +3054,377 @@ public void compactRange(final ColumnFamilyHandle columnFamilyHandle, *
      *
    • {@link #compactRange(ColumnFamilyHandle)}
    • *
    • - * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} + * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)} *
    • *
    • - * {@link #compactRange(ColumnFamilyHandle, byte[], byte[], - * boolean, int, int)} + * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} *
    • *
    * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance. - * @param reduce_level reduce level after compaction - * @param target_level target level to compact to - * @param target_path_id the target path id of output path + * @deprecated Use {@link #compactRange(ColumnFamilyHandle, byte[], byte[], CompactRangeOptions)} instead + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance. + * @param begin start of key range (included in range) + * @param end end of key range (excluded from range) + * @param changeLevel reduce level after compaction + * @param targetLevel target level to compact to + * @param targetPathId the target path id of output path + * + * @throws RocksDBException thrown if an error occurs within the native + * part of the library. + */ + @Deprecated + public void compactRange( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, + final byte[] begin, final byte[] end, final boolean changeLevel, + final int targetLevel, final int targetPathId) + throws RocksDBException { + final CompactRangeOptions options = new CompactRangeOptions(); + options.setChangeLevel(changeLevel); + options.setTargetLevel(targetLevel); + options.setTargetPathId(targetPathId); + compactRange(nativeHandle_, + begin, begin == null ? -1 : begin.length, + end, end == null ? -1 : end.length, + options.nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); + } + + /** + *

    Range compaction of column family.

    + *

    Note: After the database has been compacted, + * all data will have been pushed down to the last level containing + * any data.

    + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance. + * @param begin start of key range (included in range) + * @param end end of key range (excluded from range) + * @param compactRangeOptions options for the compaction + * + * @throws RocksDBException thrown if an error occurs within the native + * part of the library. + */ + public void compactRange(final ColumnFamilyHandle columnFamilyHandle, + final byte[] begin, final byte[] end, + final CompactRangeOptions compactRangeOptions) throws RocksDBException { + compactRange(nativeHandle_, + begin, begin == null ? -1 : begin.length, + end, end == null ? -1 : end.length, + compactRangeOptions.nativeHandle_, columnFamilyHandle.nativeHandle_); + } + + /** + * Change the options for the column family handle. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance, or null for the default column family. + * @param mutableColumnFamilyOptions the options. + */ + public void setOptions( + /* @Nullable */final ColumnFamilyHandle columnFamilyHandle, + final MutableColumnFamilyOptions mutableColumnFamilyOptions) + throws RocksDBException { + setOptions(nativeHandle_, columnFamilyHandle.nativeHandle_, + mutableColumnFamilyOptions.getKeys(), + mutableColumnFamilyOptions.getValues()); + } + + /** + * Change the options for the default column family handle. + * + * @param mutableColumnFamilyOptions the options. + */ + public void setOptions( + final MutableColumnFamilyOptions mutableColumnFamilyOptions) + throws RocksDBException { + setOptions(null, mutableColumnFamilyOptions); + } + + /** + * Set the options for the column family handle. + * + * @param mutableDBoptions the options. + */ + public void setDBOptions(final MutableDBOptions mutableDBoptions) + throws RocksDBException { + setDBOptions(nativeHandle_, + mutableDBoptions.getKeys(), + mutableDBoptions.getValues()); + } + + /** + * Takes nputs a list of files specified by file names and + * compacts them to the specified level. + * + * Note that the behavior is different from + * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} + * in that CompactFiles() performs the compaction job using the CURRENT + * thread. + * + * @param compactionOptions compaction options + * @param inputFileNames the name of the files to compact + * @param outputLevel the level to which they should be compacted + * @param outputPathId the id of the output path, or -1 + * @param compactionJobInfo the compaction job info, this parameter + * will be updated with the info from compacting the files, + * can just be null if you don't need it. + */ + public List compactFiles( + final CompactionOptions compactionOptions, + final List inputFileNames, + final int outputLevel, + final int outputPathId, + /* @Nullable */ final CompactionJobInfo compactionJobInfo) + throws RocksDBException { + return compactFiles(compactionOptions, null, inputFileNames, outputLevel, + outputPathId, compactionJobInfo); + } + + /** + * Takes a list of files specified by file names and + * compacts them to the specified level. + * + * Note that the behavior is different from + * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} + * in that CompactFiles() performs the compaction job using the CURRENT + * thread. + * + * @param compactionOptions compaction options + * @param columnFamilyHandle columnFamilyHandle, or null for the + * default column family + * @param inputFileNames the name of the files to compact + * @param outputLevel the level to which they should be compacted + * @param outputPathId the id of the output path, or -1 + * @param compactionJobInfo the compaction job info, this parameter + * will be updated with the info from compacting the files, + * can just be null if you don't need it. + */ + public List compactFiles( + final CompactionOptions compactionOptions, + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle, + final List inputFileNames, + final int outputLevel, + final int outputPathId, + /* @Nullable */ final CompactionJobInfo compactionJobInfo) + throws RocksDBException { + return Arrays.asList(compactFiles(nativeHandle_, compactionOptions.nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + inputFileNames.toArray(new String[0]), + outputLevel, + outputPathId, + compactionJobInfo == null ? 0 : compactionJobInfo.nativeHandle_)); + } + + /** + * This function will wait until all currently running background processes + * finish. After it returns, no background process will be run until + * {@link #continueBackgroundWork()} is called + * + * @throws RocksDBException If an error occurs when pausing background work + */ + public void pauseBackgroundWork() throws RocksDBException { + pauseBackgroundWork(nativeHandle_); + } + + /** + * Resumes background work which was suspended by + * previously calling {@link #pauseBackgroundWork()} + * + * @throws RocksDBException If an error occurs when resuming background work + */ + public void continueBackgroundWork() throws RocksDBException { + continueBackgroundWork(nativeHandle_); + } + + /** + * Enable automatic compactions for the given column + * families if they were previously disabled. + * + * The function will first set the + * {@link ColumnFamilyOptions#disableAutoCompactions()} option for each + * column family to false, after which it will schedule a flush/compaction. + * + * NOTE: Setting disableAutoCompactions to 'false' through + * {@link #setOptions(ColumnFamilyHandle, MutableColumnFamilyOptions)} + * does NOT schedule a flush/compaction afterwards, and only changes the + * parameter itself within the column family option. + * + * @param columnFamilyHandles the column family handles + */ + public void enableAutoCompaction( + final List columnFamilyHandles) + throws RocksDBException { + enableAutoCompaction(nativeHandle_, + toNativeHandleList(columnFamilyHandles)); + } + + /** + * Number of levels used for this DB. + * + * @return the number of levels + */ + public int numberLevels() { + return numberLevels(null); + } + + /** + * Number of levels used for a column family in this DB. + * + * @param columnFamilyHandle the column family handle, or null + * for the default column family + * + * @return the number of levels + */ + public int numberLevels(/* @Nullable */final ColumnFamilyHandle columnFamilyHandle) { + return numberLevels(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); + } + + /** + * Maximum level to which a new compacted memtable is pushed if it + * does not create overlap. + */ + public int maxMemCompactionLevel() { + return maxMemCompactionLevel(null); + } + + /** + * Maximum level to which a new compacted memtable is pushed if it + * does not create overlap. + * + * @param columnFamilyHandle the column family handle + */ + public int maxMemCompactionLevel( + /* @Nullable */final ColumnFamilyHandle columnFamilyHandle) { + return maxMemCompactionLevel(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); + } + + /** + * Number of files in level-0 that would stop writes. + */ + public int level0StopWriteTrigger() { + return level0StopWriteTrigger(null); + } + + /** + * Number of files in level-0 that would stop writes. + * + * @param columnFamilyHandle the column family handle + */ + public int level0StopWriteTrigger( + /* @Nullable */final ColumnFamilyHandle columnFamilyHandle) { + return level0StopWriteTrigger(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); + } + + /** + * Get DB name -- the exact same name that was provided as an argument to + * as path to {@link #open(Options, String)}. + * + * @return the DB name + */ + public String getName() { + return getName(nativeHandle_); + } + + /** + * Get the Env object from the DB + * + * @return the env + */ + public Env getEnv() { + final long envHandle = getEnv(nativeHandle_); + if (envHandle == Env.getDefault().nativeHandle_) { + return Env.getDefault(); + } else { + final Env env = new RocksEnv(envHandle); + env.disOwnNativeHandle(); // we do not own the Env! + return env; + } + } + + /** + *

    Flush all memory table data.

    + * + *

    Note: it must be ensured that the FlushOptions instance + * is not GC'ed before this method finishes. If the wait parameter is + * set to false, flush processing is asynchronous.

    + * + * @param flushOptions {@link org.rocksdb.FlushOptions} instance. + * @throws RocksDBException thrown if an error occurs within the native + * part of the library. + */ + public void flush(final FlushOptions flushOptions) + throws RocksDBException { + flush(flushOptions, (List) null); + } + + /** + *

    Flush all memory table data.

    + * + *

    Note: it must be ensured that the FlushOptions instance + * is not GC'ed before this method finishes. If the wait parameter is + * set to false, flush processing is asynchronous.

    * + * @param flushOptions {@link org.rocksdb.FlushOptions} instance. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance. * @throws RocksDBException thrown if an error occurs within the native * part of the library. */ - public void compactRange(final ColumnFamilyHandle columnFamilyHandle, - final boolean reduce_level, final int target_level, - final int target_path_id) throws RocksDBException { - compactRange(nativeHandle_, reduce_level, target_level, - target_path_id, columnFamilyHandle.nativeHandle_); + public void flush(final FlushOptions flushOptions, + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) + throws RocksDBException { + flush(flushOptions, + columnFamilyHandle == null ? null : Arrays.asList(columnFamilyHandle)); } /** - *

    Range compaction of column family.

    - *

    Note: After the database has been compacted, - * all data will have been pushed down to the last level containing - * any data.

    - * - *

    Compaction outputs should be placed in options.db_paths - * [target_path_id]. Behavior is undefined if target_path_id is - * out of range.

    + * Flushes multiple column families. * - *

    See also

    - *
      - *
    • {@link #compactRange(ColumnFamilyHandle)}
    • - *
    • - * {@link #compactRange(ColumnFamilyHandle, boolean, int, int)} - *
    • - *
    • - * {@link #compactRange(ColumnFamilyHandle, byte[], byte[])} - *
    • - *
    + * If atomic flush is not enabled, this is equivalent to calling + * {@link #flush(FlushOptions, ColumnFamilyHandle)} multiple times. * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance. - * @param begin start of key range (included in range) - * @param end end of key range (excluded from range) - * @param reduce_level reduce level after compaction - * @param target_level target level to compact to - * @param target_path_id the target path id of output path + * If atomic flush is enabled, this will flush all column families + * specified up to the latest sequence number at the time when flush is + * requested. * + * @param flushOptions {@link org.rocksdb.FlushOptions} instance. + * @param columnFamilyHandles column family handles. * @throws RocksDBException thrown if an error occurs within the native * part of the library. */ - public void compactRange(final ColumnFamilyHandle columnFamilyHandle, - final byte[] begin, final byte[] end, final boolean reduce_level, - final int target_level, final int target_path_id) + public void flush(final FlushOptions flushOptions, + /* @Nullable */ final List columnFamilyHandles) throws RocksDBException { - compactRange(nativeHandle_, begin, begin.length, end, end.length, - reduce_level, target_level, target_path_id, - columnFamilyHandle.nativeHandle_); + flush(nativeHandle_, flushOptions.nativeHandle_, + toNativeHandleList(columnFamilyHandles)); } /** - * This function will wait until all currently running background processes - * finish. After it returns, no background process will be run until - * {@link #continueBackgroundWork()} is called + * Flush the WAL memory buffer to the file. If {@code sync} is true, + * it calls {@link #syncWal()} afterwards. * - * @throws RocksDBException If an error occurs when pausing background work + * @param sync true to also fsync to disk. */ - public void pauseBackgroundWork() throws RocksDBException { - pauseBackgroundWork(nativeHandle_); + public void flushWal(final boolean sync) throws RocksDBException { + flushWal(nativeHandle_, sync); } /** - * Resumes backround work which was suspended by - * previously calling {@link #pauseBackgroundWork()} + * Sync the WAL. * - * @throws RocksDBException If an error occurs when resuming background work + * Note that {@link #write(WriteOptions, WriteBatch)} followed by + * {@link #syncWal()} is not exactly the same as + * {@link #write(WriteOptions, WriteBatch)} with + * {@link WriteOptions#sync()} set to true; In the latter case the changes + * won't be visible until the sync is done. + * + * Currently only works if {@link Options#allowMmapWrites()} is set to false. */ - public void continueBackgroundWork() throws RocksDBException { - continueBackgroundWork(nativeHandle_); + public void syncWal() throws RocksDBException { + syncWal(nativeHandle_); } /** @@ -2045,6 +3437,25 @@ public long getLatestSequenceNumber() { return getLatestSequenceNumber(nativeHandle_); } + /** + * Instructs DB to preserve deletes with sequence numbers >= sequenceNumber. + * + * Has no effect if DBOptions#preserveDeletes() is set to false. + * + * This function assumes that user calls this function with monotonically + * increasing seqnums (otherwise we can't guarantee that a particular delete + * hasn't been already processed). + * + * @param sequenceNumber the minimum sequence number to preserve + * + * @return true if the value was successfully updated, + * false if user attempted to call if with + * sequenceNumber <= current value. + */ + public boolean setPreserveDeletesSequenceNumber(final long sequenceNumber) { + return setPreserveDeletesSequenceNumber(nativeHandle_, sequenceNumber); + } + /** *

    Prevent file deletions. Compactions will continue to occur, * but no obsolete files will be deleted. Calling this multiple @@ -2082,6 +3493,78 @@ public void enableFileDeletions(final boolean force) enableFileDeletions(nativeHandle_, force); } + public static class LiveFiles { + /** + * The valid size of the manifest file. The manifest file is an ever growing + * file, but only the portion specified here is valid for this snapshot. + */ + public final long manifestFileSize; + + /** + * The files are relative to the {@link #getName()} and are not + * absolute paths. Despite being relative paths, the file names begin + * with "/". + */ + public final List files; + + LiveFiles(final long manifestFileSize, final List files) { + this.manifestFileSize = manifestFileSize; + this.files = files; + } + } + + /** + * Retrieve the list of all files in the database after flushing the memtable. + * + * See {@link #getLiveFiles(boolean)}. + * + * @return the live files + */ + public LiveFiles getLiveFiles() throws RocksDBException { + return getLiveFiles(true); + } + + /** + * Retrieve the list of all files in the database. + * + * In case you have multiple column families, even if {@code flushMemtable} + * is true, you still need to call {@link #getSortedWalFiles()} + * after {@link #getLiveFiles(boolean)} to compensate for new data that + * arrived to already-flushed column families while other column families + * were flushing. + * + * NOTE: Calling {@link #getLiveFiles(boolean)} followed by + * {@link #getSortedWalFiles()} can generate a lossless backup. + * + * @param flushMemtable set to true to flush before recoding the live + * files. Setting to false is useful when we don't want to wait for flush + * which may have to wait for compaction to complete taking an + * indeterminate time. + * + * @return the live files + */ + public LiveFiles getLiveFiles(final boolean flushMemtable) + throws RocksDBException { + final String[] result = getLiveFiles(nativeHandle_, flushMemtable); + if (result == null) { + return null; + } + final String[] files = Arrays.copyOf(result, result.length - 1); + final long manifestFileSize = Long.parseLong(result[result.length - 1]); + + return new LiveFiles(manifestFileSize, Arrays.asList(files)); + } + + /** + * Retrieve the sorted list of all wal files with earliest file first. + * + * @return the log files + */ + public List getSortedWalFiles() throws RocksDBException { + final LogFile[] logFiles = getSortedWalFiles(nativeHandle_); + return Arrays.asList(logFiles); + } + /** *

    Returns an iterator that is positioned at a write-batch containing * seq_number. If the sequence number is non existent, it returns an iterator @@ -2105,21 +3588,46 @@ public TransactionLogIterator getUpdatesSince(final long sequenceNumber) getUpdatesSince(nativeHandle_, sequenceNumber)); } - public void setOptions(final ColumnFamilyHandle columnFamilyHandle, - final MutableColumnFamilyOptions mutableColumnFamilyOptions) - throws RocksDBException { - setOptions(nativeHandle_, columnFamilyHandle.nativeHandle_, - mutableColumnFamilyOptions.getKeys(), - mutableColumnFamilyOptions.getValues()); + /** + * Delete the file name from the db directory and update the internal state to + * reflect that. Supports deletion of sst and log files only. 'name' must be + * path relative to the db directory. eg. 000001.sst, /archive/000003.log + * + * @param name the file name + */ + public void deleteFile(final String name) throws RocksDBException { + deleteFile(nativeHandle_, name); } - private long[] toNativeHandleList(final List objectList) { - final int len = objectList.size(); - final long[] handleList = new long[len]; - for (int i = 0; i < len; i++) { - handleList[i] = objectList.get(i).nativeHandle_; - } - return handleList; + /** + * Gets a list of all table files metadata. + * + * @return table files metadata. + */ + public List getLiveFilesMetaData() { + return Arrays.asList(getLiveFilesMetaData(nativeHandle_)); + } + + /** + * Obtains the meta data of the specified column family of the DB. + * + * @param columnFamilyHandle the column family + * + * @return the column family metadata + */ + public ColumnFamilyMetaData getColumnFamilyMetaData( + /* @Nullable */ final ColumnFamilyHandle columnFamilyHandle) { + return getColumnFamilyMetaData(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); + } + + /** + * Obtains the meta data of the default column family of the DB. + * + * @return the column family metadata + */ + public ColumnFamilyMetaData GetColumnFamilyMetaData() { + return getColumnFamilyMetaData(null); } /** @@ -2143,7 +3651,7 @@ public void ingestExternalFile(final List filePathList, final IngestExternalFileOptions ingestExternalFileOptions) throws RocksDBException { ingestExternalFile(nativeHandle_, getDefaultColumnFamily().nativeHandle_, - filePathList.toArray(new String[filePathList.size()]), + filePathList.toArray(new String[0]), filePathList.size(), ingestExternalFileOptions.nativeHandle_); } @@ -2170,21 +3678,218 @@ public void ingestExternalFile(final ColumnFamilyHandle columnFamilyHandle, final IngestExternalFileOptions ingestExternalFileOptions) throws RocksDBException { ingestExternalFile(nativeHandle_, columnFamilyHandle.nativeHandle_, - filePathList.toArray(new String[filePathList.size()]), + filePathList.toArray(new String[0]), filePathList.size(), ingestExternalFileOptions.nativeHandle_); } /** - * Private constructor. + * Verify checksum * - * @param nativeHandle The native handle of the C++ RocksDB object + * @throws RocksDBException if the checksum is not valid */ - protected RocksDB(final long nativeHandle) { - super(nativeHandle); + public void verifyChecksum() throws RocksDBException { + verifyChecksum(nativeHandle_); + } + + /** + * Gets the handle for the default column family + * + * @return The handle of the default column family + */ + public ColumnFamilyHandle getDefaultColumnFamily() { + final ColumnFamilyHandle cfHandle = new ColumnFamilyHandle(this, + getDefaultColumnFamily(nativeHandle_)); + cfHandle.disOwnNativeHandle(); + return cfHandle; + } + + /** + * Get the properties of all tables. + * + * @param columnFamilyHandle the column family handle, or null for the default + * column family. + * + * @return the properties + */ + public Map getPropertiesOfAllTables( + /* @Nullable */final ColumnFamilyHandle columnFamilyHandle) + throws RocksDBException { + return getPropertiesOfAllTables(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); + } + + /** + * Get the properties of all tables in the default column family. + * + * @return the properties + */ + public Map getPropertiesOfAllTables() + throws RocksDBException { + return getPropertiesOfAllTables(null); + } + + /** + * Get the properties of tables in range. + * + * @param columnFamilyHandle the column family handle, or null for the default + * column family. + * @param ranges the ranges over which to get the table properties + * + * @return the properties + */ + public Map getPropertiesOfTablesInRange( + /* @Nullable */final ColumnFamilyHandle columnFamilyHandle, + final List ranges) throws RocksDBException { + return getPropertiesOfTablesInRange(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + toRangeSliceHandles(ranges)); + } + + /** + * Get the properties of tables in range for the default column family. + * + * @param ranges the ranges over which to get the table properties + * + * @return the properties + */ + public Map getPropertiesOfTablesInRange( + final List ranges) throws RocksDBException { + return getPropertiesOfTablesInRange(null, ranges); + } + + /** + * Suggest the range to compact. + * + * @param columnFamilyHandle the column family handle, or null for the default + * column family. + * + * @return the suggested range. + */ + public Range suggestCompactRange( + /* @Nullable */final ColumnFamilyHandle columnFamilyHandle) + throws RocksDBException { + final long[] rangeSliceHandles = suggestCompactRange(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_); + return new Range(new Slice(rangeSliceHandles[0]), + new Slice(rangeSliceHandles[1])); + } + + /** + * Suggest the range to compact for the default column family. + * + * @return the suggested range. + */ + public Range suggestCompactRange() + throws RocksDBException { + return suggestCompactRange(null); + } + + /** + * Promote L0. + * + * @param columnFamilyHandle the column family handle, + * or null for the default column family. + */ + public void promoteL0( + /* @Nullable */final ColumnFamilyHandle columnFamilyHandle, + final int targetLevel) throws RocksDBException { + promoteL0(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + targetLevel); + } + + /** + * Promote L0 for the default column family. + */ + public void promoteL0(final int targetLevel) + throws RocksDBException { + promoteL0(null, targetLevel); + } + + /** + * Trace DB operations. + * + * Use {@link #endTrace()} to stop tracing. + * + * @param traceOptions the options + * @param traceWriter the trace writer + */ + public void startTrace(final TraceOptions traceOptions, + final AbstractTraceWriter traceWriter) throws RocksDBException { + startTrace(nativeHandle_, traceOptions.getMaxTraceFileSize(), + traceWriter.nativeHandle_); + /** + * NOTE: {@link #startTrace(long, long, long) transfers the ownership + * from Java to C++, so we must disown the native handle here. + */ + traceWriter.disOwnNativeHandle(); + } + + /** + * Stop tracing DB operations. + * + * See {@link #startTrace(TraceOptions, AbstractTraceWriter)} + */ + public void endTrace() throws RocksDBException { + endTrace(nativeHandle_); + } + + /** + * Static method to destroy the contents of the specified database. + * Be very careful using this method. + * + * @param path the path to the Rocksdb database. + * @param options {@link org.rocksdb.Options} instance. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public static void destroyDB(final String path, final Options options) + throws RocksDBException { + destroyDB(path, options.nativeHandle_); + } + + private /* @Nullable */ long[] toNativeHandleList( + /* @Nullable */ final List objectList) { + if (objectList == null) { + return null; + } + final int len = objectList.size(); + final long[] handleList = new long[len]; + for (int i = 0; i < len; i++) { + handleList[i] = objectList.get(i).nativeHandle_; + } + return handleList; + } + + private static long[] toRangeSliceHandles(final List ranges) { + final long rangeSliceHandles[] = new long [ranges.size() * 2]; + for (int i = 0, j = 0; i < ranges.size(); i++) { + final Range range = ranges.get(i); + rangeSliceHandles[j++] = range.start.getNativeHandle(); + rangeSliceHandles[j++] = range.limit.getNativeHandle(); + } + return rangeSliceHandles; + } + + protected void storeOptionsInstance(DBOptionsInterface options) { + options_ = options; + } + + private static void checkBounds(int offset, int len, int size) { + if ((offset | len | (offset + len) | (size - (offset + len))) < 0) { + throw new IndexOutOfBoundsException(String.format("offset(%d), len(%d), size(%d)", offset, len, size)); + } + } + + private static int computeCapacityHint(final int estimatedNumberOfItems) { + // Default load factor for HashMap is 0.75, so N * 1.5 will be at the load + // limit. We add +1 for a buffer. + return (int)Math.ceil(estimatedNumberOfItems * 1.5 + 1.0); } // native methods - protected native static long open(final long optionsHandle, + private native static long open(final long optionsHandle, final String path) throws RocksDBException; /** @@ -2199,11 +3904,11 @@ protected native static long open(final long optionsHandle, * * @throws RocksDBException thrown if the database could not be opened */ - protected native static long[] open(final long optionsHandle, + private native static long[] open(final long optionsHandle, final String path, final byte[][] columnFamilyNames, final long[] columnFamilyOptions) throws RocksDBException; - protected native static long openROnly(final long optionsHandle, + private native static long openROnly(final long optionsHandle, final String path) throws RocksDBException; /** @@ -2218,167 +3923,258 @@ protected native static long openROnly(final long optionsHandle, * * @throws RocksDBException thrown if the database could not be opened */ - protected native static long[] openROnly(final long optionsHandle, + private native static long[] openROnly(final long optionsHandle, final String path, final byte[][] columnFamilyNames, final long[] columnFamilyOptions ) throws RocksDBException; - protected native static byte[][] listColumnFamilies(long optionsHandle, - String path) throws RocksDBException; - protected native void put(long handle, byte[] key, int keyOffset, - int keyLength, byte[] value, int valueOffset, int valueLength) + @Override protected native void disposeInternal(final long handle); + + private native static void closeDatabase(final long handle) + throws RocksDBException; + private native static byte[][] listColumnFamilies(final long optionsHandle, + final String path) throws RocksDBException; + private native long createColumnFamily(final long handle, + final byte[] columnFamilyName, final int columnFamilyNamelen, + final long columnFamilyOptions) throws RocksDBException; + private native long[] createColumnFamilies(final long handle, + final long columnFamilyOptionsHandle, final byte[][] columnFamilyNames) + throws RocksDBException; + private native long[] createColumnFamilies(final long handle, + final long columnFamilyOptionsHandles[], final byte[][] columnFamilyNames) + throws RocksDBException; + private native void dropColumnFamily( + final long handle, final long cfHandle) throws RocksDBException; + private native void dropColumnFamilies(final long handle, + final long[] cfHandles) throws RocksDBException; + //TODO(AR) best way to express DestroyColumnFamilyHandle? ...maybe in ColumnFamilyHandle? + private native void put(final long handle, final byte[] key, + final int keyOffset, final int keyLength, final byte[] value, + final int valueOffset, int valueLength) throws RocksDBException; + private native void put(final long handle, final byte[] key, final int keyOffset, + final int keyLength, final byte[] value, final int valueOffset, + final int valueLength, final long cfHandle) throws RocksDBException; + private native void put(final long handle, final long writeOptHandle, + final byte[] key, final int keyOffset, final int keyLength, + final byte[] value, final int valueOffset, final int valueLength) + throws RocksDBException; + private native void put(final long handle, final long writeOptHandle, + final byte[] key, final int keyOffset, final int keyLength, + final byte[] value, final int valueOffset, final int valueLength, + final long cfHandle) throws RocksDBException; + private native void delete(final long handle, final byte[] key, + final int keyOffset, final int keyLength) throws RocksDBException; + private native void delete(final long handle, final byte[] key, + final int keyOffset, final int keyLength, final long cfHandle) + throws RocksDBException; + private native void delete(final long handle, final long writeOptHandle, + final byte[] key, final int keyOffset, final int keyLength) + throws RocksDBException; + private native void delete(final long handle, final long writeOptHandle, + final byte[] key, final int keyOffset, final int keyLength, + final long cfHandle) throws RocksDBException; + private native void singleDelete( + final long handle, final byte[] key, final int keyLen) throws RocksDBException; - protected native void put(long handle, byte[] key, int keyOffset, - int keyLength, byte[] value, int valueOffset, int valueLength, - long cfHandle) throws RocksDBException; - protected native void put(long handle, long writeOptHandle, byte[] key, - int keyOffset, int keyLength, byte[] value, int valueOffset, - int valueLength) throws RocksDBException; - protected native void put(long handle, long writeOptHandle, byte[] key, - int keyOffset, int keyLength, byte[] value, int valueOffset, - int valueLength, long cfHandle) throws RocksDBException; - protected native void write0(final long handle, long writeOptHandle, - long wbHandle) throws RocksDBException; - protected native void write1(final long handle, long writeOptHandle, - long wbwiHandle) throws RocksDBException; - protected native boolean keyMayExist(final long handle, final byte[] key, + private native void singleDelete( + final long handle, final byte[] key, final int keyLen, + final long cfHandle) throws RocksDBException; + private native void singleDelete( + final long handle, final long writeOptHandle, final byte[] key, + final int keyLen) throws RocksDBException; + private native void singleDelete( + final long handle, final long writeOptHandle, + final byte[] key, final int keyLen, final long cfHandle) + throws RocksDBException; + private native void deleteRange(final long handle, final byte[] beginKey, + final int beginKeyOffset, final int beginKeyLength, final byte[] endKey, + final int endKeyOffset, final int endKeyLength) throws RocksDBException; + private native void deleteRange(final long handle, final byte[] beginKey, + final int beginKeyOffset, final int beginKeyLength, final byte[] endKey, + final int endKeyOffset, final int endKeyLength, final long cfHandle) + throws RocksDBException; + private native void deleteRange(final long handle, final long writeOptHandle, + final byte[] beginKey, final int beginKeyOffset, final int beginKeyLength, + final byte[] endKey, final int endKeyOffset, final int endKeyLength) + throws RocksDBException; + private native void deleteRange( + final long handle, final long writeOptHandle, final byte[] beginKey, + final int beginKeyOffset, final int beginKeyLength, final byte[] endKey, + final int endKeyOffset, final int endKeyLength, final long cfHandle) + throws RocksDBException; + private native void merge(final long handle, final byte[] key, + final int keyOffset, final int keyLength, final byte[] value, + final int valueOffset, final int valueLength) throws RocksDBException; + private native void merge(final long handle, final byte[] key, + final int keyOffset, final int keyLength, final byte[] value, + final int valueOffset, final int valueLength, final long cfHandle) + throws RocksDBException; + private native void merge(final long handle, final long writeOptHandle, + final byte[] key, final int keyOffset, final int keyLength, + final byte[] value, final int valueOffset, final int valueLength) + throws RocksDBException; + private native void merge(final long handle, final long writeOptHandle, + final byte[] key, final int keyOffset, final int keyLength, + final byte[] value, final int valueOffset, final int valueLength, + final long cfHandle) throws RocksDBException; + private native void write0(final long handle, final long writeOptHandle, + final long wbHandle) throws RocksDBException; + private native void write1(final long handle, final long writeOptHandle, + final long wbwiHandle) throws RocksDBException; + private native int get(final long handle, final byte[] key, + final int keyOffset, final int keyLength, final byte[] value, + final int valueOffset, final int valueLength) throws RocksDBException; + private native int get(final long handle, final byte[] key, + final int keyOffset, final int keyLength, byte[] value, + final int valueOffset, final int valueLength, final long cfHandle) + throws RocksDBException; + private native int get(final long handle, final long readOptHandle, + final byte[] key, final int keyOffset, final int keyLength, + final byte[] value, final int valueOffset, final int valueLength) + throws RocksDBException; + private native int get(final long handle, final long readOptHandle, + final byte[] key, final int keyOffset, final int keyLength, + final byte[] value, final int valueOffset, final int valueLength, + final long cfHandle) throws RocksDBException; + private native byte[] get(final long handle, byte[] key, final int keyOffset, + final int keyLength) throws RocksDBException; + private native byte[] get(final long handle, final byte[] key, + final int keyOffset, final int keyLength, final long cfHandle) + throws RocksDBException; + private native byte[] get(final long handle, final long readOptHandle, + final byte[] key, final int keyOffset, final int keyLength) + throws RocksDBException; + private native byte[] get(final long handle, + final long readOptHandle, final byte[] key, final int keyOffset, + final int keyLength, final long cfHandle) throws RocksDBException; + private native byte[][] multiGet(final long dbHandle, final byte[][] keys, + final int[] keyOffsets, final int[] keyLengths); + private native byte[][] multiGet(final long dbHandle, final byte[][] keys, + final int[] keyOffsets, final int[] keyLengths, + final long[] columnFamilyHandles); + private native byte[][] multiGet(final long dbHandle, final long rOptHandle, + final byte[][] keys, final int[] keyOffsets, final int[] keyLengths); + private native byte[][] multiGet(final long dbHandle, final long rOptHandle, + final byte[][] keys, final int[] keyOffsets, final int[] keyLengths, + final long[] columnFamilyHandles); + private native boolean keyMayExist(final long handle, final byte[] key, final int keyOffset, final int keyLength, final StringBuilder stringBuilder); - protected native boolean keyMayExist(final long handle, final byte[] key, + private native boolean keyMayExist(final long handle, final byte[] key, final int keyOffset, final int keyLength, final long cfHandle, final StringBuilder stringBuilder); - protected native boolean keyMayExist(final long handle, + private native boolean keyMayExist(final long handle, final long optionsHandle, final byte[] key, final int keyOffset, final int keyLength, final StringBuilder stringBuilder); - protected native boolean keyMayExist(final long handle, + private native boolean keyMayExist(final long handle, final long optionsHandle, final byte[] key, final int keyOffset, final int keyLength, final long cfHandle, final StringBuilder stringBuilder); - protected native void merge(long handle, byte[] key, int keyOffset, - int keyLength, byte[] value, int valueOffset, int valueLength) + private native long iterator(final long handle); + private native long iterator(final long handle, final long readOptHandle); + private native long iteratorCF(final long handle, final long cfHandle); + private native long iteratorCF(final long handle, final long cfHandle, + final long readOptHandle); + private native long[] iterators(final long handle, + final long[] columnFamilyHandles, final long readOptHandle) throws RocksDBException; - protected native void merge(long handle, byte[] key, int keyOffset, - int keyLength, byte[] value, int valueOffset, int valueLength, - long cfHandle) throws RocksDBException; - protected native void merge(long handle, long writeOptHandle, byte[] key, - int keyOffset, int keyLength, byte[] value, int valueOffset, - int valueLength) throws RocksDBException; - protected native void merge(long handle, long writeOptHandle, byte[] key, - int keyOffset, int keyLength, byte[] value, int valueOffset, - int valueLength, long cfHandle) throws RocksDBException; - protected native int get(long handle, byte[] key, int keyOffset, - int keyLength, byte[] value, int valueOffset, int valueLength) + private native long getSnapshot(final long nativeHandle); + private native void releaseSnapshot( + final long nativeHandle, final long snapshotHandle); + private native String getProperty(final long nativeHandle, + final long cfHandle, final String property, final int propertyLength) throws RocksDBException; - protected native int get(long handle, byte[] key, int keyOffset, - int keyLength, byte[] value, int valueOffset, int valueLength, - long cfHandle) throws RocksDBException; - protected native int get(long handle, long readOptHandle, byte[] key, - int keyOffset, int keyLength, byte[] value, int valueOffset, - int valueLength) throws RocksDBException; - protected native int get(long handle, long readOptHandle, byte[] key, - int keyOffset, int keyLength, byte[] value, int valueOffset, - int valueLength, long cfHandle) throws RocksDBException; - protected native byte[][] multiGet(final long dbHandle, final byte[][] keys, - final int[] keyOffsets, final int[] keyLengths); - protected native byte[][] multiGet(final long dbHandle, final byte[][] keys, - final int[] keyOffsets, final int[] keyLengths, - final long[] columnFamilyHandles); - protected native byte[][] multiGet(final long dbHandle, final long rOptHandle, - final byte[][] keys, final int[] keyOffsets, final int[] keyLengths); - protected native byte[][] multiGet(final long dbHandle, final long rOptHandle, - final byte[][] keys, final int[] keyOffsets, final int[] keyLengths, - final long[] columnFamilyHandles); - protected native byte[] get(long handle, byte[] key, int keyOffset, - int keyLength) throws RocksDBException; - protected native byte[] get(long handle, byte[] key, int keyOffset, - int keyLength, long cfHandle) throws RocksDBException; - protected native byte[] get(long handle, long readOptHandle, - byte[] key, int keyOffset, int keyLength) throws RocksDBException; - protected native byte[] get(long handle, long readOptHandle, byte[] key, - int keyOffset, int keyLength, long cfHandle) throws RocksDBException; - protected native void delete(long handle, byte[] key, int keyOffset, - int keyLength) throws RocksDBException; - protected native void delete(long handle, byte[] key, int keyOffset, - int keyLength, long cfHandle) throws RocksDBException; - protected native void delete(long handle, long writeOptHandle, byte[] key, - int keyOffset, int keyLength) throws RocksDBException; - protected native void delete(long handle, long writeOptHandle, byte[] key, - int keyOffset, int keyLength, long cfHandle) throws RocksDBException; - protected native void singleDelete( - long handle, byte[] key, int keyLen) throws RocksDBException; - protected native void singleDelete( - long handle, byte[] key, int keyLen, long cfHandle) + private native Map getMapProperty(final long nativeHandle, + final long cfHandle, final String property, final int propertyLength) throws RocksDBException; - protected native void singleDelete( - long handle, long writeOptHandle, - byte[] key, int keyLen) throws RocksDBException; - protected native void singleDelete( - long handle, long writeOptHandle, - byte[] key, int keyLen, long cfHandle) throws RocksDBException; - protected native void deleteRange(long handle, byte[] beginKey, int beginKeyOffset, - int beginKeyLength, byte[] endKey, int endKeyOffset, int endKeyLength) + private native long getLongProperty(final long nativeHandle, + final long cfHandle, final String property, final int propertyLength) throws RocksDBException; - protected native void deleteRange(long handle, byte[] beginKey, int beginKeyOffset, - int beginKeyLength, byte[] endKey, int endKeyOffset, int endKeyLength, long cfHandle) + private native void resetStats(final long nativeHandle) throws RocksDBException; - protected native void deleteRange(long handle, long writeOptHandle, byte[] beginKey, - int beginKeyOffset, int beginKeyLength, byte[] endKey, int endKeyOffset, int endKeyLength) + private native long getAggregatedLongProperty(final long nativeHandle, + final String property, int propertyLength) throws RocksDBException; + private native long[] getApproximateSizes(final long nativeHandle, + final long columnFamilyHandle, final long[] rangeSliceHandles, + final byte includeFlags); + private final native long[] getApproximateMemTableStats( + final long nativeHandle, final long columnFamilyHandle, + final long rangeStartSliceHandle, final long rangeLimitSliceHandle); + private native void compactRange(final long handle, + /* @Nullable */ final byte[] begin, final int beginLen, + /* @Nullable */ final byte[] end, final int endLen, + final long compactRangeOptHandle, final long cfHandle) throws RocksDBException; - protected native void deleteRange(long handle, long writeOptHandle, byte[] beginKey, - int beginKeyOffset, int beginKeyLength, byte[] endKey, int endKeyOffset, int endKeyLength, - long cfHandle) throws RocksDBException; - protected native String getProperty0(long nativeHandle, - String property, int propertyLength) throws RocksDBException; - protected native String getProperty0(long nativeHandle, long cfHandle, - String property, int propertyLength) throws RocksDBException; - protected native long getLongProperty(long nativeHandle, String property, - int propertyLength) throws RocksDBException; - protected native long getLongProperty(long nativeHandle, long cfHandle, - String property, int propertyLength) throws RocksDBException; - protected native long iterator(long handle); - protected native long iterator(long handle, long readOptHandle); - protected native long iteratorCF(long handle, long cfHandle); - protected native long iteratorCF(long handle, long cfHandle, - long readOptHandle); - protected native long[] iterators(final long handle, - final long[] columnFamilyHandles, final long readOptHandle) + private native void setOptions(final long handle, final long cfHandle, + final String[] keys, final String[] values) throws RocksDBException; + private native void setDBOptions(final long handle, + final String[] keys, final String[] values) throws RocksDBException; + private native String[] compactFiles(final long handle, + final long compactionOptionsHandle, + final long columnFamilyHandle, + final String[] inputFileNames, + final int outputLevel, + final int outputPathId, + final long compactionJobInfoHandle) throws RocksDBException; + private native void pauseBackgroundWork(final long handle) throws RocksDBException; - protected native long getSnapshot(long nativeHandle); - protected native void releaseSnapshot(long nativeHandle, long snapshotHandle); - @Override protected final native void disposeInternal(final long handle); - private native long getDefaultColumnFamily(long handle); - private native long createColumnFamily(final long handle, - final byte[] columnFamilyName, final long columnFamilyOptions) + private native void continueBackgroundWork(final long handle) throws RocksDBException; - private native void dropColumnFamily(long handle, long cfHandle) + private native void enableAutoCompaction(final long handle, + final long[] columnFamilyHandles) throws RocksDBException; + private native int numberLevels(final long handle, + final long columnFamilyHandle); + private native int maxMemCompactionLevel(final long handle, + final long columnFamilyHandle); + private native int level0StopWriteTrigger(final long handle, + final long columnFamilyHandle); + private native String getName(final long handle); + private native long getEnv(final long handle); + private native void flush(final long handle, final long flushOptHandle, + /* @Nullable */ final long[] cfHandles) throws RocksDBException; + private native void flushWal(final long handle, final boolean sync) throws RocksDBException; - private native void flush(long handle, long flushOptHandle) + private native void syncWal(final long handle) throws RocksDBException; + private native long getLatestSequenceNumber(final long handle); + private native boolean setPreserveDeletesSequenceNumber(final long handle, + final long sequenceNumber); + private native void disableFileDeletions(long handle) throws RocksDBException; + private native void enableFileDeletions(long handle, boolean force) throws RocksDBException; - private native void flush(long handle, long flushOptHandle, long cfHandle) + private native String[] getLiveFiles(final long handle, + final boolean flushMemtable) throws RocksDBException; + private native LogFile[] getSortedWalFiles(final long handle) throws RocksDBException; - private native void compactRange0(long handle, boolean reduce_level, - int target_level, int target_path_id) throws RocksDBException; - private native void compactRange0(long handle, byte[] begin, int beginLen, - byte[] end, int endLen, boolean reduce_level, int target_level, - int target_path_id) throws RocksDBException; - private native void compactRange(long handle, boolean reduce_level, - int target_level, int target_path_id, long cfHandle) + private native long getUpdatesSince(final long handle, + final long sequenceNumber) throws RocksDBException; + private native void deleteFile(final long handle, final String name) throws RocksDBException; - private native void compactRange(long handle, byte[] begin, int beginLen, - byte[] end, int endLen, boolean reduce_level, int target_level, - int target_path_id, long cfHandle) throws RocksDBException; - private native void pauseBackgroundWork(long handle) throws RocksDBException; - private native void continueBackgroundWork(long handle) throws RocksDBException; - private native long getLatestSequenceNumber(long handle); - private native void disableFileDeletions(long handle) throws RocksDBException; - private native void enableFileDeletions(long handle, boolean force) + private native LiveFileMetaData[] getLiveFilesMetaData(final long handle); + private native ColumnFamilyMetaData getColumnFamilyMetaData( + final long handle, final long columnFamilyHandle); + private native void ingestExternalFile(final long handle, + final long columnFamilyHandle, final String[] filePathList, + final int filePathListLen, final long ingestExternalFileOptionsHandle) throws RocksDBException; - private native long getUpdatesSince(long handle, long sequenceNumber) + private native void verifyChecksum(final long handle) throws RocksDBException; + private native long getDefaultColumnFamily(final long handle); + private native Map getPropertiesOfAllTables( + final long handle, final long columnFamilyHandle) throws RocksDBException; + private native Map getPropertiesOfTablesInRange( + final long handle, final long columnFamilyHandle, + final long[] rangeSliceHandles); + private native long[] suggestCompactRange(final long handle, + final long columnFamilyHandle) throws RocksDBException; + private native void promoteL0(final long handle, + final long columnFamilyHandle, final int tragetLevel) throws RocksDBException; - private native void setOptions(long handle, long cfHandle, String[] keys, - String[] values) throws RocksDBException; - private native void ingestExternalFile(long handle, long cfHandle, - String[] filePathList, int filePathListLen, - long ingest_external_file_options_handle) throws RocksDBException; + private native void startTrace(final long handle, final long maxTraceFileSize, + final long traceWriterHandle) throws RocksDBException; + private native void endTrace(final long handle) throws RocksDBException; + + + private native static void destroyDB(final String path, + final long optionsHandle) throws RocksDBException; + protected DBOptionsInterface options_; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java index 8fe61fd451..b3681d77db 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksEnv.java @@ -25,19 +25,8 @@ public class RocksEnv extends Env { */ RocksEnv(final long handle) { super(handle); - disOwnNativeHandle(); } - /** - *

    The helper function of {@link #dispose()} which all subclasses of - * {@link RocksObject} must implement to release their associated C++ - * resource.

    - * - *

    Note: this class is used to use the default - * RocksEnv with RocksJava. The default env allocation is managed - * by C++.

    - */ @Override - protected final void disposeInternal(final long handle) { - } + protected native final void disposeInternal(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java index 9e9c648092..12c06f04e5 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIterator.java @@ -57,6 +57,7 @@ public byte[] value() { @Override final native void next0(long handle); @Override final native void prev0(long handle); @Override final native void seek0(long handle, byte[] target, int targetLen); + @Override final native void seekForPrev0(long handle, byte[] target, int targetLen); @Override final native void status0(long handle) throws RocksDBException; private native byte[] key0(long handle); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java index 12fdbb1973..a5a9eb88d8 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksIteratorInterface.java @@ -41,7 +41,7 @@ public interface RocksIteratorInterface { void seekToLast(); /** - *

    Position at the first entry in the source whose key is that or + *

    Position at the first entry in the source whose key is at or * past target.

    * *

    The iterator is valid after this call if the source contains @@ -52,6 +52,18 @@ public interface RocksIteratorInterface { */ void seek(byte[] target); + /** + *

    Position at the first entry in the source whose key is that or + * before target.

    + * + *

    The iterator is valid after this call if the source contains + * a key that comes at or before target.

    + * + * @param target byte array describing a key or a + * key prefix to seek for. + */ + void seekForPrev(byte[] target); + /** *

    Moves to the next entry in the source. After this call, Valid() is * true if the iterator was not positioned at the last entry in the source.

    diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java index d18d0ceb97..0afa5f6623 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/RocksMemEnv.java @@ -6,22 +6,34 @@ package org.rocksdb; /** - * RocksDB memory environment. + * Memory environment. */ +//TODO(AR) rename to MemEnv public class RocksMemEnv extends Env { /** - *

    Creates a new RocksDB environment that stores its data + *

    Creates a new environment that stores its data * in memory and delegates all non-file-storage tasks to - * base_env. The caller must delete the result when it is + * {@code baseEnv}.

    + * + *

    The caller must delete the result when it is * no longer needed.

    * - *

    {@code *base_env} must remain live while the result is in use.

    + * @param baseEnv the base environment, + * must remain live while the result is in use. + */ + public RocksMemEnv(final Env baseEnv) { + super(createMemEnv(baseEnv.nativeHandle_)); + } + + /** + * @deprecated Use {@link #RocksMemEnv(Env)}. */ + @Deprecated public RocksMemEnv() { - super(createMemEnv()); + this(Env.getDefault()); } - private static native long createMemEnv(); + private static native long createMemEnv(final long baseEnvHandle); @Override protected final native void disposeInternal(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SizeApproximationFlag.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SizeApproximationFlag.java new file mode 100644 index 0000000000..7807e7c835 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SizeApproximationFlag.java @@ -0,0 +1,30 @@ +package org.rocksdb; + +import java.util.List; + +/** + * Flags for + * {@link RocksDB#getApproximateSizes(ColumnFamilyHandle, List, SizeApproximationFlag...)} + * that specify whether memtable stats should be included, + * or file stats approximation or both. + */ +public enum SizeApproximationFlag { + NONE((byte)0x0), + INCLUDE_MEMTABLES((byte)0x1), + INCLUDE_FILES((byte)0x2); + + private final byte value; + + SizeApproximationFlag(final byte value) { + this.value = value; + } + + /** + * Get the internal byte representation. + * + * @return the internal representation. + */ + byte getValue() { + return value; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Slice.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Slice.java index a122c3769d..50d9f76525 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Slice.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Slice.java @@ -39,6 +39,30 @@ private Slice() { super(); } + /** + *

    Package-private Slice constructor which is used to construct + * Slice instances from C++ side. As the reference to this + * object is also managed from C++ side the handle will be disowned.

    + * + * @param nativeHandle address of native instance. + */ + Slice(final long nativeHandle) { + this(nativeHandle, false); + } + + /** + *

    Package-private Slice constructor which is used to construct + * Slice instances using a handle.

    + * + * @param nativeHandle address of native instance. + * @param owningNativeHandle true if the Java side owns the memory pointed to + * by this reference, false if ownership belongs to the C++ side + */ + Slice(final long nativeHandle, final boolean owningNativeHandle) { + super(); + setNativeHandle(nativeHandle, owningNativeHandle); + } + /** *

    Constructs a slice where the data is taken from * a String.

    diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java index a6b53f495f..39cdf0c2d2 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Snapshot.java @@ -11,6 +11,10 @@ public class Snapshot extends RocksObject { Snapshot(final long nativeHandle) { super(nativeHandle); + + // The pointer to the snapshot is always released + // by the database instance. + disOwnNativeHandle(); } /** @@ -20,17 +24,17 @@ public class Snapshot extends RocksObject { * this snapshot. */ public long getSequenceNumber() { - assert(isOwningHandle()); return getSequenceNumber(nativeHandle_); } - /** - * Dont release C++ Snapshot pointer. The pointer - * to the snapshot is released by the database - * instance. - */ @Override protected final void disposeInternal(final long handle) { + /** + * Nothing to release, we never own the pointer for a + * Snapshot. The pointer + * to the snapshot is released by the database + * instance. + */ } private native long getSequenceNumber(long handle); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileManager.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileManager.java new file mode 100644 index 0000000000..8805410aa8 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileManager.java @@ -0,0 +1,251 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.Map; + +/** + * SstFileManager is used to track SST files in the DB and control their + * deletion rate. + * + * All SstFileManager public functions are thread-safe. + * + * SstFileManager is not extensible. + */ +//@ThreadSafe +public final class SstFileManager extends RocksObject { + + public static final long RATE_BYTES_PER_SEC_DEFAULT = 0; + public static final boolean DELETE_EXISTING_TRASH_DEFAULT = true; + public static final double MAX_TRASH_DB_RATION_DEFAULT = 0.25; + public static final long BYTES_MAX_DELETE_CHUNK_DEFAULT = 64 * 1024 * 1024; + + /** + * Create a new SstFileManager that can be shared among multiple RocksDB + * instances to track SST file and control there deletion rate. + * + * @param env the environment. + * + * @throws RocksDBException thrown if error happens in underlying native library. + */ + public SstFileManager(final Env env) throws RocksDBException { + this(env, null); + } + + /** + * Create a new SstFileManager that can be shared among multiple RocksDB + * instances to track SST file and control there deletion rate. + * + * @param env the environment. + * @param logger if not null, the logger will be used to log errors. + * + * @throws RocksDBException thrown if error happens in underlying native library. + */ + public SstFileManager(final Env env, /*@Nullable*/ final Logger logger) + throws RocksDBException { + this(env, logger, RATE_BYTES_PER_SEC_DEFAULT); + } + + /** + * Create a new SstFileManager that can be shared among multiple RocksDB + * instances to track SST file and control there deletion rate. + * + * @param env the environment. + * @param logger if not null, the logger will be used to log errors. + * + * == Deletion rate limiting specific arguments == + * @param rateBytesPerSec how many bytes should be deleted per second, If + * this value is set to 1024 (1 Kb / sec) and we deleted a file of size + * 4 Kb in 1 second, we will wait for another 3 seconds before we delete + * other files, Set to 0 to disable deletion rate limiting. + * + * @throws RocksDBException thrown if error happens in underlying native library. + */ + public SstFileManager(final Env env, /*@Nullable*/ final Logger logger, + final long rateBytesPerSec) throws RocksDBException { + this(env, logger, rateBytesPerSec, MAX_TRASH_DB_RATION_DEFAULT); + } + + /** + * Create a new SstFileManager that can be shared among multiple RocksDB + * instances to track SST file and control there deletion rate. + * + * @param env the environment. + * @param logger if not null, the logger will be used to log errors. + * + * == Deletion rate limiting specific arguments == + * @param rateBytesPerSec how many bytes should be deleted per second, If + * this value is set to 1024 (1 Kb / sec) and we deleted a file of size + * 4 Kb in 1 second, we will wait for another 3 seconds before we delete + * other files, Set to 0 to disable deletion rate limiting. + * @param maxTrashDbRatio if the trash size constitutes for more than this + * fraction of the total DB size we will start deleting new files passed + * to DeleteScheduler immediately. + * + * @throws RocksDBException thrown if error happens in underlying native library. + */ + public SstFileManager(final Env env, /*@Nullable*/ final Logger logger, + final long rateBytesPerSec, final double maxTrashDbRatio) + throws RocksDBException { + this(env, logger, rateBytesPerSec, maxTrashDbRatio, + BYTES_MAX_DELETE_CHUNK_DEFAULT); + } + + /** + * Create a new SstFileManager that can be shared among multiple RocksDB + * instances to track SST file and control there deletion rate. + * + * @param env the environment. + * @param logger if not null, the logger will be used to log errors. + * + * == Deletion rate limiting specific arguments == + * @param rateBytesPerSec how many bytes should be deleted per second, If + * this value is set to 1024 (1 Kb / sec) and we deleted a file of size + * 4 Kb in 1 second, we will wait for another 3 seconds before we delete + * other files, Set to 0 to disable deletion rate limiting. + * @param maxTrashDbRatio if the trash size constitutes for more than this + * fraction of the total DB size we will start deleting new files passed + * to DeleteScheduler immediately. + * @param bytesMaxDeleteChunk if a single file is larger than delete chunk, + * ftruncate the file by this size each time, rather than dropping the whole + * file. 0 means to always delete the whole file. + * + * @throws RocksDBException thrown if error happens in underlying native library. + */ + public SstFileManager(final Env env, /*@Nullable*/final Logger logger, + final long rateBytesPerSec, final double maxTrashDbRatio, + final long bytesMaxDeleteChunk) throws RocksDBException { + super(newSstFileManager(env.nativeHandle_, + logger != null ? logger.nativeHandle_ : 0, + rateBytesPerSec, maxTrashDbRatio, bytesMaxDeleteChunk)); + } + + + /** + * Update the maximum allowed space that should be used by RocksDB, if + * the total size of the SST files exceeds {@code maxAllowedSpace}, writes to + * RocksDB will fail. + * + * Setting {@code maxAllowedSpace} to 0 will disable this feature; + * maximum allowed space will be infinite (Default value). + * + * @param maxAllowedSpace the maximum allowed space that should be used by + * RocksDB. + */ + public void setMaxAllowedSpaceUsage(final long maxAllowedSpace) { + setMaxAllowedSpaceUsage(nativeHandle_, maxAllowedSpace); + } + + /** + * Set the amount of buffer room each compaction should be able to leave. + * In other words, at its maximum disk space consumption, the compaction + * should still leave {@code compactionBufferSize} available on the disk so + * that other background functions may continue, such as logging and flushing. + * + * @param compactionBufferSize the amount of buffer room each compaction + * should be able to leave. + */ + public void setCompactionBufferSize(final long compactionBufferSize) { + setCompactionBufferSize(nativeHandle_, compactionBufferSize); + } + + /** + * Determines if the total size of SST files exceeded the maximum allowed + * space usage. + * + * @return true when the maximum allows space usage has been exceeded. + */ + public boolean isMaxAllowedSpaceReached() { + return isMaxAllowedSpaceReached(nativeHandle_); + } + + /** + * Determines if the total size of SST files as well as estimated size + * of ongoing compactions exceeds the maximums allowed space usage. + * + * @return true when the total size of SST files as well as estimated size + * of ongoing compactions exceeds the maximums allowed space usage. + */ + public boolean isMaxAllowedSpaceReachedIncludingCompactions() { + return isMaxAllowedSpaceReachedIncludingCompactions(nativeHandle_); + } + + /** + * Get the total size of all tracked files. + * + * @return the total size of all tracked files. + */ + public long getTotalSize() { + return getTotalSize(nativeHandle_); + } + + /** + * Gets all tracked files and their corresponding sizes. + * + * @return a map containing all tracked files and there corresponding sizes. + */ + public Map getTrackedFiles() { + return getTrackedFiles(nativeHandle_); + } + + /** + * Gets the delete rate limit. + * + * @return the delete rate limit (in bytes per second). + */ + public long getDeleteRateBytesPerSecond() { + return getDeleteRateBytesPerSecond(nativeHandle_); + } + + /** + * Set the delete rate limit. + * + * Zero means disable delete rate limiting and delete files immediately. + * + * @param deleteRate the delete rate limit (in bytes per second). + */ + public void setDeleteRateBytesPerSecond(final long deleteRate) { + setDeleteRateBytesPerSecond(nativeHandle_, deleteRate); + } + + /** + * Get the trash/DB size ratio where new files will be deleted immediately. + * + * @return the trash/DB size ratio. + */ + public double getMaxTrashDBRatio() { + return getMaxTrashDBRatio(nativeHandle_); + } + + /** + * Set the trash/DB size ratio where new files will be deleted immediately. + * + * @param ratio the trash/DB size ratio. + */ + public void setMaxTrashDBRatio(final double ratio) { + setMaxTrashDBRatio(nativeHandle_, ratio); + } + + private native static long newSstFileManager(final long handle, + final long logger_handle, final long rateBytesPerSec, + final double maxTrashDbRatio, final long bytesMaxDeleteChunk) + throws RocksDBException; + private native void setMaxAllowedSpaceUsage(final long handle, + final long maxAllowedSpace); + private native void setCompactionBufferSize(final long handle, + final long compactionBufferSize); + private native boolean isMaxAllowedSpaceReached(final long handle); + private native boolean isMaxAllowedSpaceReachedIncludingCompactions( + final long handle); + private native long getTotalSize(final long handle); + private native Map getTrackedFiles(final long handle); + private native long getDeleteRateBytesPerSecond(final long handle); + private native void setDeleteRateBytesPerSecond(final long handle, + final long deleteRate); + private native double getMaxTrashDBRatio(final long handle); + private native void setMaxTrashDBRatio(final long handle, final double ratio); + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileMetaData.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileMetaData.java new file mode 100644 index 0000000000..52e984dff2 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileMetaData.java @@ -0,0 +1,150 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * The metadata that describes a SST file. + */ +public class SstFileMetaData { + private final String fileName; + private final String path; + private final long size; + private final long smallestSeqno; + private final long largestSeqno; + private final byte[] smallestKey; + private final byte[] largestKey; + private final long numReadsSampled; + private final boolean beingCompacted; + private final long numEntries; + private final long numDeletions; + + /** + * Called from JNI C++ + */ + protected SstFileMetaData( + final String fileName, + final String path, + final long size, + final long smallestSeqno, + final long largestSeqno, + final byte[] smallestKey, + final byte[] largestKey, + final long numReadsSampled, + final boolean beingCompacted, + final long numEntries, + final long numDeletions) { + this.fileName = fileName; + this.path = path; + this.size = size; + this.smallestSeqno = smallestSeqno; + this.largestSeqno = largestSeqno; + this.smallestKey = smallestKey; + this.largestKey = largestKey; + this.numReadsSampled = numReadsSampled; + this.beingCompacted = beingCompacted; + this.numEntries = numEntries; + this.numDeletions = numDeletions; + } + + /** + * Get the name of the file. + * + * @return the name of the file. + */ + public String fileName() { + return fileName; + } + + /** + * Get the full path where the file locates. + * + * @return the full path + */ + public String path() { + return path; + } + + /** + * Get the file size in bytes. + * + * @return file size + */ + public long size() { + return size; + } + + /** + * Get the smallest sequence number in file. + * + * @return the smallest sequence number + */ + public long smallestSeqno() { + return smallestSeqno; + } + + /** + * Get the largest sequence number in file. + * + * @return the largest sequence number + */ + public long largestSeqno() { + return largestSeqno; + } + + /** + * Get the smallest user defined key in the file. + * + * @return the smallest user defined key + */ + public byte[] smallestKey() { + return smallestKey; + } + + /** + * Get the largest user defined key in the file. + * + * @return the largest user defined key + */ + public byte[] largestKey() { + return largestKey; + } + + /** + * Get the number of times the file has been read. + * + * @return the number of times the file has been read + */ + public long numReadsSampled() { + return numReadsSampled; + } + + /** + * Returns true if the file is currently being compacted. + * + * @return true if the file is currently being compacted, false otherwise. + */ + public boolean beingCompacted() { + return beingCompacted; + } + + /** + * Get the number of entries. + * + * @return the number of entries. + */ + public long numEntries() { + return numEntries; + } + + /** + * Get the number of deletions. + * + * @return the number of deletions. + */ + public long numDeletions() { + return numDeletions; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java index 5f35f0f61d..447e41ea9d 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/SstFileWriter.java @@ -30,7 +30,8 @@ public class SstFileWriter extends RocksObject { public SstFileWriter(final EnvOptions envOptions, final Options options, final AbstractComparator> comparator) { super(newSstFileWriter( - envOptions.nativeHandle_, options.nativeHandle_, comparator.getNativeHandle())); + envOptions.nativeHandle_, options.nativeHandle_, comparator.nativeHandle_, + comparator.getComparatorType().getValue())); } /** @@ -224,7 +225,7 @@ public void finish() throws RocksDBException { private native static long newSstFileWriter( final long envOptionsHandle, final long optionsHandle, - final long userComparatorHandle); + final long userComparatorHandle, final byte comparatorType); private native static long newSstFileWriter(final long envOptionsHandle, final long optionsHandle); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StateType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StateType.java new file mode 100644 index 0000000000..803456bb2d --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StateType.java @@ -0,0 +1,53 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * The type used to refer to a thread state. + * + * A state describes lower-level action of a thread + * such as reading / writing a file or waiting for a mutex. + */ +public enum StateType { + STATE_UNKNOWN((byte)0x0), + STATE_MUTEX_WAIT((byte)0x1); + + private final byte value; + + StateType(final byte value) { + this.value = value; + } + + /** + * Get the internal representation value. + * + * @return the internal representation value. + */ + byte getValue() { + return value; + } + + /** + * Get the State type from the internal representation value. + * + * @param value the internal representation value. + * + * @return the state type + * + * @throws IllegalArgumentException if the value does not match + * a StateType + */ + static StateType fromValue(final byte value) + throws IllegalArgumentException { + for (final StateType threadType : StateType.values()) { + if (threadType.value == value) { + return threadType; + } + } + throw new IllegalArgumentException( + "Unknown value for StateType: " + value); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Statistics.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Statistics.java index 10c072c897..0938a6d583 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Statistics.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Statistics.java @@ -117,6 +117,8 @@ public String getHistogramString(final HistogramType histogramType) { /** * Resets all ticker and histogram stats. + * + * @throws RocksDBException if an error occurs when resetting the statistics. */ public void reset() throws RocksDBException { assert(isOwningHandle()); @@ -126,6 +128,7 @@ public void reset() throws RocksDBException { /** * String representation of the statistic object. */ + @Override public String toString() { assert(isOwningHandle()); return toString(nativeHandle_); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java index 48cf8af88e..fb3f57150f 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatisticsCollector.java @@ -93,9 +93,9 @@ public void run() { statsCallback.histogramCallback(histogramType, histogramData); } } - - Thread.sleep(_statsCollectionInterval); } + + Thread.sleep(_statsCollectionInterval); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java index cc2a87c6a2..58504b84a2 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StatsLevel.java @@ -60,6 +60,6 @@ public static StatsLevel getStatsLevel(final byte value) { } } throw new IllegalArgumentException( - "Illegal value provided for InfoLogLevel."); + "Illegal value provided for StatsLevel."); } } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Status.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Status.java index d34b72c691..e633940c29 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Status.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Status.java @@ -54,6 +54,7 @@ public String getCodeString() { return builder.toString(); } + // should stay in sync with /include/rocksdb/status.h:Code and /java/rocksjni/portal.h:toJavaStatusCode public enum Code { Ok( (byte)0x0), NotFound( (byte)0x1), @@ -68,7 +69,8 @@ public enum Code { Aborted( (byte)0xA), Busy( (byte)0xB), Expired( (byte)0xC), - TryAgain( (byte)0xD); + TryAgain( (byte)0xD), + Undefined( (byte)0x7F); private final byte value; @@ -83,16 +85,30 @@ public static Code getCode(final byte value) { } } throw new IllegalArgumentException( - "Illegal value provided for Code."); + "Illegal value provided for Code (" + value + ")."); + } + + /** + * Returns the byte value of the enumerations value. + * + * @return byte representation + */ + public byte getValue() { + return value; } } + // should stay in sync with /include/rocksdb/status.h:SubCode and /java/rocksjni/portal.h:toJavaStatusSubCode public enum SubCode { None( (byte)0x0), MutexTimeout( (byte)0x1), LockTimeout( (byte)0x2), LockLimit( (byte)0x3), - MaxSubCode( (byte)0x7E); + NoSpace( (byte)0x4), + Deadlock( (byte)0x5), + StaleFile( (byte)0x6), + MemoryLimit( (byte)0x7), + Undefined( (byte)0x7F); private final byte value; @@ -107,7 +123,16 @@ public static SubCode getSubCode(final byte value) { } } throw new IllegalArgumentException( - "Illegal value provided for SubCode."); + "Illegal value provided for SubCode (" + value + ")."); + } + + /** + * Returns the byte value of the enumerations value. + * + * @return byte representation + */ + public byte getValue() { + return value; } } } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java index 85c36adc7c..978cad6ccf 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/StringAppendOperator.java @@ -11,9 +11,13 @@ */ public class StringAppendOperator extends MergeOperator { public StringAppendOperator() { - super(newSharedStringAppendOperator()); + this(','); } - private native static long newSharedStringAppendOperator(); + public StringAppendOperator(char delim) { + super(newSharedStringAppendOperator(delim)); + } + + private native static long newSharedStringAppendOperator(final char delim); @Override protected final native void disposeInternal(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TableFilter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TableFilter.java new file mode 100644 index 0000000000..45605063b5 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TableFilter.java @@ -0,0 +1,20 @@ +package org.rocksdb; + +/** + * Filter for iterating a table. + */ +public interface TableFilter { + + /** + * A callback to determine whether relevant keys for this scan exist in a + * given table based on the table's properties. The callback is passed the + * properties of each table during iteration. If the callback returns false, + * the table will not be scanned. This option only affects Iterators and has + * no impact on point lookups. + * + * @param tableProperties the table properties. + * + * @return true if the table should be scanned, false otherwise. + */ + boolean filter(final TableProperties tableProperties); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TableProperties.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TableProperties.java new file mode 100644 index 0000000000..5fe98da67b --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TableProperties.java @@ -0,0 +1,365 @@ +package org.rocksdb; + +import java.util.Map; + +/** + * TableProperties contains read-only properties of its associated + * table. + */ +public class TableProperties { + private final long dataSize; + private final long indexSize; + private final long indexPartitions; + private final long topLevelIndexSize; + private final long indexKeyIsUserKey; + private final long indexValueIsDeltaEncoded; + private final long filterSize; + private final long rawKeySize; + private final long rawValueSize; + private final long numDataBlocks; + private final long numEntries; + private final long numDeletions; + private final long numMergeOperands; + private final long numRangeDeletions; + private final long formatVersion; + private final long fixedKeyLen; + private final long columnFamilyId; + private final long creationTime; + private final long oldestKeyTime; + private final byte[] columnFamilyName; + private final String filterPolicyName; + private final String comparatorName; + private final String mergeOperatorName; + private final String prefixExtractorName; + private final String propertyCollectorsNames; + private final String compressionName; + private final Map userCollectedProperties; + private final Map readableProperties; + private final Map propertiesOffsets; + + /** + * Access is private as this will only be constructed from + * C++ via JNI. + */ + private TableProperties(final long dataSize, final long indexSize, + final long indexPartitions, final long topLevelIndexSize, + final long indexKeyIsUserKey, final long indexValueIsDeltaEncoded, + final long filterSize, final long rawKeySize, final long rawValueSize, + final long numDataBlocks, final long numEntries, final long numDeletions, + final long numMergeOperands, final long numRangeDeletions, + final long formatVersion, final long fixedKeyLen, + final long columnFamilyId, final long creationTime, + final long oldestKeyTime, final byte[] columnFamilyName, + final String filterPolicyName, final String comparatorName, + final String mergeOperatorName, final String prefixExtractorName, + final String propertyCollectorsNames, final String compressionName, + final Map userCollectedProperties, + final Map readableProperties, + final Map propertiesOffsets) { + this.dataSize = dataSize; + this.indexSize = indexSize; + this.indexPartitions = indexPartitions; + this.topLevelIndexSize = topLevelIndexSize; + this.indexKeyIsUserKey = indexKeyIsUserKey; + this.indexValueIsDeltaEncoded = indexValueIsDeltaEncoded; + this.filterSize = filterSize; + this.rawKeySize = rawKeySize; + this.rawValueSize = rawValueSize; + this.numDataBlocks = numDataBlocks; + this.numEntries = numEntries; + this.numDeletions = numDeletions; + this.numMergeOperands = numMergeOperands; + this.numRangeDeletions = numRangeDeletions; + this.formatVersion = formatVersion; + this.fixedKeyLen = fixedKeyLen; + this.columnFamilyId = columnFamilyId; + this.creationTime = creationTime; + this.oldestKeyTime = oldestKeyTime; + this.columnFamilyName = columnFamilyName; + this.filterPolicyName = filterPolicyName; + this.comparatorName = comparatorName; + this.mergeOperatorName = mergeOperatorName; + this.prefixExtractorName = prefixExtractorName; + this.propertyCollectorsNames = propertyCollectorsNames; + this.compressionName = compressionName; + this.userCollectedProperties = userCollectedProperties; + this.readableProperties = readableProperties; + this.propertiesOffsets = propertiesOffsets; + } + + /** + * Get the total size of all data blocks. + * + * @return the total size of all data blocks. + */ + public long getDataSize() { + return dataSize; + } + + /** + * Get the size of index block. + * + * @return the size of index block. + */ + public long getIndexSize() { + return indexSize; + } + + /** + * Get the total number of index partitions + * if {@link IndexType#kTwoLevelIndexSearch} is used. + * + * @return the total number of index partitions. + */ + public long getIndexPartitions() { + return indexPartitions; + } + + /** + * Size of the top-level index + * if {@link IndexType#kTwoLevelIndexSearch} is used. + * + * @return the size of the top-level index. + */ + public long getTopLevelIndexSize() { + return topLevelIndexSize; + } + + /** + * Whether the index key is user key. + * Otherwise it includes 8 byte of sequence + * number added by internal key format. + * + * @return the index key + */ + public long getIndexKeyIsUserKey() { + return indexKeyIsUserKey; + } + + /** + * Whether delta encoding is used to encode the index values. + * + * @return whether delta encoding is used to encode the index values. + */ + public long getIndexValueIsDeltaEncoded() { + return indexValueIsDeltaEncoded; + } + + /** + * Get the size of filter block. + * + * @return the size of filter block. + */ + public long getFilterSize() { + return filterSize; + } + + /** + * Get the total raw key size. + * + * @return the total raw key size. + */ + public long getRawKeySize() { + return rawKeySize; + } + + /** + * Get the total raw value size. + * + * @return the total raw value size. + */ + public long getRawValueSize() { + return rawValueSize; + } + + /** + * Get the number of blocks in this table. + * + * @return the number of blocks in this table. + */ + public long getNumDataBlocks() { + return numDataBlocks; + } + + /** + * Get the number of entries in this table. + * + * @return the number of entries in this table. + */ + public long getNumEntries() { + return numEntries; + } + + /** + * Get the number of deletions in the table. + * + * @return the number of deletions in the table. + */ + public long getNumDeletions() { + return numDeletions; + } + + /** + * Get the number of merge operands in the table. + * + * @return the number of merge operands in the table. + */ + public long getNumMergeOperands() { + return numMergeOperands; + } + + /** + * Get the number of range deletions in this table. + * + * @return the number of range deletions in this table. + */ + public long getNumRangeDeletions() { + return numRangeDeletions; + } + + /** + * Get the format version, reserved for backward compatibility. + * + * @return the format version. + */ + public long getFormatVersion() { + return formatVersion; + } + + /** + * Get the length of the keys. + * + * @return 0 when the key is variable length, otherwise number of + * bytes for each key. + */ + public long getFixedKeyLen() { + return fixedKeyLen; + } + + /** + * Get the ID of column family for this SST file, + * corresponding to the column family identified by + * {@link #getColumnFamilyName()}. + * + * @return the id of the column family. + */ + public long getColumnFamilyId() { + return columnFamilyId; + } + + /** + * The time when the SST file was created. + * Since SST files are immutable, this is equivalent + * to last modified time. + * + * @return the created time. + */ + public long getCreationTime() { + return creationTime; + } + + /** + * Get the timestamp of the earliest key. + * + * @return 0 means unknown, otherwise the timestamp. + */ + public long getOldestKeyTime() { + return oldestKeyTime; + } + + /** + * Get the name of the column family with which this + * SST file is associated. + * + * @return the name of the column family, or null if the + * column family is unknown. + */ + /*@Nullable*/ public byte[] getColumnFamilyName() { + return columnFamilyName; + } + + /** + * Get the name of the filter policy used in this table. + * + * @return the name of the filter policy, or null if + * no filter policy is used. + */ + /*@Nullable*/ public String getFilterPolicyName() { + return filterPolicyName; + } + + /** + * Get the name of the comparator used in this table. + * + * @return the name of the comparator. + */ + public String getComparatorName() { + return comparatorName; + } + + /** + * Get the name of the merge operator used in this table. + * + * @return the name of the merge operator, or null if no merge operator + * is used. + */ + /*@Nullable*/ public String getMergeOperatorName() { + return mergeOperatorName; + } + + /** + * Get the name of the prefix extractor used in this table. + * + * @return the name of the prefix extractor, or null if no prefix + * extractor is used. + */ + /*@Nullable*/ public String getPrefixExtractorName() { + return prefixExtractorName; + } + + /** + * Get the names of the property collectors factories used in this table. + * + * @return the names of the property collector factories separated + * by commas, e.g. {collector_name[1]},{collector_name[2]},... + */ + public String getPropertyCollectorsNames() { + return propertyCollectorsNames; + } + + /** + * Get the name of the compression algorithm used to compress the SST files. + * + * @return the name of the compression algorithm. + */ + public String getCompressionName() { + return compressionName; + } + + /** + * Get the user collected properties. + * + * @return the user collected properties. + */ + public Map getUserCollectedProperties() { + return userCollectedProperties; + } + + /** + * Get the readable properties. + * + * @return the readable properties. + */ + public Map getReadableProperties() { + return readableProperties; + } + + /** + * The offset of the value of each property in the file. + * + * @return the offset of each property. + */ + public Map getPropertiesOffsets() { + return propertiesOffsets; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ThreadStatus.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ThreadStatus.java new file mode 100644 index 0000000000..062df5889e --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ThreadStatus.java @@ -0,0 +1,224 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.Map; + +public class ThreadStatus { + private final long threadId; + private final ThreadType threadType; + private final String dbName; + private final String cfName; + private final OperationType operationType; + private final long operationElapsedTime; // microseconds + private final OperationStage operationStage; + private final long operationProperties[]; + private final StateType stateType; + + /** + * Invoked from C++ via JNI + */ + private ThreadStatus(final long threadId, + final byte threadTypeValue, + final String dbName, + final String cfName, + final byte operationTypeValue, + final long operationElapsedTime, + final byte operationStageValue, + final long[] operationProperties, + final byte stateTypeValue) { + this.threadId = threadId; + this.threadType = ThreadType.fromValue(threadTypeValue); + this.dbName = dbName; + this.cfName = cfName; + this.operationType = OperationType.fromValue(operationTypeValue); + this.operationElapsedTime = operationElapsedTime; + this.operationStage = OperationStage.fromValue(operationStageValue); + this.operationProperties = operationProperties; + this.stateType = StateType.fromValue(stateTypeValue); + } + + /** + * Get the unique ID of the thread. + * + * @return the thread id + */ + public long getThreadId() { + return threadId; + } + + /** + * Get the type of the thread. + * + * @return the type of the thread. + */ + public ThreadType getThreadType() { + return threadType; + } + + /** + * The name of the DB instance that the thread is currently + * involved with. + * + * @return the name of the db, or null if the thread is not involved + * in any DB operation. + */ + /* @Nullable */ public String getDbName() { + return dbName; + } + + /** + * The name of the Column Family that the thread is currently + * involved with. + * + * @return the name of the db, or null if the thread is not involved + * in any column Family operation. + */ + /* @Nullable */ public String getCfName() { + return cfName; + } + + /** + * Get the operation (high-level action) that the current thread is involved + * with. + * + * @return the operation + */ + public OperationType getOperationType() { + return operationType; + } + + /** + * Get the elapsed time of the current thread operation in microseconds. + * + * @return the elapsed time + */ + public long getOperationElapsedTime() { + return operationElapsedTime; + } + + /** + * Get the current stage where the thread is involved in the current + * operation. + * + * @return the current stage of the current operation + */ + public OperationStage getOperationStage() { + return operationStage; + } + + /** + * Get the list of properties that describe some details about the current + * operation. + * + * Each field in might have different meanings for different operations. + * + * @return the properties + */ + public long[] getOperationProperties() { + return operationProperties; + } + + /** + * Get the state (lower-level action) that the current thread is involved + * with. + * + * @return the state + */ + public StateType getStateType() { + return stateType; + } + + /** + * Get the name of the thread type. + * + * @param threadType the thread type + * + * @return the name of the thread type. + */ + public static String getThreadTypeName(final ThreadType threadType) { + return getThreadTypeName(threadType.getValue()); + } + + /** + * Get the name of an operation given its type. + * + * @param operationType the type of operation. + * + * @return the name of the operation. + */ + public static String getOperationName(final OperationType operationType) { + return getOperationName(operationType.getValue()); + } + + public static String microsToString(final long operationElapsedTime) { + return microsToStringNative(operationElapsedTime); + } + + /** + * Obtain a human-readable string describing the specified operation stage. + * + * @param operationStage the stage of the operation. + * + * @return the description of the operation stage. + */ + public static String getOperationStageName( + final OperationStage operationStage) { + return getOperationStageName(operationStage.getValue()); + } + + /** + * Obtain the name of the "i"th operation property of the + * specified operation. + * + * @param operationType the operation type. + * @param i the index of the operation property. + * + * @return the name of the operation property + */ + public static String getOperationPropertyName( + final OperationType operationType, final int i) { + return getOperationPropertyName(operationType.getValue(), i); + } + + /** + * Translate the "i"th property of the specified operation given + * a property value. + * + * @param operationType the operation type. + * @param operationProperties the operation properties. + * + * @return the property values. + */ + public static Map interpretOperationProperties( + final OperationType operationType, final long[] operationProperties) { + return interpretOperationProperties(operationType.getValue(), + operationProperties); + } + + /** + * Obtain the name of a state given its type. + * + * @param stateType the state type. + * + * @return the name of the state. + */ + public static String getStateName(final StateType stateType) { + return getStateName(stateType.getValue()); + } + + private static native String getThreadTypeName(final byte threadTypeValue); + private static native String getOperationName(final byte operationTypeValue); + private static native String microsToStringNative( + final long operationElapsedTime); + private static native String getOperationStageName( + final byte operationStageTypeValue); + private static native String getOperationPropertyName( + final byte operationTypeValue, final int i); + private static native MapinterpretOperationProperties( + final byte operationTypeValue, final long[] operationProperties); + private static native String getStateName(final byte stateTypeValue); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ThreadType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ThreadType.java new file mode 100644 index 0000000000..cc329f4425 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/ThreadType.java @@ -0,0 +1,65 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * The type of a thread. + */ +public enum ThreadType { + /** + * RocksDB BG thread in high-pri thread pool. + */ + HIGH_PRIORITY((byte)0x0), + + /** + * RocksDB BG thread in low-pri thread pool. + */ + LOW_PRIORITY((byte)0x1), + + /** + * User thread (Non-RocksDB BG thread). + */ + USER((byte)0x2), + + /** + * RocksDB BG thread in bottom-pri thread pool + */ + BOTTOM_PRIORITY((byte)0x3); + + private final byte value; + + ThreadType(final byte value) { + this.value = value; + } + + /** + * Get the internal representation value. + * + * @return the internal representation value. + */ + byte getValue() { + return value; + } + + /** + * Get the Thread type from the internal representation value. + * + * @param value the internal representation value. + * + * @return the thread type + * + * @throws IllegalArgumentException if the value does not match a ThreadType + */ + static ThreadType fromValue(final byte value) + throws IllegalArgumentException { + for (final ThreadType threadType : ThreadType.values()) { + if (threadType.value == value) { + return threadType; + } + } + throw new IllegalArgumentException("Unknown value for ThreadType: " + value); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TickerType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TickerType.java index 948079c75a..551e366dc5 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TickerType.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TickerType.java @@ -5,6 +5,16 @@ package org.rocksdb; +/** + * The logical mapping of tickers defined in rocksdb::Tickers. + * + * Java byte value mappings don't align 1:1 to the c++ values. c++ rocksdb::Tickers enumeration type + * is uint32_t and java org.rocksdb.TickerType is byte, this causes mapping issues when + * rocksdb::Tickers value is greater then 127 (0x7F) for jbyte jni interface as range greater is not + * available. Without breaking interface in minor versions, value mappings for + * org.rocksdb.TickerType leverage full byte range [-128 (-0x80), (0x7F)]. Newer tickers added + * should descend into negative values until TICKER_ENUM_MAX reaches -128 (-0x80). + */ public enum TickerType { /** @@ -304,7 +314,8 @@ public enum TickerType { RATE_LIMIT_DELAY_MILLIS((byte) 0x37), /** - * Number of iterators currently open. + * Number of iterators created. + * */ NO_ITERATORS((byte) 0x38), @@ -465,8 +476,248 @@ public enum TickerType { */ NUMBER_RATE_LIMITER_DRAINS((byte) 0x5C), - TICKER_ENUM_MAX((byte) 0x5D); + /** + * Number of internal skipped during iteration + */ + NUMBER_ITER_SKIP((byte) 0x5D), + + /** + * Number of MultiGet keys found (vs number requested) + */ + NUMBER_MULTIGET_KEYS_FOUND((byte) 0x5E), + + // -0x01 to fixate the new value that incorrectly changed TICKER_ENUM_MAX + /** + * Number of iterators created. + */ + NO_ITERATOR_CREATED((byte) -0x01), + + /** + * Number of iterators deleted. + */ + NO_ITERATOR_DELETED((byte) 0x60), + + /** + * Deletions obsoleted before bottom level due to file gap optimization. + */ + COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE((byte) 0x61), + + /** + * If a compaction was cancelled in sfm to prevent ENOSPC + */ + COMPACTION_CANCELLED((byte) 0x62), + + /** + * # of times bloom FullFilter has not avoided the reads. + */ + BLOOM_FILTER_FULL_POSITIVE((byte) 0x63), + + /** + * # of times bloom FullFilter has not avoided the reads and data actually + * exist. + */ + BLOOM_FILTER_FULL_TRUE_POSITIVE((byte) 0x64), + + /** + * BlobDB specific stats + * # of Put/PutTTL/PutUntil to BlobDB. + */ + BLOB_DB_NUM_PUT((byte) 0x65), + + /** + * # of Write to BlobDB. + */ + BLOB_DB_NUM_WRITE((byte) 0x66), + + /** + * # of Get to BlobDB. + */ + BLOB_DB_NUM_GET((byte) 0x67), + + /** + * # of MultiGet to BlobDB. + */ + BLOB_DB_NUM_MULTIGET((byte) 0x68), + + /** + * # of Seek/SeekToFirst/SeekToLast/SeekForPrev to BlobDB iterator. + */ + BLOB_DB_NUM_SEEK((byte) 0x69), + + /** + * # of Next to BlobDB iterator. + */ + BLOB_DB_NUM_NEXT((byte) 0x6A), + + /** + * # of Prev to BlobDB iterator. + */ + BLOB_DB_NUM_PREV((byte) 0x6B), + + /** + * # of keys written to BlobDB. + */ + BLOB_DB_NUM_KEYS_WRITTEN((byte) 0x6C), + + /** + * # of keys read from BlobDB. + */ + BLOB_DB_NUM_KEYS_READ((byte) 0x6D), + + /** + * # of bytes (key + value) written to BlobDB. + */ + BLOB_DB_BYTES_WRITTEN((byte) 0x6E), + + /** + * # of bytes (keys + value) read from BlobDB. + */ + BLOB_DB_BYTES_READ((byte) 0x6F), + + /** + * # of keys written by BlobDB as non-TTL inlined value. + */ + BLOB_DB_WRITE_INLINED((byte) 0x70), + + /** + * # of keys written by BlobDB as TTL inlined value. + */ + BLOB_DB_WRITE_INLINED_TTL((byte) 0x71), + + /** + * # of keys written by BlobDB as non-TTL blob value. + */ + BLOB_DB_WRITE_BLOB((byte) 0x72), + + /** + * # of keys written by BlobDB as TTL blob value. + */ + BLOB_DB_WRITE_BLOB_TTL((byte) 0x73), + + /** + * # of bytes written to blob file. + */ + BLOB_DB_BLOB_FILE_BYTES_WRITTEN((byte) 0x74), + + /** + * # of bytes read from blob file. + */ + BLOB_DB_BLOB_FILE_BYTES_READ((byte) 0x75), + + /** + * # of times a blob files being synced. + */ + BLOB_DB_BLOB_FILE_SYNCED((byte) 0x76), + + /** + * # of blob index evicted from base DB by BlobDB compaction filter because + * of expiration. + */ + BLOB_DB_BLOB_INDEX_EXPIRED_COUNT((byte) 0x77), + + /** + * Size of blob index evicted from base DB by BlobDB compaction filter + * because of expiration. + */ + BLOB_DB_BLOB_INDEX_EXPIRED_SIZE((byte) 0x78), + + /** + * # of blob index evicted from base DB by BlobDB compaction filter because + * of corresponding file deleted. + */ + BLOB_DB_BLOB_INDEX_EVICTED_COUNT((byte) 0x79), + + /** + * Size of blob index evicted from base DB by BlobDB compaction filter + * because of corresponding file deleted. + */ + BLOB_DB_BLOB_INDEX_EVICTED_SIZE((byte) 0x7A), + + /** + * # of blob files being garbage collected. + */ + BLOB_DB_GC_NUM_FILES((byte) 0x7B), + + /** + * # of blob files generated by garbage collection. + */ + BLOB_DB_GC_NUM_NEW_FILES((byte) 0x7C), + + /** + * # of BlobDB garbage collection failures. + */ + BLOB_DB_GC_FAILURES((byte) 0x7D), + + /** + * # of keys drop by BlobDB garbage collection because they had been + * overwritten. + */ + BLOB_DB_GC_NUM_KEYS_OVERWRITTEN((byte) 0x7E), + /** + * # of keys drop by BlobDB garbage collection because of expiration. + */ + BLOB_DB_GC_NUM_KEYS_EXPIRED((byte) 0x7F), + + /** + * # of keys relocated to new blob file by garbage collection. + */ + BLOB_DB_GC_NUM_KEYS_RELOCATED((byte) -0x02), + + /** + * # of bytes drop by BlobDB garbage collection because they had been + * overwritten. + */ + BLOB_DB_GC_BYTES_OVERWRITTEN((byte) -0x03), + + /** + * # of bytes drop by BlobDB garbage collection because of expiration. + */ + BLOB_DB_GC_BYTES_EXPIRED((byte) -0x04), + + /** + * # of bytes relocated to new blob file by garbage collection. + */ + BLOB_DB_GC_BYTES_RELOCATED((byte) -0x05), + + /** + * # of blob files evicted because of BlobDB is full. + */ + BLOB_DB_FIFO_NUM_FILES_EVICTED((byte) -0x06), + + /** + * # of keys in the blob files evicted because of BlobDB is full. + */ + BLOB_DB_FIFO_NUM_KEYS_EVICTED((byte) -0x07), + + /** + * # of bytes in the blob files evicted because of BlobDB is full. + */ + BLOB_DB_FIFO_BYTES_EVICTED((byte) -0x08), + + /** + * These counters indicate a performance issue in WritePrepared transactions. + * We should not seem them ticking them much. + * # of times prepare_mutex_ is acquired in the fast path. + */ + TXN_PREPARE_MUTEX_OVERHEAD((byte) -0x09), + + /** + * # of times old_commit_map_mutex_ is acquired in the fast path. + */ + TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD((byte) -0x0A), + + /** + * # of times we checked a batch for duplicate keys. + */ + TXN_DUPLICATE_KEY_OVERHEAD((byte) -0x0B), + + /** + * # of times snapshot_mutex_ is acquired in the fast path. + */ + TXN_SNAPSHOT_MUTEX_OVERHEAD((byte) -0x0C), + + TICKER_ENUM_MAX((byte) 0x5F); private final byte value; @@ -474,6 +725,13 @@ public enum TickerType { this.value = value; } + /** + * @deprecated Exposes internal value of native enum mappings. + * This method will be marked package private in the next major release. + * + * @return the internal representation + */ + @Deprecated public byte getValue() { return value; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TimedEnv.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TimedEnv.java new file mode 100644 index 0000000000..dc8b5d6efb --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TimedEnv.java @@ -0,0 +1,30 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Timed environment. + */ +public class TimedEnv extends Env { + + /** + *

    Creates a new environment that measures function call times for + * filesystem operations, reporting results to variables in PerfContext.

    + * + * + *

    The caller must delete the result when it is + * no longer needed.

    + * + * @param baseEnv the base environment, + * must remain live while the result is in use. + */ + public TimedEnv(final Env baseEnv) { + super(createTimedEnv(baseEnv.nativeHandle_)); + } + + private static native long createTimedEnv(final long baseEnvHandle); + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TraceOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TraceOptions.java new file mode 100644 index 0000000000..657b263c6d --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TraceOptions.java @@ -0,0 +1,32 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * TraceOptions is used for + * {@link RocksDB#startTrace(TraceOptions, AbstractTraceWriter)}. + */ +public class TraceOptions { + private final long maxTraceFileSize; + + public TraceOptions() { + this.maxTraceFileSize = 64 * 1024 * 1024 * 1024; // 64 GB + } + + public TraceOptions(final long maxTraceFileSize) { + this.maxTraceFileSize = maxTraceFileSize; + } + + /** + * To avoid the trace file size grows large than the storage space, + * user can set the max trace file size in Bytes. Default is 64GB + * + * @return the max trace size + */ + public long getMaxTraceFileSize() { + return maxTraceFileSize; + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TraceWriter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TraceWriter.java new file mode 100644 index 0000000000..cb0234e9b2 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TraceWriter.java @@ -0,0 +1,36 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * TraceWriter allows exporting RocksDB traces to any system, + * one operation at a time. + */ +public interface TraceWriter { + + /** + * Write the data. + * + * @param data the data + * + * @throws RocksDBException if an error occurs whilst writing. + */ + void write(final Slice data) throws RocksDBException; + + /** + * Close the writer. + * + * @throws RocksDBException if an error occurs whilst closing the writer. + */ + void closeWriter() throws RocksDBException; + + /** + * Get the size of the file that this writer is writing to. + * + * @return the file size + */ + long getFileSize(); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Transaction.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Transaction.java new file mode 100644 index 0000000000..96f1143d4f --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/Transaction.java @@ -0,0 +1,1868 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.List; + +/** + * Provides BEGIN/COMMIT/ROLLBACK transactions. + * + * To use transactions, you must first create either an + * {@link OptimisticTransactionDB} or a {@link TransactionDB} + * + * To create a transaction, use + * {@link OptimisticTransactionDB#beginTransaction(org.rocksdb.WriteOptions)} or + * {@link TransactionDB#beginTransaction(org.rocksdb.WriteOptions)} + * + * It is up to the caller to synchronize access to this object. + * + * See samples/src/main/java/OptimisticTransactionSample.java and + * samples/src/main/java/TransactionSample.java for some simple + * examples. + */ +public class Transaction extends RocksObject { + + private final RocksDB parent; + + /** + * Intentionally package private + * as this is called from + * {@link OptimisticTransactionDB#beginTransaction(org.rocksdb.WriteOptions)} + * or {@link TransactionDB#beginTransaction(org.rocksdb.WriteOptions)} + * + * @param parent This must be either {@link TransactionDB} or + * {@link OptimisticTransactionDB} + * @param transactionHandle The native handle to the underlying C++ + * transaction object + */ + Transaction(final RocksDB parent, final long transactionHandle) { + super(transactionHandle); + this.parent = parent; + } + + /** + * If a transaction has a snapshot set, the transaction will ensure that + * any keys successfully written(or fetched via {@link #getForUpdate}) have + * not been modified outside of this transaction since the time the snapshot + * was set. + * + * If a snapshot has not been set, the transaction guarantees that keys have + * not been modified since the time each key was first written (or fetched via + * {@link #getForUpdate}). + * + * Using {@link #setSnapshot()} will provide stricter isolation guarantees + * at the expense of potentially more transaction failures due to conflicts + * with other writes. + * + * Calling {@link #setSnapshot()} has no effect on keys written before this + * function has been called. + * + * {@link #setSnapshot()} may be called multiple times if you would like to + * change the snapshot used for different operations in this transaction. + * + * Calling {@link #setSnapshot()} will not affect the version of Data returned + * by get(...) methods. See {@link #get} for more details. + */ + public void setSnapshot() { + assert(isOwningHandle()); + setSnapshot(nativeHandle_); + } + + /** + * Similar to {@link #setSnapshot()}, but will not change the current snapshot + * until put/merge/delete/getForUpdate/multiGetForUpdate is called. + * By calling this function, the transaction will essentially call + * {@link #setSnapshot()} for you right before performing the next + * write/getForUpdate. + * + * Calling {@link #setSnapshotOnNextOperation()} will not affect what + * snapshot is returned by {@link #getSnapshot} until the next + * write/getForUpdate is executed. + * + * When the snapshot is created the notifier's snapshotCreated method will + * be called so that the caller can get access to the snapshot. + * + * This is an optimization to reduce the likelihood of conflicts that + * could occur in between the time {@link #setSnapshot()} is called and the + * first write/getForUpdate operation. i.e. this prevents the following + * race-condition: + * + * txn1->setSnapshot(); + * txn2->put("A", ...); + * txn2->commit(); + * txn1->getForUpdate(opts, "A", ...); * FAIL! + */ + public void setSnapshotOnNextOperation() { + assert(isOwningHandle()); + setSnapshotOnNextOperation(nativeHandle_); + } + + /** + * Similar to {@link #setSnapshot()}, but will not change the current snapshot + * until put/merge/delete/getForUpdate/multiGetForUpdate is called. + * By calling this function, the transaction will essentially call + * {@link #setSnapshot()} for you right before performing the next + * write/getForUpdate. + * + * Calling {@link #setSnapshotOnNextOperation()} will not affect what + * snapshot is returned by {@link #getSnapshot} until the next + * write/getForUpdate is executed. + * + * When the snapshot is created the + * {@link AbstractTransactionNotifier#snapshotCreated(Snapshot)} method will + * be called so that the caller can get access to the snapshot. + * + * This is an optimization to reduce the likelihood of conflicts that + * could occur in between the time {@link #setSnapshot()} is called and the + * first write/getForUpdate operation. i.e. this prevents the following + * race-condition: + * + * txn1->setSnapshot(); + * txn2->put("A", ...); + * txn2->commit(); + * txn1->getForUpdate(opts, "A", ...); * FAIL! + * + * @param transactionNotifier A handler for receiving snapshot notifications + * for the transaction + * + */ + public void setSnapshotOnNextOperation( + final AbstractTransactionNotifier transactionNotifier) { + assert(isOwningHandle()); + setSnapshotOnNextOperation(nativeHandle_, transactionNotifier.nativeHandle_); + } + + /** + * Returns the Snapshot created by the last call to {@link #setSnapshot()}. + * + * REQUIRED: The returned Snapshot is only valid up until the next time + * {@link #setSnapshot()}/{@link #setSnapshotOnNextOperation()} is called, + * {@link #clearSnapshot()} is called, or the Transaction is deleted. + * + * @return The snapshot or null if there is no snapshot + */ + public Snapshot getSnapshot() { + assert(isOwningHandle()); + final long snapshotNativeHandle = getSnapshot(nativeHandle_); + if(snapshotNativeHandle == 0) { + return null; + } else { + final Snapshot snapshot = new Snapshot(snapshotNativeHandle); + return snapshot; + } + } + + /** + * Clears the current snapshot (i.e. no snapshot will be 'set') + * + * This removes any snapshot that currently exists or is set to be created + * on the next update operation ({@link #setSnapshotOnNextOperation()}). + * + * Calling {@link #clearSnapshot()} has no effect on keys written before this + * function has been called. + * + * If a reference to a snapshot was retrieved via {@link #getSnapshot()}, it + * will no longer be valid and should be discarded after a call to + * {@link #clearSnapshot()}. + */ + public void clearSnapshot() { + assert(isOwningHandle()); + clearSnapshot(nativeHandle_); + } + + /** + * Prepare the current transaction for 2PC + */ + void prepare() throws RocksDBException { + //TODO(AR) consider a Java'ish version of this function, which returns an AutoCloseable (commit) + assert(isOwningHandle()); + prepare(nativeHandle_); + } + + /** + * Write all batched keys to the db atomically. + * + * Returns OK on success. + * + * May return any error status that could be returned by DB:Write(). + * + * If this transaction was created by an {@link OptimisticTransactionDB} + * Status::Busy() may be returned if the transaction could not guarantee + * that there are no write conflicts. Status::TryAgain() may be returned + * if the memtable history size is not large enough + * (See max_write_buffer_number_to_maintain). + * + * If this transaction was created by a {@link TransactionDB}, + * Status::Expired() may be returned if this transaction has lived for + * longer than {@link TransactionOptions#getExpiration()}. + * + * @throws RocksDBException if an error occurs when committing the transaction + */ + public void commit() throws RocksDBException { + assert(isOwningHandle()); + commit(nativeHandle_); + } + + /** + * Discard all batched writes in this transaction. + * + * @throws RocksDBException if an error occurs when rolling back the transaction + */ + public void rollback() throws RocksDBException { + assert(isOwningHandle()); + rollback(nativeHandle_); + } + + /** + * Records the state of the transaction for future calls to + * {@link #rollbackToSavePoint()}. + * + * May be called multiple times to set multiple save points. + * + * @throws RocksDBException if an error occurs whilst setting a save point + */ + public void setSavePoint() throws RocksDBException { + assert(isOwningHandle()); + setSavePoint(nativeHandle_); + } + + /** + * Undo all operations in this transaction (put, merge, delete, putLogData) + * since the most recent call to {@link #setSavePoint()} and removes the most + * recent {@link #setSavePoint()}. + * + * If there is no previous call to {@link #setSavePoint()}, + * returns Status::NotFound() + * + * @throws RocksDBException if an error occurs when rolling back to a save point + */ + public void rollbackToSavePoint() throws RocksDBException { + assert(isOwningHandle()); + rollbackToSavePoint(nativeHandle_); + } + + /** + * This function is similar to + * {@link RocksDB#get(ColumnFamilyHandle, ReadOptions, byte[])} except it will + * also read pending changes in this transaction. + * Currently, this function will return Status::MergeInProgress if the most + * recent write to the queried key in this batch is a Merge. + * + * If {@link ReadOptions#snapshot()} is not set, the current version of the + * key will be read. Calling {@link #setSnapshot()} does not affect the + * version of the data returned. + * + * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect + * what is read from the DB but will NOT change which keys are read from this + * transaction (the keys in this transaction do not yet belong to any snapshot + * and will be fetched regardless). + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} instance + * @param readOptions Read options. + * @param key the key to retrieve the value for. + * + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying native + * library. + */ + public byte[] get(final ColumnFamilyHandle columnFamilyHandle, + final ReadOptions readOptions, final byte[] key) throws RocksDBException { + assert(isOwningHandle()); + return get(nativeHandle_, readOptions.nativeHandle_, key, key.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * This function is similar to + * {@link RocksDB#get(ReadOptions, byte[])} except it will + * also read pending changes in this transaction. + * Currently, this function will return Status::MergeInProgress if the most + * recent write to the queried key in this batch is a Merge. + * + * If {@link ReadOptions#snapshot()} is not set, the current version of the + * key will be read. Calling {@link #setSnapshot()} does not affect the + * version of the data returned. + * + * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect + * what is read from the DB but will NOT change which keys are read from this + * transaction (the keys in this transaction do not yet belong to any snapshot + * and will be fetched regardless). + * + * @param readOptions Read options. + * @param key the key to retrieve the value for. + * + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying native + * library. + */ + public byte[] get(final ReadOptions readOptions, final byte[] key) + throws RocksDBException { + assert(isOwningHandle()); + return get(nativeHandle_, readOptions.nativeHandle_, key, key.length); + } + + /** + * This function is similar to + * {@link RocksDB#multiGet(ReadOptions, List, List)} except it will + * also read pending changes in this transaction. + * Currently, this function will return Status::MergeInProgress if the most + * recent write to the queried key in this batch is a Merge. + * + * If {@link ReadOptions#snapshot()} is not set, the current version of the + * key will be read. Calling {@link #setSnapshot()} does not affect the + * version of the data returned. + * + * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect + * what is read from the DB but will NOT change which keys are read from this + * transaction (the keys in this transaction do not yet belong to any snapshot + * and will be fetched regardless). + * + * @param readOptions Read options. + * @param columnFamilyHandles {@link java.util.List} containing + * {@link org.rocksdb.ColumnFamilyHandle} instances. + * @param keys of keys for which values need to be retrieved. + * + * @return Array of values, one for each key + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + * @throws IllegalArgumentException thrown if the size of passed keys is not + * equal to the amount of passed column family handles. + */ + public byte[][] multiGet(final ReadOptions readOptions, + final List columnFamilyHandles, + final byte[][] keys) throws RocksDBException { + assert(isOwningHandle()); + // Check if key size equals cfList size. If not a exception must be + // thrown. If not a Segmentation fault happens. + if (keys.length != columnFamilyHandles.size()) { + throw new IllegalArgumentException( + "For each key there must be a ColumnFamilyHandle."); + } + if(keys.length == 0) { + return new byte[0][0]; + } + final long[] cfHandles = new long[columnFamilyHandles.size()]; + for (int i = 0; i < columnFamilyHandles.size(); i++) { + cfHandles[i] = columnFamilyHandles.get(i).nativeHandle_; + } + + return multiGet(nativeHandle_, readOptions.nativeHandle_, + keys, cfHandles); + } + + /** + * This function is similar to + * {@link RocksDB#multiGet(ReadOptions, List)} except it will + * also read pending changes in this transaction. + * Currently, this function will return Status::MergeInProgress if the most + * recent write to the queried key in this batch is a Merge. + * + * If {@link ReadOptions#snapshot()} is not set, the current version of the + * key will be read. Calling {@link #setSnapshot()} does not affect the + * version of the data returned. + * + * Note that setting {@link ReadOptions#setSnapshot(Snapshot)} will affect + * what is read from the DB but will NOT change which keys are read from this + * transaction (the keys in this transaction do not yet belong to any snapshot + * and will be fetched regardless). + * + * @param readOptions Read options.= + * {@link org.rocksdb.ColumnFamilyHandle} instances. + * @param keys of keys for which values need to be retrieved. + * + * @return Array of values, one for each key + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[][] multiGet(final ReadOptions readOptions, + final byte[][] keys) throws RocksDBException { + assert(isOwningHandle()); + if(keys.length == 0) { + return new byte[0][0]; + } + + return multiGet(nativeHandle_, readOptions.nativeHandle_, + keys); + } + + /** + * Read this key and ensure that this transaction will only + * be able to be committed if this key is not written outside this + * transaction after it has first been read (or after the snapshot if a + * snapshot is set in this transaction). The transaction behavior is the + * same regardless of whether the key exists or not. + * + * Note: Currently, this function will return Status::MergeInProgress + * if the most recent write to the queried key in this batch is a Merge. + * + * The values returned by this function are similar to + * {@link RocksDB#get(ColumnFamilyHandle, ReadOptions, byte[])}. + * If value==nullptr, then this function will not read any data, but will + * still ensure that this key cannot be written to by outside of this + * transaction. + * + * If this transaction was created by an {@link OptimisticTransactionDB}, + * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)} + * could cause {@link #commit()} to fail. Otherwise, it could return any error + * that could be returned by + * {@link RocksDB#get(ColumnFamilyHandle, ReadOptions, byte[])}. + * + * If this transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * {@link Status.Code#MergeInProgress} if merge operations cannot be + * resolved. + * + * @param readOptions Read options. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key to retrieve the value for. + * @param exclusive true if the transaction should have exclusive access to + * the key, otherwise false for shared access. + * @param do_validate true if it should validate the snapshot before doing the read + * + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] getForUpdate(final ReadOptions readOptions, + final ColumnFamilyHandle columnFamilyHandle, final byte[] key, final boolean exclusive, + final boolean do_validate) throws RocksDBException { + assert (isOwningHandle()); + return getForUpdate(nativeHandle_, readOptions.nativeHandle_, key, key.length, + columnFamilyHandle.nativeHandle_, exclusive, do_validate); + } + + /** + * Same as + * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean, boolean)} + * with do_validate=true. + * + * @param readOptions Read options. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key to retrieve the value for. + * @param exclusive true if the transaction should have exclusive access to + * the key, otherwise false for shared access. + * + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] getForUpdate(final ReadOptions readOptions, + final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final boolean exclusive) throws RocksDBException { + assert(isOwningHandle()); + return getForUpdate(nativeHandle_, readOptions.nativeHandle_, key, key.length, + columnFamilyHandle.nativeHandle_, exclusive, true /*do_validate*/); + } + + /** + * Read this key and ensure that this transaction will only + * be able to be committed if this key is not written outside this + * transaction after it has first been read (or after the snapshot if a + * snapshot is set in this transaction). The transaction behavior is the + * same regardless of whether the key exists or not. + * + * Note: Currently, this function will return Status::MergeInProgress + * if the most recent write to the queried key in this batch is a Merge. + * + * The values returned by this function are similar to + * {@link RocksDB#get(ReadOptions, byte[])}. + * If value==nullptr, then this function will not read any data, but will + * still ensure that this key cannot be written to by outside of this + * transaction. + * + * If this transaction was created on an {@link OptimisticTransactionDB}, + * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)} + * could cause {@link #commit()} to fail. Otherwise, it could return any error + * that could be returned by + * {@link RocksDB#get(ReadOptions, byte[])}. + * + * If this transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * {@link Status.Code#MergeInProgress} if merge operations cannot be + * resolved. + * + * @param readOptions Read options. + * @param key the key to retrieve the value for. + * @param exclusive true if the transaction should have exclusive access to + * the key, otherwise false for shared access. + * + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[] getForUpdate(final ReadOptions readOptions, final byte[] key, + final boolean exclusive) throws RocksDBException { + assert(isOwningHandle()); + return getForUpdate( + nativeHandle_, readOptions.nativeHandle_, key, key.length, exclusive, true /*do_validate*/); + } + + /** + * A multi-key version of + * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}. + * + * + * @param readOptions Read options. + * @param columnFamilyHandles {@link org.rocksdb.ColumnFamilyHandle} + * instances + * @param keys the keys to retrieve the values for. + * + * @return Array of values, one for each key + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[][] multiGetForUpdate(final ReadOptions readOptions, + final List columnFamilyHandles, + final byte[][] keys) throws RocksDBException { + assert(isOwningHandle()); + // Check if key size equals cfList size. If not a exception must be + // thrown. If not a Segmentation fault happens. + if (keys.length != columnFamilyHandles.size()){ + throw new IllegalArgumentException( + "For each key there must be a ColumnFamilyHandle."); + } + if(keys.length == 0) { + return new byte[0][0]; + } + final long[] cfHandles = new long[columnFamilyHandles.size()]; + for (int i = 0; i < columnFamilyHandles.size(); i++) { + cfHandles[i] = columnFamilyHandles.get(i).nativeHandle_; + } + return multiGetForUpdate(nativeHandle_, readOptions.nativeHandle_, + keys, cfHandles); + } + + /** + * A multi-key version of {@link #getForUpdate(ReadOptions, byte[], boolean)}. + * + * + * @param readOptions Read options. + * @param keys the keys to retrieve the values for. + * + * @return Array of values, one for each key + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public byte[][] multiGetForUpdate(final ReadOptions readOptions, + final byte[][] keys) throws RocksDBException { + assert(isOwningHandle()); + if(keys.length == 0) { + return new byte[0][0]; + } + + return multiGetForUpdate(nativeHandle_, + readOptions.nativeHandle_, keys); + } + + /** + * Returns an iterator that will iterate on all keys in the default + * column family including both keys in the DB and uncommitted keys in this + * transaction. + * + * Setting {@link ReadOptions#setSnapshot(Snapshot)} will affect what is read + * from the DB but will NOT change which keys are read from this transaction + * (the keys in this transaction do not yet belong to any snapshot and will be + * fetched regardless). + * + * Caller is responsible for deleting the returned Iterator. + * + * The returned iterator is only valid until {@link #commit()}, + * {@link #rollback()}, or {@link #rollbackToSavePoint()} is called. + * + * @param readOptions Read options. + * + * @return instance of iterator object. + */ + public RocksIterator getIterator(final ReadOptions readOptions) { + assert(isOwningHandle()); + return new RocksIterator(parent, getIterator(nativeHandle_, + readOptions.nativeHandle_)); + } + + /** + * Returns an iterator that will iterate on all keys in the default + * column family including both keys in the DB and uncommitted keys in this + * transaction. + * + * Setting {@link ReadOptions#setSnapshot(Snapshot)} will affect what is read + * from the DB but will NOT change which keys are read from this transaction + * (the keys in this transaction do not yet belong to any snapshot and will be + * fetched regardless). + * + * Caller is responsible for calling {@link RocksIterator#close()} on + * the returned Iterator. + * + * The returned iterator is only valid until {@link #commit()}, + * {@link #rollback()}, or {@link #rollbackToSavePoint()} is called. + * + * @param readOptions Read options. + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * + * @return instance of iterator object. + */ + public RocksIterator getIterator(final ReadOptions readOptions, + final ColumnFamilyHandle columnFamilyHandle) { + assert(isOwningHandle()); + return new RocksIterator(parent, getIterator(nativeHandle_, + readOptions.nativeHandle_, columnFamilyHandle.nativeHandle_)); + } + + /** + * Similar to {@link RocksDB#put(ColumnFamilyHandle, byte[], byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param columnFamilyHandle The column family to put the key/value into + * @param key the specified key to be inserted. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void put(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, final byte[] value, + final boolean assume_tracked) throws RocksDBException { + assert (isOwningHandle()); + put(nativeHandle_, key, key.length, value, value.length, columnFamilyHandle.nativeHandle_, + assume_tracked); + } + + /* + * Same as + * {@link #put(ColumnFamilyHandle, byte[], byte[], boolean)} + * with assume_tracked=false. + */ + public void put(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final byte[] value) throws RocksDBException { + assert(isOwningHandle()); + put(nativeHandle_, key, key.length, value, value.length, columnFamilyHandle.nativeHandle_, + /*assume_tracked*/ false); + } + + /** + * Similar to {@link RocksDB#put(byte[], byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param key the specified key to be inserted. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void put(final byte[] key, final byte[] value) + throws RocksDBException { + assert(isOwningHandle()); + put(nativeHandle_, key, key.length, value, value.length); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #put(ColumnFamilyHandle, byte[], byte[])} but allows + * you to specify the key and value in several parts that will be + * concatenated together. + * + * @param columnFamilyHandle The column family to put the key/value into + * @param keyParts the specified key to be inserted. + * @param valueParts the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void put(final ColumnFamilyHandle columnFamilyHandle, final byte[][] keyParts, + final byte[][] valueParts, final boolean assume_tracked) throws RocksDBException { + assert (isOwningHandle()); + put(nativeHandle_, keyParts, keyParts.length, valueParts, valueParts.length, + columnFamilyHandle.nativeHandle_, assume_tracked); + } + + /* + * Same as + * {@link #put(ColumnFamilyHandle, byte[][], byte[][], boolean)} + * with assume_tracked=false. + */ + public void put(final ColumnFamilyHandle columnFamilyHandle, + final byte[][] keyParts, final byte[][] valueParts) + throws RocksDBException { + assert(isOwningHandle()); + put(nativeHandle_, keyParts, keyParts.length, valueParts, valueParts.length, + columnFamilyHandle.nativeHandle_, /*assume_tracked*/ false); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #put(byte[], byte[])} but allows + * you to specify the key and value in several parts that will be + * concatenated together + * + * @param keyParts the specified key to be inserted. + * @param valueParts the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void put(final byte[][] keyParts, final byte[][] valueParts) + throws RocksDBException { + assert(isOwningHandle()); + put(nativeHandle_, keyParts, keyParts.length, valueParts, + valueParts.length); + } + + /** + * Similar to {@link RocksDB#merge(ColumnFamilyHandle, byte[], byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param columnFamilyHandle The column family to merge the key/value into + * @param key the specified key to be merged. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void merge(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final byte[] value, final boolean assume_tracked) throws RocksDBException { + assert (isOwningHandle()); + merge(nativeHandle_, key, key.length, value, value.length, columnFamilyHandle.nativeHandle_, + assume_tracked); + } + + /* + * Same as + * {@link #merge(ColumnFamilyHandle, byte[], byte[], boolean)} + * with assume_tracked=false. + */ + public void merge(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final byte[] value) throws RocksDBException { + assert(isOwningHandle()); + merge(nativeHandle_, key, key.length, value, value.length, columnFamilyHandle.nativeHandle_, + /*assume_tracked*/ false); + } + + /** + * Similar to {@link RocksDB#merge(byte[], byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param key the specified key to be merged. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void merge(final byte[] key, final byte[] value) + throws RocksDBException { + assert(isOwningHandle()); + merge(nativeHandle_, key, key.length, value, value.length); + } + + /** + * Similar to {@link RocksDB#delete(ColumnFamilyHandle, byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param columnFamilyHandle The column family to delete the key/value from + * @param key the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void delete(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final boolean assume_tracked) throws RocksDBException { + assert (isOwningHandle()); + delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_, assume_tracked); + } + + /* + * Same as + * {@link #delete(ColumnFamilyHandle, byte[], boolean)} + * with assume_tracked=false. + */ + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException { + assert(isOwningHandle()); + delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_, + /*assume_tracked*/ false); + } + + /** + * Similar to {@link RocksDB#delete(byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param key the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void delete(final byte[] key) throws RocksDBException { + assert(isOwningHandle()); + delete(nativeHandle_, key, key.length); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #delete(ColumnFamilyHandle, byte[])} but allows + * you to specify the key in several parts that will be + * concatenated together. + * + * @param columnFamilyHandle The column family to delete the key/value from + * @param keyParts the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void delete(final ColumnFamilyHandle columnFamilyHandle, final byte[][] keyParts, + final boolean assume_tracked) throws RocksDBException { + assert (isOwningHandle()); + delete( + nativeHandle_, keyParts, keyParts.length, columnFamilyHandle.nativeHandle_, assume_tracked); + } + + /* + * Same as + * {@link #delete(ColumnFamilyHandle, byte[][], boolean)} + * with assume_tracked=false. + */ + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final byte[][] keyParts) throws RocksDBException { + assert(isOwningHandle()); + delete(nativeHandle_, keyParts, keyParts.length, columnFamilyHandle.nativeHandle_, + /*assume_tracked*/ false); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #delete(byte[])} but allows + * you to specify key the in several parts that will be + * concatenated together. + * + * @param keyParts the specified key to be deleted + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void delete(final byte[][] keyParts) throws RocksDBException { + assert(isOwningHandle()); + delete(nativeHandle_, keyParts, keyParts.length); + } + + /** + * Similar to {@link RocksDB#singleDelete(ColumnFamilyHandle, byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param columnFamilyHandle The column family to delete the key/value from + * @param key the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, final byte[] key, + final boolean assume_tracked) throws RocksDBException { + assert (isOwningHandle()); + singleDelete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_, assume_tracked); + } + + /* + * Same as + * {@link #singleDelete(ColumnFamilyHandle, byte[], boolean)} + * with assume_tracked=false. + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, final byte[] key) + throws RocksDBException { + assert(isOwningHandle()); + singleDelete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_, + /*assume_tracked*/ false); + } + + /** + * Similar to {@link RocksDB#singleDelete(byte[])}, but + * will also perform conflict checking on the keys be written. + * + * If this Transaction was created on an {@link OptimisticTransactionDB}, + * these functions should always succeed. + * + * If this Transaction was created on a {@link TransactionDB}, an + * {@link RocksDBException} may be thrown with an accompanying {@link Status} + * when: + * {@link Status.Code#Busy} if there is a write conflict, + * {@link Status.Code#TimedOut} if a lock could not be acquired, + * {@link Status.Code#TryAgain} if the memtable history size is not large + * enough. See + * {@link ColumnFamilyOptions#maxWriteBufferNumberToMaintain()} + * + * @param key the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final byte[] key) throws RocksDBException { + assert(isOwningHandle()); + singleDelete(nativeHandle_, key, key.length); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #singleDelete(ColumnFamilyHandle, byte[])} but allows + * you to specify the key in several parts that will be + * concatenated together. + * + * @param columnFamilyHandle The column family to delete the key/value from + * @param keyParts the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, final byte[][] keyParts, + final boolean assume_tracked) throws RocksDBException { + assert (isOwningHandle()); + singleDelete( + nativeHandle_, keyParts, keyParts.length, columnFamilyHandle.nativeHandle_, assume_tracked); + } + + /* + * Same as + * {@link #singleDelete(ColumnFamilyHandle, byte[][], boolean)} + * with assume_tracked=false. + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, final byte[][] keyParts) + throws RocksDBException { + assert(isOwningHandle()); + singleDelete(nativeHandle_, keyParts, keyParts.length, columnFamilyHandle.nativeHandle_, + /*assume_tracked*/ false); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #singleDelete(byte[])} but allows + * you to specify the key in several parts that will be + * concatenated together. + * + * @param keyParts the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final byte[][] keyParts) throws RocksDBException { + assert(isOwningHandle()); + singleDelete(nativeHandle_, keyParts, keyParts.length); + } + + /** + * Similar to {@link RocksDB#put(ColumnFamilyHandle, byte[], byte[])}, + * but operates on the transactions write batch. This write will only happen + * if this transaction gets committed successfully. + * + * Unlike {@link #put(ColumnFamilyHandle, byte[], byte[])} no conflict + * checking will be performed for this key. + * + * If this Transaction was created on a {@link TransactionDB}, this function + * will still acquire locks necessary to make sure this write doesn't cause + * conflicts in other transactions; This may cause a {@link RocksDBException} + * with associated {@link Status.Code#Busy}. + * + * @param columnFamilyHandle The column family to put the key/value into + * @param key the specified key to be inserted. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void putUntracked(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final byte[] value) throws RocksDBException { + assert(isOwningHandle()); + putUntracked(nativeHandle_, key, key.length, value, value.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * Similar to {@link RocksDB#put(byte[], byte[])}, + * but operates on the transactions write batch. This write will only happen + * if this transaction gets committed successfully. + * + * Unlike {@link #put(byte[], byte[])} no conflict + * checking will be performed for this key. + * + * If this Transaction was created on a {@link TransactionDB}, this function + * will still acquire locks necessary to make sure this write doesn't cause + * conflicts in other transactions; This may cause a {@link RocksDBException} + * with associated {@link Status.Code#Busy}. + * + * @param key the specified key to be inserted. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void putUntracked(final byte[] key, final byte[] value) + throws RocksDBException { + assert(isOwningHandle()); + putUntracked(nativeHandle_, key, key.length, value, value.length); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #putUntracked(ColumnFamilyHandle, byte[], byte[])} but + * allows you to specify the key and value in several parts that will be + * concatenated together. + * + * @param columnFamilyHandle The column family to put the key/value into + * @param keyParts the specified key to be inserted. + * @param valueParts the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void putUntracked(final ColumnFamilyHandle columnFamilyHandle, + final byte[][] keyParts, final byte[][] valueParts) + throws RocksDBException { + assert(isOwningHandle()); + putUntracked(nativeHandle_, keyParts, keyParts.length, valueParts, + valueParts.length, columnFamilyHandle.nativeHandle_); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #putUntracked(byte[], byte[])} but + * allows you to specify the key and value in several parts that will be + * concatenated together. + * + * @param keyParts the specified key to be inserted. + * @param valueParts the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void putUntracked(final byte[][] keyParts, final byte[][] valueParts) + throws RocksDBException { + assert(isOwningHandle()); + putUntracked(nativeHandle_, keyParts, keyParts.length, valueParts, + valueParts.length); + } + + /** + * Similar to {@link RocksDB#merge(ColumnFamilyHandle, byte[], byte[])}, + * but operates on the transactions write batch. This write will only happen + * if this transaction gets committed successfully. + * + * Unlike {@link #merge(ColumnFamilyHandle, byte[], byte[])} no conflict + * checking will be performed for this key. + * + * If this Transaction was created on a {@link TransactionDB}, this function + * will still acquire locks necessary to make sure this write doesn't cause + * conflicts in other transactions; This may cause a {@link RocksDBException} + * with associated {@link Status.Code#Busy}. + * + * @param columnFamilyHandle The column family to merge the key/value into + * @param key the specified key to be merged. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void mergeUntracked(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key, final byte[] value) throws RocksDBException { + mergeUntracked(nativeHandle_, key, key.length, value, value.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * Similar to {@link RocksDB#merge(byte[], byte[])}, + * but operates on the transactions write batch. This write will only happen + * if this transaction gets committed successfully. + * + * Unlike {@link #merge(byte[], byte[])} no conflict + * checking will be performed for this key. + * + * If this Transaction was created on a {@link TransactionDB}, this function + * will still acquire locks necessary to make sure this write doesn't cause + * conflicts in other transactions; This may cause a {@link RocksDBException} + * with associated {@link Status.Code#Busy}. + * + * @param key the specified key to be merged. + * @param value the value associated with the specified key. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void mergeUntracked(final byte[] key, final byte[] value) + throws RocksDBException { + assert(isOwningHandle()); + mergeUntracked(nativeHandle_, key, key.length, value, value.length); + } + + /** + * Similar to {@link RocksDB#delete(ColumnFamilyHandle, byte[])}, + * but operates on the transactions write batch. This write will only happen + * if this transaction gets committed successfully. + * + * Unlike {@link #delete(ColumnFamilyHandle, byte[])} no conflict + * checking will be performed for this key. + * + * If this Transaction was created on a {@link TransactionDB}, this function + * will still acquire locks necessary to make sure this write doesn't cause + * conflicts in other transactions; This may cause a {@link RocksDBException} + * with associated {@link Status.Code#Busy}. + * + * @param columnFamilyHandle The column family to delete the key/value from + * @param key the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void deleteUntracked(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException { + assert(isOwningHandle()); + deleteUntracked(nativeHandle_, key, key.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * Similar to {@link RocksDB#delete(byte[])}, + * but operates on the transactions write batch. This write will only happen + * if this transaction gets committed successfully. + * + * Unlike {@link #delete(byte[])} no conflict + * checking will be performed for this key. + * + * If this Transaction was created on a {@link TransactionDB}, this function + * will still acquire locks necessary to make sure this write doesn't cause + * conflicts in other transactions; This may cause a {@link RocksDBException} + * with associated {@link Status.Code#Busy}. + * + * @param key the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void deleteUntracked(final byte[] key) throws RocksDBException { + assert(isOwningHandle()); + deleteUntracked(nativeHandle_, key, key.length); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #deleteUntracked(ColumnFamilyHandle, byte[])} but allows + * you to specify the key in several parts that will be + * concatenated together. + * + * @param columnFamilyHandle The column family to delete the key/value from + * @param keyParts the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void deleteUntracked(final ColumnFamilyHandle columnFamilyHandle, + final byte[][] keyParts) throws RocksDBException { + assert(isOwningHandle()); + deleteUntracked(nativeHandle_, keyParts, keyParts.length, + columnFamilyHandle.nativeHandle_); + } + + //TODO(AR) refactor if we implement org.rocksdb.SliceParts in future + /** + * Similar to {@link #deleteUntracked(byte[])} but allows + * you to specify the key in several parts that will be + * concatenated together. + * + * @param keyParts the specified key to be deleted. + * + * @throws RocksDBException when one of the TransactionalDB conditions + * described above occurs, or in the case of an unexpected error + */ + public void deleteUntracked(final byte[][] keyParts) throws RocksDBException { + assert(isOwningHandle()); + deleteUntracked(nativeHandle_, keyParts, keyParts.length); + } + + /** + * Similar to {@link WriteBatch#putLogData(byte[])} + * + * @param blob binary object to be inserted + */ + public void putLogData(final byte[] blob) { + assert(isOwningHandle()); + putLogData(nativeHandle_, blob, blob.length); + } + + /** + * By default, all put/merge/delete operations will be indexed in the + * transaction so that get/getForUpdate/getIterator can search for these + * keys. + * + * If the caller does not want to fetch the keys about to be written, + * they may want to avoid indexing as a performance optimization. + * Calling {@link #disableIndexing()} will turn off indexing for all future + * put/merge/delete operations until {@link #enableIndexing()} is called. + * + * If a key is put/merge/deleted after {@link #disableIndexing()} is called + * and then is fetched via get/getForUpdate/getIterator, the result of the + * fetch is undefined. + */ + public void disableIndexing() { + assert(isOwningHandle()); + disableIndexing(nativeHandle_); + } + + /** + * Re-enables indexing after a previous call to {@link #disableIndexing()} + */ + public void enableIndexing() { + assert(isOwningHandle()); + enableIndexing(nativeHandle_); + } + + /** + * Returns the number of distinct Keys being tracked by this transaction. + * If this transaction was created by a {@link TransactionDB}, this is the + * number of keys that are currently locked by this transaction. + * If this transaction was created by an {@link OptimisticTransactionDB}, + * this is the number of keys that need to be checked for conflicts at commit + * time. + * + * @return the number of distinct Keys being tracked by this transaction + */ + public long getNumKeys() { + assert(isOwningHandle()); + return getNumKeys(nativeHandle_); + } + + /** + * Returns the number of puts that have been applied to this + * transaction so far. + * + * @return the number of puts that have been applied to this transaction + */ + public long getNumPuts() { + assert(isOwningHandle()); + return getNumPuts(nativeHandle_); + } + + /** + * Returns the number of deletes that have been applied to this + * transaction so far. + * + * @return the number of deletes that have been applied to this transaction + */ + public long getNumDeletes() { + assert(isOwningHandle()); + return getNumDeletes(nativeHandle_); + } + + /** + * Returns the number of merges that have been applied to this + * transaction so far. + * + * @return the number of merges that have been applied to this transaction + */ + public long getNumMerges() { + assert(isOwningHandle()); + return getNumMerges(nativeHandle_); + } + + /** + * Returns the elapsed time in milliseconds since this Transaction began. + * + * @return the elapsed time in milliseconds since this transaction began. + */ + public long getElapsedTime() { + assert(isOwningHandle()); + return getElapsedTime(nativeHandle_); + } + + /** + * Fetch the underlying write batch that contains all pending changes to be + * committed. + * + * Note: You should not write or delete anything from the batch directly and + * should only use the functions in the {@link Transaction} class to + * write to this transaction. + * + * @return The write batch + */ + public WriteBatchWithIndex getWriteBatch() { + assert(isOwningHandle()); + final WriteBatchWithIndex writeBatchWithIndex = + new WriteBatchWithIndex(getWriteBatch(nativeHandle_)); + return writeBatchWithIndex; + } + + /** + * Change the value of {@link TransactionOptions#getLockTimeout()} + * (in milliseconds) for this transaction. + * + * Has no effect on OptimisticTransactions. + * + * @param lockTimeout the timeout (in milliseconds) for locks used by this + * transaction. + */ + public void setLockTimeout(final long lockTimeout) { + assert(isOwningHandle()); + setLockTimeout(nativeHandle_, lockTimeout); + } + + /** + * Return the WriteOptions that will be used during {@link #commit()}. + * + * @return the WriteOptions that will be used + */ + public WriteOptions getWriteOptions() { + assert(isOwningHandle()); + final WriteOptions writeOptions = + new WriteOptions(getWriteOptions(nativeHandle_)); + return writeOptions; + } + + /** + * Reset the WriteOptions that will be used during {@link #commit()}. + * + * @param writeOptions The new WriteOptions + */ + public void setWriteOptions(final WriteOptions writeOptions) { + assert(isOwningHandle()); + setWriteOptions(nativeHandle_, writeOptions.nativeHandle_); + } + + /** + * If this key was previously fetched in this transaction using + * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}/ + * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, calling + * {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} will tell + * the transaction that it no longer needs to do any conflict checking + * for this key. + * + * If a key has been fetched N times via + * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}/ + * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, then + * {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} will only have an + * effect if it is also called N times. If this key has been written to in + * this transaction, {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} + * will have no effect. + * + * If {@link #setSavePoint()} has been called after the + * {@link #getForUpdate(ReadOptions, ColumnFamilyHandle, byte[], boolean)}, + * {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} will not have any + * effect. + * + * If this Transaction was created by an {@link OptimisticTransactionDB}, + * calling {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} can affect + * whether this key is conflict checked at commit time. + * If this Transaction was created by a {@link TransactionDB}, + * calling {@link #undoGetForUpdate(ColumnFamilyHandle, byte[])} may release + * any held locks for this key. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the key to retrieve the value for. + */ + public void undoGetForUpdate(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) { + assert(isOwningHandle()); + undoGetForUpdate(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); + } + + /** + * If this key was previously fetched in this transaction using + * {@link #getForUpdate(ReadOptions, byte[], boolean)}/ + * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, calling + * {@link #undoGetForUpdate(byte[])} will tell + * the transaction that it no longer needs to do any conflict checking + * for this key. + * + * If a key has been fetched N times via + * {@link #getForUpdate(ReadOptions, byte[], boolean)}/ + * {@link #multiGetForUpdate(ReadOptions, List, byte[][])}, then + * {@link #undoGetForUpdate(byte[])} will only have an + * effect if it is also called N times. If this key has been written to in + * this transaction, {@link #undoGetForUpdate(byte[])} + * will have no effect. + * + * If {@link #setSavePoint()} has been called after the + * {@link #getForUpdate(ReadOptions, byte[], boolean)}, + * {@link #undoGetForUpdate(byte[])} will not have any + * effect. + * + * If this Transaction was created by an {@link OptimisticTransactionDB}, + * calling {@link #undoGetForUpdate(byte[])} can affect + * whether this key is conflict checked at commit time. + * If this Transaction was created by a {@link TransactionDB}, + * calling {@link #undoGetForUpdate(byte[])} may release + * any held locks for this key. + * + * @param key the key to retrieve the value for. + */ + public void undoGetForUpdate(final byte[] key) { + assert(isOwningHandle()); + undoGetForUpdate(nativeHandle_, key, key.length); + } + + /** + * Adds the keys from the WriteBatch to the transaction + * + * @param writeBatch The write batch to read from + * + * @throws RocksDBException if an error occurs whilst rebuilding from the + * write batch. + */ + public void rebuildFromWriteBatch(final WriteBatch writeBatch) + throws RocksDBException { + assert(isOwningHandle()); + rebuildFromWriteBatch(nativeHandle_, writeBatch.nativeHandle_); + } + + /** + * Get the Commit time Write Batch. + * + * @return the commit time write batch. + */ + public WriteBatch getCommitTimeWriteBatch() { + assert(isOwningHandle()); + final WriteBatch writeBatch = + new WriteBatch(getCommitTimeWriteBatch(nativeHandle_)); + return writeBatch; + } + + /** + * Set the log number. + * + * @param logNumber the log number + */ + public void setLogNumber(final long logNumber) { + assert(isOwningHandle()); + setLogNumber(nativeHandle_, logNumber); + } + + /** + * Get the log number. + * + * @return the log number + */ + public long getLogNumber() { + assert(isOwningHandle()); + return getLogNumber(nativeHandle_); + } + + /** + * Set the name of the transaction. + * + * @param transactionName the name of the transaction + * + * @throws RocksDBException if an error occurs when setting the transaction + * name. + */ + public void setName(final String transactionName) throws RocksDBException { + assert(isOwningHandle()); + setName(nativeHandle_, transactionName); + } + + /** + * Get the name of the transaction. + * + * @return the name of the transaction + */ + public String getName() { + assert(isOwningHandle()); + return getName(nativeHandle_); + } + + /** + * Get the ID of the transaction. + * + * @return the ID of the transaction. + */ + public long getID() { + assert(isOwningHandle()); + return getID(nativeHandle_); + } + + /** + * Determine if a deadlock has been detected. + * + * @return true if a deadlock has been detected. + */ + public boolean isDeadlockDetect() { + assert(isOwningHandle()); + return isDeadlockDetect(nativeHandle_); + } + + /** + * Get the list of waiting transactions. + * + * @return The list of waiting transactions. + */ + public WaitingTransactions getWaitingTxns() { + assert(isOwningHandle()); + return getWaitingTxns(nativeHandle_); + } + + /** + * Get the execution status of the transaction. + * + * NOTE: The execution status of an Optimistic Transaction + * never changes. This is only useful for non-optimistic transactions! + * + * @return The execution status of the transaction + */ + public TransactionState getState() { + assert(isOwningHandle()); + return TransactionState.getTransactionState( + getState(nativeHandle_)); + } + + /** + * The globally unique id with which the transaction is identified. This id + * might or might not be set depending on the implementation. Similarly the + * implementation decides the point in lifetime of a transaction at which it + * assigns the id. Although currently it is the case, the id is not guaranteed + * to remain the same across restarts. + * + * @return the transaction id. + */ + @Experimental("NOTE: Experimental feature") + public long getId() { + assert(isOwningHandle()); + return getId(nativeHandle_); + } + + public enum TransactionState { + STARTED((byte)0), + AWAITING_PREPARE((byte)1), + PREPARED((byte)2), + AWAITING_COMMIT((byte)3), + COMMITED((byte)4), + AWAITING_ROLLBACK((byte)5), + ROLLEDBACK((byte)6), + LOCKS_STOLEN((byte)7); + + private final byte value; + + TransactionState(final byte value) { + this.value = value; + } + + /** + * Get TransactionState by byte value. + * + * @param value byte representation of TransactionState. + * + * @return {@link org.rocksdb.Transaction.TransactionState} instance or null. + * @throws java.lang.IllegalArgumentException if an invalid + * value is provided. + */ + public static TransactionState getTransactionState(final byte value) { + for (final TransactionState transactionState : TransactionState.values()) { + if (transactionState.value == value){ + return transactionState; + } + } + throw new IllegalArgumentException( + "Illegal value provided for TransactionState."); + } + } + + /** + * Called from C++ native method {@link #getWaitingTxns(long)} + * to construct a WaitingTransactions object. + * + * @param columnFamilyId The id of the {@link ColumnFamilyHandle} + * @param key The key + * @param transactionIds The transaction ids + * + * @return The waiting transactions + */ + private WaitingTransactions newWaitingTransactions( + final long columnFamilyId, final String key, + final long[] transactionIds) { + return new WaitingTransactions(columnFamilyId, key, transactionIds); + } + + public static class WaitingTransactions { + private final long columnFamilyId; + private final String key; + private final long[] transactionIds; + + private WaitingTransactions(final long columnFamilyId, final String key, + final long[] transactionIds) { + this.columnFamilyId = columnFamilyId; + this.key = key; + this.transactionIds = transactionIds; + } + + /** + * Get the Column Family ID. + * + * @return The column family ID + */ + public long getColumnFamilyId() { + return columnFamilyId; + } + + /** + * Get the key on which the transactions are waiting. + * + * @return The key + */ + public String getKey() { + return key; + } + + /** + * Get the IDs of the waiting transactions. + * + * @return The IDs of the waiting transactions + */ + public long[] getTransactionIds() { + return transactionIds; + } + } + + private native void setSnapshot(final long handle); + private native void setSnapshotOnNextOperation(final long handle); + private native void setSnapshotOnNextOperation(final long handle, + final long transactionNotifierHandle); + private native long getSnapshot(final long handle); + private native void clearSnapshot(final long handle); + private native void prepare(final long handle) throws RocksDBException; + private native void commit(final long handle) throws RocksDBException; + private native void rollback(final long handle) throws RocksDBException; + private native void setSavePoint(final long handle) throws RocksDBException; + private native void rollbackToSavePoint(final long handle) + throws RocksDBException; + private native byte[] get(final long handle, final long readOptionsHandle, + final byte key[], final int keyLength, final long columnFamilyHandle) + throws RocksDBException; + private native byte[] get(final long handle, final long readOptionsHandle, + final byte key[], final int keyLen) throws RocksDBException; + private native byte[][] multiGet(final long handle, + final long readOptionsHandle, final byte[][] keys, + final long[] columnFamilyHandles) throws RocksDBException; + private native byte[][] multiGet(final long handle, + final long readOptionsHandle, final byte[][] keys) + throws RocksDBException; + private native byte[] getForUpdate(final long handle, final long readOptionsHandle, + final byte key[], final int keyLength, final long columnFamilyHandle, final boolean exclusive, + final boolean do_validate) throws RocksDBException; + private native byte[] getForUpdate(final long handle, final long readOptionsHandle, + final byte key[], final int keyLen, final boolean exclusive, final boolean do_validate) + throws RocksDBException; + private native byte[][] multiGetForUpdate(final long handle, + final long readOptionsHandle, final byte[][] keys, + final long[] columnFamilyHandles) throws RocksDBException; + private native byte[][] multiGetForUpdate(final long handle, + final long readOptionsHandle, final byte[][] keys) + throws RocksDBException; + private native long getIterator(final long handle, + final long readOptionsHandle); + private native long getIterator(final long handle, + final long readOptionsHandle, final long columnFamilyHandle); + private native void put(final long handle, final byte[] key, final int keyLength, + final byte[] value, final int valueLength, final long columnFamilyHandle, + final boolean assume_tracked) throws RocksDBException; + private native void put(final long handle, final byte[] key, + final int keyLength, final byte[] value, final int valueLength) + throws RocksDBException; + private native void put(final long handle, final byte[][] keys, final int keysLength, + final byte[][] values, final int valuesLength, final long columnFamilyHandle, + final boolean assume_tracked) throws RocksDBException; + private native void put(final long handle, final byte[][] keys, + final int keysLength, final byte[][] values, final int valuesLength) + throws RocksDBException; + private native void merge(final long handle, final byte[] key, final int keyLength, + final byte[] value, final int valueLength, final long columnFamilyHandle, + final boolean assume_tracked) throws RocksDBException; + private native void merge(final long handle, final byte[] key, + final int keyLength, final byte[] value, final int valueLength) + throws RocksDBException; + private native void delete(final long handle, final byte[] key, final int keyLength, + final long columnFamilyHandle, final boolean assume_tracked) throws RocksDBException; + private native void delete(final long handle, final byte[] key, + final int keyLength) throws RocksDBException; + private native void delete(final long handle, final byte[][] keys, final int keysLength, + final long columnFamilyHandle, final boolean assume_tracked) throws RocksDBException; + private native void delete(final long handle, final byte[][] keys, + final int keysLength) throws RocksDBException; + private native void singleDelete(final long handle, final byte[] key, final int keyLength, + final long columnFamilyHandle, final boolean assume_tracked) throws RocksDBException; + private native void singleDelete(final long handle, final byte[] key, + final int keyLength) throws RocksDBException; + private native void singleDelete(final long handle, final byte[][] keys, final int keysLength, + final long columnFamilyHandle, final boolean assume_tracked) throws RocksDBException; + private native void singleDelete(final long handle, final byte[][] keys, + final int keysLength) throws RocksDBException; + private native void putUntracked(final long handle, final byte[] key, + final int keyLength, final byte[] value, final int valueLength, + final long columnFamilyHandle) throws RocksDBException; + private native void putUntracked(final long handle, final byte[] key, + final int keyLength, final byte[] value, final int valueLength) + throws RocksDBException; + private native void putUntracked(final long handle, final byte[][] keys, + final int keysLength, final byte[][] values, final int valuesLength, + final long columnFamilyHandle) throws RocksDBException; + private native void putUntracked(final long handle, final byte[][] keys, + final int keysLength, final byte[][] values, final int valuesLength) + throws RocksDBException; + private native void mergeUntracked(final long handle, final byte[] key, + final int keyLength, final byte[] value, final int valueLength, + final long columnFamilyHandle) throws RocksDBException; + private native void mergeUntracked(final long handle, final byte[] key, + final int keyLength, final byte[] value, final int valueLength) + throws RocksDBException; + private native void deleteUntracked(final long handle, final byte[] key, + final int keyLength, final long columnFamilyHandle) + throws RocksDBException; + private native void deleteUntracked(final long handle, final byte[] key, + final int keyLength) throws RocksDBException; + private native void deleteUntracked(final long handle, final byte[][] keys, + final int keysLength, final long columnFamilyHandle) + throws RocksDBException; + private native void deleteUntracked(final long handle, final byte[][] keys, + final int keysLength) throws RocksDBException; + private native void putLogData(final long handle, final byte[] blob, + final int blobLength); + private native void disableIndexing(final long handle); + private native void enableIndexing(final long handle); + private native long getNumKeys(final long handle); + private native long getNumPuts(final long handle); + private native long getNumDeletes(final long handle); + private native long getNumMerges(final long handle); + private native long getElapsedTime(final long handle); + private native long getWriteBatch(final long handle); + private native void setLockTimeout(final long handle, final long lockTimeout); + private native long getWriteOptions(final long handle); + private native void setWriteOptions(final long handle, + final long writeOptionsHandle); + private native void undoGetForUpdate(final long handle, final byte[] key, + final int keyLength, final long columnFamilyHandle); + private native void undoGetForUpdate(final long handle, final byte[] key, + final int keyLength); + private native void rebuildFromWriteBatch(final long handle, + final long writeBatchHandle) throws RocksDBException; + private native long getCommitTimeWriteBatch(final long handle); + private native void setLogNumber(final long handle, final long logNumber); + private native long getLogNumber(final long handle); + private native void setName(final long handle, final String name) + throws RocksDBException; + private native String getName(final long handle); + private native long getID(final long handle); + private native boolean isDeadlockDetect(final long handle); + private native WaitingTransactions getWaitingTxns(final long handle); + private native byte getState(final long handle); + private native long getId(final long handle); + + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionDB.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionDB.java new file mode 100644 index 0000000000..a1a09cf963 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionDB.java @@ -0,0 +1,404 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Database with Transaction support + */ +public class TransactionDB extends RocksDB + implements TransactionalDB { + + private TransactionDBOptions transactionDbOptions_; + + /** + * Private constructor. + * + * @param nativeHandle The native handle of the C++ TransactionDB object + */ + private TransactionDB(final long nativeHandle) { + super(nativeHandle); + } + + /** + * Open a TransactionDB, similar to {@link RocksDB#open(Options, String)}. + * + * @param options {@link org.rocksdb.Options} instance. + * @param transactionDbOptions {@link org.rocksdb.TransactionDBOptions} + * instance. + * @param path the path to the rocksdb. + * + * @return a {@link TransactionDB} instance on success, null if the specified + * {@link TransactionDB} can not be opened. + * + * @throws RocksDBException if an error occurs whilst opening the database. + */ + public static TransactionDB open(final Options options, + final TransactionDBOptions transactionDbOptions, final String path) + throws RocksDBException { + final TransactionDB tdb = new TransactionDB(open(options.nativeHandle_, + transactionDbOptions.nativeHandle_, path)); + + // when non-default Options is used, keeping an Options reference + // in RocksDB can prevent Java to GC during the life-time of + // the currently-created RocksDB. + tdb.storeOptionsInstance(options); + tdb.storeTransactionDbOptions(transactionDbOptions); + + return tdb; + } + + /** + * Open a TransactionDB, similar to + * {@link RocksDB#open(DBOptions, String, List, List)}. + * + * @param dbOptions {@link org.rocksdb.DBOptions} instance. + * @param transactionDbOptions {@link org.rocksdb.TransactionDBOptions} + * instance. + * @param path the path to the rocksdb. + * @param columnFamilyDescriptors list of column family descriptors + * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances + * + * @return a {@link TransactionDB} instance on success, null if the specified + * {@link TransactionDB} can not be opened. + * + * @throws RocksDBException if an error occurs whilst opening the database. + */ + public static TransactionDB open(final DBOptions dbOptions, + final TransactionDBOptions transactionDbOptions, + final String path, + final List columnFamilyDescriptors, + final List columnFamilyHandles) + throws RocksDBException { + + final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; + final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; + for (int i = 0; i < columnFamilyDescriptors.size(); i++) { + final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors + .get(i); + cfNames[i] = cfDescriptor.columnFamilyName(); + cfOptionHandles[i] = cfDescriptor.columnFamilyOptions().nativeHandle_; + } + + final long[] handles = open(dbOptions.nativeHandle_, + transactionDbOptions.nativeHandle_, path, cfNames, cfOptionHandles); + final TransactionDB tdb = new TransactionDB(handles[0]); + + // when non-default Options is used, keeping an Options reference + // in RocksDB can prevent Java to GC during the life-time of + // the currently-created RocksDB. + tdb.storeOptionsInstance(dbOptions); + tdb.storeTransactionDbOptions(transactionDbOptions); + + for (int i = 1; i < handles.length; i++) { + columnFamilyHandles.add(new ColumnFamilyHandle(tdb, handles[i])); + } + + return tdb; + } + + /** + * This is similar to {@link #close()} except that it + * throws an exception if any error occurs. + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + * + * @throws RocksDBException if an error occurs whilst closing. + */ + public void closeE() throws RocksDBException { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } finally { + disposeInternal(); + } + } + } + + /** + * This is similar to {@link #closeE()} except that it + * silently ignores any errors. + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + */ + @Override + public void close() { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } catch (final RocksDBException e) { + // silently ignore the error report + } finally { + disposeInternal(); + } + } + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions) { + return new Transaction(this, beginTransaction(nativeHandle_, + writeOptions.nativeHandle_)); + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions, + final TransactionOptions transactionOptions) { + return new Transaction(this, beginTransaction(nativeHandle_, + writeOptions.nativeHandle_, transactionOptions.nativeHandle_)); + } + + // TODO(AR) consider having beingTransaction(... oldTransaction) set a + // reference count inside Transaction, so that we can always call + // Transaction#close but the object is only disposed when there are as many + // closes as beginTransaction. Makes the try-with-resources paradigm easier for + // java developers + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions, + final Transaction oldTransaction) { + final long jtxnHandle = beginTransaction_withOld(nativeHandle_, + writeOptions.nativeHandle_, oldTransaction.nativeHandle_); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_txn + assert(jtxnHandle == oldTransaction.nativeHandle_); + + return oldTransaction; + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions, + final TransactionOptions transactionOptions, + final Transaction oldTransaction) { + final long jtxn_handle = beginTransaction_withOld(nativeHandle_, + writeOptions.nativeHandle_, transactionOptions.nativeHandle_, + oldTransaction.nativeHandle_); + + // RocksJava relies on the assumption that + // we do not allocate a new Transaction object + // when providing an old_txn + assert(jtxn_handle == oldTransaction.nativeHandle_); + + return oldTransaction; + } + + public Transaction getTransactionByName(final String transactionName) { + final long jtxnHandle = getTransactionByName(nativeHandle_, transactionName); + if(jtxnHandle == 0) { + return null; + } + + final Transaction txn = new Transaction(this, jtxnHandle); + + // this instance doesn't own the underlying C++ object + txn.disOwnNativeHandle(); + + return txn; + } + + public List getAllPreparedTransactions() { + final long[] jtxnHandles = getAllPreparedTransactions(nativeHandle_); + + final List txns = new ArrayList<>(); + for(final long jtxnHandle : jtxnHandles) { + final Transaction txn = new Transaction(this, jtxnHandle); + + // this instance doesn't own the underlying C++ object + txn.disOwnNativeHandle(); + + txns.add(txn); + } + return txns; + } + + public static class KeyLockInfo { + private final String key; + private final long[] transactionIDs; + private final boolean exclusive; + + public KeyLockInfo(final String key, final long transactionIDs[], + final boolean exclusive) { + this.key = key; + this.transactionIDs = transactionIDs; + this.exclusive = exclusive; + } + + /** + * Get the key. + * + * @return the key + */ + public String getKey() { + return key; + } + + /** + * Get the Transaction IDs. + * + * @return the Transaction IDs. + */ + public long[] getTransactionIDs() { + return transactionIDs; + } + + /** + * Get the Lock status. + * + * @return true if the lock is exclusive, false if the lock is shared. + */ + public boolean isExclusive() { + return exclusive; + } + } + + /** + * Returns map of all locks held. + * + * @return a map of all the locks held. + */ + public Map getLockStatusData() { + return getLockStatusData(nativeHandle_); + } + + /** + * Called from C++ native method {@link #getDeadlockInfoBuffer(long)} + * to construct a DeadlockInfo object. + * + * @param transactionID The transaction id + * @param columnFamilyId The id of the {@link ColumnFamilyHandle} + * @param waitingKey the key that we are waiting on + * @param exclusive true if the lock is exclusive, false if the lock is shared + * + * @return The waiting transactions + */ + private DeadlockInfo newDeadlockInfo( + final long transactionID, final long columnFamilyId, + final String waitingKey, final boolean exclusive) { + return new DeadlockInfo(transactionID, columnFamilyId, + waitingKey, exclusive); + } + + public static class DeadlockInfo { + private final long transactionID; + private final long columnFamilyId; + private final String waitingKey; + private final boolean exclusive; + + private DeadlockInfo(final long transactionID, final long columnFamilyId, + final String waitingKey, final boolean exclusive) { + this.transactionID = transactionID; + this.columnFamilyId = columnFamilyId; + this.waitingKey = waitingKey; + this.exclusive = exclusive; + } + + /** + * Get the Transaction ID. + * + * @return the transaction ID + */ + public long getTransactionID() { + return transactionID; + } + + /** + * Get the Column Family ID. + * + * @return The column family ID + */ + public long getColumnFamilyId() { + return columnFamilyId; + } + + /** + * Get the key that we are waiting on. + * + * @return the key that we are waiting on + */ + public String getWaitingKey() { + return waitingKey; + } + + /** + * Get the Lock status. + * + * @return true if the lock is exclusive, false if the lock is shared. + */ + public boolean isExclusive() { + return exclusive; + } + } + + public static class DeadlockPath { + final DeadlockInfo[] path; + final boolean limitExceeded; + + public DeadlockPath(final DeadlockInfo[] path, final boolean limitExceeded) { + this.path = path; + this.limitExceeded = limitExceeded; + } + + public boolean isEmpty() { + return path.length == 0 && !limitExceeded; + } + } + + public DeadlockPath[] getDeadlockInfoBuffer() { + return getDeadlockInfoBuffer(nativeHandle_); + } + + public void setDeadlockInfoBufferSize(final int targetSize) { + setDeadlockInfoBufferSize(nativeHandle_, targetSize); + } + + private void storeTransactionDbOptions( + final TransactionDBOptions transactionDbOptions) { + this.transactionDbOptions_ = transactionDbOptions; + } + + @Override protected final native void disposeInternal(final long handle); + + private static native long open(final long optionsHandle, + final long transactionDbOptionsHandle, final String path) + throws RocksDBException; + private static native long[] open(final long dbOptionsHandle, + final long transactionDbOptionsHandle, final String path, + final byte[][] columnFamilyNames, final long[] columnFamilyOptions); + private native static void closeDatabase(final long handle) + throws RocksDBException; + private native long beginTransaction(final long handle, + final long writeOptionsHandle); + private native long beginTransaction(final long handle, + final long writeOptionsHandle, final long transactionOptionsHandle); + private native long beginTransaction_withOld(final long handle, + final long writeOptionsHandle, final long oldTransactionHandle); + private native long beginTransaction_withOld(final long handle, + final long writeOptionsHandle, final long transactionOptionsHandle, + final long oldTransactionHandle); + private native long getTransactionByName(final long handle, + final String name); + private native long[] getAllPreparedTransactions(final long handle); + private native Map getLockStatusData( + final long handle); + private native DeadlockPath[] getDeadlockInfoBuffer(final long handle); + private native void setDeadlockInfoBufferSize(final long handle, + final int targetSize); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionDBOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionDBOptions.java new file mode 100644 index 0000000000..76f545cde6 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionDBOptions.java @@ -0,0 +1,217 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public class TransactionDBOptions extends RocksObject { + + public TransactionDBOptions() { + super(newTransactionDBOptions()); + } + + /** + * Specifies the maximum number of keys that can be locked at the same time + * per column family. + * + * If the number of locked keys is greater than {@link #getMaxNumLocks()}, + * transaction writes (or GetForUpdate) will return an error. + * + * @return The maximum number of keys that can be locked + */ + public long getMaxNumLocks() { + assert(isOwningHandle()); + return getMaxNumLocks(nativeHandle_); + } + + /** + * Specifies the maximum number of keys that can be locked at the same time + * per column family. + * + * If the number of locked keys is greater than {@link #getMaxNumLocks()}, + * transaction writes (or GetForUpdate) will return an error. + * + * @param maxNumLocks The maximum number of keys that can be locked; + * If this value is not positive, no limit will be enforced. + * + * @return this TransactionDBOptions instance + */ + public TransactionDBOptions setMaxNumLocks(final long maxNumLocks) { + assert(isOwningHandle()); + setMaxNumLocks(nativeHandle_, maxNumLocks); + return this; + } + + /** + * The number of sub-tables per lock table (per column family) + * + * @return The number of sub-tables + */ + public long getNumStripes() { + assert(isOwningHandle()); + return getNumStripes(nativeHandle_); + } + + /** + * Increasing this value will increase the concurrency by dividing the lock + * table (per column family) into more sub-tables, each with their own + * separate mutex. + * + * Default: 16 + * + * @param numStripes The number of sub-tables + * + * @return this TransactionDBOptions instance + */ + public TransactionDBOptions setNumStripes(final long numStripes) { + assert(isOwningHandle()); + setNumStripes(nativeHandle_, numStripes); + return this; + } + + /** + * The default wait timeout in milliseconds when + * a transaction attempts to lock a key if not specified by + * {@link TransactionOptions#setLockTimeout(long)} + * + * If 0, no waiting is done if a lock cannot instantly be acquired. + * If negative, there is no timeout. + * + * @return the default wait timeout in milliseconds + */ + public long getTransactionLockTimeout() { + assert(isOwningHandle()); + return getTransactionLockTimeout(nativeHandle_); + } + + /** + * If positive, specifies the default wait timeout in milliseconds when + * a transaction attempts to lock a key if not specified by + * {@link TransactionOptions#setLockTimeout(long)} + * + * If 0, no waiting is done if a lock cannot instantly be acquired. + * If negative, there is no timeout. Not using a timeout is not recommended + * as it can lead to deadlocks. Currently, there is no deadlock-detection to + * recover from a deadlock. + * + * Default: 1000 + * + * @param transactionLockTimeout the default wait timeout in milliseconds + * + * @return this TransactionDBOptions instance + */ + public TransactionDBOptions setTransactionLockTimeout( + final long transactionLockTimeout) { + assert(isOwningHandle()); + setTransactionLockTimeout(nativeHandle_, transactionLockTimeout); + return this; + } + + /** + * The wait timeout in milliseconds when writing a key + * OUTSIDE of a transaction (ie by calling {@link RocksDB#put}, + * {@link RocksDB#merge}, {@link RocksDB#remove} or {@link RocksDB#write} + * directly). + * + * If 0, no waiting is done if a lock cannot instantly be acquired. + * If negative, there is no timeout and will block indefinitely when acquiring + * a lock. + * + * @return the timeout in milliseconds when writing a key OUTSIDE of a + * transaction + */ + public long getDefaultLockTimeout() { + assert(isOwningHandle()); + return getDefaultLockTimeout(nativeHandle_); + } + + /** + * If positive, specifies the wait timeout in milliseconds when writing a key + * OUTSIDE of a transaction (ie by calling {@link RocksDB#put}, + * {@link RocksDB#merge}, {@link RocksDB#remove} or {@link RocksDB#write} + * directly). + * + * If 0, no waiting is done if a lock cannot instantly be acquired. + * If negative, there is no timeout and will block indefinitely when acquiring + * a lock. + * + * Not using a timeout can lead to deadlocks. Currently, there + * is no deadlock-detection to recover from a deadlock. While DB writes + * cannot deadlock with other DB writes, they can deadlock with a transaction. + * A negative timeout should only be used if all transactions have a small + * expiration set. + * + * Default: 1000 + * + * @param defaultLockTimeout the timeout in milliseconds when writing a key + * OUTSIDE of a transaction + * @return this TransactionDBOptions instance + */ + public TransactionDBOptions setDefaultLockTimeout( + final long defaultLockTimeout) { + assert(isOwningHandle()); + setDefaultLockTimeout(nativeHandle_, defaultLockTimeout); + return this; + } + +// /** +// * If set, the {@link TransactionDB} will use this implementation of a mutex +// * and condition variable for all transaction locking instead of the default +// * mutex/condvar implementation. +// * +// * @param transactionDbMutexFactory the mutex factory for the transactions +// * +// * @return this TransactionDBOptions instance +// */ +// public TransactionDBOptions setCustomMutexFactory( +// final TransactionDBMutexFactory transactionDbMutexFactory) { +// +// } + + /** + * The policy for when to write the data into the DB. The default policy is to + * write only the committed data {@link TxnDBWritePolicy#WRITE_COMMITTED}. + * The data could be written before the commit phase. The DB then needs to + * provide the mechanisms to tell apart committed from uncommitted data. + * + * @return The write policy. + */ + public TxnDBWritePolicy getWritePolicy() { + assert(isOwningHandle()); + return TxnDBWritePolicy.getTxnDBWritePolicy(getWritePolicy(nativeHandle_)); + } + + /** + * The policy for when to write the data into the DB. The default policy is to + * write only the committed data {@link TxnDBWritePolicy#WRITE_COMMITTED}. + * The data could be written before the commit phase. The DB then needs to + * provide the mechanisms to tell apart committed from uncommitted data. + * + * @param writePolicy The write policy. + * + * @return this TransactionDBOptions instance + */ + public TransactionDBOptions setWritePolicy( + final TxnDBWritePolicy writePolicy) { + assert(isOwningHandle()); + setWritePolicy(nativeHandle_, writePolicy.getValue()); + return this; + } + + private native static long newTransactionDBOptions(); + private native long getMaxNumLocks(final long handle); + private native void setMaxNumLocks(final long handle, + final long maxNumLocks); + private native long getNumStripes(final long handle); + private native void setNumStripes(final long handle, final long numStripes); + private native long getTransactionLockTimeout(final long handle); + private native void setTransactionLockTimeout(final long handle, + final long transactionLockTimeout); + private native long getDefaultLockTimeout(final long handle); + private native void setDefaultLockTimeout(final long handle, + final long transactionLockTimeout); + private native byte getWritePolicy(final long handle); + private native void setWritePolicy(final long handle, final byte writePolicy); + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionOptions.java new file mode 100644 index 0000000000..1cd936ae64 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionOptions.java @@ -0,0 +1,189 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public class TransactionOptions extends RocksObject + implements TransactionalOptions { + + public TransactionOptions() { + super(newTransactionOptions()); + } + + @Override + public boolean isSetSnapshot() { + assert(isOwningHandle()); + return isSetSnapshot(nativeHandle_); + } + + @Override + public TransactionOptions setSetSnapshot(final boolean setSnapshot) { + assert(isOwningHandle()); + setSetSnapshot(nativeHandle_, setSnapshot); + return this; + } + + /** + * True means that before acquiring locks, this transaction will + * check if doing so will cause a deadlock. If so, it will return with + * {@link Status.Code#Busy}. The user should retry their transaction. + * + * @return true if a deadlock is detected. + */ + public boolean isDeadlockDetect() { + assert(isOwningHandle()); + return isDeadlockDetect(nativeHandle_); + } + + /** + * Setting to true means that before acquiring locks, this transaction will + * check if doing so will cause a deadlock. If so, it will return with + * {@link Status.Code#Busy}. The user should retry their transaction. + * + * @param deadlockDetect true if we should detect deadlocks. + * + * @return this TransactionOptions instance + */ + public TransactionOptions setDeadlockDetect(final boolean deadlockDetect) { + assert(isOwningHandle()); + setDeadlockDetect(nativeHandle_, deadlockDetect); + return this; + } + + /** + * The wait timeout in milliseconds when a transaction attempts to lock a key. + * + * If 0, no waiting is done if a lock cannot instantly be acquired. + * If negative, {@link TransactionDBOptions#getTransactionLockTimeout(long)} + * will be used + * + * @return the lock timeout in milliseconds + */ + public long getLockTimeout() { + assert(isOwningHandle()); + return getLockTimeout(nativeHandle_); + } + + /** + * If positive, specifies the wait timeout in milliseconds when + * a transaction attempts to lock a key. + * + * If 0, no waiting is done if a lock cannot instantly be acquired. + * If negative, {@link TransactionDBOptions#getTransactionLockTimeout(long)} + * will be used + * + * Default: -1 + * + * @param lockTimeout the lock timeout in milliseconds + * + * @return this TransactionOptions instance + */ + public TransactionOptions setLockTimeout(final long lockTimeout) { + assert(isOwningHandle()); + setLockTimeout(nativeHandle_, lockTimeout); + return this; + } + + /** + * Expiration duration in milliseconds. + * + * If non-negative, transactions that last longer than this many milliseconds + * will fail to commit. If not set, a forgotten transaction that is never + * committed, rolled back, or deleted will never relinquish any locks it + * holds. This could prevent keys from being written by other writers. + * + * @return expiration the expiration duration in milliseconds + */ + public long getExpiration() { + assert(isOwningHandle()); + return getExpiration(nativeHandle_); + } + + /** + * Expiration duration in milliseconds. + * + * If non-negative, transactions that last longer than this many milliseconds + * will fail to commit. If not set, a forgotten transaction that is never + * committed, rolled back, or deleted will never relinquish any locks it + * holds. This could prevent keys from being written by other writers. + * + * Default: -1 + * + * @param expiration the expiration duration in milliseconds + * + * @return this TransactionOptions instance + */ + public TransactionOptions setExpiration(final long expiration) { + assert(isOwningHandle()); + setExpiration(nativeHandle_, expiration); + return this; + } + + /** + * Gets the number of traversals to make during deadlock detection. + * + * @return the number of traversals to make during + * deadlock detection + */ + public long getDeadlockDetectDepth() { + return getDeadlockDetectDepth(nativeHandle_); + } + + /** + * Sets the number of traversals to make during deadlock detection. + * + * Default: 50 + * + * @param deadlockDetectDepth the number of traversals to make during + * deadlock detection + * + * @return this TransactionOptions instance + */ + public TransactionOptions setDeadlockDetectDepth( + final long deadlockDetectDepth) { + setDeadlockDetectDepth(nativeHandle_, deadlockDetectDepth); + return this; + } + + /** + * Get the maximum number of bytes that may be used for the write batch. + * + * @return the maximum number of bytes, 0 means no limit. + */ + public long getMaxWriteBatchSize() { + return getMaxWriteBatchSize(nativeHandle_); + } + + /** + * Set the maximum number of bytes that may be used for the write batch. + * + * @param maxWriteBatchSize the maximum number of bytes, 0 means no limit. + * + * @return this TransactionOptions instance + */ + public TransactionOptions setMaxWriteBatchSize(final long maxWriteBatchSize) { + setMaxWriteBatchSize(nativeHandle_, maxWriteBatchSize); + return this; + } + + private native static long newTransactionOptions(); + private native boolean isSetSnapshot(final long handle); + private native void setSetSnapshot(final long handle, + final boolean setSnapshot); + private native boolean isDeadlockDetect(final long handle); + private native void setDeadlockDetect(final long handle, + final boolean deadlockDetect); + private native long getLockTimeout(final long handle); + private native void setLockTimeout(final long handle, final long lockTimeout); + private native long getExpiration(final long handle); + private native void setExpiration(final long handle, final long expiration); + private native long getDeadlockDetectDepth(final long handle); + private native void setDeadlockDetectDepth(final long handle, + final long deadlockDetectDepth); + private native long getMaxWriteBatchSize(final long handle); + private native void setMaxWriteBatchSize(final long handle, + final long maxWriteBatchSize); + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionalDB.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionalDB.java new file mode 100644 index 0000000000..3f0eceda85 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionalDB.java @@ -0,0 +1,68 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + + +interface TransactionalDB + extends AutoCloseable { + + /** + * Starts a new Transaction. + * + * Caller is responsible for calling {@link #close()} on the returned + * transaction when it is no longer needed. + * + * @param writeOptions Any write options for the transaction + * @return a new transaction + */ + Transaction beginTransaction(final WriteOptions writeOptions); + + /** + * Starts a new Transaction. + * + * Caller is responsible for calling {@link #close()} on the returned + * transaction when it is no longer needed. + * + * @param writeOptions Any write options for the transaction + * @param transactionOptions Any options for the transaction + * @return a new transaction + */ + Transaction beginTransaction(final WriteOptions writeOptions, + final T transactionOptions); + + /** + * Starts a new Transaction. + * + * Caller is responsible for calling {@link #close()} on the returned + * transaction when it is no longer needed. + * + * @param writeOptions Any write options for the transaction + * @param oldTransaction this Transaction will be reused instead of allocating + * a new one. This is an optimization to avoid extra allocations + * when repeatedly creating transactions. + * @return The oldTransaction which has been reinitialized as a new + * transaction + */ + Transaction beginTransaction(final WriteOptions writeOptions, + final Transaction oldTransaction); + + /** + * Starts a new Transaction. + * + * Caller is responsible for calling {@link #close()} on the returned + * transaction when it is no longer needed. + * + * @param writeOptions Any write options for the transaction + * @param transactionOptions Any options for the transaction + * @param oldTransaction this Transaction will be reused instead of allocating + * a new one. This is an optimization to avoid extra allocations + * when repeatedly creating transactions. + * @return The oldTransaction which has been reinitialized as a new + * transaction + */ + Transaction beginTransaction(final WriteOptions writeOptions, + final T transactionOptions, final Transaction oldTransaction); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionalOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionalOptions.java new file mode 100644 index 0000000000..87aaa7986f --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TransactionalOptions.java @@ -0,0 +1,31 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + + +interface TransactionalOptions extends AutoCloseable { + + /** + * True indicates snapshots will be set, just like if + * {@link Transaction#setSnapshot()} had been called + * + * @return whether a snapshot will be set + */ + boolean isSetSnapshot(); + + /** + * Setting the setSnapshot to true is the same as calling + * {@link Transaction#setSnapshot()}. + * + * Default: false + * + * @param The type of transactional options. + * @param setSnapshot Whether to set a snapshot + * + * @return this TransactionalOptions instance + */ + T setSetSnapshot(final boolean setSnapshot); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java index 740f51268e..26eee4a878 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TtlDB.java @@ -139,6 +139,55 @@ public static TtlDB open(final DBOptions options, final String db_path, return ttlDB; } + /** + *

    Close the TtlDB instance and release resource.

    + * + * This is similar to {@link #close()} except that it + * throws an exception if any error occurs. + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + * + * @throws RocksDBException if an error occurs whilst closing. + */ + public void closeE() throws RocksDBException { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } finally { + disposeInternal(); + } + } + } + + /** + *

    Close the TtlDB instance and release resource.

    + * + * + * This will not fsync the WAL files. + * If syncing is required, the caller must first call {@link #syncWal()} + * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch + * with {@link WriteOptions#setSync(boolean)} set to true. + * + * See also {@link #close()}. + */ + @Override + public void close() { + if (owningHandle_.compareAndSet(true, false)) { + try { + closeDatabase(nativeHandle_); + } catch (final RocksDBException e) { + // silently ignore the error report + } finally { + disposeInternal(); + } + } + } + /** *

    Creates a new ttl based column family with a name defined * in given ColumnFamilyDescriptor and allocates a @@ -160,22 +209,8 @@ public ColumnFamilyHandle createColumnFamilyWithTtl( final int ttl) throws RocksDBException { return new ColumnFamilyHandle(this, createColumnFamilyWithTtl(nativeHandle_, - columnFamilyDescriptor.columnFamilyName(), - columnFamilyDescriptor.columnFamilyOptions().nativeHandle_, ttl)); - } - - /** - *

    Close the TtlDB instance and release resource.

    - * - *

    Internally, TtlDB owns the {@code rocksdb::DB} pointer - * to its associated {@link org.rocksdb.RocksDB}. The release - * of that RocksDB pointer is handled in the destructor of the - * c++ {@code rocksdb::TtlDB} and should be transparent to - * Java developers.

    - */ - @Override - public void close() { - super.close(); + columnFamilyDescriptor.getName(), + columnFamilyDescriptor.getOptions().nativeHandle_, ttl)); } /** @@ -193,10 +228,7 @@ protected TtlDB(final long nativeHandle) { super(nativeHandle); } - @Override protected void finalize() throws Throwable { - close(); //TODO(AR) revisit here when implementing AutoCloseable - super.finalize(); - } + @Override protected native void disposeInternal(final long handle); private native static long open(final long optionsHandle, final String db_path, final int ttl, final boolean readOnly) @@ -208,4 +240,6 @@ private native static long[] openCF(final long optionsHandle, private native long createColumnFamilyWithTtl(final long handle, final byte[] columnFamilyName, final long columnFamilyOptions, int ttl) throws RocksDBException; + private native static void closeDatabase(final long handle) + throws RocksDBException; } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java new file mode 100644 index 0000000000..837ce6157f --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/TxnDBWritePolicy.java @@ -0,0 +1,62 @@ +// Copyright (c) 2011-present, Facebook, 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). +package org.rocksdb; + +/** + * The transaction db write policy. + */ +public enum TxnDBWritePolicy { + /** + * Write only the committed data. + */ + WRITE_COMMITTED((byte)0x00), + + /** + * Write data after the prepare phase of 2pc. + */ + WRITE_PREPARED((byte)0x1), + + /** + * Write data before the prepare phase of 2pc. + */ + WRITE_UNPREPARED((byte)0x2); + + private byte value; + + TxnDBWritePolicy(final byte value) { + this.value = value; + } + + /** + *

    Returns the byte value of the enumerations value.

    + * + * @return byte representation + */ + public byte getValue() { + return value; + } + + /** + *

    Get the TxnDBWritePolicy enumeration value by + * passing the byte identifier to this method.

    + * + * @param byteIdentifier of TxnDBWritePolicy. + * + * @return TxnDBWritePolicy instance. + * + * @throws IllegalArgumentException If TxnDBWritePolicy cannot be found for + * the provided byteIdentifier + */ + public static TxnDBWritePolicy getTxnDBWritePolicy(final byte byteIdentifier) { + for (final TxnDBWritePolicy txnDBWritePolicy : TxnDBWritePolicy.values()) { + if (txnDBWritePolicy.getValue() == byteIdentifier) { + return txnDBWritePolicy; + } + } + + throw new IllegalArgumentException( + "Illegal value provided for TxnDBWritePolicy."); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/UInt64AddOperator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/UInt64AddOperator.java new file mode 100644 index 0000000000..cce9b298d8 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/UInt64AddOperator.java @@ -0,0 +1,19 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Uint64AddOperator is a merge operator that accumlates a long + * integer value. + */ +public class UInt64AddOperator extends MergeOperator { + public UInt64AddOperator() { + super(newSharedUInt64AddOperator()); + } + + private native static long newSharedUInt64AddOperator(); + @Override protected final native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java index d3fc47b631..d8b9eeceda 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WALRecoveryMode.java @@ -65,7 +65,7 @@ public byte getValue() { * * @param byteIdentifier of WALRecoveryMode. * - * @return CompressionType instance. + * @return WALRecoveryMode instance. * * @throws IllegalArgumentException If WALRecoveryMode cannot be found for the * provided byteIdentifier diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java index d45da2b3a1..482351e996 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WBWIRocksIterator.java @@ -45,6 +45,7 @@ public WriteEntry entry() { @Override final native void next0(long handle); @Override final native void prev0(long handle); @Override final native void seek0(long handle, byte[] target, int targetLen); + @Override final native void seekForPrev0(long handle, byte[] target, int targetLen); @Override final native void status0(long handle) throws RocksDBException; private native long[] entry1(final long handle); @@ -54,10 +55,13 @@ public WriteEntry entry() { * that created the record in the Write Batch */ public enum WriteType { - PUT((byte)0x1), - MERGE((byte)0x2), - DELETE((byte)0x4), - LOG((byte)0x8); + PUT((byte)0x0), + MERGE((byte)0x1), + DELETE((byte)0x2), + SINGLE_DELETE((byte)0x3), + DELETE_RANGE((byte)0x4), + LOG((byte)0x5), + XID((byte)0x6); final byte id; WriteType(final byte id) { diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalFileType.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalFileType.java new file mode 100644 index 0000000000..fed27ed117 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalFileType.java @@ -0,0 +1,55 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public enum WalFileType { + /** + * Indicates that WAL file is in archive directory. WAL files are moved from + * the main db directory to archive directory once they are not live and stay + * there until cleaned up. Files are cleaned depending on archive size + * (Options::WAL_size_limit_MB) and time since last cleaning + * (Options::WAL_ttl_seconds). + */ + kArchivedLogFile((byte)0x0), + + /** + * Indicates that WAL file is live and resides in the main db directory + */ + kAliveLogFile((byte)0x1); + + private final byte value; + + WalFileType(final byte value) { + this.value = value; + } + + /** + * Get the internal representation value. + * + * @return the internal representation value + */ + byte getValue() { + return value; + } + + /** + * Get the WalFileType from the internal representation value. + * + * @return the wal file type. + * + * @throws IllegalArgumentException if the value is unknown. + */ + static WalFileType fromValue(final byte value) { + for (final WalFileType walFileType : WalFileType.values()) { + if(walFileType.value == value) { + return walFileType; + } + } + + throw new IllegalArgumentException( + "Illegal value provided for WalFileType: " + value); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalFilter.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalFilter.java new file mode 100644 index 0000000000..37e36213ae --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalFilter.java @@ -0,0 +1,87 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import java.util.Map; + +/** + * WALFilter allows an application to inspect write-ahead-log (WAL) + * records or modify their processing on recovery. + */ +public interface WalFilter { + + /** + * Provide ColumnFamily->LogNumber map to filter + * so that filter can determine whether a log number applies to a given + * column family (i.e. that log hasn't been flushed to SST already for the + * column family). + * + * We also pass in name>id map as only name is known during + * recovery (as handles are opened post-recovery). + * while write batch callbacks happen in terms of column family id. + * + * @param cfLognumber column_family_id to lognumber map + * @param cfNameId column_family_name to column_family_id map + */ + void columnFamilyLogNumberMap(final Map cfLognumber, + final Map cfNameId); + + /** + * LogRecord is invoked for each log record encountered for all the logs + * during replay on logs on recovery. This method can be used to: + * * inspect the record (using the batch parameter) + * * ignoring current record + * (by returning WalProcessingOption::kIgnoreCurrentRecord) + * * reporting corrupted record + * (by returning WalProcessingOption::kCorruptedRecord) + * * stop log replay + * (by returning kStop replay) - please note that this implies + * discarding the logs from current record onwards. + * + * @param logNumber log number of the current log. + * Filter might use this to determine if the log + * record is applicable to a certain column family. + * @param logFileName log file name - only for informational purposes + * @param batch batch encountered in the log during recovery + * @param newBatch new batch to populate if filter wants to change + * the batch (for example to filter some records out, or alter some + * records). Please note that the new batch MUST NOT contain + * more records than original, else recovery would be failed. + * + * @return Processing option for the current record. + */ + LogRecordFoundResult logRecordFound(final long logNumber, + final String logFileName, final WriteBatch batch, + final WriteBatch newBatch); + + class LogRecordFoundResult { + public static LogRecordFoundResult CONTINUE_UNCHANGED = + new LogRecordFoundResult(WalProcessingOption.CONTINUE_PROCESSING, false); + + final WalProcessingOption walProcessingOption; + final boolean batchChanged; + + /** + * @param walProcessingOption the processing option + * @param batchChanged Whether batch was changed by the filter. + * It must be set to true if newBatch was populated, + * else newBatch has no effect. + */ + public LogRecordFoundResult(final WalProcessingOption walProcessingOption, + final boolean batchChanged) { + this.walProcessingOption = walProcessingOption; + this.batchChanged = batchChanged; + } + } + + /** + * Returns a name that identifies this WAL filter. + * The name will be printed to LOG file on start up for diagnosis. + * + * @return the name + */ + String name(); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalProcessingOption.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalProcessingOption.java new file mode 100644 index 0000000000..889602edc9 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WalProcessingOption.java @@ -0,0 +1,54 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +public enum WalProcessingOption { + /** + * Continue processing as usual. + */ + CONTINUE_PROCESSING((byte)0x0), + + /** + * Ignore the current record but continue processing of log(s). + */ + IGNORE_CURRENT_RECORD((byte)0x1), + + /** + * Stop replay of logs and discard logs. + * Logs won't be replayed on subsequent recovery. + */ + STOP_REPLAY((byte)0x2), + + /** + * Corrupted record detected by filter. + */ + CORRUPTED_RECORD((byte)0x3); + + private final byte value; + + WalProcessingOption(final byte value) { + this.value = value; + } + + /** + * Get the internal representation. + * + * @return the internal representation. + */ + byte getValue() { + return value; + } + + public static WalProcessingOption fromValue(final byte value) { + for (final WalProcessingOption walProcessingOption : WalProcessingOption.values()) { + if (walProcessingOption.value == value) { + return walProcessingOption; + } + } + throw new IllegalArgumentException( + "Illegal value provided for WalProcessingOption: " + value); + } +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java index 272e9b4cdf..5673a25efb 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatch.java @@ -39,6 +39,16 @@ public WriteBatch(final int reserved_bytes) { super(newWriteBatch(reserved_bytes)); } + /** + * Constructs a WriteBatch instance from a serialized representation + * as returned by {@link #data()}. + * + * @param serialized the serialized representation. + */ + public WriteBatch(final byte[] serialized) { + super(newWriteBatch(serialized, serialized.length)); + } + /** * Support for iterating over the contents of a batch. * @@ -51,6 +61,137 @@ public void iterate(final Handler handler) throws RocksDBException { iterate(nativeHandle_, handler.nativeHandle_); } + /** + * Retrieve the serialized version of this batch. + * + * @return the serialized representation of this write batch. + * + * @throws RocksDBException if an error occurs whilst retrieving + * the serialized batch data. + */ + public byte[] data() throws RocksDBException { + return data(nativeHandle_); + } + + /** + * Retrieve data size of the batch. + * + * @return the serialized data size of the batch. + */ + public long getDataSize() { + return getDataSize(nativeHandle_); + } + + /** + * Returns true if Put will be called during Iterate. + * + * @return true if Put will be called during Iterate. + */ + public boolean hasPut() { + return hasPut(nativeHandle_); + } + + /** + * Returns true if Delete will be called during Iterate. + * + * @return true if Delete will be called during Iterate. + */ + public boolean hasDelete() { + return hasDelete(nativeHandle_); + } + + /** + * Returns true if SingleDelete will be called during Iterate. + * + * @return true if SingleDelete will be called during Iterate. + */ + public boolean hasSingleDelete() { + return hasSingleDelete(nativeHandle_); + } + + /** + * Returns true if DeleteRange will be called during Iterate. + * + * @return true if DeleteRange will be called during Iterate. + */ + public boolean hasDeleteRange() { + return hasDeleteRange(nativeHandle_); + } + + /** + * Returns true if Merge will be called during Iterate. + * + * @return true if Merge will be called during Iterate. + */ + public boolean hasMerge() { + return hasMerge(nativeHandle_); + } + + /** + * Returns true if MarkBeginPrepare will be called during Iterate. + * + * @return true if MarkBeginPrepare will be called during Iterate. + */ + public boolean hasBeginPrepare() { + return hasBeginPrepare(nativeHandle_); + } + + /** + * Returns true if MarkEndPrepare will be called during Iterate. + * + * @return true if MarkEndPrepare will be called during Iterate. + */ + public boolean hasEndPrepare() { + return hasEndPrepare(nativeHandle_); + } + + /** + * Returns true if MarkCommit will be called during Iterate. + * + * @return true if MarkCommit will be called during Iterate. + */ + public boolean hasCommit() { + return hasCommit(nativeHandle_); + } + + /** + * Returns true if MarkRollback will be called during Iterate. + * + * @return true if MarkRollback will be called during Iterate. + */ + public boolean hasRollback() { + return hasRollback(nativeHandle_); + } + + @Override + public WriteBatch getWriteBatch() { + return this; + } + + /** + * Marks this point in the WriteBatch as the last record to + * be inserted into the WAL, provided the WAL is enabled. + */ + public void markWalTerminationPoint() { + markWalTerminationPoint(nativeHandle_); + } + + /** + * Gets the WAL termination point. + * + * See {@link #markWalTerminationPoint()} + * + * @return the WAL termination point + */ + public SavePoint getWalTerminationPoint() { + return getWalTerminationPoint(nativeHandle_); + } + + @Override + WriteBatch getWriteBatch(final long handle) { + return this; + } + /** *

    Private WriteBatch constructor which is used to construct * WriteBatch instances from C++ side. As the reference to this @@ -87,10 +228,14 @@ public void iterate(final Handler handler) throws RocksDBException { @Override final native void merge(final long handle, final byte[] key, final int keyLen, final byte[] value, final int valueLen, final long cfHandle); - @Override final native void remove(final long handle, final byte[] key, - final int keyLen); - @Override final native void remove(final long handle, final byte[] key, - final int keyLen, final long cfHandle); + @Override final native void delete(final long handle, final byte[] key, + final int keyLen) throws RocksDBException; + @Override final native void delete(final long handle, final byte[] key, + final int keyLen, final long cfHandle) throws RocksDBException; + @Override final native void singleDelete(final long handle, final byte[] key, + final int keyLen) throws RocksDBException; + @Override final native void singleDelete(final long handle, final byte[] key, + final int keyLen, final long cfHandle) throws RocksDBException; @Override final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, final byte[] endKey, final int endKeyLen); @@ -98,36 +243,79 @@ final native void deleteRange(final long handle, final byte[] beginKey, final in final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, final byte[] endKey, final int endKeyLen, final long cfHandle); @Override final native void putLogData(final long handle, - final byte[] blob, final int blobLen); + final byte[] blob, final int blobLen) throws RocksDBException; @Override final native void clear0(final long handle); @Override final native void setSavePoint0(final long handle); @Override final native void rollbackToSavePoint0(final long handle); + @Override final native void popSavePoint(final long handle) throws RocksDBException; + @Override final native void setMaxBytes(final long nativeHandle, + final long maxBytes); private native static long newWriteBatch(final int reserved_bytes); + private native static long newWriteBatch(final byte[] serialized, + final int serializedLength); private native void iterate(final long handle, final long handlerHandle) throws RocksDBException; - + private native byte[] data(final long nativeHandle) throws RocksDBException; + private native long getDataSize(final long nativeHandle); + private native boolean hasPut(final long nativeHandle); + private native boolean hasDelete(final long nativeHandle); + private native boolean hasSingleDelete(final long nativeHandle); + private native boolean hasDeleteRange(final long nativeHandle); + private native boolean hasMerge(final long nativeHandle); + private native boolean hasBeginPrepare(final long nativeHandle); + private native boolean hasEndPrepare(final long nativeHandle); + private native boolean hasCommit(final long nativeHandle); + private native boolean hasRollback(final long nativeHandle); + private native void markWalTerminationPoint(final long nativeHandle); + private native SavePoint getWalTerminationPoint(final long nativeHandle); /** * Handler callback for iterating over the contents of a batch. */ public static abstract class Handler - extends AbstractImmutableNativeReference { - private final long nativeHandle_; + extends RocksCallbackObject { public Handler() { - super(true); - this.nativeHandle_ = createNewHandler0(); + super(null); + } + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return createNewHandler0(); } - public abstract void put(byte[] key, byte[] value); - public abstract void merge(byte[] key, byte[] value); - public abstract void delete(byte[] key); - public abstract void deleteRange(byte[] beginKey, byte[] endKey); - public abstract void logData(byte[] blob); + public abstract void put(final int columnFamilyId, final byte[] key, + final byte[] value) throws RocksDBException; + public abstract void put(final byte[] key, final byte[] value); + public abstract void merge(final int columnFamilyId, final byte[] key, + final byte[] value) throws RocksDBException; + public abstract void merge(final byte[] key, final byte[] value); + public abstract void delete(final int columnFamilyId, final byte[] key) + throws RocksDBException; + public abstract void delete(final byte[] key); + public abstract void singleDelete(final int columnFamilyId, + final byte[] key) throws RocksDBException; + public abstract void singleDelete(final byte[] key); + public abstract void deleteRange(final int columnFamilyId, + final byte[] beginKey, final byte[] endKey) throws RocksDBException; + public abstract void deleteRange(final byte[] beginKey, + final byte[] endKey); + public abstract void logData(final byte[] blob); + public abstract void putBlobIndex(final int columnFamilyId, + final byte[] key, final byte[] value) throws RocksDBException; + public abstract void markBeginPrepare() throws RocksDBException; + public abstract void markEndPrepare(final byte[] xid) + throws RocksDBException; + public abstract void markNoop(final boolean emptyBatch) + throws RocksDBException; + public abstract void markRollback(final byte[] xid) + throws RocksDBException; + public abstract void markCommit(final byte[] xid) + throws RocksDBException; /** * shouldContinue is called by the underlying iterator - * WriteBatch::Iterate. If it returns false, + * {@link WriteBatch#iterate(Handler)}. If it returns false, * iteration is halted. Otherwise, it continues * iterating. The default implementation always * returns true. @@ -139,15 +327,59 @@ public boolean shouldContinue() { return true; } + private native long createNewHandler0(); + } + + /** + * A structure for describing the save point in the Write Batch. + */ + public static class SavePoint { + private long size; + private long count; + private long contentFlags; + + public SavePoint(final long size, final long count, + final long contentFlags) { + this.size = size; + this.count = count; + this.contentFlags = contentFlags; + } + + public void clear() { + this.size = 0; + this.count = 0; + this.contentFlags = 0; + } + + /** + * Get the size of the serialized representation. + * + * @return the size of the serialized representation. + */ + public long getSize() { + return size; + } + /** - * Deletes underlying C++ handler pointer. + * Get the number of elements. + * + * @return the number of elements. */ - @Override - protected void disposeInternal() { - disposeInternal(nativeHandle_); + public long getCount() { + return count; } - private native long createNewHandler0(); - private native void disposeInternal(final long handle); + /** + * Get the content flags. + * + * @return the content flags. + */ + public long getContentFlags() { + return contentFlags; + } + + public boolean isCleared() { + return (size | count | contentFlags) == 0; + } } } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java index cd024ad58d..e0999e21b6 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchInterface.java @@ -23,8 +23,9 @@ public interface WriteBatchInterface { * * @param key the specified key to be inserted. * @param value the value associated with the specified key. + * @throws RocksDBException thrown if error happens in underlying native library. */ - void put(byte[] key, byte[] value); + void put(byte[] key, byte[] value) throws RocksDBException; /** *

    Store the mapping "key->value" within given column @@ -34,9 +35,10 @@ public interface WriteBatchInterface { * instance * @param key the specified key to be inserted. * @param value the value associated with the specified key. + * @throws RocksDBException thrown if error happens in underlying native library. */ void put(ColumnFamilyHandle columnFamilyHandle, - byte[] key, byte[] value); + byte[] key, byte[] value) throws RocksDBException; /** *

    Merge "value" with the existing value of "key" in the database. @@ -45,8 +47,9 @@ void put(ColumnFamilyHandle columnFamilyHandle, * @param key the specified key to be merged. * @param value the value to be merged with the current value for * the specified key. + * @throws RocksDBException thrown if error happens in underlying native library. */ - void merge(byte[] key, byte[] value); + void merge(byte[] key, byte[] value) throws RocksDBException; /** *

    Merge "value" with the existing value of "key" in given column family. @@ -56,24 +59,102 @@ void put(ColumnFamilyHandle columnFamilyHandle, * @param key the specified key to be merged. * @param value the value to be merged with the current value for * the specified key. + * @throws RocksDBException thrown if error happens in underlying native library. */ void merge(ColumnFamilyHandle columnFamilyHandle, - byte[] key, byte[] value); + byte[] key, byte[] value) throws RocksDBException; /** *

    If the database contains a mapping for "key", erase it. Else do nothing.

    * * @param key Key to delete within database + * + * @deprecated Use {@link #delete(byte[])} + * @throws RocksDBException thrown if error happens in underlying native library. + */ + @Deprecated + void remove(byte[] key) throws RocksDBException; + + /** + *

    If column family contains a mapping for "key", erase it. Else do nothing.

    + * + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key Key to delete within database + * + * @deprecated Use {@link #delete(ColumnFamilyHandle, byte[])} + * @throws RocksDBException thrown if error happens in underlying native library. + */ + @Deprecated + void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key) + throws RocksDBException; + + /** + *

    If the database contains a mapping for "key", erase it. Else do nothing.

    + * + * @param key Key to delete within database + * @throws RocksDBException thrown if error happens in underlying native library. */ - void remove(byte[] key); + void delete(byte[] key) throws RocksDBException; /** *

    If column family contains a mapping for "key", erase it. Else do nothing.

    * * @param columnFamilyHandle {@link ColumnFamilyHandle} instance * @param key Key to delete within database + * @throws RocksDBException thrown if error happens in underlying native library. + */ + void delete(ColumnFamilyHandle columnFamilyHandle, byte[] key) + throws RocksDBException; + + /** + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. + * + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. */ - void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key); + @Experimental("Performance optimization for a very specific workload") + void singleDelete(final byte[] key) throws RocksDBException; + + /** + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. + * + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * @param columnFamilyHandle The column family to delete the key from + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + @Experimental("Performance optimization for a very specific workload") + void singleDelete(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException; /** * Removes the database entries in the range ["beginKey", "endKey"), i.e., @@ -88,8 +169,9 @@ void merge(ColumnFamilyHandle columnFamilyHandle, * First key to delete within database (included) * @param endKey * Last key to delete within database (excluded) + * @throws RocksDBException thrown if error happens in underlying native library. */ - void deleteRange(byte[] beginKey, byte[] endKey); + void deleteRange(byte[] beginKey, byte[] endKey) throws RocksDBException; /** * Removes the database entries in the range ["beginKey", "endKey"), i.e., @@ -105,8 +187,10 @@ void merge(ColumnFamilyHandle columnFamilyHandle, * First key to delete within database (included) * @param endKey * Last key to delete within database (excluded) + * @throws RocksDBException thrown if error happens in underlying native library. */ - void deleteRange(ColumnFamilyHandle columnFamilyHandle, byte[] beginKey, byte[] endKey); + void deleteRange(ColumnFamilyHandle columnFamilyHandle, byte[] beginKey, + byte[] endKey) throws RocksDBException; /** * Append a blob of arbitrary size to the records in this batch. The blob will @@ -121,8 +205,9 @@ void merge(ColumnFamilyHandle columnFamilyHandle, * replication. * * @param blob binary object to be inserted + * @throws RocksDBException thrown if error happens in underlying native library. */ - void putLogData(byte[] blob); + void putLogData(byte[] blob) throws RocksDBException; /** * Clear all updates buffered in this batch @@ -143,4 +228,30 @@ void merge(ColumnFamilyHandle columnFamilyHandle, * @throws RocksDBException if there is no previous call to SetSavePoint() */ void rollbackToSavePoint() throws RocksDBException; + + /** + * Pop the most recent save point. + * + * That is to say that it removes the last save point, + * which was set by {@link #setSavePoint()}. + * + * @throws RocksDBException If there is no previous call to + * {@link #setSavePoint()}, an exception with + * {@link Status.Code#NotFound} will be thrown. + */ + void popSavePoint() throws RocksDBException; + + /** + * Set the maximum size of the write batch. + * + * @param maxBytes the maximum size in bytes. + */ + void setMaxBytes(long maxBytes); + + /** + * Get the underlying Write Batch. + * + * @return the underlying WriteBatch. + */ + WriteBatch getWriteBatch(); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java index fdf89b2798..2ad91042d4 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBatchWithIndex.java @@ -60,8 +60,21 @@ public WriteBatchWithIndex( final AbstractComparator> fallbackIndexComparator, final int reservedBytes, final boolean overwriteKey) { - super(newWriteBatchWithIndex(fallbackIndexComparator.getNativeHandle(), - reservedBytes, overwriteKey)); + super(newWriteBatchWithIndex(fallbackIndexComparator.nativeHandle_, + fallbackIndexComparator.getComparatorType().getValue(), reservedBytes, + overwriteKey)); + } + + /** + *

    Private WriteBatchWithIndex constructor which is used to construct + * WriteBatchWithIndex instances from C++ side. As the reference to this + * object is also managed from C++ side the handle will be disowned.

    + * + * @param nativeHandle address of native instance. + */ + WriteBatchWithIndex(final long nativeHandle) { + super(nativeHandle); + disOwnNativeHandle(); } /** @@ -101,6 +114,12 @@ public WBWIRocksIterator newIterator() { * creating a new Iterator that will use {@link org.rocksdb.WBWIRocksIterator} * as a delta and baseIterator as a base * + * Updating write batch with the current key of the iterator is not safe. + * We strongly recommand users not to do it. It will invalidate the current + * key() and value() of the iterator. This invalidation happens even before + * the write batch update finishes. The state may recover after Next() is + * called. + * * @param columnFamilyHandle The column family to iterate over * @param baseIterator The base iterator, * e.g. {@link org.rocksdb.RocksDB#newIterator()} @@ -110,12 +129,10 @@ public WBWIRocksIterator newIterator() { public RocksIterator newIteratorWithBase( final ColumnFamilyHandle columnFamilyHandle, final RocksIterator baseIterator) { - RocksIterator iterator = new RocksIterator( - baseIterator.parent_, - iteratorWithBase(nativeHandle_, - columnFamilyHandle.nativeHandle_, - baseIterator.nativeHandle_)); - //when the iterator is deleted it will also delete the baseIterator + RocksIterator iterator = new RocksIterator(baseIterator.parent_, + iteratorWithBase( + nativeHandle_, columnFamilyHandle.nativeHandle_, baseIterator.nativeHandle_)); + // when the iterator is deleted it will also delete the baseIterator baseIterator.disOwnNativeHandle(); return iterator; } @@ -132,8 +149,7 @@ public RocksIterator newIteratorWithBase( * point-in-timefrom baseIterator and modifications made in this write batch. */ public RocksIterator newIteratorWithBase(final RocksIterator baseIterator) { - return newIteratorWithBase(baseIterator.parent_.getDefaultColumnFamily(), - baseIterator); + return newIteratorWithBase(baseIterator.parent_.getDefaultColumnFamily(), baseIterator); } /** @@ -244,10 +260,14 @@ public byte[] getFromBatchAndDB(final RocksDB db, final ReadOptions options, @Override final native void merge(final long handle, final byte[] key, final int keyLen, final byte[] value, final int valueLen, final long cfHandle); - @Override final native void remove(final long handle, final byte[] key, - final int keyLen); - @Override final native void remove(final long handle, final byte[] key, - final int keyLen, final long cfHandle); + @Override final native void delete(final long handle, final byte[] key, + final int keyLen) throws RocksDBException; + @Override final native void delete(final long handle, final byte[] key, + final int keyLen, final long cfHandle) throws RocksDBException; + @Override final native void singleDelete(final long handle, final byte[] key, + final int keyLen) throws RocksDBException; + @Override final native void singleDelete(final long handle, final byte[] key, + final int keyLen, final long cfHandle) throws RocksDBException; @Override final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, final byte[] endKey, final int endKeyLen); @@ -255,20 +275,25 @@ final native void deleteRange(final long handle, final byte[] beginKey, final in final native void deleteRange(final long handle, final byte[] beginKey, final int beginKeyLen, final byte[] endKey, final int endKeyLen, final long cfHandle); @Override final native void putLogData(final long handle, final byte[] blob, - final int blobLen); + final int blobLen) throws RocksDBException; @Override final native void clear0(final long handle); @Override final native void setSavePoint0(final long handle); @Override final native void rollbackToSavePoint0(final long handle); + @Override final native void popSavePoint(final long handle) throws RocksDBException; + @Override final native void setMaxBytes(final long nativeHandle, + final long maxBytes); + @Override final native WriteBatch getWriteBatch(final long handle); private native static long newWriteBatchWithIndex(); private native static long newWriteBatchWithIndex(final boolean overwriteKey); private native static long newWriteBatchWithIndex( - final long fallbackIndexComparatorHandle, final int reservedBytes, + final long fallbackIndexComparatorHandle, + final byte comparatorType, final int reservedBytes, final boolean overwriteKey); private native long iterator0(final long handle); private native long iterator1(final long handle, final long cfHandle); - private native long iteratorWithBase(final long handle, - final long baseIteratorHandle, final long cfHandle); + private native long iteratorWithBase( + final long handle, final long baseIteratorHandle, final long cfHandle); private native byte[] getFromBatch(final long handle, final long optHandle, final byte[] key, final int keyLen); private native byte[] getFromBatch(final long handle, final long optHandle, diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBufferManager.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBufferManager.java new file mode 100644 index 0000000000..b244aa9522 --- /dev/null +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteBufferManager.java @@ -0,0 +1,33 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +/** + * Java wrapper over native write_buffer_manager class + */ +public class WriteBufferManager extends RocksObject { + static { + RocksDB.loadLibrary(); + } + + /** + * Construct a new instance of WriteBufferManager. + * + * Check + * https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager + * for more details on when to use it + * + * @param bufferSizeBytes buffer size(in bytes) to use for native write_buffer_manager + * @param cache cache whose memory should be bounded by this write buffer manager + */ + public WriteBufferManager(final long bufferSizeBytes, final Cache cache){ + super(newWriteBufferManager(bufferSizeBytes, cache.nativeHandle_)); + } + + private native static long newWriteBufferManager(final long bufferSizeBytes, final long cacheHandle); + @Override + protected native void disposeInternal(final long handle); +} diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java index b9e8ad81c2..71789ed1fd 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/WriteOptions.java @@ -20,6 +20,25 @@ public WriteOptions() { } + // TODO(AR) consider ownership + WriteOptions(final long nativeHandle) { + super(nativeHandle); + disOwnNativeHandle(); + } + + /** + * Copy constructor for WriteOptions. + * + * NOTE: This does a shallow copy, which means comparator, merge_operator, compaction_filter, + * compaction_filter_factory and other pointers will be cloned! + * + * @param other The ColumnFamilyOptions to copy. + */ + public WriteOptions(WriteOptions other) { + super(copyWriteOptions(other.nativeHandle_)); + } + + /** * If true, the write will be flushed from the operating system * buffer cache (by calling WritableFile::Sync()) before the write @@ -71,7 +90,10 @@ public boolean sync() { /** * If true, writes will not first go to the write ahead log, - * and the write may got lost after a crash. + * and the write may got lost after a crash. The backup engine + * relies on write-ahead logs to back up the memtable, so if + * you disable write-ahead logs, you must create backups with + * flush_before_backup=true to avoid losing unflushed memtable data. * * @param flag a boolean flag to specify whether to disable * write-ahead-log on writes. @@ -84,7 +106,10 @@ public WriteOptions setDisableWAL(final boolean flag) { /** * If true, writes will not first go to the write ahead log, - * and the write may got lost after a crash. + * and the write may got lost after a crash. The backup engine + * relies on write-ahead logs to back up the memtable, so if + * you disable write-ahead logs, you must create backups with + * flush_before_backup=true to avoid losing unflushed memtable data. * * @return boolean value indicating if WAL is disabled. */ @@ -144,7 +169,41 @@ public boolean noSlowdown() { return noSlowdown(nativeHandle_); } + /** + * If true, this write request is of lower priority if compaction is + * behind. In this case that, {@link #noSlowdown()} == true, the request + * will be cancelled immediately with {@link Status.Code#Incomplete} returned. + * Otherwise, it will be slowed down. The slowdown value is determined by + * RocksDB to guarantee it introduces minimum impacts to high priority writes. + * + * Default: false + * + * @param lowPri true if the write request should be of lower priority than + * compactions which are behind. + * + * @return the instance of the current WriteOptions. + */ + public WriteOptions setLowPri(final boolean lowPri) { + setLowPri(nativeHandle_, lowPri); + return this; + } + + /** + * Returns true if this write request is of lower priority if compaction is + * behind. + * + * See {@link #setLowPri(boolean)}. + * + * @return true if this write request is of lower priority, false otherwise. + */ + public boolean lowPri() { + return lowPri(nativeHandle_); + } + private native static long newWriteOptions(); + private native static long copyWriteOptions(long handle); + @Override protected final native void disposeInternal(final long handle); + private native void setSync(long handle, boolean flag); private native boolean sync(long handle); private native void setDisableWAL(long handle, boolean flag); @@ -155,5 +214,6 @@ private native void setIgnoreMissingColumnFamilies(final long handle, private native void setNoSlowdown(final long handle, final boolean noSlowdown); private native boolean noSlowdown(final long handle); - @Override protected final native void disposeInternal(final long handle); + private native void setLowPri(final long handle, final boolean lowPri); + private native boolean lowPri(final long handle); } diff --git a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java index f84e14bc19..bf005a3481 100644 --- a/thirdparty/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java +++ b/thirdparty/rocksdb/java/src/main/java/org/rocksdb/util/Environment.java @@ -8,10 +8,18 @@ public static boolean isPowerPC() { return ARCH.contains("ppc"); } + public static boolean isS390x() { + return ARCH.contains("s390x"); + } + public static boolean isWindows() { return (OS.contains("win")); } + public static boolean isFreeBSD() { + return (OS.contains("freebsd")); + } + public static boolean isMac() { return (OS.contains("mac")); } @@ -29,6 +37,10 @@ public static boolean isSolaris() { return OS.contains("sunos"); } + public static boolean isOpenBSD() { + return (OS.contains("openbsd")); + } + public static boolean is64Bit() { if (ARCH.indexOf("sparcv9") >= 0) { return true; @@ -49,11 +61,15 @@ public static String getJniLibraryName(final String name) { final String arch = is64Bit() ? "64" : "32"; if(isPowerPC()) { return String.format("%sjni-linux-%s", name, ARCH); + } else if(isS390x()) { + return String.format("%sjni-linux%s", name, ARCH); } else { return String.format("%sjni-linux%s", name, arch); } } else if (isMac()) { return String.format("%sjni-osx", name); + } else if (isFreeBSD()) { + return String.format("%sjni-freebsd%s", name, is64Bit() ? "64" : "32"); } else if (isAix() && is64Bit()) { return String.format("%sjni-aix64", name); } else if (isSolaris()) { @@ -61,6 +77,8 @@ public static String getJniLibraryName(final String name) { return String.format("%sjni-solaris%s", name, arch); } else if (isWindows() && is64Bit()) { return String.format("%sjni-win64", name); + } else if (isOpenBSD()) { + return String.format("%sjni-openbsd%s", name, is64Bit() ? "64" : "32"); } throw new UnsupportedOperationException(String.format("Cannot determine JNI library name for ARCH='%s' OS='%s' name='%s'", ARCH, OS, name)); @@ -71,7 +89,7 @@ public static String getJniLibraryFileName(final String name) { } private static String appendLibOsSuffix(final String libraryFileName, final boolean shared) { - if (isUnix() || isAix() || isSolaris()) { + if (isUnix() || isAix() || isSolaris() || isFreeBSD() || isOpenBSD()) { return libraryFileName + ".so"; } else if (isMac()) { return libraryFileName + (shared ? ".dylib" : ".jnilib"); diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/AbstractTransactionTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/AbstractTransactionTest.java new file mode 100644 index 0000000000..7cac3015b9 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/AbstractTransactionTest.java @@ -0,0 +1,902 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * Base class of {@link TransactionTest} and {@link OptimisticTransactionTest} + */ +public abstract class AbstractTransactionTest { + + protected final static byte[] TXN_TEST_COLUMN_FAMILY = "txn_test_cf" + .getBytes(); + + protected static final Random rand = PlatformRandomHelper. + getPlatformSpecificRandomFactory(); + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + public abstract DBContainer startDb() + throws RocksDBException; + + @Test + public void setSnapshot() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.setSnapshot(); + } + } + + @Test + public void setSnapshotOnNextOperation() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.setSnapshotOnNextOperation(); + txn.put("key1".getBytes(), "value1".getBytes()); + } + } + + @Test + public void setSnapshotOnNextOperation_transactionNotifier() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + + try(final TestTransactionNotifier notifier = new TestTransactionNotifier()) { + txn.setSnapshotOnNextOperation(notifier); + txn.put("key1".getBytes(), "value1".getBytes()); + + txn.setSnapshotOnNextOperation(notifier); + txn.put("key2".getBytes(), "value2".getBytes()); + + assertThat(notifier.getCreatedSnapshots().size()).isEqualTo(2); + } + } + } + + @Test + public void getSnapshot() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.setSnapshot(); + final Snapshot snapshot = txn.getSnapshot(); + assertThat(snapshot.isOwningHandle()).isFalse(); + } + } + + @Test + public void getSnapshot_null() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + final Snapshot snapshot = txn.getSnapshot(); + assertThat(snapshot).isNull(); + } + } + + @Test + public void clearSnapshot() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.setSnapshot(); + txn.clearSnapshot(); + } + } + + @Test + public void clearSnapshot_none() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.clearSnapshot(); + } + } + + @Test + public void commit() throws RocksDBException { + final byte k1[] = "rollback-key1".getBytes(UTF_8); + final byte v1[] = "rollback-value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb()) { + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + txn.commit(); + } + + try(final ReadOptions readOptions = new ReadOptions(); + final Transaction txn2 = dbContainer.beginTransaction()) { + assertThat(txn2.get(readOptions, k1)).isEqualTo(v1); + } + } + } + + @Test + public void rollback() throws RocksDBException { + final byte k1[] = "rollback-key1".getBytes(UTF_8); + final byte v1[] = "rollback-value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb()) { + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + txn.rollback(); + } + + try(final ReadOptions readOptions = new ReadOptions(); + final Transaction txn2 = dbContainer.beginTransaction()) { + assertThat(txn2.get(readOptions, k1)).isNull(); + } + } + } + + @Test + public void savePoint() throws RocksDBException { + final byte k1[] = "savePoint-key1".getBytes(UTF_8); + final byte v1[] = "savePoint-value1".getBytes(UTF_8); + final byte k2[] = "savePoint-key2".getBytes(UTF_8); + final byte v2[] = "savePoint-value2".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + + txn.setSavePoint(); + + txn.put(k2, v2); + + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + assertThat(txn.get(readOptions, k2)).isEqualTo(v2); + + txn.rollbackToSavePoint(); + + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + assertThat(txn.get(readOptions, k2)).isNull(); + + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + assertThat(txn2.get(readOptions, k1)).isEqualTo(v1); + assertThat(txn2.get(readOptions, k2)).isNull(); + } + } + } + + @Test + public void getPut_cf() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + assertThat(txn.get(testCf, readOptions, k1)).isNull(); + txn.put(testCf, k1, v1); + assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); + } + } + + @Test + public void getPut() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.get(readOptions, k1)).isNull(); + txn.put(k1, v1); + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + } + } + + @Test + public void multiGetPut_cf() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + final List cfList = Arrays.asList(testCf, testCf); + + assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(new byte[][] { null, null }); + + txn.put(testCf, keys[0], values[0]); + txn.put(testCf, keys[1], values[1]); + assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); + } + } + + @Test + public void multiGetPut() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + + assertThat(txn.multiGet(readOptions, keys)).isEqualTo(new byte[][] { null, null }); + + txn.put(keys[0], values[0]); + txn.put(keys[1], values[1]); + assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); + } + } + + @Test + public void getForUpdate_cf() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isNull(); + txn.put(testCf, k1, v1); + assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); + } + } + + @Test + public void getForUpdate() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getForUpdate(readOptions, k1, true)).isNull(); + txn.put(k1, v1); + assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1); + } + } + + @Test + public void multiGetForUpdate_cf() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + final List cfList = Arrays.asList(testCf, testCf); + + assertThat(txn.multiGetForUpdate(readOptions, cfList, keys)) + .isEqualTo(new byte[][] { null, null }); + + txn.put(testCf, keys[0], values[0]); + txn.put(testCf, keys[1], values[1]); + assertThat(txn.multiGetForUpdate(readOptions, cfList, keys)) + .isEqualTo(values); + } + } + + @Test + public void multiGetForUpdate() throws RocksDBException { + final byte keys[][] = new byte[][]{ + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][]{ + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + + try (final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.multiGetForUpdate(readOptions, keys)).isEqualTo(new byte[][]{null, null}); + + txn.put(keys[0], values[0]); + txn.put(keys[1], values[1]); + assertThat(txn.multiGetForUpdate(readOptions, keys)).isEqualTo(values); + } + } + + @Test + public void getIterator() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + txn.put(k1, v1); + + try(final RocksIterator iterator = txn.getIterator(readOptions)) { + iterator.seek(k1); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo(k1); + assertThat(iterator.value()).isEqualTo(v1); + } + } + } + + @Test + public void getIterator_cf() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + txn.put(testCf, k1, v1); + + try(final RocksIterator iterator = txn.getIterator(readOptions, testCf)) { + iterator.seek(k1); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo(k1); + assertThat(iterator.value()).isEqualTo(v1); + } + } + } + + @Test + public void merge_cf() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + txn.merge(testCf, k1, v1); + } + } + + @Test + public void merge() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.merge(k1, v1); + } + } + + + @Test + public void delete_cf() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + txn.put(testCf, k1, v1); + assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); + + txn.delete(testCf, k1); + assertThat(txn.get(testCf, readOptions, k1)).isNull(); + } + } + + @Test + public void delete() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + + txn.delete(k1); + assertThat(txn.get(readOptions, k1)).isNull(); + } + } + + @Test + public void delete_parts_cf() throws RocksDBException { + final byte keyParts[][] = new byte[][] { + "ke".getBytes(UTF_8), + "y1".getBytes(UTF_8)}; + final byte valueParts[][] = new byte[][] { + "val".getBytes(UTF_8), + "ue1".getBytes(UTF_8)}; + final byte[] key = concat(keyParts); + final byte[] value = concat(valueParts); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + txn.put(testCf, keyParts, valueParts); + assertThat(txn.get(testCf, readOptions, key)).isEqualTo(value); + + txn.delete(testCf, keyParts); + + assertThat(txn.get(testCf, readOptions, key)) + .isNull(); + } + } + + @Test + public void delete_parts() throws RocksDBException { + final byte keyParts[][] = new byte[][] { + "ke".getBytes(UTF_8), + "y1".getBytes(UTF_8)}; + final byte valueParts[][] = new byte[][] { + "val".getBytes(UTF_8), + "ue1".getBytes(UTF_8)}; + final byte[] key = concat(keyParts); + final byte[] value = concat(valueParts); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + + txn.put(keyParts, valueParts); + + assertThat(txn.get(readOptions, key)).isEqualTo(value); + + txn.delete(keyParts); + + assertThat(txn.get(readOptions, key)).isNull(); + } + } + + @Test + public void getPutUntracked_cf() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + assertThat(txn.get(testCf, readOptions, k1)).isNull(); + txn.putUntracked(testCf, k1, v1); + assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); + } + } + + @Test + public void getPutUntracked() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.get(readOptions, k1)).isNull(); + txn.putUntracked(k1, v1); + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + } + } + + @Test + public void multiGetPutUntracked_cf() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + + final List cfList = Arrays.asList(testCf, testCf); + + assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(new byte[][] { null, null }); + txn.putUntracked(testCf, keys[0], values[0]); + txn.putUntracked(testCf, keys[1], values[1]); + assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); + } + } + + @Test + public void multiGetPutUntracked() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + + assertThat(txn.multiGet(readOptions, keys)).isEqualTo(new byte[][] { null, null }); + txn.putUntracked(keys[0], values[0]); + txn.putUntracked(keys[1], values[1]); + assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); + } + } + + @Test + public void mergeUntracked_cf() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + txn.mergeUntracked(testCf, k1, v1); + } + } + + @Test + public void mergeUntracked() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.mergeUntracked(k1, v1); + } + } + + @Test + public void deleteUntracked_cf() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + txn.put(testCf, k1, v1); + assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); + + txn.deleteUntracked(testCf, k1); + assertThat(txn.get(testCf, readOptions, k1)).isNull(); + } + } + + @Test + public void deleteUntracked() throws RocksDBException { + final byte[] k1 = "key1".getBytes(UTF_8); + final byte[] v1 = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + + txn.deleteUntracked(k1); + assertThat(txn.get(readOptions, k1)).isNull(); + } + } + + @Test + public void deleteUntracked_parts_cf() throws RocksDBException { + final byte keyParts[][] = new byte[][] { + "ke".getBytes(UTF_8), + "y1".getBytes(UTF_8)}; + final byte valueParts[][] = new byte[][] { + "val".getBytes(UTF_8), + "ue1".getBytes(UTF_8)}; + final byte[] key = concat(keyParts); + final byte[] value = concat(valueParts); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + txn.put(testCf, keyParts, valueParts); + assertThat(txn.get(testCf, readOptions, key)).isEqualTo(value); + + txn.deleteUntracked(testCf, keyParts); + assertThat(txn.get(testCf, readOptions, key)).isNull(); + } + } + + @Test + public void deleteUntracked_parts() throws RocksDBException { + final byte keyParts[][] = new byte[][] { + "ke".getBytes(UTF_8), + "y1".getBytes(UTF_8)}; + final byte valueParts[][] = new byte[][] { + "val".getBytes(UTF_8), + "ue1".getBytes(UTF_8)}; + final byte[] key = concat(keyParts); + final byte[] value = concat(valueParts); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.put(keyParts, valueParts); + assertThat(txn.get(readOptions, key)).isEqualTo(value); + + txn.deleteUntracked(keyParts); + assertThat(txn.get(readOptions, key)).isNull(); + } + } + + @Test + public void putLogData() throws RocksDBException { + final byte[] blob = "blobby".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.putLogData(blob); + } + } + + @Test + public void enabledDisableIndexing() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.disableIndexing(); + txn.enableIndexing(); + txn.disableIndexing(); + txn.enableIndexing(); + } + } + + @Test + public void numKeys() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte k2[] = "key2".getBytes(UTF_8); + final byte v2[] = "value2".getBytes(UTF_8); + final byte k3[] = "key3".getBytes(UTF_8); + final byte v3[] = "value3".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + txn.put(k1, v1); + txn.put(testCf, k2, v2); + txn.merge(k3, v3); + txn.delete(testCf, k2); + + assertThat(txn.getNumKeys()).isEqualTo(3); + assertThat(txn.getNumPuts()).isEqualTo(2); + assertThat(txn.getNumMerges()).isEqualTo(1); + assertThat(txn.getNumDeletes()).isEqualTo(1); + } + } + + @Test + public void elapsedTime() throws RocksDBException, InterruptedException { + final long preStartTxnTime = System.currentTimeMillis(); + try (final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + Thread.sleep(2); + + final long txnElapsedTime = txn.getElapsedTime(); + assertThat(txnElapsedTime).isLessThan(System.currentTimeMillis() - preStartTxnTime); + assertThat(txnElapsedTime).isGreaterThan(0); + } + } + + @Test + public void getWriteBatch() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + + txn.put(k1, v1); + + final WriteBatchWithIndex writeBatch = txn.getWriteBatch(); + assertThat(writeBatch).isNotNull(); + assertThat(writeBatch.isOwningHandle()).isFalse(); + assertThat(writeBatch.count()).isEqualTo(1); + } + } + + @Test + public void setLockTimeout() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + txn.setLockTimeout(1000); + } + } + + @Test + public void writeOptions() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final WriteOptions writeOptions = new WriteOptions() + .setDisableWAL(true) + .setSync(true); + final Transaction txn = dbContainer.beginTransaction(writeOptions)) { + + txn.put(k1, v1); + + WriteOptions txnWriteOptions = txn.getWriteOptions(); + assertThat(txnWriteOptions).isNotNull(); + assertThat(txnWriteOptions.isOwningHandle()).isFalse(); + assertThat(txnWriteOptions).isNotSameAs(writeOptions); + assertThat(txnWriteOptions.disableWAL()).isTrue(); + assertThat(txnWriteOptions.sync()).isTrue(); + + txn.setWriteOptions(txnWriteOptions.setSync(false)); + txnWriteOptions = txn.getWriteOptions(); + assertThat(txnWriteOptions).isNotNull(); + assertThat(txnWriteOptions.isOwningHandle()).isFalse(); + assertThat(txnWriteOptions).isNotSameAs(writeOptions); + assertThat(txnWriteOptions.disableWAL()).isTrue(); + assertThat(txnWriteOptions.sync()).isFalse(); + } + } + + @Test + public void undoGetForUpdate_cf() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isNull(); + txn.put(testCf, k1, v1); + assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); + txn.undoGetForUpdate(testCf, k1); + } + } + + @Test + public void undoGetForUpdate() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getForUpdate(readOptions, k1, true)).isNull(); + txn.put(k1, v1); + assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1); + txn.undoGetForUpdate(k1); + } + } + + @Test + public void rebuildFromWriteBatch() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte k2[] = "key2".getBytes(UTF_8); + final byte v2[] = "value2".getBytes(UTF_8); + final byte k3[] = "key3".getBytes(UTF_8); + final byte v3[] = "value3".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions(); + final Transaction txn = dbContainer.beginTransaction()) { + + txn.put(k1, v1); + + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + assertThat(txn.getNumKeys()).isEqualTo(1); + + try(final WriteBatch writeBatch = new WriteBatch()) { + writeBatch.put(k2, v2); + writeBatch.put(k3, v3); + txn.rebuildFromWriteBatch(writeBatch); + + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + assertThat(txn.get(readOptions, k2)).isEqualTo(v2); + assertThat(txn.get(readOptions, k3)).isEqualTo(v3); + assertThat(txn.getNumKeys()).isEqualTo(3); + } + } + } + + @Test + public void getCommitTimeWriteBatch() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + + txn.put(k1, v1); + final WriteBatch writeBatch = txn.getCommitTimeWriteBatch(); + + assertThat(writeBatch).isNotNull(); + assertThat(writeBatch.isOwningHandle()).isFalse(); + assertThat(writeBatch.count()).isEqualTo(0); + } + } + + @Test + public void logNumber() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getLogNumber()).isEqualTo(0); + final long logNumber = rand.nextLong(); + txn.setLogNumber(logNumber); + assertThat(txn.getLogNumber()).isEqualTo(logNumber); + } + } + + private static byte[] concat(final byte[][] bufs) { + int resultLength = 0; + for(final byte[] buf : bufs) { + resultLength += buf.length; + } + + final byte[] result = new byte[resultLength]; + int resultOffset = 0; + for(final byte[] buf : bufs) { + final int srcLength = buf.length; + System.arraycopy(buf, 0, result, resultOffset, srcLength); + resultOffset += srcLength; + } + + return result; + } + + private static class TestTransactionNotifier + extends AbstractTransactionNotifier { + private final List createdSnapshots = new ArrayList<>(); + + @Override + public void snapshotCreated(final Snapshot newSnapshot) { + createdSnapshots.add(newSnapshot); + } + + public List getCreatedSnapshots() { + return createdSnapshots; + } + } + + protected static abstract class DBContainer + implements AutoCloseable { + protected final WriteOptions writeOptions; + protected final List columnFamilyHandles; + protected final ColumnFamilyOptions columnFamilyOptions; + protected final DBOptions options; + + public DBContainer(final WriteOptions writeOptions, + final List columnFamilyHandles, + final ColumnFamilyOptions columnFamilyOptions, + final DBOptions options) { + this.writeOptions = writeOptions; + this.columnFamilyHandles = columnFamilyHandles; + this.columnFamilyOptions = columnFamilyOptions; + this.options = options; + } + + public abstract Transaction beginTransaction(); + + public abstract Transaction beginTransaction( + final WriteOptions writeOptions); + + public ColumnFamilyHandle getTestColumnFamily() { + return columnFamilyHandles.get(1); + } + + @Override + public abstract void close(); + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java index 1caae5098e..7c50df3571 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupEngineTest.java @@ -11,6 +11,7 @@ import org.junit.rules.TemporaryFolder; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; import static org.assertj.core.api.Assertions.assertThat; @@ -205,6 +206,26 @@ public void restoreFromBackup() } } + @Test + public void backupDbWithMetadata() throws RocksDBException { + // Open empty database. + try (final Options opt = new Options().setCreateIfMissing(true); + final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { + // Fill database with some test values + prepareDatabase(db); + + // Create two backups + try (final BackupableDBOptions bopt = + new BackupableDBOptions(backupFolder.getRoot().getAbsolutePath()); + final BackupEngine be = BackupEngine.open(opt.getEnv(), bopt)) { + final String metadata = String.valueOf(ThreadLocalRandom.current().nextInt()); + be.createNewBackupWithMetadata(db, metadata, true); + final List backupInfoList = verifyNumberOfValidBackups(be, 1); + assertThat(backupInfoList.get(0).appMetadata()).isEqualTo(metadata); + } + } + } + /** * Verify backups. * diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java index c223014fd2..0b4992184c 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BackupableDBOptionsTest.java @@ -45,7 +45,7 @@ public void env() { assertThat(backupableDBOptions.backupEnv()). isNull(); - try(final Env env = new RocksMemEnv()) { + try(final Env env = new RocksMemEnv(Env.getDefault())) { backupableDBOptions.setBackupEnv(env); assertThat(backupableDBOptions.backupEnv()) .isEqualTo(env); diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java index 8edc8b89fd..fe9f863250 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/BlockBasedTableConfigTest.java @@ -6,7 +6,12 @@ package org.rocksdb; import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; @@ -16,73 +21,68 @@ public class BlockBasedTableConfigTest { public static final RocksMemoryResource rocksMemoryResource = new RocksMemoryResource(); - @Test - public void noBlockCache() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setNoBlockCache(true); - assertThat(blockBasedTableConfig.noBlockCache()).isTrue(); - } + @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); @Test - public void blockCacheSize() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockCacheSize(8 * 1024); - assertThat(blockBasedTableConfig.blockCacheSize()). - isEqualTo(8 * 1024); - } + public void cacheIndexAndFilterBlocks() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setCacheIndexAndFilterBlocks(true); + assertThat(blockBasedTableConfig.cacheIndexAndFilterBlocks()). + isTrue(); - @Test - public void blockSizeDeviation() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockSizeDeviation(12); - assertThat(blockBasedTableConfig.blockSizeDeviation()). - isEqualTo(12); } @Test - public void blockRestartInterval() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockRestartInterval(15); - assertThat(blockBasedTableConfig.blockRestartInterval()). - isEqualTo(15); + public void cacheIndexAndFilterBlocksWithHighPriority() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setCacheIndexAndFilterBlocksWithHighPriority(true); + assertThat(blockBasedTableConfig.cacheIndexAndFilterBlocksWithHighPriority()). + isTrue(); } @Test - public void wholeKeyFiltering() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setWholeKeyFiltering(false); - assertThat(blockBasedTableConfig.wholeKeyFiltering()). - isFalse(); + public void pinL0FilterAndIndexBlocksInCache() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setPinL0FilterAndIndexBlocksInCache(true); + assertThat(blockBasedTableConfig.pinL0FilterAndIndexBlocksInCache()). + isTrue(); } @Test - public void cacheIndexAndFilterBlocks() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setCacheIndexAndFilterBlocks(true); - assertThat(blockBasedTableConfig.cacheIndexAndFilterBlocks()). - isTrue(); - + public void pinTopLevelIndexAndFilter() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setPinTopLevelIndexAndFilter(false); + assertThat(blockBasedTableConfig.pinTopLevelIndexAndFilter()). + isFalse(); } @Test - public void hashIndexAllowCollision() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setHashIndexAllowCollision(false); - assertThat(blockBasedTableConfig.hashIndexAllowCollision()). - isFalse(); + public void indexType() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + assertThat(IndexType.values().length).isEqualTo(3); + blockBasedTableConfig.setIndexType(IndexType.kHashSearch); + assertThat(blockBasedTableConfig.indexType().equals( + IndexType.kHashSearch)); + assertThat(IndexType.valueOf("kBinarySearch")).isNotNull(); + blockBasedTableConfig.setIndexType(IndexType.valueOf("kBinarySearch")); + assertThat(blockBasedTableConfig.indexType().equals( + IndexType.kBinarySearch)); } @Test - public void blockCacheCompressedSize() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockCacheCompressedSize(40); - assertThat(blockBasedTableConfig.blockCacheCompressedSize()). - isEqualTo(40); + public void dataBlockIndexType() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash); + assertThat(blockBasedTableConfig.dataBlockIndexType().equals( + DataBlockIndexType.kDataBlockBinaryAndHash)); + blockBasedTableConfig.setDataBlockIndexType(DataBlockIndexType.kDataBlockBinarySearch); + assertThat(blockBasedTableConfig.dataBlockIndexType().equals( + DataBlockIndexType.kDataBlockBinarySearch)); } @Test public void checksumType() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); assertThat(ChecksumType.values().length).isEqualTo(3); assertThat(ChecksumType.valueOf("kxxHash")). isEqualTo(ChecksumType.kxxHash); @@ -93,79 +93,301 @@ public void checksumType() { } @Test - public void indexType() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - assertThat(IndexType.values().length).isEqualTo(3); - blockBasedTableConfig.setIndexType(IndexType.kHashSearch); - assertThat(blockBasedTableConfig.indexType().equals( - IndexType.kHashSearch)); - assertThat(IndexType.valueOf("kBinarySearch")).isNotNull(); - blockBasedTableConfig.setIndexType(IndexType.valueOf("kBinarySearch")); - assertThat(blockBasedTableConfig.indexType().equals( - IndexType.kBinarySearch)); + public void noBlockCache() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setNoBlockCache(true); + assertThat(blockBasedTableConfig.noBlockCache()).isTrue(); } @Test - public void blockCacheCompressedNumShardBits() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setBlockCacheCompressedNumShardBits(4); - assertThat(blockBasedTableConfig.blockCacheCompressedNumShardBits()). - isEqualTo(4); + public void blockCache() { + try ( + final Cache cache = new LRUCache(17 * 1024 * 1024); + final Options options = new Options().setTableFormatConfig( + new BlockBasedTableConfig().setBlockCache(cache))) { + assertThat(options.tableFactoryName()).isEqualTo("BlockBasedTable"); + } } @Test - public void cacheNumShardBits() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); - blockBasedTableConfig.setCacheNumShardBits(5); - assertThat(blockBasedTableConfig.cacheNumShardBits()). - isEqualTo(5); + public void blockCacheIntegration() throws RocksDBException { + try (final Cache cache = new LRUCache(8 * 1024 * 1024); + final Statistics statistics = new Statistics()) { + for (int shard = 0; shard < 8; shard++) { + try (final Options options = + new Options() + .setCreateIfMissing(true) + .setStatistics(statistics) + .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache)); + final RocksDB db = + RocksDB.open(options, dbFolder.getRoot().getAbsolutePath() + "/" + shard)) { + final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); + final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); + + db.put(key, value); + db.flush(new FlushOptions()); + db.get(key); + + assertThat(statistics.getTickerCount(TickerType.BLOCK_CACHE_ADD)).isEqualTo(shard + 1); + } + } + } + } + + @Test + public void persistentCache() throws RocksDBException { + try (final DBOptions dbOptions = new DBOptions(). + setInfoLogLevel(InfoLogLevel.INFO_LEVEL). + setCreateIfMissing(true); + final Logger logger = new Logger(dbOptions) { + @Override + protected void log(final InfoLogLevel infoLogLevel, final String logMsg) { + System.out.println(infoLogLevel.name() + ": " + logMsg); + } + }) { + try (final PersistentCache persistentCache = + new PersistentCache(Env.getDefault(), dbFolder.getRoot().getPath(), 1024 * 1024 * 100, logger, false); + final Options options = new Options().setTableFormatConfig( + new BlockBasedTableConfig().setPersistentCache(persistentCache))) { + assertThat(options.tableFactoryName()).isEqualTo("BlockBasedTable"); + } + } + } + + @Test + public void blockCacheCompressed() { + try (final Cache cache = new LRUCache(17 * 1024 * 1024); + final Options options = new Options().setTableFormatConfig( + new BlockBasedTableConfig().setBlockCacheCompressed(cache))) { + assertThat(options.tableFactoryName()).isEqualTo("BlockBasedTable"); + } + } + + @Ignore("See issue: https://github.com/facebook/rocksdb/issues/4822") + @Test + public void blockCacheCompressedIntegration() throws RocksDBException { + final byte[] key1 = "some-key1".getBytes(StandardCharsets.UTF_8); + final byte[] key2 = "some-key1".getBytes(StandardCharsets.UTF_8); + final byte[] key3 = "some-key1".getBytes(StandardCharsets.UTF_8); + final byte[] key4 = "some-key1".getBytes(StandardCharsets.UTF_8); + final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); + + try (final Cache compressedCache = new LRUCache(8 * 1024 * 1024); + final Statistics statistics = new Statistics()) { + + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig() + .setNoBlockCache(true) + .setBlockCache(null) + .setBlockCacheCompressed(compressedCache) + .setFormatVersion(4); + + try (final Options options = new Options() + .setCreateIfMissing(true) + .setStatistics(statistics) + .setTableFormatConfig(blockBasedTableConfig)) { + + for (int shard = 0; shard < 8; shard++) { + try (final FlushOptions flushOptions = new FlushOptions(); + final WriteOptions writeOptions = new WriteOptions(); + final ReadOptions readOptions = new ReadOptions(); + final RocksDB db = + RocksDB.open(options, dbFolder.getRoot().getAbsolutePath() + "/" + shard)) { + + db.put(writeOptions, key1, value); + db.put(writeOptions, key2, value); + db.put(writeOptions, key3, value); + db.put(writeOptions, key4, value); + db.flush(flushOptions); + + db.get(readOptions, key1); + db.get(readOptions, key2); + db.get(readOptions, key3); + db.get(readOptions, key4); + + assertThat(statistics.getTickerCount(TickerType.BLOCK_CACHE_COMPRESSED_ADD)).isEqualTo(shard + 1); + } + } + } + } } @Test public void blockSize() { - BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); blockBasedTableConfig.setBlockSize(10); assertThat(blockBasedTableConfig.blockSize()).isEqualTo(10); } + @Test + public void blockSizeDeviation() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setBlockSizeDeviation(12); + assertThat(blockBasedTableConfig.blockSizeDeviation()). + isEqualTo(12); + } + + @Test + public void blockRestartInterval() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setBlockRestartInterval(15); + assertThat(blockBasedTableConfig.blockRestartInterval()). + isEqualTo(15); + } + + @Test + public void indexBlockRestartInterval() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setIndexBlockRestartInterval(15); + assertThat(blockBasedTableConfig.indexBlockRestartInterval()). + isEqualTo(15); + } @Test - public void blockBasedTableWithFilter() { + public void metadataBlockSize() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setMetadataBlockSize(1024); + assertThat(blockBasedTableConfig.metadataBlockSize()). + isEqualTo(1024); + } + + @Test + public void partitionFilters() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setPartitionFilters(true); + assertThat(blockBasedTableConfig.partitionFilters()). + isTrue(); + } + + @Test + public void useDeltaEncoding() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setUseDeltaEncoding(false); + assertThat(blockBasedTableConfig.useDeltaEncoding()). + isFalse(); + } + + @Test + public void blockBasedTableWithFilterPolicy() { try(final Options options = new Options() .setTableFormatConfig(new BlockBasedTableConfig() - .setFilter(new BloomFilter(10)))) { + .setFilterPolicy(new BloomFilter(10)))) { assertThat(options.tableFactoryName()). isEqualTo("BlockBasedTable"); } } @Test - public void blockBasedTableWithoutFilter() { + public void blockBasedTableWithoutFilterPolicy() { try(final Options options = new Options().setTableFormatConfig( - new BlockBasedTableConfig().setFilter(null))) { + new BlockBasedTableConfig().setFilterPolicy(null))) { assertThat(options.tableFactoryName()). isEqualTo("BlockBasedTable"); } } @Test - public void blockBasedTableFormatVersion() { - BlockBasedTableConfig config = new BlockBasedTableConfig(); - for (int version=0; version<=2; version++) { - config.setFormatVersion(version); - assertThat(config.formatVersion()).isEqualTo(version); + public void wholeKeyFiltering() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setWholeKeyFiltering(false); + assertThat(blockBasedTableConfig.wholeKeyFiltering()). + isFalse(); + } + + @Test + public void verifyCompression() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setVerifyCompression(true); + assertThat(blockBasedTableConfig.verifyCompression()). + isTrue(); + } + + @Test + public void readAmpBytesPerBit() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setReadAmpBytesPerBit(2); + assertThat(blockBasedTableConfig.readAmpBytesPerBit()). + isEqualTo(2); + } + + @Test + public void formatVersion() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + for (int version = 0; version < 5; version++) { + blockBasedTableConfig.setFormatVersion(version); + assertThat(blockBasedTableConfig.formatVersion()).isEqualTo(version); } } @Test(expected = AssertionError.class) - public void blockBasedTableFormatVersionFailNegative() { - BlockBasedTableConfig config = new BlockBasedTableConfig(); - config.setFormatVersion(-1); + public void formatVersionFailNegative() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setFormatVersion(-1); } @Test(expected = AssertionError.class) - public void blockBasedTableFormatVersionFailIllegalVersion() { - BlockBasedTableConfig config = new BlockBasedTableConfig(); - config.setFormatVersion(3); + public void formatVersionFailIllegalVersion() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setFormatVersion(99); + } + + @Test + public void enableIndexCompression() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setEnableIndexCompression(false); + assertThat(blockBasedTableConfig.enableIndexCompression()). + isFalse(); + } + + @Test + public void blockAlign() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setBlockAlign(true); + assertThat(blockBasedTableConfig.blockAlign()). + isTrue(); + } + + @Deprecated + @Test + public void hashIndexAllowCollision() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setHashIndexAllowCollision(false); + assertThat(blockBasedTableConfig.hashIndexAllowCollision()). + isTrue(); // NOTE: setHashIndexAllowCollision should do nothing! + } + + @Deprecated + @Test + public void blockCacheSize() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setBlockCacheSize(8 * 1024); + assertThat(blockBasedTableConfig.blockCacheSize()). + isEqualTo(8 * 1024); + } + + @Deprecated + @Test + public void blockCacheNumShardBits() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setCacheNumShardBits(5); + assertThat(blockBasedTableConfig.cacheNumShardBits()). + isEqualTo(5); + } + + @Deprecated + @Test + public void blockCacheCompressedSize() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setBlockCacheCompressedSize(40); + assertThat(blockBasedTableConfig.blockCacheCompressedSize()). + isEqualTo(40); + } + + @Deprecated + @Test + public void blockCacheCompressedNumShardBits() { + final BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig(); + blockBasedTableConfig.setBlockCacheCompressedNumShardBits(4); + assertThat(blockBasedTableConfig.blockCacheCompressedNumShardBits()). + isEqualTo(4); } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java index 75749437b8..2cd8f0de9c 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyOptionsTest.java @@ -7,6 +7,7 @@ import org.junit.ClassRule; import org.junit.Test; +import org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory; import java.util.ArrayList; import java.util.List; @@ -24,6 +25,18 @@ public class ColumnFamilyOptionsTest { public static final Random rand = PlatformRandomHelper. getPlatformSpecificRandomFactory(); + @Test + public void copyConstructor() { + ColumnFamilyOptions origOpts = new ColumnFamilyOptions(); + origOpts.setNumLevels(rand.nextInt(8)); + origOpts.setTargetFileSizeMultiplier(rand.nextInt(100)); + origOpts.setLevel0StopWritesTrigger(rand.nextInt(50)); + ColumnFamilyOptions copyOpts = new ColumnFamilyOptions(origOpts); + assertThat(origOpts.numLevels()).isEqualTo(copyOpts.numLevels()); + assertThat(origOpts.targetFileSizeMultiplier()).isEqualTo(copyOpts.targetFileSizeMultiplier()); + assertThat(origOpts.level0StopWritesTrigger()).isEqualTo(copyOpts.level0StopWritesTrigger()); + } + @Test public void getColumnFamilyOptionsFromProps() { Properties properties = new Properties(); @@ -451,6 +464,23 @@ public void bottommostCompressionType() { } } + @Test + public void bottommostCompressionOptions() { + try (final ColumnFamilyOptions columnFamilyOptions = + new ColumnFamilyOptions(); + final CompressionOptions bottommostCompressionOptions = + new CompressionOptions() + .setMaxDictBytes(123)) { + + columnFamilyOptions.setBottommostCompressionOptions( + bottommostCompressionOptions); + assertThat(columnFamilyOptions.bottommostCompressionOptions()) + .isEqualTo(bottommostCompressionOptions); + assertThat(columnFamilyOptions.bottommostCompressionOptions() + .maxDictBytes()).isEqualTo(123); + } + } + @Test public void compressionOptions() { try (final ColumnFamilyOptions columnFamilyOptions @@ -529,6 +559,15 @@ public void reportBgIoStats() { } } + @Test + public void ttl() { + try (final ColumnFamilyOptions options = new ColumnFamilyOptions()) { + options.setTtl(1000 * 60); + assertThat(options.ttl()). + isEqualTo(1000 * 60); + } + } + @Test public void compactionOptionsUniversal() { try (final ColumnFamilyOptions opt = new ColumnFamilyOptions(); @@ -564,4 +603,23 @@ public void forceConsistencyChecks() { isEqualTo(booleanValue); } } + + @Test + public void compactionFilter() { + try(final ColumnFamilyOptions options = new ColumnFamilyOptions(); + final RemoveEmptyValueCompactionFilter cf = new RemoveEmptyValueCompactionFilter()) { + options.setCompactionFilter(cf); + assertThat(options.compactionFilter()).isEqualTo(cf); + } + } + + @Test + public void compactionFilterFactory() { + try(final ColumnFamilyOptions options = new ColumnFamilyOptions(); + final RemoveEmptyValueCompactionFilterFactory cff = new RemoveEmptyValueCompactionFilterFactory()) { + options.setCompactionFilterFactory(cff); + assertThat(options.compactionFilterFactory()).isEqualTo(cff); + } + } + } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java index 19fe332df9..84815b4766 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ColumnFamilyTest.java @@ -12,6 +12,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; public class ColumnFamilyTest { @@ -23,6 +24,31 @@ public class ColumnFamilyTest { @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); + @Test + public void columnFamilyDescriptorName() throws RocksDBException { + final byte[] cfName = "some_name".getBytes(UTF_8); + + try(final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions()) { + final ColumnFamilyDescriptor cfDescriptor = + new ColumnFamilyDescriptor(cfName, cfOptions); + assertThat(cfDescriptor.getName()).isEqualTo(cfName); + } + } + + @Test + public void columnFamilyDescriptorOptions() throws RocksDBException { + final byte[] cfName = "some_name".getBytes(UTF_8); + + try(final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions() + .setCompressionType(CompressionType.BZLIB2_COMPRESSION)) { + final ColumnFamilyDescriptor cfDescriptor = + new ColumnFamilyDescriptor(cfName, cfOptions); + + assertThat(cfDescriptor.getOptions().compressionType()) + .isEqualTo(CompressionType.BZLIB2_COMPRESSION); + } + } + @Test public void listColumnFamilies() throws RocksDBException { try (final Options options = new Options().setCreateIfMissing(true); @@ -47,6 +73,9 @@ public void defaultColumnFamily() throws RocksDBException { try { assertThat(cfh).isNotNull(); + assertThat(cfh.getName()).isEqualTo("default".getBytes(UTF_8)); + assertThat(cfh.getID()).isEqualTo(0); + final byte[] key = "key".getBytes(); final byte[] value = "value".getBytes(); @@ -64,15 +93,25 @@ public void defaultColumnFamily() throws RocksDBException { @Test public void createColumnFamily() throws RocksDBException { + final byte[] cfName = "new_cf".getBytes(UTF_8); + final ColumnFamilyDescriptor cfDescriptor = new ColumnFamilyDescriptor(cfName, + new ColumnFamilyOptions()); + try (final Options options = new Options().setCreateIfMissing(true); final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath())) { - final ColumnFamilyHandle columnFamilyHandle = db.createColumnFamily( - new ColumnFamilyDescriptor("new_cf".getBytes(), - new ColumnFamilyOptions())); + dbFolder.getRoot().getAbsolutePath())) { + + final ColumnFamilyHandle columnFamilyHandle = db.createColumnFamily(cfDescriptor); + try { + assertThat(columnFamilyHandle.getName()).isEqualTo(cfName); + assertThat(columnFamilyHandle.getID()).isEqualTo(1); + + final ColumnFamilyDescriptor latestDescriptor = columnFamilyHandle.getDescriptor(); + assertThat(latestDescriptor.getName()).isEqualTo(cfName); + final List columnFamilyNames = RocksDB.listColumnFamilies( - options, dbFolder.getRoot().getAbsolutePath()); + options, dbFolder.getRoot().getAbsolutePath()); assertThat(columnFamilyNames).isNotNull(); assertThat(columnFamilyNames.size()).isGreaterThan(0); assertThat(columnFamilyNames.size()).isEqualTo(2); @@ -190,10 +229,51 @@ public void createWriteDropColumnFamily() throws RocksDBException { new ColumnFamilyOptions())); db.put(tmpColumnFamilyHandle, "key".getBytes(), "value".getBytes()); db.dropColumnFamily(tmpColumnFamilyHandle); + assertThat(tmpColumnFamilyHandle.isOwningHandle()).isTrue(); + } finally { + if (tmpColumnFamilyHandle != null) { + tmpColumnFamilyHandle.close(); + } + for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { + columnFamilyHandle.close(); + } + } + } + } + + @Test + public void createWriteDropColumnFamilies() throws RocksDBException { + final List cfDescriptors = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("new_cf".getBytes())); + final List columnFamilyHandleList = new ArrayList<>(); + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final RocksDB db = RocksDB.open(options, + dbFolder.getRoot().getAbsolutePath(), cfDescriptors, + columnFamilyHandleList)) { + ColumnFamilyHandle tmpColumnFamilyHandle = null; + ColumnFamilyHandle tmpColumnFamilyHandle2 = null; + try { + tmpColumnFamilyHandle = db.createColumnFamily( + new ColumnFamilyDescriptor("tmpCF".getBytes(), + new ColumnFamilyOptions())); + tmpColumnFamilyHandle2 = db.createColumnFamily( + new ColumnFamilyDescriptor("tmpCF2".getBytes(), + new ColumnFamilyOptions())); + db.put(tmpColumnFamilyHandle, "key".getBytes(), "value".getBytes()); + db.put(tmpColumnFamilyHandle2, "key".getBytes(), "value".getBytes()); + db.dropColumnFamilies(Arrays.asList(tmpColumnFamilyHandle, tmpColumnFamilyHandle2)); + assertThat(tmpColumnFamilyHandle.isOwningHandle()).isTrue(); + assertThat(tmpColumnFamilyHandle2.isOwningHandle()).isTrue(); } finally { if (tmpColumnFamilyHandle != null) { tmpColumnFamilyHandle.close(); } + if (tmpColumnFamilyHandle2 != null) { + tmpColumnFamilyHandle2.close(); + } for (ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { columnFamilyHandle.close(); } @@ -339,6 +419,50 @@ public void multiGet() throws RocksDBException { } } + @Test + public void multiGetAsList() throws RocksDBException { + final List cfDescriptors = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("new_cf".getBytes())); + final List columnFamilyHandleList = new ArrayList<>(); + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final RocksDB db = RocksDB.open(options, + dbFolder.getRoot().getAbsolutePath(), + cfDescriptors, columnFamilyHandleList)) { + try { + db.put(columnFamilyHandleList.get(0), "key".getBytes(), + "value".getBytes()); + db.put(columnFamilyHandleList.get(1), "newcfkey".getBytes(), + "value".getBytes()); + + final List keys = Arrays.asList(new byte[][]{ + "key".getBytes(), "newcfkey".getBytes() + }); + List retValues = db.multiGetAsList(columnFamilyHandleList, + keys); + assertThat(retValues.size()).isEqualTo(2); + assertThat(new String(retValues.get(0))) + .isEqualTo("value"); + assertThat(new String(retValues.get(1))) + .isEqualTo("value"); + retValues = db.multiGetAsList(new ReadOptions(), columnFamilyHandleList, + keys); + assertThat(retValues.size()).isEqualTo(2); + assertThat(new String(retValues.get(0))) + .isEqualTo("value"); + assertThat(new String(retValues.get(1))) + .isEqualTo("value"); + } finally { + for (final ColumnFamilyHandle columnFamilyHandle : + columnFamilyHandleList) { + columnFamilyHandle.close(); + } + } + } + } + @Test public void properties() throws RocksDBException { final List cfDescriptors = Arrays.asList( @@ -365,6 +489,10 @@ public void properties() throws RocksDBException { "rocksdb.stats")).isNotNull(); assertThat(db.getProperty(columnFamilyHandleList.get(1), "rocksdb.sstables")).isNotNull(); + assertThat(db.getAggregatedLongProperty("rocksdb.estimate-num-keys")). + isNotNull(); + assertThat(db.getAggregatedLongProperty("rocksdb.estimate-num-keys")). + isGreaterThanOrEqualTo(0); } finally { for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java new file mode 100644 index 0000000000..18c187ddba --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactRangeOptionsTest.java @@ -0,0 +1,98 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; +import org.rocksdb.CompactRangeOptions.BottommostLevelCompaction; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompactRangeOptionsTest { + + static { + RocksDB.loadLibrary(); + } + + @Test + public void exclusiveManualCompaction() { + CompactRangeOptions opt = new CompactRangeOptions(); + boolean value = false; + opt.setExclusiveManualCompaction(value); + assertThat(opt.exclusiveManualCompaction()).isEqualTo(value); + value = true; + opt.setExclusiveManualCompaction(value); + assertThat(opt.exclusiveManualCompaction()).isEqualTo(value); + } + + @Test + public void bottommostLevelCompaction() { + CompactRangeOptions opt = new CompactRangeOptions(); + BottommostLevelCompaction value = BottommostLevelCompaction.kSkip; + opt.setBottommostLevelCompaction(value); + assertThat(opt.bottommostLevelCompaction()).isEqualTo(value); + value = BottommostLevelCompaction.kForce; + opt.setBottommostLevelCompaction(value); + assertThat(opt.bottommostLevelCompaction()).isEqualTo(value); + value = BottommostLevelCompaction.kIfHaveCompactionFilter; + opt.setBottommostLevelCompaction(value); + assertThat(opt.bottommostLevelCompaction()).isEqualTo(value); + } + + @Test + public void changeLevel() { + CompactRangeOptions opt = new CompactRangeOptions(); + boolean value = false; + opt.setChangeLevel(value); + assertThat(opt.changeLevel()).isEqualTo(value); + value = true; + opt.setChangeLevel(value); + assertThat(opt.changeLevel()).isEqualTo(value); + } + + @Test + public void targetLevel() { + CompactRangeOptions opt = new CompactRangeOptions(); + int value = 2; + opt.setTargetLevel(value); + assertThat(opt.targetLevel()).isEqualTo(value); + value = 3; + opt.setTargetLevel(value); + assertThat(opt.targetLevel()).isEqualTo(value); + } + + @Test + public void targetPathId() { + CompactRangeOptions opt = new CompactRangeOptions(); + int value = 2; + opt.setTargetPathId(value); + assertThat(opt.targetPathId()).isEqualTo(value); + value = 3; + opt.setTargetPathId(value); + assertThat(opt.targetPathId()).isEqualTo(value); + } + + @Test + public void allowWriteStall() { + CompactRangeOptions opt = new CompactRangeOptions(); + boolean value = false; + opt.setAllowWriteStall(value); + assertThat(opt.allowWriteStall()).isEqualTo(value); + value = true; + opt.setAllowWriteStall(value); + assertThat(opt.allowWriteStall()).isEqualTo(value); + } + + @Test + public void maxSubcompactions() { + CompactRangeOptions opt = new CompactRangeOptions(); + int value = 2; + opt.setMaxSubcompactions(value); + assertThat(opt.maxSubcompactions()).isEqualTo(value); + value = 3; + opt.setMaxSubcompactions(value); + assertThat(opt.maxSubcompactions()).isEqualTo(value); + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java new file mode 100644 index 0000000000..efa29b1d9f --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java @@ -0,0 +1,67 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompactionFilterFactoryTest { + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void columnFamilyOptions_setCompactionFilterFactory() + throws RocksDBException { + try(final DBOptions options = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final RemoveEmptyValueCompactionFilterFactory compactionFilterFactory + = new RemoveEmptyValueCompactionFilterFactory(); + final ColumnFamilyOptions new_cf_opts + = new ColumnFamilyOptions() + .setCompactionFilterFactory(compactionFilterFactory)) { + + final List cfNames = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)); + + final List cfHandles = new ArrayList<>(); + + try (final RocksDB rocksDb = RocksDB.open(options, + dbFolder.getRoot().getAbsolutePath(), cfNames, cfHandles); + ) { + try { + final byte[] key1 = "key1".getBytes(); + final byte[] key2 = "key2".getBytes(); + + final byte[] value1 = "value1".getBytes(); + final byte[] value2 = new byte[0]; + + rocksDb.put(cfHandles.get(1), key1, value1); + rocksDb.put(cfHandles.get(1), key2, value2); + + rocksDb.compactRange(cfHandles.get(1)); + + assertThat(rocksDb.get(cfHandles.get(1), key1)).isEqualTo(value1); + assertThat(rocksDb.keyMayExist(cfHandles.get(1), key2, new StringBuilder())).isFalse(); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java new file mode 100644 index 0000000000..6c920439c5 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionJobInfoTest.java @@ -0,0 +1,114 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompactionJobInfoTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Test + public void columnFamilyName() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.columnFamilyName()) + .isEmpty(); + } + } + + @Test + public void status() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.status().getCode()) + .isEqualTo(Status.Code.Ok); + } + } + + @Test + public void threadId() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.threadId()) + .isEqualTo(0); + } + } + + @Test + public void jobId() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.jobId()) + .isEqualTo(0); + } + } + + @Test + public void baseInputLevel() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.baseInputLevel()) + .isEqualTo(0); + } + } + + @Test + public void outputLevel() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.outputLevel()) + .isEqualTo(0); + } + } + + @Test + public void inputFiles() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.inputFiles()) + .isEmpty(); + } + } + + @Test + public void outputFiles() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.outputFiles()) + .isEmpty(); + } + } + + @Test + public void tableProperties() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.tableProperties()) + .isEmpty(); + } + } + + @Test + public void compactionReason() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.compactionReason()) + .isEqualTo(CompactionReason.kUnknown); + } + } + + @Test + public void compression() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.compression()) + .isEqualTo(CompressionType.NO_COMPRESSION); + } + } + + @Test + public void stats() { + try (final CompactionJobInfo compactionJobInfo = new CompactionJobInfo()) { + assertThat(compactionJobInfo.stats()) + .isNotNull(); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java new file mode 100644 index 0000000000..7be7226dac --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionJobStatsTest.java @@ -0,0 +1,196 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompactionJobStatsTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Test + public void reset() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + compactionJobStats.reset(); + assertThat(compactionJobStats.elapsedMicros()).isEqualTo(0); + } + } + + @Test + public void add() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats(); + final CompactionJobStats otherCompactionJobStats = new CompactionJobStats()) { + compactionJobStats.add(otherCompactionJobStats); + } + } + + @Test + public void elapsedMicros() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.elapsedMicros()).isEqualTo(0); + } + } + + @Test + public void numInputRecords() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numInputRecords()).isEqualTo(0); + } + } + + @Test + public void numInputFiles() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numInputFiles()).isEqualTo(0); + } + } + + @Test + public void numInputFilesAtOutputLevel() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numInputFilesAtOutputLevel()).isEqualTo(0); + } + } + + @Test + public void numOutputRecords() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numOutputRecords()).isEqualTo(0); + } + } + + @Test + public void numOutputFiles() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numOutputFiles()).isEqualTo(0); + } + } + + @Test + public void isManualCompaction() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.isManualCompaction()).isFalse(); + } + } + + @Test + public void totalInputBytes() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.totalInputBytes()).isEqualTo(0); + } + } + + @Test + public void totalOutputBytes() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.totalOutputBytes()).isEqualTo(0); + } + } + + + @Test + public void numRecordsReplaced() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numRecordsReplaced()).isEqualTo(0); + } + } + + @Test + public void totalInputRawKeyBytes() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.totalInputRawKeyBytes()).isEqualTo(0); + } + } + + @Test + public void totalInputRawValueBytes() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.totalInputRawValueBytes()).isEqualTo(0); + } + } + + @Test + public void numInputDeletionRecords() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numInputDeletionRecords()).isEqualTo(0); + } + } + + @Test + public void numExpiredDeletionRecords() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numExpiredDeletionRecords()).isEqualTo(0); + } + } + + @Test + public void numCorruptKeys() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numCorruptKeys()).isEqualTo(0); + } + } + + @Test + public void fileWriteNanos() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.fileWriteNanos()).isEqualTo(0); + } + } + + @Test + public void fileRangeSyncNanos() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.fileRangeSyncNanos()).isEqualTo(0); + } + } + + @Test + public void fileFsyncNanos() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.fileFsyncNanos()).isEqualTo(0); + } + } + + @Test + public void filePrepareWriteNanos() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.filePrepareWriteNanos()).isEqualTo(0); + } + } + + @Test + public void smallestOutputKeyPrefix() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.smallestOutputKeyPrefix()).isEmpty(); + } + } + + @Test + public void largestOutputKeyPrefix() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.largestOutputKeyPrefix()).isEmpty(); + } + } + + @Test + public void numSingleDelFallthru() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numSingleDelFallthru()).isEqualTo(0); + } + } + + @Test + public void numSingleDelMismatch() { + try (final CompactionJobStats compactionJobStats = new CompactionJobStats()) { + assertThat(compactionJobStats.numSingleDelMismatch()).isEqualTo(0); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java index 370a28e819..841615e67e 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsFIFOTest.java @@ -18,9 +18,18 @@ public class CompactionOptionsFIFOTest { @Test public void maxTableFilesSize() { final long size = 500 * 1024 * 1026; - try(final CompactionOptionsFIFO opt = new CompactionOptionsFIFO()) { + try (final CompactionOptionsFIFO opt = new CompactionOptionsFIFO()) { opt.setMaxTableFilesSize(size); assertThat(opt.maxTableFilesSize()).isEqualTo(size); } } + + @Test + public void allowCompaction() { + final boolean allowCompaction = true; + try (final CompactionOptionsFIFO opt = new CompactionOptionsFIFO()) { + opt.setAllowCompaction(allowCompaction); + assertThat(opt.allowCompaction()).isEqualTo(allowCompaction); + } + } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsTest.java new file mode 100644 index 0000000000..b1726e866c --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompactionOptionsTest.java @@ -0,0 +1,52 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompactionOptionsTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Test + public void compression() { + try (final CompactionOptions compactionOptions = new CompactionOptions()) { + assertThat(compactionOptions.compression()) + .isEqualTo(CompressionType.SNAPPY_COMPRESSION); + compactionOptions.setCompression(CompressionType.NO_COMPRESSION); + assertThat(compactionOptions.compression()) + .isEqualTo(CompressionType.NO_COMPRESSION); + } + } + + @Test + public void outputFileSizeLimit() { + final long mb250 = 1024 * 1024 * 250; + try (final CompactionOptions compactionOptions = new CompactionOptions()) { + assertThat(compactionOptions.outputFileSizeLimit()) + .isEqualTo(-1); + compactionOptions.setOutputFileSizeLimit(mb250); + assertThat(compactionOptions.outputFileSizeLimit()) + .isEqualTo(mb250); + } + } + + @Test + public void maxSubcompactions() { + try (final CompactionOptions compactionOptions = new CompactionOptions()) { + assertThat(compactionOptions.maxSubcompactions()) + .isEqualTo(0); + compactionOptions.setMaxSubcompactions(9); + assertThat(compactionOptions.maxSubcompactions()) + .isEqualTo(9); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java index c49224ca36..116552c328 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/CompressionOptionsTest.java @@ -50,4 +50,22 @@ public void maxDictBytes() { assertThat(opt.maxDictBytes()).isEqualTo(maxDictBytes); } } + + @Test + public void zstdMaxTrainBytes() { + final int zstdMaxTrainBytes = 999; + try(final CompressionOptions opt = new CompressionOptions()) { + opt.setZStdMaxTrainBytes(zstdMaxTrainBytes); + assertThat(opt.zstdMaxTrainBytes()).isEqualTo(zstdMaxTrainBytes); + } + } + + @Test + public void enabled() { + try(final CompressionOptions opt = new CompressionOptions()) { + assertThat(opt.enabled()).isFalse(); + opt.setEnabled(true); + assertThat(opt.enabled()).isTrue(); + } + } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java index 11b7435d8a..e6ebc46cd2 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/DBOptionsTest.java @@ -22,6 +22,19 @@ public class DBOptionsTest { public static final Random rand = PlatformRandomHelper. getPlatformSpecificRandomFactory(); + @Test + public void copyConstructor() { + DBOptions origOpts = new DBOptions(); + origOpts.setCreateIfMissing(rand.nextBoolean()); + origOpts.setAllow2pc(rand.nextBoolean()); + origOpts.setBaseBackgroundCompactions(rand.nextInt(10)); + DBOptions copyOpts = new DBOptions(origOpts); + assertThat(origOpts.createIfMissing()).isEqualTo(copyOpts.createIfMissing()); + assertThat(origOpts.allow2pc()).isEqualTo(copyOpts.allow2pc()); + assertThat(origOpts.baseBackgroundCompactions()).isEqualTo( + copyOpts.baseBackgroundCompactions()); + } + @Test public void getDBOptionsFromProps() { // setup sample properties @@ -240,6 +253,15 @@ public void maxBackgroundFlushes() { } } + @Test + public void maxBackgroundJobs() { + try (final DBOptions opt = new DBOptions()) { + final int intValue = rand.nextInt(); + opt.setMaxBackgroundJobs(intValue); + assertThat(opt.maxBackgroundJobs()).isEqualTo(intValue); + } + } + @Test public void maxLogFileSize() throws RocksDBException { try(final DBOptions opt = new DBOptions()) { @@ -402,6 +424,26 @@ public void dbWriteBufferSize() { } } + @Test + public void setWriteBufferManager() throws RocksDBException { + try (final DBOptions opt = new DBOptions(); + final Cache cache = new LRUCache(1 * 1024 * 1024); + final WriteBufferManager writeBufferManager = new WriteBufferManager(2000l, cache)) { + opt.setWriteBufferManager(writeBufferManager); + assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); + } + } + + @Test + public void setWriteBufferManagerWithZeroBufferSize() throws RocksDBException { + try (final DBOptions opt = new DBOptions(); + final Cache cache = new LRUCache(1 * 1024 * 1024); + final WriteBufferManager writeBufferManager = new WriteBufferManager(0l, cache)) { + opt.setWriteBufferManager(writeBufferManager); + assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); + } + } + @Test public void accessHintOnCompactionStart() { try(final DBOptions opt = new DBOptions()) { @@ -492,6 +534,15 @@ public void delayedWriteRate() { } } + @Test + public void enablePipelinedWrite() { + try(final DBOptions opt = new DBOptions()) { + assertThat(opt.enablePipelinedWrite()).isFalse(); + opt.setEnablePipelinedWrite(true); + assertThat(opt.enablePipelinedWrite()).isTrue(); + } + } + @Test public void allowConcurrentMemtableWrite() { try (final DBOptions opt = new DBOptions()) { @@ -573,6 +624,38 @@ public void rowCache() { } } + @Test + public void walFilter() { + try (final DBOptions opt = new DBOptions()) { + assertThat(opt.walFilter()).isNull(); + + try (final AbstractWalFilter walFilter = new AbstractWalFilter() { + @Override + public void columnFamilyLogNumberMap( + final Map cfLognumber, + final Map cfNameId) { + // no-op + } + + @Override + public LogRecordFoundResult logRecordFound(final long logNumber, + final String logFileName, final WriteBatch batch, + final WriteBatch newBatch) { + return new LogRecordFoundResult( + WalProcessingOption.CONTINUE_PROCESSING, false); + } + + @Override + public String name() { + return "test-wal-filter"; + } + }) { + opt.setWalFilter(walFilter); + assertThat(opt.walFilter()).isEqualTo(walFilter); + } + } + } + @Test public void failIfOptionsFileError() { try (final DBOptions opt = new DBOptions()) { @@ -609,6 +692,51 @@ public void avoidFlushDuringShutdown() { } } + @Test + public void allowIngestBehind() { + try (final DBOptions opt = new DBOptions()) { + assertThat(opt.allowIngestBehind()).isFalse(); + opt.setAllowIngestBehind(true); + assertThat(opt.allowIngestBehind()).isTrue(); + } + } + + @Test + public void preserveDeletes() { + try (final DBOptions opt = new DBOptions()) { + assertThat(opt.preserveDeletes()).isFalse(); + opt.setPreserveDeletes(true); + assertThat(opt.preserveDeletes()).isTrue(); + } + } + + @Test + public void twoWriteQueues() { + try (final DBOptions opt = new DBOptions()) { + assertThat(opt.twoWriteQueues()).isFalse(); + opt.setTwoWriteQueues(true); + assertThat(opt.twoWriteQueues()).isTrue(); + } + } + + @Test + public void manualWalFlush() { + try (final DBOptions opt = new DBOptions()) { + assertThat(opt.manualWalFlush()).isFalse(); + opt.setManualWalFlush(true); + assertThat(opt.manualWalFlush()).isTrue(); + } + } + + @Test + public void atomicFlush() { + try (final DBOptions opt = new DBOptions()) { + assertThat(opt.atomicFlush()).isFalse(); + opt.setAtomicFlush(true); + assertThat(opt.atomicFlush()).isTrue(); + } + } + @Test public void rateLimiter() { try(final DBOptions options = new DBOptions(); @@ -621,6 +749,15 @@ public void rateLimiter() { } } + @Test + public void sstFileManager() throws RocksDBException { + try (final DBOptions options = new DBOptions(); + final SstFileManager sstFileManager = + new SstFileManager(Env.getDefault())) { + options.setSstFileManager(sstFileManager); + } + } + @Test public void statistics() { try(final DBOptions options = new DBOptions()) { diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/DefaultEnvTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/DefaultEnvTest.java new file mode 100644 index 0000000000..9e4f043871 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/DefaultEnvTest.java @@ -0,0 +1,113 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultEnvTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void backgroundThreads() { + try (final Env defaultEnv = RocksEnv.getDefault()) { + defaultEnv.setBackgroundThreads(5, Priority.BOTTOM); + assertThat(defaultEnv.getBackgroundThreads(Priority.BOTTOM)).isEqualTo(5); + + defaultEnv.setBackgroundThreads(5); + assertThat(defaultEnv.getBackgroundThreads(Priority.LOW)).isEqualTo(5); + + defaultEnv.setBackgroundThreads(5, Priority.LOW); + assertThat(defaultEnv.getBackgroundThreads(Priority.LOW)).isEqualTo(5); + + defaultEnv.setBackgroundThreads(5, Priority.HIGH); + assertThat(defaultEnv.getBackgroundThreads(Priority.HIGH)).isEqualTo(5); + } + } + + @Test + public void threadPoolQueueLen() { + try (final Env defaultEnv = RocksEnv.getDefault()) { + assertThat(defaultEnv.getThreadPoolQueueLen(Priority.BOTTOM)).isEqualTo(0); + assertThat(defaultEnv.getThreadPoolQueueLen(Priority.LOW)).isEqualTo(0); + assertThat(defaultEnv.getThreadPoolQueueLen(Priority.HIGH)).isEqualTo(0); + } + } + + @Test + public void incBackgroundThreadsIfNeeded() { + try (final Env defaultEnv = RocksEnv.getDefault()) { + defaultEnv.incBackgroundThreadsIfNeeded(20, Priority.BOTTOM); + assertThat(defaultEnv.getBackgroundThreads(Priority.BOTTOM)).isGreaterThanOrEqualTo(20); + + defaultEnv.incBackgroundThreadsIfNeeded(20, Priority.LOW); + assertThat(defaultEnv.getBackgroundThreads(Priority.LOW)).isGreaterThanOrEqualTo(20); + + defaultEnv.incBackgroundThreadsIfNeeded(20, Priority.HIGH); + assertThat(defaultEnv.getBackgroundThreads(Priority.HIGH)).isGreaterThanOrEqualTo(20); + } + } + + @Test + public void lowerThreadPoolIOPriority() { + try (final Env defaultEnv = RocksEnv.getDefault()) { + defaultEnv.lowerThreadPoolIOPriority(Priority.BOTTOM); + + defaultEnv.lowerThreadPoolIOPriority(Priority.LOW); + + defaultEnv.lowerThreadPoolIOPriority(Priority.HIGH); + } + } + + @Test + public void lowerThreadPoolCPUPriority() { + try (final Env defaultEnv = RocksEnv.getDefault()) { + defaultEnv.lowerThreadPoolCPUPriority(Priority.BOTTOM); + + defaultEnv.lowerThreadPoolCPUPriority(Priority.LOW); + + defaultEnv.lowerThreadPoolCPUPriority(Priority.HIGH); + } + } + + @Test + public void threadList() throws RocksDBException { + try (final Env defaultEnv = RocksEnv.getDefault()) { + final Collection threadList = defaultEnv.getThreadList(); + assertThat(threadList.size()).isGreaterThan(0); + } + } + + @Test + public void threadList_integration() throws RocksDBException { + try (final Env env = RocksEnv.getDefault(); + final Options opt = new Options() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true) + .setEnv(env)) { + // open database + try (final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath())) { + + final List threadList = env.getThreadList(); + assertThat(threadList.size()).isGreaterThan(0); + } + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java index 9933b1e1db..9be61b7d70 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/EnvOptionsTest.java @@ -18,6 +18,18 @@ public class EnvOptionsTest { public static final Random rand = PlatformRandomHelper.getPlatformSpecificRandomFactory(); + @Test + public void dbOptionsConstructor() { + final long compactionReadaheadSize = 4 * 1024 * 1024; + try (final DBOptions dbOptions = new DBOptions() + .setCompactionReadaheadSize(compactionReadaheadSize)) { + try (final EnvOptions envOptions = new EnvOptions(dbOptions)) { + assertThat(envOptions.compactionReadaheadSize()) + .isEqualTo(compactionReadaheadSize); + } + } + } + @Test public void useMmapReads() { try (final EnvOptions envOptions = new EnvOptions()) { diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/FlushOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/FlushOptionsTest.java new file mode 100644 index 0000000000..f90ae911d8 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/FlushOptionsTest.java @@ -0,0 +1,31 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FlushOptionsTest { + + @Test + public void waitForFlush() { + try (final FlushOptions flushOptions = new FlushOptions()) { + assertThat(flushOptions.waitForFlush()).isTrue(); + flushOptions.setWaitForFlush(false); + assertThat(flushOptions.waitForFlush()).isFalse(); + } + } + + @Test + public void allowWriteStall() { + try (final FlushOptions flushOptions = new FlushOptions()) { + assertThat(flushOptions.allowWriteStall()).isFalse(); + flushOptions.setAllowWriteStall(true); + assertThat(flushOptions.allowWriteStall()).isTrue(); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/HdfsEnvTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/HdfsEnvTest.java new file mode 100644 index 0000000000..3a91c5cad4 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/HdfsEnvTest.java @@ -0,0 +1,45 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class HdfsEnvTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + // expect org.rocksdb.RocksDBException: Not compiled with hdfs support + @Test(expected = RocksDBException.class) + public void construct() throws RocksDBException { + try (final Env env = new HdfsEnv("hdfs://localhost:5000")) { + // no-op + } + } + + // expect org.rocksdb.RocksDBException: Not compiled with hdfs support + @Test(expected = RocksDBException.class) + public void construct_integration() throws RocksDBException { + try (final Env env = new HdfsEnv("hdfs://localhost:5000"); + final Options options = new Options() + .setCreateIfMissing(true) + .setEnv(env); + ) { + try (final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getPath())) { + db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + } + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java index 48ecfa16a9..b215dd17ff 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/InfoLogLevelTest.java @@ -27,6 +27,7 @@ public void testInfoLogLevel() throws RocksDBException, try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath())) { db.put("key".getBytes(), "value".getBytes()); + db.flush(new FlushOptions().setWaitForFlush(true)); assertThat(getLogContentsWithoutHeader()).isNotEmpty(); } } @@ -93,7 +94,7 @@ private String getLogContentsWithoutHeader() throws IOException { int first_non_header = lines.length; // Identify the last line of the header for (int i = lines.length - 1; i >= 0; --i) { - if (lines[i].indexOf("Options.") >= 0 && lines[i].indexOf(':') >= 0) { + if (lines[i].indexOf("DB pointer") >= 0) { first_non_header = i + 1; break; } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java index 83e0dd17af..a3973ccd9c 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java @@ -84,4 +84,24 @@ public void allowBlockingFlush() { assertThat(options.allowBlockingFlush()).isEqualTo(allowBlockingFlush); } } + + @Test + public void ingestBehind() { + try (final IngestExternalFileOptions options = + new IngestExternalFileOptions()) { + assertThat(options.ingestBehind()).isFalse(); + options.setIngestBehind(true); + assertThat(options.ingestBehind()).isTrue(); + } + } + + @Test + public void writeGlobalSeqno() { + try (final IngestExternalFileOptions options = + new IngestExternalFileOptions()) { + assertThat(options.writeGlobalSeqno()).isTrue(); + options.setWriteGlobalSeqno(false); + assertThat(options.writeGlobalSeqno()).isFalse(); + } + } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java index 8092270eb2..577fe2eadf 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/KeyMayExistTest.java @@ -48,12 +48,33 @@ public void keyMayExist() throws RocksDBException { assertThat(exists).isTrue(); assertThat(retValue.toString()).isEqualTo("value"); + // Slice key + StringBuilder builder = new StringBuilder("prefix"); + int offset = builder.toString().length(); + builder.append("slice key 0"); + int len = builder.toString().length() - offset; + builder.append("suffix"); + + byte[] sliceKey = builder.toString().getBytes(); + byte[] sliceValue = "slice value 0".getBytes(); + db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); + + retValue = new StringBuilder(); + exists = db.keyMayExist(sliceKey, offset, len, retValue); + assertThat(exists).isTrue(); + assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue); + // Test without column family but with readOptions try (final ReadOptions readOptions = new ReadOptions()) { retValue = new StringBuilder(); exists = db.keyMayExist(readOptions, "key".getBytes(), retValue); assertThat(exists).isTrue(); assertThat(retValue.toString()).isEqualTo("value"); + + retValue = new StringBuilder(); + exists = db.keyMayExist(readOptions, sliceKey, offset, len, retValue); + assertThat(exists).isTrue(); + assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue); } // Test with column family @@ -63,6 +84,13 @@ public void keyMayExist() throws RocksDBException { assertThat(exists).isTrue(); assertThat(retValue.toString()).isEqualTo("value"); + // Test slice sky with column family + retValue = new StringBuilder(); + exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len, + retValue); + assertThat(exists).isTrue(); + assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue); + // Test with column family and readOptions try (final ReadOptions readOptions = new ReadOptions()) { retValue = new StringBuilder(); @@ -71,11 +99,23 @@ public void keyMayExist() throws RocksDBException { retValue); assertThat(exists).isTrue(); assertThat(retValue.toString()).isEqualTo("value"); + + // Test slice key with column family and read options + retValue = new StringBuilder(); + exists = db.keyMayExist(readOptions, + columnFamilyHandleList.get(0), sliceKey, offset, len, + retValue); + assertThat(exists).isTrue(); + assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue); } // KeyMayExist in CF1 must return false assertThat(db.keyMayExist(columnFamilyHandleList.get(1), "key".getBytes(), retValue)).isFalse(); + + // slice key + assertThat(db.keyMayExist(columnFamilyHandleList.get(1), + sliceKey, 1, 3, retValue)).isFalse(); } finally { for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MemoryUtilTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MemoryUtilTest.java new file mode 100644 index 0000000000..73fcc87c32 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MemoryUtilTest.java @@ -0,0 +1,143 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MemoryUtilTest { + + private static final String MEMTABLE_SIZE = "rocksdb.size-all-mem-tables"; + private static final String UNFLUSHED_MEMTABLE_SIZE = "rocksdb.cur-size-all-mem-tables"; + private static final String TABLE_READERS = "rocksdb.estimate-table-readers-mem"; + + private final byte[] key = "some-key".getBytes(StandardCharsets.UTF_8); + private final byte[] value = "some-value".getBytes(StandardCharsets.UTF_8); + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Rule public TemporaryFolder dbFolder1 = new TemporaryFolder(); + @Rule public TemporaryFolder dbFolder2 = new TemporaryFolder(); + + /** + * Test MemoryUtil.getApproximateMemoryUsageByType before and after a put + get + */ + @Test + public void getApproximateMemoryUsageByType() throws RocksDBException { + try (final Cache cache = new LRUCache(8 * 1024 * 1024); + final Options options = + new Options() + .setCreateIfMissing(true) + .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache)); + final FlushOptions flushOptions = + new FlushOptions().setWaitForFlush(true); + final RocksDB db = + RocksDB.open(options, dbFolder1.getRoot().getAbsolutePath())) { + + List dbs = new ArrayList<>(1); + dbs.add(db); + Set caches = new HashSet<>(1); + caches.add(cache); + Map usage = MemoryUtil.getApproximateMemoryUsageByType(dbs, caches); + + assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo( + db.getAggregatedLongProperty(MEMTABLE_SIZE)); + assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo( + db.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE)); + assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo( + db.getAggregatedLongProperty(TABLE_READERS)); + assertThat(usage.get(MemoryUsageType.kCacheTotal)).isEqualTo(0); + + db.put(key, value); + db.flush(flushOptions); + db.get(key); + + usage = MemoryUtil.getApproximateMemoryUsageByType(dbs, caches); + assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isGreaterThan(0); + assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo( + db.getAggregatedLongProperty(MEMTABLE_SIZE)); + assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isGreaterThan(0); + assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo( + db.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE)); + assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isGreaterThan(0); + assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo( + db.getAggregatedLongProperty(TABLE_READERS)); + assertThat(usage.get(MemoryUsageType.kCacheTotal)).isGreaterThan(0); + + } + } + + /** + * Test MemoryUtil.getApproximateMemoryUsageByType with null inputs + */ + @Test + public void getApproximateMemoryUsageByTypeNulls() throws RocksDBException { + Map usage = MemoryUtil.getApproximateMemoryUsageByType(null, null); + + assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo(null); + assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo(null); + assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo(null); + assertThat(usage.get(MemoryUsageType.kCacheTotal)).isEqualTo(null); + } + + /** + * Test MemoryUtil.getApproximateMemoryUsageByType with two DBs and two caches + */ + @Test + public void getApproximateMemoryUsageByTypeMultiple() throws RocksDBException { + try (final Cache cache1 = new LRUCache(1 * 1024 * 1024); + final Options options1 = + new Options() + .setCreateIfMissing(true) + .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache1)); + final RocksDB db1 = + RocksDB.open(options1, dbFolder1.getRoot().getAbsolutePath()); + final Cache cache2 = new LRUCache(1 * 1024 * 1024); + final Options options2 = + new Options() + .setCreateIfMissing(true) + .setTableFormatConfig(new BlockBasedTableConfig().setBlockCache(cache2)); + final RocksDB db2 = + RocksDB.open(options2, dbFolder2.getRoot().getAbsolutePath()); + final FlushOptions flushOptions = + new FlushOptions().setWaitForFlush(true); + + ) { + List dbs = new ArrayList<>(1); + dbs.add(db1); + dbs.add(db2); + Set caches = new HashSet<>(1); + caches.add(cache1); + caches.add(cache2); + + for (RocksDB db: dbs) { + db.put(key, value); + db.flush(flushOptions); + db.get(key); + } + + Map usage = MemoryUtil.getApproximateMemoryUsageByType(dbs, caches); + assertThat(usage.get(MemoryUsageType.kMemTableTotal)).isEqualTo( + db1.getAggregatedLongProperty(MEMTABLE_SIZE) + db2.getAggregatedLongProperty(MEMTABLE_SIZE)); + assertThat(usage.get(MemoryUsageType.kMemTableUnFlushed)).isEqualTo( + db1.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE) + db2.getAggregatedLongProperty(UNFLUSHED_MEMTABLE_SIZE)); + assertThat(usage.get(MemoryUsageType.kTableReadersTotal)).isEqualTo( + db1.getAggregatedLongProperty(TABLE_READERS) + db2.getAggregatedLongProperty(TABLE_READERS)); + assertThat(usage.get(MemoryUsageType.kCacheTotal)).isGreaterThan(0); + + } + } + +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java index 73b90869cf..5546984761 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MergeTest.java @@ -5,6 +5,7 @@ package org.rocksdb; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import java.util.ArrayList; @@ -44,6 +45,38 @@ public void stringOption() } } + private byte[] longToByteArray(long l) { + ByteBuffer buf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE); + buf.putLong(l); + return buf.array(); + } + + private long longFromByteArray(byte[] a) { + ByteBuffer buf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE); + buf.put(a); + buf.flip(); + return buf.getLong(); + } + + @Test + public void uint64AddOption() + throws InterruptedException, RocksDBException { + try (final Options opt = new Options() + .setCreateIfMissing(true) + .setMergeOperatorName("uint64add"); + final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath())) { + // writing (long)100 under key + db.put("key".getBytes(), longToByteArray(100)); + // merge (long)1 under key + db.merge("key".getBytes(), longToByteArray(1)); + + final byte[] value = db.get("key".getBytes()); + final long longValue = longFromByteArray(value); + assertThat(longValue).isEqualTo(101); + } + } + @Test public void cFStringOption() throws InterruptedException, RocksDBException { @@ -86,6 +119,48 @@ public void cFStringOption() } } + @Test + public void cFUInt64AddOption() + throws InterruptedException, RocksDBException { + + try (final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions() + .setMergeOperatorName("uint64add"); + final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions() + .setMergeOperatorName("uint64add") + ) { + final List cfDescriptors = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1), + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt2) + ); + + final List columnFamilyHandleList = new ArrayList<>(); + try (final DBOptions opt = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath(), cfDescriptors, + columnFamilyHandleList)) { + try { + // writing (long)100 under key + db.put(columnFamilyHandleList.get(1), + "cfkey".getBytes(), longToByteArray(100)); + // merge (long)1 under key + db.merge(columnFamilyHandleList.get(1), + "cfkey".getBytes(), longToByteArray(1)); + + byte[] value = db.get(columnFamilyHandleList.get(1), + "cfkey".getBytes()); + long longValue = longFromByteArray(value); + assertThat(longValue).isEqualTo(101); + } finally { + for (final ColumnFamilyHandle handle : columnFamilyHandleList) { + handle.close(); + } + } + } + } + } + @Test public void operatorOption() throws InterruptedException, RocksDBException { @@ -108,6 +183,28 @@ public void operatorOption() } } + @Test + public void uint64AddOperatorOption() + throws InterruptedException, RocksDBException { + try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator(); + final Options opt = new Options() + .setCreateIfMissing(true) + .setMergeOperator(uint64AddOperator); + final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath())) { + // Writing (long)100 under key + db.put("key".getBytes(), longToByteArray(100)); + + // Writing (long)1 under key + db.merge("key".getBytes(), longToByteArray(1)); + + final byte[] value = db.get("key".getBytes()); + final long longValue = longFromByteArray(value); + + assertThat(longValue).isEqualTo(101); + } + } + @Test public void cFOperatorOption() throws InterruptedException, RocksDBException { @@ -170,6 +267,68 @@ public void cFOperatorOption() } } + @Test + public void cFUInt64AddOperatorOption() + throws InterruptedException, RocksDBException { + try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator(); + final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions() + .setMergeOperator(uint64AddOperator); + final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions() + .setMergeOperator(uint64AddOperator) + ) { + final List cfDescriptors = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1), + new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpt2) + ); + final List columnFamilyHandleList = new ArrayList<>(); + try (final DBOptions opt = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath(), cfDescriptors, + columnFamilyHandleList) + ) { + try { + // writing (long)100 under key + db.put(columnFamilyHandleList.get(1), + "cfkey".getBytes(), longToByteArray(100)); + // merge (long)1 under key + db.merge(columnFamilyHandleList.get(1), + "cfkey".getBytes(), longToByteArray(1)); + byte[] value = db.get(columnFamilyHandleList.get(1), + "cfkey".getBytes()); + long longValue = longFromByteArray(value); + + // Test also with createColumnFamily + try (final ColumnFamilyOptions cfHandleOpts = + new ColumnFamilyOptions() + .setMergeOperator(uint64AddOperator); + final ColumnFamilyHandle cfHandle = + db.createColumnFamily( + new ColumnFamilyDescriptor("new_cf2".getBytes(), + cfHandleOpts)) + ) { + // writing (long)200 under cfkey2 + db.put(cfHandle, "cfkey2".getBytes(), longToByteArray(200)); + // merge (long)50 under cfkey2 + db.merge(cfHandle, new WriteOptions(), "cfkey2".getBytes(), + longToByteArray(50)); + value = db.get(cfHandle, "cfkey2".getBytes()); + long longValueTmpCf = longFromByteArray(value); + + assertThat(longValue).isEqualTo(101); + assertThat(longValueTmpCf).isEqualTo(250); + } + } finally { + for (final ColumnFamilyHandle columnFamilyHandle : + columnFamilyHandleList) { + columnFamilyHandle.close(); + } + } + } + } + } + @Test public void operatorGcBehaviour() throws RocksDBException { @@ -182,7 +341,6 @@ public void operatorGcBehaviour() //no-op } - // test reuse try (final Options opt = new Options() .setMergeOperator(stringAppendOperator); @@ -213,6 +371,48 @@ public void operatorGcBehaviour() } } + @Test + public void uint64AddOperatorGcBehaviour() + throws RocksDBException { + try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator()) { + try (final Options opt = new Options() + .setCreateIfMissing(true) + .setMergeOperator(uint64AddOperator); + final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath())) { + //no-op + } + + // test reuse + try (final Options opt = new Options() + .setMergeOperator(uint64AddOperator); + final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath())) { + //no-op + } + + // test param init + try (final UInt64AddOperator uint64AddOperator2 = new UInt64AddOperator(); + final Options opt = new Options() + .setMergeOperator(uint64AddOperator2); + final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath())) { + //no-op + } + + // test replace one with another merge operator instance + try (final Options opt = new Options() + .setMergeOperator(uint64AddOperator); + final UInt64AddOperator newUInt64AddOperator = new UInt64AddOperator()) { + opt.setMergeOperator(newUInt64AddOperator); + try (final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath())) { + //no-op + } + } + } + } + @Test public void emptyStringInSetMergeOperatorByName() { try (final Options opt = new Options() diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java new file mode 100644 index 0000000000..1ce3e11775 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/MutableDBOptionsTest.java @@ -0,0 +1,84 @@ +// Copyright (c) 2011-present, Facebook, 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). +package org.rocksdb; + +import org.junit.Test; +import org.rocksdb.MutableDBOptions.MutableDBOptionsBuilder; + +import java.util.NoSuchElementException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MutableDBOptionsTest { + + @Test + public void builder() { + final MutableDBOptionsBuilder builder = + MutableDBOptions.builder(); + builder + .setBytesPerSync(1024 * 1024 * 7) + .setMaxBackgroundJobs(5) + .setAvoidFlushDuringShutdown(false); + + assertThat(builder.bytesPerSync()).isEqualTo(1024 * 1024 * 7); + assertThat(builder.maxBackgroundJobs()).isEqualTo(5); + assertThat(builder.avoidFlushDuringShutdown()).isEqualTo(false); + } + + @Test(expected = NoSuchElementException.class) + public void builder_getWhenNotSet() { + final MutableDBOptionsBuilder builder = + MutableDBOptions.builder(); + + builder.bytesPerSync(); + } + + @Test + public void builder_build() { + final MutableDBOptions options = MutableDBOptions + .builder() + .setBytesPerSync(1024 * 1024 * 7) + .setMaxBackgroundJobs(5) + .build(); + + assertThat(options.getKeys().length).isEqualTo(2); + assertThat(options.getValues().length).isEqualTo(2); + assertThat(options.getKeys()[0]) + .isEqualTo( + MutableDBOptions.DBOption.bytes_per_sync.name()); + assertThat(options.getValues()[0]).isEqualTo("7340032"); + assertThat(options.getKeys()[1]) + .isEqualTo( + MutableDBOptions.DBOption.max_background_jobs.name()); + assertThat(options.getValues()[1]).isEqualTo("5"); + } + + @Test + public void mutableColumnFamilyOptions_toString() { + final String str = MutableDBOptions + .builder() + .setMaxOpenFiles(99) + .setDelayedWriteRate(789) + .setAvoidFlushDuringShutdown(true) + .build() + .toString(); + + assertThat(str).isEqualTo("max_open_files=99;delayed_write_rate=789;" + + "avoid_flush_during_shutdown=true"); + } + + @Test + public void mutableColumnFamilyOptions_parse() { + final String str = "max_open_files=99;delayed_write_rate=789;" + + "avoid_flush_during_shutdown=true"; + + final MutableDBOptionsBuilder builder = + MutableDBOptions.parse(str); + + assertThat(builder.maxOpenFiles()).isEqualTo(99); + assertThat(builder.delayedWriteRate()).isEqualTo(789); + assertThat(builder.avoidFlushDuringShutdown()).isEqualTo(true); + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java new file mode 100644 index 0000000000..d1bdf0f884 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/NativeComparatorWrapperTest.java @@ -0,0 +1,92 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.*; +import java.util.Comparator; + +import static org.junit.Assert.assertEquals; + +public class NativeComparatorWrapperTest { + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + private static final Random random = new Random(); + + @Test + public void rountrip() throws RocksDBException { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + final int ITERATIONS = 1_000; + + final String[] storedKeys = new String[ITERATIONS]; + try (final NativeStringComparatorWrapper comparator = new NativeStringComparatorWrapper(); + final Options opt = new Options() + .setCreateIfMissing(true) + .setComparator(comparator)) { + + // store random integer keys + try (final RocksDB db = RocksDB.open(opt, dbPath)) { + for (int i = 0; i < ITERATIONS; i++) { + final String strKey = randomString(); + final byte key[] = strKey.getBytes(); + // does key already exist (avoid duplicates) + if (i > 0 && db.get(key) != null) { + i--; // generate a different key + } else { + db.put(key, "value".getBytes()); + storedKeys[i] = strKey; + } + } + } + + // sort the stored keys into ascending alpha-numeric order + Arrays.sort(storedKeys, new Comparator() { + @Override + public int compare(final String o1, final String o2) { + return o1.compareTo(o2); + } + }); + + // re-open db and read from start to end + // string keys should be in ascending + // order + try (final RocksDB db = RocksDB.open(opt, dbPath); + final RocksIterator it = db.newIterator()) { + int count = 0; + for (it.seekToFirst(); it.isValid(); it.next()) { + final String strKey = new String(it.key()); + assertEquals(storedKeys[count++], strKey); + } + } + } + } + + private String randomString() { + final char[] chars = new char[12]; + for(int i = 0; i < 12; i++) { + final int letterCode = random.nextInt(24); + final char letter = (char) (((int) 'a') + letterCode); + chars[i] = letter; + } + return String.copyValueOf(chars); + } + + public static class NativeStringComparatorWrapper + extends NativeComparatorWrapper { + + @Override + protected long initializeNative(final long... nativeParameterHandles) { + return newStringComparator(); + } + + private native long newStringComparator(); + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java new file mode 100644 index 0000000000..519b70b1d2 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionDBTest.java @@ -0,0 +1,131 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OptimisticTransactionDBTest { + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void open() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final OptimisticTransactionDB otdb = OptimisticTransactionDB.open(options, + dbFolder.getRoot().getAbsolutePath())) { + assertThat(otdb).isNotNull(); + } + } + + @Test + public void open_columnFamilies() throws RocksDBException { + try(final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final ColumnFamilyOptions myCfOpts = new ColumnFamilyOptions()) { + + final List columnFamilyDescriptors = + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("myCf".getBytes(), myCfOpts)); + + final List columnFamilyHandles = new ArrayList<>(); + + try (final OptimisticTransactionDB otdb = OptimisticTransactionDB.open(dbOptions, + dbFolder.getRoot().getAbsolutePath(), + columnFamilyDescriptors, columnFamilyHandles)) { + try { + assertThat(otdb).isNotNull(); + } finally { + for (final ColumnFamilyHandle handle : columnFamilyHandles) { + handle.close(); + } + } + } + } + } + + @Test + public void beginTransaction() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( + options, dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions()) { + + try(final Transaction txn = otdb.beginTransaction(writeOptions)) { + assertThat(txn).isNotNull(); + } + } + } + + @Test + public void beginTransaction_transactionOptions() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( + options, dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions(); + final OptimisticTransactionOptions optimisticTxnOptions = + new OptimisticTransactionOptions()) { + + try(final Transaction txn = otdb.beginTransaction(writeOptions, + optimisticTxnOptions)) { + assertThat(txn).isNotNull(); + } + } + } + + @Test + public void beginTransaction_withOld() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( + options, dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions()) { + + try(final Transaction txn = otdb.beginTransaction(writeOptions)) { + final Transaction txnReused = otdb.beginTransaction(writeOptions, txn); + assertThat(txnReused).isSameAs(txn); + } + } + } + + @Test + public void beginTransaction_withOld_transactionOptions() + throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final OptimisticTransactionDB otdb = OptimisticTransactionDB.open( + options, dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions(); + final OptimisticTransactionOptions optimisticTxnOptions = + new OptimisticTransactionOptions()) { + + try(final Transaction txn = otdb.beginTransaction(writeOptions)) { + final Transaction txnReused = otdb.beginTransaction(writeOptions, + optimisticTxnOptions, txn); + assertThat(txnReused).isSameAs(txn); + } + } + } + + @Test + public void baseDB() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final OptimisticTransactionDB otdb = OptimisticTransactionDB.open(options, + dbFolder.getRoot().getAbsolutePath())) { + assertThat(otdb).isNotNull(); + final RocksDB db = otdb.getBaseDB(); + assertThat(db).isNotNull(); + assertThat(db.isOwningHandle()).isFalse(); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java new file mode 100644 index 0000000000..4a57e33568 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionOptionsTest.java @@ -0,0 +1,37 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; +import org.rocksdb.util.DirectBytewiseComparator; + +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OptimisticTransactionOptionsTest { + + private static final Random rand = PlatformRandomHelper. + getPlatformSpecificRandomFactory(); + + @Test + public void setSnapshot() { + try (final OptimisticTransactionOptions opt = new OptimisticTransactionOptions()) { + final boolean boolValue = rand.nextBoolean(); + opt.setSetSnapshot(boolValue); + assertThat(opt.isSetSnapshot()).isEqualTo(boolValue); + } + } + + @Test + public void comparator() { + try (final OptimisticTransactionOptions opt = new OptimisticTransactionOptions(); + final ComparatorOptions copt = new ComparatorOptions(); + final DirectComparator comparator = new DirectBytewiseComparator(copt)) { + opt.setComparator(comparator); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java new file mode 100644 index 0000000000..f44816e64b --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptimisticTransactionTest.java @@ -0,0 +1,350 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class OptimisticTransactionTest extends AbstractTransactionTest { + + @Test + public void getForUpdate_cf_conflict() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte v12[] = "value12".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(testCf, k1, v1); + assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); + + // NOTE: txn2 updates k1, during txn3 + txn2.put(testCf, k1, v12); + assertThat(txn2.get(testCf, readOptions, k1)).isEqualTo(v12); + txn2.commit(); + + try { + txn3.commit(); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void getForUpdate_conflict() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte v12[] = "value12".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.getForUpdate(readOptions, k1, true)).isEqualTo(v1); + + // NOTE: txn2 updates k1, during txn3 + txn2.put(k1, v12); + assertThat(txn2.get(readOptions, k1)).isEqualTo(v12); + txn2.commit(); + + try { + txn3.commit(); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void multiGetForUpdate_cf_conflict() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + final byte[] otherValue = "otherValue".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + final List cfList = Arrays.asList(testCf, testCf); + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(testCf, keys[0], values[0]); + txn.put(testCf, keys[1], values[1]); + assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.multiGetForUpdate(readOptions, cfList, keys)) + .isEqualTo(values); + + // NOTE: txn2 updates k1, during txn3 + txn2.put(testCf, keys[0], otherValue); + assertThat(txn2.get(testCf, readOptions, keys[0])) + .isEqualTo(otherValue); + txn2.commit(); + + try { + txn3.commit(); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void multiGetForUpdate_conflict() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + final byte[] otherValue = "otherValue".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(keys[0], values[0]); + txn.put(keys[1], values[1]); + assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.multiGetForUpdate(readOptions, keys)) + .isEqualTo(values); + + // NOTE: txn2 updates k1, during txn3 + txn2.put(keys[0], otherValue); + assertThat(txn2.get(readOptions, keys[0])) + .isEqualTo(otherValue); + txn2.commit(); + + try { + txn3.commit(); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.Busy); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void undoGetForUpdate_cf_conflict() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte v12[] = "value12".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(testCf, k1, v1); + assertThat(txn.get(testCf, readOptions, k1)).isEqualTo(v1); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); + + // undo the getForUpdate + txn3.undoGetForUpdate(testCf, k1); + + // NOTE: txn2 updates k1, during txn3 + txn2.put(testCf, k1, v12); + assertThat(txn2.get(testCf, readOptions, k1)).isEqualTo(v12); + txn2.commit(); + + // should not cause an exception + // because we undid the getForUpdate above! + txn3.commit(); + } + } + } + } + + @Test + public void undoGetForUpdate_conflict() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte v12[] = "value12".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + assertThat(txn.get(readOptions, k1)).isEqualTo(v1); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.getForUpdate(readOptions, k1, true)).isEqualTo(v1); + + // undo the getForUpdate + txn3.undoGetForUpdate(k1); + + // NOTE: txn2 updates k1, during txn3 + txn2.put(k1, v12); + assertThat(txn2.get(readOptions, k1)).isEqualTo(v12); + txn2.commit(); + + // should not cause an exception + // because we undid the getForUpdate above! + txn3.commit(); + } + } + } + } + + @Test + public void name() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getName()).isEmpty(); + final String name = "my-transaction-" + rand.nextLong(); + + try { + txn.setName(name); + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode() == Status.Code.InvalidArgument); + return; + } + + fail("Optimistic transactions cannot be named."); + } + } + + @Override + public OptimisticTransactionDBContainer startDb() + throws RocksDBException { + final DBOptions options = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + + final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); + final List columnFamilyDescriptors = + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor(TXN_TEST_COLUMN_FAMILY, + columnFamilyOptions)); + final List columnFamilyHandles = new ArrayList<>(); + + final OptimisticTransactionDB optimisticTxnDb; + try { + optimisticTxnDb = OptimisticTransactionDB.open( + options, dbFolder.getRoot().getAbsolutePath(), + columnFamilyDescriptors, columnFamilyHandles); + } catch(final RocksDBException e) { + columnFamilyOptions.close(); + options.close(); + throw e; + } + + final WriteOptions writeOptions = new WriteOptions(); + final OptimisticTransactionOptions optimisticTxnOptions = + new OptimisticTransactionOptions(); + + return new OptimisticTransactionDBContainer(optimisticTxnOptions, + writeOptions, columnFamilyHandles, optimisticTxnDb, columnFamilyOptions, + options); + } + + private static class OptimisticTransactionDBContainer + extends DBContainer { + + private final OptimisticTransactionOptions optimisticTxnOptions; + private final OptimisticTransactionDB optimisticTxnDb; + + public OptimisticTransactionDBContainer( + final OptimisticTransactionOptions optimisticTxnOptions, + final WriteOptions writeOptions, + final List columnFamilyHandles, + final OptimisticTransactionDB optimisticTxnDb, + final ColumnFamilyOptions columnFamilyOptions, + final DBOptions options) { + super(writeOptions, columnFamilyHandles, columnFamilyOptions, + options); + this.optimisticTxnOptions = optimisticTxnOptions; + this.optimisticTxnDb = optimisticTxnDb; + } + + @Override + public Transaction beginTransaction() { + return optimisticTxnDb.beginTransaction(writeOptions, + optimisticTxnOptions); + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions) { + return optimisticTxnDb.beginTransaction(writeOptions, + optimisticTxnOptions); + } + + @Override + public void close() { + optimisticTxnOptions.close(); + writeOptions.close(); + for(final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { + columnFamilyHandle.close(); + } + optimisticTxnDb.close(); + options.close(); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java index 6afcab3300..e27a33d7df 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptionsTest.java @@ -6,13 +6,11 @@ package org.rocksdb; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; +import java.util.*; import org.junit.ClassRule; import org.junit.Test; +import org.rocksdb.test.RemoveEmptyValueCompactionFilterFactory; import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +24,18 @@ public class OptionsTest { public static final Random rand = PlatformRandomHelper. getPlatformSpecificRandomFactory(); + @Test + public void copyConstructor() { + Options origOpts = new Options(); + origOpts.setNumLevels(rand.nextInt(8)); + origOpts.setTargetFileSizeMultiplier(rand.nextInt(100)); + origOpts.setLevel0StopWritesTrigger(rand.nextInt(50)); + Options copyOpts = new Options(origOpts); + assertThat(origOpts.numLevels()).isEqualTo(copyOpts.numLevels()); + assertThat(origOpts.targetFileSizeMultiplier()).isEqualTo(copyOpts.targetFileSizeMultiplier()); + assertThat(origOpts.level0StopWritesTrigger()).isEqualTo(copyOpts.level0StopWritesTrigger()); + } + @Test public void setIncreaseParallelism() { try (final Options opt = new Options()) { @@ -458,6 +468,15 @@ public void maxBackgroundFlushes() { } } + @Test + public void maxBackgroundJobs() { + try (final Options opt = new Options()) { + final int intValue = rand.nextInt(); + opt.setMaxBackgroundJobs(intValue); + assertThat(opt.maxBackgroundJobs()).isEqualTo(intValue); + } + } + @Test public void maxLogFileSize() throws RocksDBException { try (final Options opt = new Options()) { @@ -624,6 +643,26 @@ public void dbWriteBufferSize() { } } + @Test + public void setWriteBufferManager() throws RocksDBException { + try (final Options opt = new Options(); + final Cache cache = new LRUCache(1 * 1024 * 1024); + final WriteBufferManager writeBufferManager = new WriteBufferManager(2000l, cache)) { + opt.setWriteBufferManager(writeBufferManager); + assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); + } + } + + @Test + public void setWriteBufferManagerWithZeroBufferSize() throws RocksDBException { + try (final Options opt = new Options(); + final Cache cache = new LRUCache(1 * 1024 * 1024); + final WriteBufferManager writeBufferManager = new WriteBufferManager(0l, cache)) { + opt.setWriteBufferManager(writeBufferManager); + assertThat(opt.writeBufferManager()).isEqualTo(writeBufferManager); + } + } + @Test public void accessHintOnCompactionStart() { try (final Options opt = new Options()) { @@ -714,6 +753,15 @@ public void delayedWriteRate() { } } + @Test + public void enablePipelinedWrite() { + try(final Options opt = new Options()) { + assertThat(opt.enablePipelinedWrite()).isFalse(); + opt.setEnablePipelinedWrite(true); + assertThat(opt.enablePipelinedWrite()).isTrue(); + } + } + @Test public void allowConcurrentMemtableWrite() { try (final Options opt = new Options()) { @@ -795,6 +843,38 @@ public void rowCache() { } } + @Test + public void walFilter() { + try (final Options opt = new Options()) { + assertThat(opt.walFilter()).isNull(); + + try (final AbstractWalFilter walFilter = new AbstractWalFilter() { + @Override + public void columnFamilyLogNumberMap( + final Map cfLognumber, + final Map cfNameId) { + // no-op + } + + @Override + public LogRecordFoundResult logRecordFound(final long logNumber, + final String logFileName, final WriteBatch batch, + final WriteBatch newBatch) { + return new LogRecordFoundResult( + WalProcessingOption.CONTINUE_PROCESSING, false); + } + + @Override + public String name() { + return "test-wal-filter"; + } + }) { + opt.setWalFilter(walFilter); + assertThat(opt.walFilter()).isEqualTo(walFilter); + } + } + } + @Test public void failIfOptionsFileError() { try (final Options opt = new Options()) { @@ -831,6 +911,52 @@ public void avoidFlushDuringShutdown() { } } + + @Test + public void allowIngestBehind() { + try (final Options opt = new Options()) { + assertThat(opt.allowIngestBehind()).isFalse(); + opt.setAllowIngestBehind(true); + assertThat(opt.allowIngestBehind()).isTrue(); + } + } + + @Test + public void preserveDeletes() { + try (final Options opt = new Options()) { + assertThat(opt.preserveDeletes()).isFalse(); + opt.setPreserveDeletes(true); + assertThat(opt.preserveDeletes()).isTrue(); + } + } + + @Test + public void twoWriteQueues() { + try (final Options opt = new Options()) { + assertThat(opt.twoWriteQueues()).isFalse(); + opt.setTwoWriteQueues(true); + assertThat(opt.twoWriteQueues()).isTrue(); + } + } + + @Test + public void manualWalFlush() { + try (final Options opt = new Options()) { + assertThat(opt.manualWalFlush()).isFalse(); + opt.setManualWalFlush(true); + assertThat(opt.manualWalFlush()).isTrue(); + } + } + + @Test + public void atomicFlush() { + try (final Options opt = new Options()) { + assertThat(opt.atomicFlush()).isFalse(); + opt.setAtomicFlush(true); + assertThat(opt.atomicFlush()).isTrue(); + } + } + @Test public void env() { try (final Options options = new Options(); @@ -924,6 +1050,20 @@ public void bottommostCompressionType() { } } + @Test + public void bottommostCompressionOptions() { + try (final Options options = new Options(); + final CompressionOptions bottommostCompressionOptions = new CompressionOptions() + .setMaxDictBytes(123)) { + + options.setBottommostCompressionOptions(bottommostCompressionOptions); + assertThat(options.bottommostCompressionOptions()) + .isEqualTo(bottommostCompressionOptions); + assertThat(options.bottommostCompressionOptions().maxDictBytes()) + .isEqualTo(123); + } + } + @Test public void compressionOptions() { try (final Options options = new Options(); @@ -978,6 +1118,15 @@ public void rateLimiter() { } } + @Test + public void sstFileManager() throws RocksDBException { + try (final Options options = new Options(); + final SstFileManager sstFileManager = + new SstFileManager(Env.getDefault())) { + options.setSstFileManager(sstFileManager); + } + } + @Test public void shouldSetTestPrefixExtractor() { try (final Options options = new Options()) { @@ -1057,6 +1206,15 @@ public void reportBgIoStats() { } } + @Test + public void ttl() { + try (final Options options = new Options()) { + options.setTtl(1000 * 60); + assertThat(options.ttl()). + isEqualTo(1000 * 60); + } + } + @Test public void compactionOptionsUniversal() { try (final Options options = new Options(); @@ -1092,4 +1250,23 @@ public void forceConsistencyChecks() { isEqualTo(booleanValue); } } + + @Test + public void compactionFilter() { + try(final Options options = new Options(); + final RemoveEmptyValueCompactionFilter cf = new RemoveEmptyValueCompactionFilter()) { + options.setCompactionFilter(cf); + assertThat(options.compactionFilter()).isEqualTo(cf); + } + } + + @Test + public void compactionFilterFactory() { + try(final Options options = new Options(); + final RemoveEmptyValueCompactionFilterFactory cff = new RemoveEmptyValueCompactionFilterFactory()) { + options.setCompactionFilterFactory(cff); + assertThat(options.compactionFilterFactory()).isEqualTo(cff); + } + } + } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptionsUtilTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptionsUtilTest.java new file mode 100644 index 0000000000..e79951aa85 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/OptionsUtilTest.java @@ -0,0 +1,126 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OptionsUtilTest { + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = new RocksMemoryResource(); + + @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); + + enum TestAPI { LOAD_LATEST_OPTIONS, LOAD_OPTIONS_FROM_FILE } + + @Test + public void loadLatestOptions() throws RocksDBException { + verifyOptions(TestAPI.LOAD_LATEST_OPTIONS); + } + + @Test + public void loadOptionsFromFile() throws RocksDBException { + verifyOptions(TestAPI.LOAD_OPTIONS_FROM_FILE); + } + + @Test + public void getLatestOptionsFileName() throws RocksDBException { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final Options options = new Options().setCreateIfMissing(true); + final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db).isNotNull(); + } + + String fName = OptionsUtil.getLatestOptionsFileName(dbPath, Env.getDefault()); + assertThat(fName).isNotNull(); + assert(fName.startsWith("OPTIONS-") == true); + // System.out.println("latest options fileName: " + fName); + } + + private void verifyOptions(TestAPI apiType) throws RocksDBException { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + final Options options = new Options() + .setCreateIfMissing(true) + .setParanoidChecks(false) + .setMaxOpenFiles(478) + .setDelayedWriteRate(1234567L); + final ColumnFamilyOptions baseDefaultCFOpts = new ColumnFamilyOptions(); + final byte[] secondCFName = "new_cf".getBytes(); + final ColumnFamilyOptions baseSecondCFOpts = + new ColumnFamilyOptions() + .setWriteBufferSize(70 * 1024) + .setMaxWriteBufferNumber(7) + .setMaxBytesForLevelBase(53 * 1024 * 1024) + .setLevel0FileNumCompactionTrigger(3) + .setLevel0SlowdownWritesTrigger(51) + .setBottommostCompressionType(CompressionType.ZSTD_COMPRESSION); + + // Create a database with a new column family + try (final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db).isNotNull(); + + // create column family + try (final ColumnFamilyHandle columnFamilyHandle = + db.createColumnFamily(new ColumnFamilyDescriptor(secondCFName, baseSecondCFOpts))) { + assert(columnFamilyHandle != null); + } + } + + // Read the options back and verify + DBOptions dbOptions = new DBOptions(); + final List cfDescs = new ArrayList<>(); + String path = dbPath; + if (apiType == TestAPI.LOAD_LATEST_OPTIONS) { + OptionsUtil.loadLatestOptions(path, Env.getDefault(), dbOptions, cfDescs, false); + } else if (apiType == TestAPI.LOAD_OPTIONS_FROM_FILE) { + path = dbPath + "/" + OptionsUtil.getLatestOptionsFileName(dbPath, Env.getDefault()); + OptionsUtil.loadOptionsFromFile(path, Env.getDefault(), dbOptions, cfDescs, false); + } + + assertThat(dbOptions.createIfMissing()).isEqualTo(options.createIfMissing()); + assertThat(dbOptions.paranoidChecks()).isEqualTo(options.paranoidChecks()); + assertThat(dbOptions.maxOpenFiles()).isEqualTo(options.maxOpenFiles()); + assertThat(dbOptions.delayedWriteRate()).isEqualTo(options.delayedWriteRate()); + + assertThat(cfDescs.size()).isEqualTo(2); + assertThat(cfDescs.get(0)).isNotNull(); + assertThat(cfDescs.get(1)).isNotNull(); + assertThat(cfDescs.get(0).columnFamilyName()).isEqualTo(RocksDB.DEFAULT_COLUMN_FAMILY); + assertThat(cfDescs.get(1).columnFamilyName()).isEqualTo(secondCFName); + + ColumnFamilyOptions defaultCFOpts = cfDescs.get(0).columnFamilyOptions(); + assertThat(defaultCFOpts.writeBufferSize()).isEqualTo(baseDefaultCFOpts.writeBufferSize()); + assertThat(defaultCFOpts.maxWriteBufferNumber()) + .isEqualTo(baseDefaultCFOpts.maxWriteBufferNumber()); + assertThat(defaultCFOpts.maxBytesForLevelBase()) + .isEqualTo(baseDefaultCFOpts.maxBytesForLevelBase()); + assertThat(defaultCFOpts.level0FileNumCompactionTrigger()) + .isEqualTo(baseDefaultCFOpts.level0FileNumCompactionTrigger()); + assertThat(defaultCFOpts.level0SlowdownWritesTrigger()) + .isEqualTo(baseDefaultCFOpts.level0SlowdownWritesTrigger()); + assertThat(defaultCFOpts.bottommostCompressionType()) + .isEqualTo(baseDefaultCFOpts.bottommostCompressionType()); + + ColumnFamilyOptions secondCFOpts = cfDescs.get(1).columnFamilyOptions(); + assertThat(secondCFOpts.writeBufferSize()).isEqualTo(baseSecondCFOpts.writeBufferSize()); + assertThat(secondCFOpts.maxWriteBufferNumber()) + .isEqualTo(baseSecondCFOpts.maxWriteBufferNumber()); + assertThat(secondCFOpts.maxBytesForLevelBase()) + .isEqualTo(baseSecondCFOpts.maxBytesForLevelBase()); + assertThat(secondCFOpts.level0FileNumCompactionTrigger()) + .isEqualTo(baseSecondCFOpts.level0FileNumCompactionTrigger()); + assertThat(secondCFOpts.level0SlowdownWritesTrigger()) + .isEqualTo(baseSecondCFOpts.level0SlowdownWritesTrigger()); + assertThat(secondCFOpts.bottommostCompressionType()) + .isEqualTo(baseSecondCFOpts.bottommostCompressionType()); + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java index 27567e89d1..c78f9876e6 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RateLimiterTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.rocksdb.RateLimiter.*; public class RateLimiterTest { @@ -16,17 +17,21 @@ public class RateLimiterTest { new RocksMemoryResource(); @Test - public void setBytesPerSecond() { + public void bytesPerSecond() { try(final RateLimiter rateLimiter = - new RateLimiter(1000, 100 * 1000, 1)) { + new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, + DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { + assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0); rateLimiter.setBytesPerSecond(2000); + assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0); } } @Test public void getSingleBurstBytes() { try(final RateLimiter rateLimiter = - new RateLimiter(1000, 100 * 1000, 1)) { + new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, + DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { assertThat(rateLimiter.getSingleBurstBytes()).isEqualTo(100); } } @@ -34,7 +39,8 @@ public void getSingleBurstBytes() { @Test public void getTotalBytesThrough() { try(final RateLimiter rateLimiter = - new RateLimiter(1000, 100 * 1000, 1)) { + new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, + DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { assertThat(rateLimiter.getTotalBytesThrough()).isEqualTo(0); } } @@ -42,8 +48,18 @@ public void getTotalBytesThrough() { @Test public void getTotalRequests() { try(final RateLimiter rateLimiter = - new RateLimiter(1000, 100 * 1000, 1)) { + new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, + DEFAULT_FAIRNESS, DEFAULT_MODE, DEFAULT_AUTOTUNE)) { assertThat(rateLimiter.getTotalRequests()).isEqualTo(0); } } + + @Test + public void autoTune() { + try(final RateLimiter rateLimiter = + new RateLimiter(1000, DEFAULT_REFILL_PERIOD_MICROS, + DEFAULT_FAIRNESS, DEFAULT_MODE, true)) { + assertThat(rateLimiter.getBytesPerSecond()).isGreaterThan(0); + } + } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java index da048c4431..9708cd0b1f 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/ReadOptionsTest.java @@ -5,6 +5,7 @@ package org.rocksdb; +import java.util.Arrays; import java.util.Random; import org.junit.ClassRule; @@ -23,6 +24,30 @@ public class ReadOptionsTest { @Rule public ExpectedException exception = ExpectedException.none(); + @Test + public void altConstructor() { + try (final ReadOptions opt = new ReadOptions(true, true)) { + assertThat(opt.verifyChecksums()).isTrue(); + assertThat(opt.fillCache()).isTrue(); + } + } + + @Test + public void copyConstructor() { + try (final ReadOptions opt = new ReadOptions()) { + opt.setVerifyChecksums(false); + opt.setFillCache(false); + opt.setIterateUpperBound(buildRandomSlice()); + opt.setIterateLowerBound(buildRandomSlice()); + try (final ReadOptions other = new ReadOptions(opt)) { + assertThat(opt.verifyChecksums()).isEqualTo(other.verifyChecksums()); + assertThat(opt.fillCache()).isEqualTo(other.fillCache()); + assertThat(Arrays.equals(opt.iterateUpperBound().data(), other.iterateUpperBound().data())).isTrue(); + assertThat(Arrays.equals(opt.iterateLowerBound().data(), other.iterateLowerBound().data())).isTrue(); + } + } + } + @Test public void verifyChecksum() { try (final ReadOptions opt = new ReadOptions()) { @@ -127,6 +152,56 @@ public void ignoreRangeDeletions() { } } + @Test + public void iterateUpperBound() { + try (final ReadOptions opt = new ReadOptions()) { + Slice upperBound = buildRandomSlice(); + opt.setIterateUpperBound(upperBound); + assertThat(Arrays.equals(upperBound.data(), opt.iterateUpperBound().data())).isTrue(); + } + } + + @Test + public void iterateUpperBoundNull() { + try (final ReadOptions opt = new ReadOptions()) { + assertThat(opt.iterateUpperBound()).isNull(); + } + } + + @Test + public void iterateLowerBound() { + try (final ReadOptions opt = new ReadOptions()) { + Slice lowerBound = buildRandomSlice(); + opt.setIterateLowerBound(lowerBound); + assertThat(Arrays.equals(lowerBound.data(), opt.iterateLowerBound().data())).isTrue(); + } + } + + @Test + public void iterateLowerBoundNull() { + try (final ReadOptions opt = new ReadOptions()) { + assertThat(opt.iterateLowerBound()).isNull(); + } + } + + @Test + public void tableFilter() { + try (final ReadOptions opt = new ReadOptions(); + final AbstractTableFilter allTablesFilter = new AllTablesFilter()) { + opt.setTableFilter(allTablesFilter); + } + } + + @Test + public void iterStartSeqnum() { + try (final ReadOptions opt = new ReadOptions()) { + assertThat(opt.iterStartSeqnum()).isEqualTo(0); + + opt.setIterStartSeqnum(10); + assertThat(opt.iterStartSeqnum()).isEqualTo(10); + } + } + @Test public void failSetVerifyChecksumUninitialized() { try (final ReadOptions readOptions = @@ -191,6 +266,38 @@ public void failSnapshotUninitialized() { } } + @Test + public void failSetIterateUpperBoundUninitialized() { + try (final ReadOptions readOptions = + setupUninitializedReadOptions(exception)) { + readOptions.setIterateUpperBound(null); + } + } + + @Test + public void failIterateUpperBoundUninitialized() { + try (final ReadOptions readOptions = + setupUninitializedReadOptions(exception)) { + readOptions.iterateUpperBound(); + } + } + + @Test + public void failSetIterateLowerBoundUninitialized() { + try (final ReadOptions readOptions = + setupUninitializedReadOptions(exception)) { + readOptions.setIterateLowerBound(null); + } + } + + @Test + public void failIterateLowerBoundUninitialized() { + try (final ReadOptions readOptions = + setupUninitializedReadOptions(exception)) { + readOptions.iterateLowerBound(); + } + } + private ReadOptions setupUninitializedReadOptions( ExpectedException exception) { final ReadOptions readOptions = new ReadOptions(); @@ -198,4 +305,18 @@ private ReadOptions setupUninitializedReadOptions( exception.expect(AssertionError.class); return readOptions; } + + private Slice buildRandomSlice() { + final Random rand = new Random(); + byte[] sliceBytes = new byte[rand.nextInt(100) + 1]; + rand.nextBytes(sliceBytes); + return new Slice(sliceBytes); + } + + private static class AllTablesFilter extends AbstractTableFilter { + @Override + public boolean filter(final TableProperties tableProperties) { + return true; + } + } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java index 89894746d2..a7d7fee14f 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksDBTest.java @@ -4,13 +4,14 @@ // (found in the LICENSE.Apache file in the root directory). package org.rocksdb; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; +import org.junit.*; +import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; +import java.nio.ByteBuffer; import java.util.*; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @@ -58,6 +59,130 @@ public void openWhenOpen() throws RocksDBException { } } + @Test + public void createColumnFamily() throws RocksDBException { + final byte[] col1Name = "col1".getBytes(UTF_8); + + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions() + ) { + try (final ColumnFamilyHandle col1 = + db.createColumnFamily(new ColumnFamilyDescriptor(col1Name, cfOpts))) { + assertThat(col1).isNotNull(); + assertThat(col1.getName()).isEqualTo(col1Name); + } + } + + final List cfHandles = new ArrayList<>(); + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath(), + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor(col1Name)), + cfHandles)) { + try { + assertThat(cfHandles.size()).isEqualTo(2); + assertThat(cfHandles.get(1)).isNotNull(); + assertThat(cfHandles.get(1).getName()).isEqualTo(col1Name); + } finally { + for (final ColumnFamilyHandle cfHandle : + cfHandles) { + cfHandle.close(); + } + } + } + } + + + @Test + public void createColumnFamilies() throws RocksDBException { + final byte[] col1Name = "col1".getBytes(UTF_8); + final byte[] col2Name = "col2".getBytes(UTF_8); + + List cfHandles; + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions() + ) { + cfHandles = + db.createColumnFamilies(cfOpts, Arrays.asList(col1Name, col2Name)); + try { + assertThat(cfHandles).isNotNull(); + assertThat(cfHandles.size()).isEqualTo(2); + assertThat(cfHandles.get(0).getName()).isEqualTo(col1Name); + assertThat(cfHandles.get(1).getName()).isEqualTo(col2Name); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + + cfHandles = new ArrayList<>(); + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath(), + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor(col1Name), + new ColumnFamilyDescriptor(col2Name)), + cfHandles)) { + try { + assertThat(cfHandles.size()).isEqualTo(3); + assertThat(cfHandles.get(1)).isNotNull(); + assertThat(cfHandles.get(1).getName()).isEqualTo(col1Name); + assertThat(cfHandles.get(2)).isNotNull(); + assertThat(cfHandles.get(2).getName()).isEqualTo(col2Name); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + + @Test + public void createColumnFamiliesfromDescriptors() throws RocksDBException { + final byte[] col1Name = "col1".getBytes(UTF_8); + final byte[] col2Name = "col2".getBytes(UTF_8); + + List cfHandles; + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final ColumnFamilyOptions cfOpts = new ColumnFamilyOptions() + ) { + cfHandles = + db.createColumnFamilies(Arrays.asList( + new ColumnFamilyDescriptor(col1Name, cfOpts), + new ColumnFamilyDescriptor(col2Name, cfOpts))); + try { + assertThat(cfHandles).isNotNull(); + assertThat(cfHandles.size()).isEqualTo(2); + assertThat(cfHandles.get(0).getName()).isEqualTo(col1Name); + assertThat(cfHandles.get(1).getName()).isEqualTo(col2Name); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + + cfHandles = new ArrayList<>(); + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath(), + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor(col1Name), + new ColumnFamilyDescriptor(col2Name)), + cfHandles)) { + try { + assertThat(cfHandles.size()).isEqualTo(3); + assertThat(cfHandles.get(1)).isNotNull(); + assertThat(cfHandles.get(1).getName()).isEqualTo(col1Name); + assertThat(cfHandles.get(2)).isNotNull(); + assertThat(cfHandles.get(2).getName()).isEqualTo(col2Name); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + @Test public void put() throws RocksDBException { try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); @@ -68,6 +193,57 @@ public void put() throws RocksDBException { "value".getBytes()); assertThat(db.get("key2".getBytes())).isEqualTo( "12345678".getBytes()); + + + // put + Segment key3 = sliceSegment("key3"); + Segment key4 = sliceSegment("key4"); + Segment value0 = sliceSegment("value 0"); + Segment value1 = sliceSegment("value 1"); + db.put(key3.data, key3.offset, key3.len, value0.data, value0.offset, value0.len); + db.put(opt, key4.data, key4.offset, key4.len, value1.data, value1.offset, value1.len); + + // compare + Assert.assertTrue(value0.isSamePayload(db.get(key3.data, key3.offset, key3.len))); + Assert.assertTrue(value1.isSamePayload(db.get(key4.data, key4.offset, key4.len))); + } + } + + private static Segment sliceSegment(String key) { + ByteBuffer rawKey = ByteBuffer.allocate(key.length() + 4); + rawKey.put((byte)0); + rawKey.put((byte)0); + rawKey.put(key.getBytes()); + + return new Segment(rawKey.array(), 2, key.length()); + } + + private static class Segment { + final byte[] data; + final int offset; + final int len; + + public boolean isSamePayload(byte[] value) { + if (value == null) { + return false; + } + if (value.length != len) { + return false; + } + + for (int i = 0; i < value.length; i++) { + if (data[i + offset] != value[i]) { + return false; + } + } + + return true; + } + + public Segment(byte[] value, int offset, int len) { + this.data = value; + this.offset = offset; + this.len = len; } } @@ -143,6 +319,39 @@ public void getWithOutValueReadOptions() throws RocksDBException { } } + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void getOutOfArrayMaxSizeValue() throws RocksDBException { + final int numberOfValueSplits = 10; + final int splitSize = Integer.MAX_VALUE / numberOfValueSplits; + + Runtime runtime = Runtime.getRuntime(); + long neededMemory = ((long)(splitSize)) * (((long)numberOfValueSplits) + 3); + boolean isEnoughMemory = runtime.maxMemory() - runtime.totalMemory() > neededMemory; + Assume.assumeTrue(isEnoughMemory); + + final byte[] valueSplit = new byte[splitSize]; + final byte[] key = "key".getBytes(); + + thrown.expect(RocksDBException.class); + thrown.expectMessage("Requested array size exceeds VM limit"); + + // merge (numberOfValueSplits + 1) valueSplit's to get value size exceeding Integer.MAX_VALUE + try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); + final Options opt = new Options() + .setCreateIfMissing(true) + .setMergeOperator(stringAppendOperator); + final RocksDB db = RocksDB.open(opt, dbFolder.getRoot().getAbsolutePath())) { + db.put(key, valueSplit); + for (int i = 0; i < numberOfValueSplits; i++) { + db.merge(key, valueSplit); + } + db.get(key); + } + } + @Test public void multiGet() throws RocksDBException, InterruptedException { try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); @@ -182,6 +391,41 @@ public void multiGet() throws RocksDBException, InterruptedException { } } + @Test + public void multiGetAsList() throws RocksDBException, InterruptedException { + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final ReadOptions rOpt = new ReadOptions()) { + db.put("key1".getBytes(), "value".getBytes()); + db.put("key2".getBytes(), "12345678".getBytes()); + List lookupKeys = new ArrayList<>(); + lookupKeys.add("key1".getBytes()); + lookupKeys.add("key2".getBytes()); + List results = db.multiGetAsList(lookupKeys); + assertThat(results).isNotNull(); + assertThat(results).hasSize(lookupKeys.size()); + assertThat(results). + containsExactly("value".getBytes(), "12345678".getBytes()); + // test same method with ReadOptions + results = db.multiGetAsList(rOpt, lookupKeys); + assertThat(results).isNotNull(); + assertThat(results). + contains("value".getBytes(), "12345678".getBytes()); + + // remove existing key + lookupKeys.remove(1); + // add non existing key + lookupKeys.add("key3".getBytes()); + results = db.multiGetAsList(lookupKeys); + assertThat(results).isNotNull(); + assertThat(results). + containsExactly("value".getBytes(), null); + // test same call with readOptions + results = db.multiGetAsList(rOpt, lookupKeys); + assertThat(results).isNotNull(); + assertThat(results).contains("value".getBytes()); + } + } + @Test public void merge() throws RocksDBException { try (final StringAppendOperator stringAppendOperator = new StringAppendOperator(); @@ -207,6 +451,18 @@ public void merge() throws RocksDBException { db.merge(wOpt, "key2".getBytes(), "xxxx".getBytes()); assertThat(db.get("key2".getBytes())).isEqualTo( "xxxx".getBytes()); + + Segment key3 = sliceSegment("key3"); + Segment key4 = sliceSegment("key4"); + Segment value0 = sliceSegment("value 0"); + Segment value1 = sliceSegment("value 1"); + + db.merge(key3.data, key3.offset, key3.len, value0.data, value0.offset, value0.len); + db.merge(wOpt, key4.data, key4.offset, key4.len, value1.data, value1.offset, value1.len); + + // compare + Assert.assertTrue(value0.isSamePayload(db.get(key3.data, key3.offset, key3.len))); + Assert.assertTrue(value1.isSamePayload(db.get(key4.data, key4.offset, key4.len))); } } @@ -224,6 +480,18 @@ public void delete() throws RocksDBException { db.delete(wOpt, "key2".getBytes()); assertThat(db.get("key1".getBytes())).isNull(); assertThat(db.get("key2".getBytes())).isNull(); + + + Segment key3 = sliceSegment("key3"); + Segment key4 = sliceSegment("key4"); + db.put("key3".getBytes(), "key3 value".getBytes()); + db.put("key4".getBytes(), "key4 value".getBytes()); + + db.delete(key3.data, key3.offset, key3.len); + db.delete(wOpt, key4.data, key4.offset, key4.len); + + assertThat(db.get("key3".getBytes())).isNull(); + assertThat(db.get("key4".getBytes())).isNull(); } } @@ -257,8 +525,7 @@ public void singleDelete_nonExisting() throws RocksDBException { @Test public void deleteRange() throws RocksDBException { - try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); - final WriteOptions wOpt = new WriteOptions()) { + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath())) { db.put("key1".getBytes(), "value".getBytes()); db.put("key2".getBytes(), "12345678".getBytes()); db.put("key3".getBytes(), "abcdefg".getBytes()); @@ -763,4 +1030,525 @@ public void setOptions() throws RocksDBException { } } } + + @Test + public void destroyDB() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.put("key1".getBytes(), "value".getBytes()); + } + assertThat(dbFolder.getRoot().exists()).isTrue(); + RocksDB.destroyDB(dbPath, options); + assertThat(dbFolder.getRoot().exists()).isFalse(); + } + } + + @Test(expected = RocksDBException.class) + public void destroyDBFailIfOpen() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + // Fails as the db is open and locked. + RocksDB.destroyDB(dbPath, options); + } + } + } + + @Ignore("This test crashes. Re-enable after fixing.") + @Test + public void getApproximateSizes() throws RocksDBException { + final byte key1[] = "key1".getBytes(UTF_8); + final byte key2[] = "key2".getBytes(UTF_8); + final byte key3[] = "key3".getBytes(UTF_8); + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.put(key1, key1); + db.put(key2, key2); + db.put(key3, key3); + + final long[] sizes = db.getApproximateSizes( + Arrays.asList( + new Range(new Slice(key1), new Slice(key2)), + new Range(new Slice(key2), new Slice(key3)) + ), + SizeApproximationFlag.INCLUDE_FILES, + SizeApproximationFlag.INCLUDE_MEMTABLES); + + assertThat(sizes.length).isEqualTo(2); + assertThat(sizes[0]).isEqualTo(0); + assertThat(sizes[1]).isGreaterThanOrEqualTo(1); + } + } + } + + @Test + public void getApproximateMemTableStats() throws RocksDBException { + final byte key1[] = "key1".getBytes(UTF_8); + final byte key2[] = "key2".getBytes(UTF_8); + final byte key3[] = "key3".getBytes(UTF_8); + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.put(key1, key1); + db.put(key2, key2); + db.put(key3, key3); + + final RocksDB.CountAndSize stats = + db.getApproximateMemTableStats( + new Range(new Slice(key1), new Slice(key3))); + + assertThat(stats).isNotNull(); + assertThat(stats.count).isGreaterThan(1); + assertThat(stats.size).isGreaterThan(1); + } + } + } + + @Ignore("TODO(AR) re-enable when ready!") + @Test + public void compactFiles() throws RocksDBException { + final int kTestKeySize = 16; + final int kTestValueSize = 984; + final int kEntrySize = kTestKeySize + kTestValueSize; + final int kEntriesPerBuffer = 100; + final int writeBufferSize = kEntrySize * kEntriesPerBuffer; + final byte[] cfName = "pikachu".getBytes(UTF_8); + + try (final Options options = new Options() + .setCreateIfMissing(true) + .setWriteBufferSize(writeBufferSize) + .setCompactionStyle(CompactionStyle.LEVEL) + .setTargetFileSizeBase(writeBufferSize) + .setMaxBytesForLevelBase(writeBufferSize * 2) + .setLevel0StopWritesTrigger(2) + .setMaxBytesForLevelMultiplier(2) + .setCompressionType(CompressionType.NO_COMPRESSION) + .setMaxSubcompactions(4)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath); + final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(options)) { + db.createColumnFamily(new ColumnFamilyDescriptor(cfName, + cfOptions)).close(); + } + + try (final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(options)) { + final List cfDescriptors = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOptions), + new ColumnFamilyDescriptor(cfName, cfOptions) + ); + final List cfHandles = new ArrayList<>(); + try (final DBOptions dbOptions = new DBOptions(options); + final RocksDB db = RocksDB.open(dbOptions, dbPath, cfDescriptors, + cfHandles); + ) { + try (final FlushOptions flushOptions = new FlushOptions() + .setWaitForFlush(true) + .setAllowWriteStall(true); + final CompactionOptions compactionOptions = new CompactionOptions()) { + final Random rnd = new Random(301); + for (int key = 64 * kEntriesPerBuffer; key >= 0; --key) { + final byte[] value = new byte[kTestValueSize]; + rnd.nextBytes(value); + db.put(cfHandles.get(1), Integer.toString(key).getBytes(UTF_8), + value); + } + db.flush(flushOptions, cfHandles); + + final RocksDB.LiveFiles liveFiles = db.getLiveFiles(); + final List compactedFiles = + db.compactFiles(compactionOptions, cfHandles.get(1), + liveFiles.files, 1, -1, null); + assertThat(compactedFiles).isNotEmpty(); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + } + } + + @Test + public void enableAutoCompaction() throws RocksDBException { + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true)) { + final List cfDescs = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) + ); + final List cfHandles = new ArrayList<>(); + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { + try { + db.enableAutoCompaction(cfHandles); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + } + + @Test + public void numberLevels() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db.numberLevels()).isEqualTo(7); + } + } + } + + @Test + public void maxMemCompactionLevel() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db.maxMemCompactionLevel()).isEqualTo(0); + } + } + } + + @Test + public void level0StopWriteTrigger() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db.level0StopWriteTrigger()).isEqualTo(36); + } + } + } + + @Test + public void getName() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db.getName()).isEqualTo(dbPath); + } + } + } + + @Test + public void getEnv() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db.getEnv()).isEqualTo(Env.getDefault()); + } + } + } + + @Test + public void flush() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath); + final FlushOptions flushOptions = new FlushOptions()) { + db.flush(flushOptions); + } + } + } + + @Test + public void flushWal() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.flushWal(true); + } + } + } + + @Test + public void syncWal() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.syncWal(); + } + } + } + + @Test + public void setPreserveDeletesSequenceNumber() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + assertThat(db.setPreserveDeletesSequenceNumber(db.getLatestSequenceNumber())) + .isFalse(); + } + } + } + + @Test + public void getLiveFiles() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + final RocksDB.LiveFiles livefiles = db.getLiveFiles(true); + assertThat(livefiles).isNotNull(); + assertThat(livefiles.manifestFileSize).isEqualTo(13); + assertThat(livefiles.files.size()).isEqualTo(3); + assertThat(livefiles.files.get(0)).isEqualTo("/CURRENT"); + assertThat(livefiles.files.get(1)).isEqualTo("/MANIFEST-000001"); + assertThat(livefiles.files.get(2)).isEqualTo("/OPTIONS-000005"); + } + } + } + + @Test + public void getSortedWalFiles() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + final List logFiles = db.getSortedWalFiles(); + assertThat(logFiles).isNotNull(); + assertThat(logFiles.size()).isEqualTo(1); + assertThat(logFiles.get(0).type()) + .isEqualTo(WalFileType.kAliveLogFile); + } + } + } + + @Test + public void deleteFile() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.deleteFile("unknown"); + } + } + } + + @Test + public void getLiveFilesMetaData() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + final List liveFilesMetaData + = db.getLiveFilesMetaData(); + assertThat(liveFilesMetaData).isEmpty(); + } + } + } + + @Test + public void getColumnFamilyMetaData() throws RocksDBException { + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true)) { + final List cfDescs = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) + ); + final List cfHandles = new ArrayList<>(); + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { + db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + try { + final ColumnFamilyMetaData cfMetadata = + db.getColumnFamilyMetaData(cfHandles.get(0)); + assertThat(cfMetadata).isNotNull(); + assertThat(cfMetadata.name()).isEqualTo(RocksDB.DEFAULT_COLUMN_FAMILY); + assertThat(cfMetadata.levels().size()).isEqualTo(7); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + } + + @Test + public void verifyChecksum() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.verifyChecksum(); + } + } + } + + @Test + public void getPropertiesOfAllTables() throws RocksDBException { + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true)) { + final List cfDescs = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) + ); + final List cfHandles = new ArrayList<>(); + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { + db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + try { + final Map properties = + db.getPropertiesOfAllTables(cfHandles.get(0)); + assertThat(properties).isNotNull(); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + } + + @Test + public void getPropertiesOfTablesInRange() throws RocksDBException { + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true)) { + final List cfDescs = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) + ); + final List cfHandles = new ArrayList<>(); + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { + db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + db.put(cfHandles.get(0), "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); + db.put(cfHandles.get(0), "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); + try { + final Range range = new Range( + new Slice("key1".getBytes(UTF_8)), + new Slice("key3".getBytes(UTF_8))); + final Map properties = + db.getPropertiesOfTablesInRange( + cfHandles.get(0), Arrays.asList(range)); + assertThat(properties).isNotNull(); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + } + + @Test + public void suggestCompactRange() throws RocksDBException { + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true)) { + final List cfDescs = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY) + ); + final List cfHandles = new ArrayList<>(); + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath, cfDescs, cfHandles)) { + db.put(cfHandles.get(0), "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + db.put(cfHandles.get(0), "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); + db.put(cfHandles.get(0), "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); + try { + final Range range = db.suggestCompactRange(cfHandles.get(0)); + assertThat(range).isNotNull(); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + } + } + + @Test + public void promoteL0() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + db.promoteL0(2); + } + } + } + + @Test + public void startTrace() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true)) { + final String dbPath = dbFolder.getRoot().getAbsolutePath(); + try (final RocksDB db = RocksDB.open(options, dbPath)) { + final TraceOptions traceOptions = new TraceOptions(); + + try (final InMemoryTraceWriter traceWriter = new InMemoryTraceWriter()) { + db.startTrace(traceOptions, traceWriter); + + db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + + db.endTrace(); + + final List writes = traceWriter.getWrites(); + assertThat(writes.size()).isGreaterThan(0); + } + } + } + } + + @Test + public void setDBOptions() throws RocksDBException { + try (final DBOptions options = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions() + .setWriteBufferSize(4096)) { + + final List columnFamilyDescriptors = + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts)); + + // open database + final List columnFamilyHandles = new ArrayList<>(); + try (final RocksDB db = RocksDB.open(options, + dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors, columnFamilyHandles)) { + try { + final MutableDBOptions mutableOptions = + MutableDBOptions.builder() + .setBytesPerSync(1024 * 1027 * 7) + .setAvoidFlushDuringShutdown(false) + .build(); + + db.setDBOptions(mutableOptions); + } finally { + for (final ColumnFamilyHandle handle : columnFamilyHandles) { + handle.close(); + } + } + } + } + } + + private static class InMemoryTraceWriter extends AbstractTraceWriter { + private final List writes = new ArrayList<>(); + private volatile boolean closed = false; + + @Override + public void write(final Slice slice) { + if (closed) { + return; + } + final byte[] data = slice.data(); + final byte[] dataCopy = new byte[data.length]; + System.arraycopy(data, 0, dataCopy, 0, data.length); + writes.add(dataCopy); + } + + @Override + public void closeWriter() { + closed = true; + } + + @Override + public long getFileSize() { + long size = 0; + for (int i = 0; i < writes.size(); i++) { + size += writes.get(i).length; + } + return size; + } + + public List getWrites() { + return writes; + } + } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksEnvTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksEnvTest.java deleted file mode 100644 index dfb7961073..0000000000 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksEnvTest.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -package org.rocksdb; - -import org.junit.ClassRule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class RocksEnvTest { - - @ClassRule - public static final RocksMemoryResource rocksMemoryResource = - new RocksMemoryResource(); - - @Test - public void rocksEnv() { - try (final Env rocksEnv = RocksEnv.getDefault()) { - rocksEnv.setBackgroundThreads(5); - // default rocksenv will always return zero for flush pool - // no matter what was set via setBackgroundThreads - assertThat(rocksEnv.getThreadPoolQueueLen(RocksEnv.FLUSH_POOL)). - isEqualTo(0); - rocksEnv.setBackgroundThreads(5, RocksEnv.FLUSH_POOL); - // default rocksenv will always return zero for flush pool - // no matter what was set via setBackgroundThreads - assertThat(rocksEnv.getThreadPoolQueueLen(RocksEnv.FLUSH_POOL)). - isEqualTo(0); - rocksEnv.setBackgroundThreads(5, RocksEnv.COMPACTION_POOL); - // default rocksenv will always return zero for compaction pool - // no matter what was set via setBackgroundThreads - assertThat(rocksEnv.getThreadPoolQueueLen(RocksEnv.COMPACTION_POOL)). - isEqualTo(0); - } - } -} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java index 982dab4fc8..45893eec11 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksIteratorTest.java @@ -53,6 +53,48 @@ public void rocksIterator() throws RocksDBException { assertThat(iterator.value()).isEqualTo("value2".getBytes()); iterator.status(); } + + try (final RocksIterator iterator = db.newIterator()) { + iterator.seek("key0".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key1".getBytes()); + + iterator.seek("key1".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key1".getBytes()); + + iterator.seek("key1.5".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key2".getBytes()); + + iterator.seek("key2".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key2".getBytes()); + + iterator.seek("key3".getBytes()); + assertThat(iterator.isValid()).isFalse(); + } + + try (final RocksIterator iterator = db.newIterator()) { + iterator.seekForPrev("key0".getBytes()); + assertThat(iterator.isValid()).isFalse(); + + iterator.seekForPrev("key1".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key1".getBytes()); + + iterator.seekForPrev("key1.5".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key1".getBytes()); + + iterator.seekForPrev("key2".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key2".getBytes()); + + iterator.seekForPrev("key3".getBytes()); + assertThat(iterator.isValid()).isTrue(); + assertThat(iterator.key()).isEqualTo("key2".getBytes()); + } } } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java index 04fae2e95d..8e429d4ecb 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/RocksMemEnvTest.java @@ -33,7 +33,7 @@ public void memEnvFillAndReopen() throws RocksDBException { "baz".getBytes() }; - try (final Env env = new RocksMemEnv(); + try (final Env env = new RocksMemEnv(Env.getDefault()); final Options options = new Options() .setCreateIfMissing(true) .setEnv(env); @@ -107,7 +107,7 @@ public void multipleDatabaseInstances() throws RocksDBException { "baz".getBytes() }; - try (final Env env = new RocksMemEnv(); + try (final Env env = new RocksMemEnv(Env.getDefault()); final Options options = new Options() .setCreateIfMissing(true) .setEnv(env); @@ -136,7 +136,7 @@ public void multipleDatabaseInstances() throws RocksDBException { @Test(expected = RocksDBException.class) public void createIfMissingFalse() throws RocksDBException { - try (final Env env = new RocksMemEnv(); + try (final Env env = new RocksMemEnv(Env.getDefault()); final Options options = new Options() .setCreateIfMissing(false) .setEnv(env); diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/SstFileManagerTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/SstFileManagerTest.java new file mode 100644 index 0000000000..2e136e8200 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/SstFileManagerTest.java @@ -0,0 +1,66 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.*; + +public class SstFileManagerTest { + + @Test + public void maxAllowedSpaceUsage() throws RocksDBException { + try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { + sstFileManager.setMaxAllowedSpaceUsage(1024 * 1024 * 64); + assertThat(sstFileManager.isMaxAllowedSpaceReached()).isFalse(); + assertThat(sstFileManager.isMaxAllowedSpaceReachedIncludingCompactions()).isFalse(); + } + } + + @Test + public void compactionBufferSize() throws RocksDBException { + try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { + sstFileManager.setCompactionBufferSize(1024 * 1024 * 10); + assertThat(sstFileManager.isMaxAllowedSpaceReachedIncludingCompactions()).isFalse(); + } + } + + @Test + public void totalSize() throws RocksDBException { + try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { + assertThat(sstFileManager.getTotalSize()).isEqualTo(0); + } + } + + @Test + public void trackedFiles() throws RocksDBException { + try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { + assertThat(sstFileManager.getTrackedFiles()).isEqualTo(Collections.emptyMap()); + } + } + + @Test + public void deleteRateBytesPerSecond() throws RocksDBException { + try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { + assertThat(sstFileManager.getDeleteRateBytesPerSecond()).isEqualTo(SstFileManager.RATE_BYTES_PER_SEC_DEFAULT); + final long ratePerSecond = 1024 * 1024 * 52; + sstFileManager.setDeleteRateBytesPerSecond(ratePerSecond); + assertThat(sstFileManager.getDeleteRateBytesPerSecond()).isEqualTo(ratePerSecond); + } + } + + @Test + public void maxTrashDBRatio() throws RocksDBException { + try (final SstFileManager sstFileManager = new SstFileManager(Env.getDefault())) { + assertThat(sstFileManager.getMaxTrashDBRatio()).isEqualTo(SstFileManager.MAX_TRASH_DB_RATION_DEFAULT); + final double trashRatio = 0.2; + sstFileManager.setMaxTrashDBRatio(trashRatio); + assertThat(sstFileManager.getMaxTrashDBRatio()).isEqualTo(trashRatio); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java index 2103c2fc78..fbd255bdba 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/StatisticsTest.java @@ -96,6 +96,14 @@ public void getHistogramData() throws RocksDBException { final HistogramData histogramData = statistics.getHistogramData(HistogramType.BYTES_PER_READ); assertThat(histogramData).isNotNull(); assertThat(histogramData.getAverage()).isGreaterThan(0); + assertThat(histogramData.getMedian()).isGreaterThan(0); + assertThat(histogramData.getPercentile95()).isGreaterThan(0); + assertThat(histogramData.getPercentile99()).isGreaterThan(0); + assertThat(histogramData.getStandardDeviation()).isEqualTo(0.00); + assertThat(histogramData.getMax()).isGreaterThan(0); + assertThat(histogramData.getCount()).isGreaterThan(0); + assertThat(histogramData.getSum()).isGreaterThan(0); + assertThat(histogramData.getMin()).isGreaterThan(0); } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TableFilterTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TableFilterTest.java new file mode 100644 index 0000000000..862696763f --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TableFilterTest.java @@ -0,0 +1,105 @@ +package org.rocksdb; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +public class TableFilterTest { + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void readOptions() throws RocksDBException { + try (final DBOptions opt = new DBOptions(). + setCreateIfMissing(true). + setCreateMissingColumnFamilies(true); + final ColumnFamilyOptions new_cf_opts = new ColumnFamilyOptions() + ) { + final List columnFamilyDescriptors = + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("new_cf".getBytes(), new_cf_opts) + ); + + final List columnFamilyHandles = new ArrayList<>(); + + // open database + try (final RocksDB db = RocksDB.open(opt, + dbFolder.getRoot().getAbsolutePath(), + columnFamilyDescriptors, + columnFamilyHandles)) { + + try (final CfNameCollectionTableFilter cfNameCollectingTableFilter = + new CfNameCollectionTableFilter(); + final FlushOptions flushOptions = + new FlushOptions().setWaitForFlush(true); + final ReadOptions readOptions = + new ReadOptions().setTableFilter(cfNameCollectingTableFilter)) { + + db.put(columnFamilyHandles.get(0), + "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + db.put(columnFamilyHandles.get(0), + "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); + db.put(columnFamilyHandles.get(0), + "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); + db.put(columnFamilyHandles.get(1), + "key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + db.put(columnFamilyHandles.get(1), + "key2".getBytes(UTF_8), "value2".getBytes(UTF_8)); + db.put(columnFamilyHandles.get(1), + "key3".getBytes(UTF_8), "value3".getBytes(UTF_8)); + + db.flush(flushOptions, columnFamilyHandles); + + try (final RocksIterator iterator = + db.newIterator(columnFamilyHandles.get(0), readOptions)) { + iterator.seekToFirst(); + while (iterator.isValid()) { + iterator.key(); + iterator.value(); + iterator.next(); + } + } + + try (final RocksIterator iterator = + db.newIterator(columnFamilyHandles.get(1), readOptions)) { + iterator.seekToFirst(); + while (iterator.isValid()) { + iterator.key(); + iterator.value(); + iterator.next(); + } + } + + assertThat(cfNameCollectingTableFilter.cfNames.size()).isEqualTo(2); + assertThat(cfNameCollectingTableFilter.cfNames.get(0)) + .isEqualTo(RocksDB.DEFAULT_COLUMN_FAMILY); + assertThat(cfNameCollectingTableFilter.cfNames.get(1)) + .isEqualTo("new_cf".getBytes(UTF_8)); + } finally { + for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { + columnFamilyHandle.close(); + } + } + } + } + } + + private static class CfNameCollectionTableFilter extends AbstractTableFilter { + private final List cfNames = new ArrayList<>(); + + @Override + public boolean filter(final TableProperties tableProperties) { + cfNames.add(tableProperties.getColumnFamilyName()); + return true; + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TimedEnvTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TimedEnvTest.java new file mode 100644 index 0000000000..2eb5eea825 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TimedEnvTest.java @@ -0,0 +1,43 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class TimedEnvTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void construct() throws RocksDBException { + try (final Env env = new TimedEnv(Env.getDefault())) { + // no-op + } + } + + @Test + public void construct_integration() throws RocksDBException { + try (final Env env = new TimedEnv(Env.getDefault()); + final Options options = new Options() + .setCreateIfMissing(true) + .setEnv(env); + ) { + try (final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getPath())) { + db.put("key1".getBytes(UTF_8), "value1".getBytes(UTF_8)); + } + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java new file mode 100644 index 0000000000..7eaa6b16cd --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionDBOptionsTest.java @@ -0,0 +1,64 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; + +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TransactionDBOptionsTest { + + private static final Random rand = PlatformRandomHelper. + getPlatformSpecificRandomFactory(); + + @Test + public void maxNumLocks() { + try (final TransactionDBOptions opt = new TransactionDBOptions()) { + final long longValue = rand.nextLong(); + opt.setMaxNumLocks(longValue); + assertThat(opt.getMaxNumLocks()).isEqualTo(longValue); + } + } + + @Test + public void maxNumStripes() { + try (final TransactionDBOptions opt = new TransactionDBOptions()) { + final long longValue = rand.nextLong(); + opt.setNumStripes(longValue); + assertThat(opt.getNumStripes()).isEqualTo(longValue); + } + } + + @Test + public void transactionLockTimeout() { + try (final TransactionDBOptions opt = new TransactionDBOptions()) { + final long longValue = rand.nextLong(); + opt.setTransactionLockTimeout(longValue); + assertThat(opt.getTransactionLockTimeout()).isEqualTo(longValue); + } + } + + @Test + public void defaultLockTimeout() { + try (final TransactionDBOptions opt = new TransactionDBOptions()) { + final long longValue = rand.nextLong(); + opt.setDefaultLockTimeout(longValue); + assertThat(opt.getDefaultLockTimeout()).isEqualTo(longValue); + } + } + + @Test + public void writePolicy() { + try (final TransactionDBOptions opt = new TransactionDBOptions()) { + final TxnDBWritePolicy writePolicy = TxnDBWritePolicy.WRITE_UNPREPARED; // non-default + opt.setWritePolicy(writePolicy); + assertThat(opt.getWritePolicy()).isEqualTo(writePolicy); + } + } + +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionDBTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionDBTest.java new file mode 100644 index 0000000000..b0ea813ff5 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionDBTest.java @@ -0,0 +1,178 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class TransactionDBTest { + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void open() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath())) { + assertThat(tdb).isNotNull(); + } + } + + @Test + public void open_columnFamilies() throws RocksDBException { + try(final DBOptions dbOptions = new DBOptions().setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final ColumnFamilyOptions myCfOpts = new ColumnFamilyOptions()) { + + final List columnFamilyDescriptors = + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("myCf".getBytes(), myCfOpts)); + + final List columnFamilyHandles = new ArrayList<>(); + + try (final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(dbOptions, txnDbOptions, + dbFolder.getRoot().getAbsolutePath(), + columnFamilyDescriptors, columnFamilyHandles)) { + try { + assertThat(tdb).isNotNull(); + } finally { + for (final ColumnFamilyHandle handle : columnFamilyHandles) { + handle.close(); + } + } + } + } + } + + @Test + public void beginTransaction() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions()) { + + try(final Transaction txn = tdb.beginTransaction(writeOptions)) { + assertThat(txn).isNotNull(); + } + } + } + + @Test + public void beginTransaction_transactionOptions() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions(); + final TransactionOptions txnOptions = new TransactionOptions()) { + + try(final Transaction txn = tdb.beginTransaction(writeOptions, + txnOptions)) { + assertThat(txn).isNotNull(); + } + } + } + + @Test + public void beginTransaction_withOld() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions()) { + + try(final Transaction txn = tdb.beginTransaction(writeOptions)) { + final Transaction txnReused = tdb.beginTransaction(writeOptions, txn); + assertThat(txnReused).isSameAs(txn); + } + } + } + + @Test + public void beginTransaction_withOld_transactionOptions() + throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions(); + final TransactionOptions txnOptions = new TransactionOptions()) { + + try(final Transaction txn = tdb.beginTransaction(writeOptions)) { + final Transaction txnReused = tdb.beginTransaction(writeOptions, + txnOptions, txn); + assertThat(txnReused).isSameAs(txn); + } + } + } + + @Test + public void lockStatusData() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath()); + final WriteOptions writeOptions = new WriteOptions(); + final ReadOptions readOptions = new ReadOptions()) { + + try (final Transaction txn = tdb.beginTransaction(writeOptions)) { + + final byte key[] = "key".getBytes(UTF_8); + final byte value[] = "value".getBytes(UTF_8); + + txn.put(key, value); + assertThat(txn.getForUpdate(readOptions, key, true)).isEqualTo(value); + + final Map lockStatus = + tdb.getLockStatusData(); + + assertThat(lockStatus.size()).isEqualTo(1); + final Set> entrySet = lockStatus.entrySet(); + final Map.Entry entry = entrySet.iterator().next(); + final long columnFamilyId = entry.getKey(); + assertThat(columnFamilyId).isEqualTo(0); + final TransactionDB.KeyLockInfo keyLockInfo = entry.getValue(); + assertThat(keyLockInfo.getKey()).isEqualTo(new String(key, UTF_8)); + assertThat(keyLockInfo.getTransactionIDs().length).isEqualTo(1); + assertThat(keyLockInfo.getTransactionIDs()[0]).isEqualTo(txn.getId()); + assertThat(keyLockInfo.isExclusive()).isTrue(); + } + } + } + + @Test + public void deadlockInfoBuffer() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath())) { + + // TODO(AR) can we cause a deadlock so that we can test the output here? + assertThat(tdb.getDeadlockInfoBuffer()).isEmpty(); + } + } + + @Test + public void setDeadlockInfoBufferSize() throws RocksDBException { + try (final Options options = new Options().setCreateIfMissing(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final TransactionDB tdb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath())) { + tdb.setDeadlockInfoBufferSize(123); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionOptionsTest.java new file mode 100644 index 0000000000..add0439e03 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionOptionsTest.java @@ -0,0 +1,72 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; + +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TransactionOptionsTest { + + private static final Random rand = PlatformRandomHelper. + getPlatformSpecificRandomFactory(); + + @Test + public void snapshot() { + try (final TransactionOptions opt = new TransactionOptions()) { + final boolean boolValue = rand.nextBoolean(); + opt.setSetSnapshot(boolValue); + assertThat(opt.isSetSnapshot()).isEqualTo(boolValue); + } + } + + @Test + public void deadlockDetect() { + try (final TransactionOptions opt = new TransactionOptions()) { + final boolean boolValue = rand.nextBoolean(); + opt.setDeadlockDetect(boolValue); + assertThat(opt.isDeadlockDetect()).isEqualTo(boolValue); + } + } + + @Test + public void lockTimeout() { + try (final TransactionOptions opt = new TransactionOptions()) { + final long longValue = rand.nextLong(); + opt.setLockTimeout(longValue); + assertThat(opt.getLockTimeout()).isEqualTo(longValue); + } + } + + @Test + public void expiration() { + try (final TransactionOptions opt = new TransactionOptions()) { + final long longValue = rand.nextLong(); + opt.setExpiration(longValue); + assertThat(opt.getExpiration()).isEqualTo(longValue); + } + } + + @Test + public void deadlockDetectDepth() { + try (final TransactionOptions opt = new TransactionOptions()) { + final long longValue = rand.nextLong(); + opt.setDeadlockDetectDepth(longValue); + assertThat(opt.getDeadlockDetectDepth()).isEqualTo(longValue); + } + } + + @Test + public void maxWriteBatchSize() { + try (final TransactionOptions opt = new TransactionOptions()) { + final long longValue = rand.nextLong(); + opt.setMaxWriteBatchSize(longValue); + assertThat(opt.getMaxWriteBatchSize()).isEqualTo(longValue); + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionTest.java new file mode 100644 index 0000000000..57a05c9e3a --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/TransactionTest.java @@ -0,0 +1,308 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class TransactionTest extends AbstractTransactionTest { + + @Test + public void getForUpdate_cf_conflict() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte v12[] = "value12".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(testCf, k1, v1); + assertThat(txn.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.getForUpdate(readOptions, testCf, k1, true)).isEqualTo(v1); + + // NOTE: txn2 updates k1, during txn3 + try { + txn2.put(testCf, k1, v12); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void getForUpdate_conflict() throws RocksDBException { + final byte k1[] = "key1".getBytes(UTF_8); + final byte v1[] = "value1".getBytes(UTF_8); + final byte v12[] = "value12".getBytes(UTF_8); + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(k1, v1); + assertThat(txn.getForUpdate(readOptions, k1, true)).isEqualTo(v1); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.getForUpdate(readOptions, k1, true)).isEqualTo(v1); + + // NOTE: txn2 updates k1, during txn3 + try { + txn2.put(k1, v12); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void multiGetForUpdate_cf_conflict() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + final byte[] otherValue = "otherValue".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + final ColumnFamilyHandle testCf = dbContainer.getTestColumnFamily(); + final List cfList = Arrays.asList(testCf, testCf); + + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(testCf, keys[0], values[0]); + txn.put(testCf, keys[1], values[1]); + assertThat(txn.multiGet(readOptions, cfList, keys)).isEqualTo(values); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.multiGetForUpdate(readOptions, cfList, keys)) + .isEqualTo(values); + + // NOTE: txn2 updates k1, during txn3 + try { + txn2.put(testCf, keys[0], otherValue); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void multiGetForUpdate_conflict() throws RocksDBException { + final byte keys[][] = new byte[][] { + "key1".getBytes(UTF_8), + "key2".getBytes(UTF_8)}; + final byte values[][] = new byte[][] { + "value1".getBytes(UTF_8), + "value2".getBytes(UTF_8)}; + final byte[] otherValue = "otherValue".getBytes(UTF_8); + + try(final DBContainer dbContainer = startDb(); + final ReadOptions readOptions = new ReadOptions()) { + try(final Transaction txn = dbContainer.beginTransaction()) { + txn.put(keys[0], values[0]); + txn.put(keys[1], values[1]); + assertThat(txn.multiGet(readOptions, keys)).isEqualTo(values); + txn.commit(); + } + + try(final Transaction txn2 = dbContainer.beginTransaction()) { + try(final Transaction txn3 = dbContainer.beginTransaction()) { + assertThat(txn3.multiGetForUpdate(readOptions, keys)) + .isEqualTo(values); + + // NOTE: txn2 updates k1, during txn3 + try { + txn2.put(keys[0], otherValue); // should cause an exception! + } catch(final RocksDBException e) { + assertThat(e.getStatus().getCode()).isSameAs(Status.Code.TimedOut); + return; + } + } + } + + fail("Expected an exception for put after getForUpdate from conflicting" + + "transactions"); + } + } + + @Test + public void name() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getName()).isEmpty(); + final String name = "my-transaction-" + rand.nextLong(); + txn.setName(name); + assertThat(txn.getName()).isEqualTo(name); + } + } + + @Test + public void ID() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getID()).isGreaterThan(0); + } + } + + @Test + public void deadlockDetect() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.isDeadlockDetect()).isFalse(); + } + } + + @Test + public void waitingTxns() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getWaitingTxns().getTransactionIds().length).isEqualTo(0); + } + } + + @Test + public void state() throws RocksDBException { + try(final DBContainer dbContainer = startDb()) { + + try(final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getState()) + .isSameAs(Transaction.TransactionState.STARTED); + txn.commit(); + assertThat(txn.getState()) + .isSameAs(Transaction.TransactionState.COMMITED); + } + + try(final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getState()) + .isSameAs(Transaction.TransactionState.STARTED); + txn.rollback(); + assertThat(txn.getState()) + .isSameAs(Transaction.TransactionState.STARTED); + } + } + } + + @Test + public void Id() throws RocksDBException { + try(final DBContainer dbContainer = startDb(); + final Transaction txn = dbContainer.beginTransaction()) { + assertThat(txn.getId()).isNotNull(); + } + } + + @Override + public TransactionDBContainer startDb() throws RocksDBException { + final DBOptions options = new DBOptions() + .setCreateIfMissing(true) + .setCreateMissingColumnFamilies(true); + final TransactionDBOptions txnDbOptions = new TransactionDBOptions(); + final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); + final List columnFamilyDescriptors = + Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor(TXN_TEST_COLUMN_FAMILY, + columnFamilyOptions)); + final List columnFamilyHandles = new ArrayList<>(); + + final TransactionDB txnDb; + try { + txnDb = TransactionDB.open(options, txnDbOptions, + dbFolder.getRoot().getAbsolutePath(), columnFamilyDescriptors, + columnFamilyHandles); + } catch(final RocksDBException e) { + columnFamilyOptions.close(); + txnDbOptions.close(); + options.close(); + throw e; + } + + final WriteOptions writeOptions = new WriteOptions(); + final TransactionOptions txnOptions = new TransactionOptions(); + + return new TransactionDBContainer(txnOptions, writeOptions, + columnFamilyHandles, txnDb, txnDbOptions, columnFamilyOptions, options); + } + + private static class TransactionDBContainer + extends DBContainer { + private final TransactionOptions txnOptions; + private final TransactionDB txnDb; + private final TransactionDBOptions txnDbOptions; + + public TransactionDBContainer( + final TransactionOptions txnOptions, final WriteOptions writeOptions, + final List columnFamilyHandles, + final TransactionDB txnDb, final TransactionDBOptions txnDbOptions, + final ColumnFamilyOptions columnFamilyOptions, + final DBOptions options) { + super(writeOptions, columnFamilyHandles, columnFamilyOptions, + options); + this.txnOptions = txnOptions; + this.txnDb = txnDb; + this.txnDbOptions = txnDbOptions; + } + + @Override + public Transaction beginTransaction() { + return txnDb.beginTransaction(writeOptions, txnOptions); + } + + @Override + public Transaction beginTransaction(final WriteOptions writeOptions) { + return txnDb.beginTransaction(writeOptions, txnOptions); + } + + @Override + public void close() { + txnOptions.close(); + writeOptions.close(); + for(final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandles) { + columnFamilyHandle.close(); + } + txnDb.close(); + txnDbOptions.close(); + options.close(); + } + } + +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WalFilterTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WalFilterTest.java new file mode 100644 index 0000000000..aeb49165d7 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WalFilterTest.java @@ -0,0 +1,164 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.rocksdb.util.TestUtil.*; + +public class WalFilterTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void walFilter() throws RocksDBException { + // Create 3 batches with two keys each + final byte[][][] batchKeys = { + new byte[][] { + u("key1"), + u("key2") + }, + new byte[][] { + u("key3"), + u("key4") + }, + new byte[][] { + u("key5"), + u("key6") + } + + }; + + final List cfDescriptors = Arrays.asList( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor(u("pikachu")) + ); + final List cfHandles = new ArrayList<>(); + + // Test with all WAL processing options + for (final WalProcessingOption option : WalProcessingOption.values()) { + try (final Options options = optionsForLogIterTest(); + final DBOptions dbOptions = new DBOptions(options) + .setCreateMissingColumnFamilies(true); + final RocksDB db = RocksDB.open(dbOptions, + dbFolder.getRoot().getAbsolutePath(), + cfDescriptors, cfHandles)) { + try (final WriteOptions writeOptions = new WriteOptions()) { + // Write given keys in given batches + for (int i = 0; i < batchKeys.length; i++) { + final WriteBatch batch = new WriteBatch(); + for (int j = 0; j < batchKeys[i].length; j++) { + batch.put(cfHandles.get(0), batchKeys[i][j], dummyString(1024)); + } + db.write(writeOptions, batch); + } + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + cfHandles.clear(); + } + } + + // Create a test filter that would apply wal_processing_option at the first + // record + final int applyOptionForRecordIndex = 1; + try (final TestableWalFilter walFilter = + new TestableWalFilter(option, applyOptionForRecordIndex)) { + + try (final Options options = optionsForLogIterTest(); + final DBOptions dbOptions = new DBOptions(options) + .setWalFilter(walFilter)) { + + try (final RocksDB db = RocksDB.open(dbOptions, + dbFolder.getRoot().getAbsolutePath(), + cfDescriptors, cfHandles)) { + + try { + assertThat(walFilter.logNumbers).isNotEmpty(); + assertThat(walFilter.logFileNames).isNotEmpty(); + } finally { + for (final ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + cfHandles.clear(); + } + } catch (final RocksDBException e) { + if (option != WalProcessingOption.CORRUPTED_RECORD) { + // exception is expected when CORRUPTED_RECORD! + throw e; + } + } + } + } + } + } + + + private static class TestableWalFilter extends AbstractWalFilter { + private final WalProcessingOption walProcessingOption; + private final int applyOptionForRecordIndex; + Map cfLognumber; + Map cfNameId; + final List logNumbers = new ArrayList<>(); + final List logFileNames = new ArrayList<>(); + private int currentRecordIndex = 0; + + public TestableWalFilter(final WalProcessingOption walProcessingOption, + final int applyOptionForRecordIndex) { + super(); + this.walProcessingOption = walProcessingOption; + this.applyOptionForRecordIndex = applyOptionForRecordIndex; + } + + @Override + public void columnFamilyLogNumberMap(final Map cfLognumber, + final Map cfNameId) { + this.cfLognumber = cfLognumber; + this.cfNameId = cfNameId; + } + + @Override + public LogRecordFoundResult logRecordFound( + final long logNumber, final String logFileName, final WriteBatch batch, + final WriteBatch newBatch) { + + logNumbers.add(logNumber); + logFileNames.add(logFileName); + + final WalProcessingOption optionToReturn; + if (currentRecordIndex == applyOptionForRecordIndex) { + optionToReturn = walProcessingOption; + } + else { + optionToReturn = WalProcessingOption.CONTINUE_PROCESSING; + } + + currentRecordIndex++; + + return new LogRecordFoundResult(optionToReturn, false); + } + + @Override + public String name() { + return "testable-wal-filter"; + } + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java index 646a31ce7c..0c7b0d3cad 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchHandlerTest.java @@ -5,15 +5,16 @@ package org.rocksdb; -import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.ClassRule; import org.junit.Test; +import org.rocksdb.util.CapturingWriteBatchHandler; +import org.rocksdb.util.CapturingWriteBatchHandler.Event; import static org.assertj.core.api.Assertions.assertThat; +import static org.rocksdb.util.CapturingWriteBatchHandler.Action.*; public class WriteBatchHandlerTest { @@ -22,45 +23,37 @@ public class WriteBatchHandlerTest { new RocksMemoryResource(); @Test - public void writeBatchHandler() throws IOException, RocksDBException { + public void writeBatchHandler() throws RocksDBException { // setup test data - final List>> testEvents = Arrays.asList( - new Tuple<>(Action.DELETE, - new Tuple("k0".getBytes(), null)), - new Tuple<>(Action.PUT, - new Tuple<>("k1".getBytes(), "v1".getBytes())), - new Tuple<>(Action.PUT, - new Tuple<>("k2".getBytes(), "v2".getBytes())), - new Tuple<>(Action.PUT, - new Tuple<>("k3".getBytes(), "v3".getBytes())), - new Tuple<>(Action.LOG, - new Tuple(null, "log1".getBytes())), - new Tuple<>(Action.MERGE, - new Tuple<>("k2".getBytes(), "v22".getBytes())), - new Tuple<>(Action.DELETE, - new Tuple("k3".getBytes(), null)) + final List testEvents = Arrays.asList( + new Event(DELETE, "k0".getBytes(), null), + new Event(PUT, "k1".getBytes(), "v1".getBytes()), + new Event(PUT, "k2".getBytes(), "v2".getBytes()), + new Event(PUT, "k3".getBytes(), "v3".getBytes()), + new Event(LOG, null, "log1".getBytes()), + new Event(MERGE, "k2".getBytes(), "v22".getBytes()), + new Event(DELETE, "k3".getBytes(), null) ); // load test data to the write batch try (final WriteBatch batch = new WriteBatch()) { - for (final Tuple> testEvent : testEvents) { - final Tuple data = testEvent.value; - switch (testEvent.key) { + for (final Event testEvent : testEvents) { + switch (testEvent.action) { case PUT: - batch.put(data.key, data.value); + batch.put(testEvent.key, testEvent.value); break; case MERGE: - batch.merge(data.key, data.value); + batch.merge(testEvent.key, testEvent.value); break; case DELETE: - batch.remove(data.key); + batch.remove(testEvent.key); break; case LOG: - batch.putLogData(data.value); + batch.putLogData(testEvent.value); break; } } @@ -72,98 +65,12 @@ public void writeBatchHandler() throws IOException, RocksDBException { batch.iterate(handler); // compare the results to the test data - final List>> actualEvents = + final List actualEvents = handler.getEvents(); assertThat(testEvents.size()).isSameAs(actualEvents.size()); - for (int i = 0; i < testEvents.size(); i++) { - assertThat(equals(testEvents.get(i), actualEvents.get(i))).isTrue(); - } + assertThat(testEvents).isEqualTo(actualEvents); } } } - - private static boolean equals( - final Tuple> expected, - final Tuple> actual) { - if (!expected.key.equals(actual.key)) { - return false; - } - - final Tuple expectedData = expected.value; - final Tuple actualData = actual.value; - - return equals(expectedData.key, actualData.key) - && equals(expectedData.value, actualData.value); - } - - private static boolean equals(byte[] expected, byte[] actual) { - if (expected != null) { - return Arrays.equals(expected, actual); - } else { - return actual == null; - } - } - - private static class Tuple { - public final K key; - public final V value; - - public Tuple(final K key, final V value) { - this.key = key; - this.value = value; - } - } - - /** - * Enumeration of Write Batch - * event actions - */ - private enum Action { PUT, MERGE, DELETE, DELETE_RANGE, LOG } - - /** - * A simple WriteBatch Handler which adds a record - * of each event that it receives to a list - */ - private static class CapturingWriteBatchHandler extends WriteBatch.Handler { - - private final List>> events - = new ArrayList<>(); - - /** - * Returns a copy of the current events list - * - * @return a list of the events which have happened upto now - */ - public List>> getEvents() { - return new ArrayList<>(events); - } - - @Override - public void put(final byte[] key, final byte[] value) { - events.add(new Tuple<>(Action.PUT, new Tuple<>(key, value))); - } - - @Override - public void merge(final byte[] key, final byte[] value) { - events.add(new Tuple<>(Action.MERGE, new Tuple<>(key, value))); - } - - @Override - public void delete(final byte[] key) { - events.add(new Tuple<>(Action.DELETE, - new Tuple(key, null))); - } - - @Override - public void deleteRange(final byte[] beginKey, final byte[] endKey) { - events.add(new Tuple<>(Action.DELETE_RANGE, new Tuple(beginKey, endKey))); - } - - @Override - public void logData(final byte[] blob) { - events.add(new Tuple<>(Action.LOG, - new Tuple(null, blob))); - } - } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java index 83f90c8eb4..92bec3dcf2 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchTest.java @@ -12,20 +12,17 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; - -import java.io.UnsupportedEncodingException; -import java.util.Arrays; +import org.rocksdb.util.CapturingWriteBatchHandler; +import org.rocksdb.util.CapturingWriteBatchHandler.Event; +import org.rocksdb.util.WriteBatchGetter; import static org.assertj.core.api.Assertions.assertThat; +import static org.rocksdb.util.CapturingWriteBatchHandler.Action.*; +import static java.nio.charset.StandardCharsets.UTF_8; /** * This class mimics the db/write_batch_test.cc * in the c++ rocksdb library. - *

    - * Not ported yet: - *

    - * Continue(); - * PutGatherSlices(); */ public class WriteBatchTest { @ClassRule @@ -44,27 +41,45 @@ public void emptyWriteBatch() { @Test public void multipleBatchOperations() - throws UnsupportedEncodingException { - try (WriteBatch batch = new WriteBatch()) { - batch.put("foo".getBytes("US-ASCII"), "bar".getBytes("US-ASCII")); - batch.remove("box".getBytes("US-ASCII")); - batch.put("baz".getBytes("US-ASCII"), "boo".getBytes("US-ASCII")); - - WriteBatchTestInternalHelper.setSequence(batch, 100); - assertThat(WriteBatchTestInternalHelper.sequence(batch)). - isNotNull(). - isEqualTo(100); - assertThat(batch.count()).isEqualTo(3); - assertThat(new String(getContents(batch), "US-ASCII")). - isEqualTo("Put(baz, boo)@102" + - "Delete(box)@101" + - "Put(foo, bar)@100"); + throws RocksDBException { + + final byte[] foo = "foo".getBytes(UTF_8); + final byte[] bar = "bar".getBytes(UTF_8); + final byte[] box = "box".getBytes(UTF_8); + final byte[] baz = "baz".getBytes(UTF_8); + final byte[] boo = "boo".getBytes(UTF_8); + final byte[] hoo = "hoo".getBytes(UTF_8); + final byte[] hello = "hello".getBytes(UTF_8); + + try (final WriteBatch batch = new WriteBatch()) { + batch.put(foo, bar); + batch.delete(box); + batch.put(baz, boo); + batch.merge(baz, hoo); + batch.singleDelete(foo); + batch.deleteRange(baz, foo); + batch.putLogData(hello); + + try(final CapturingWriteBatchHandler handler = + new CapturingWriteBatchHandler()) { + batch.iterate(handler); + + assertThat(handler.getEvents().size()).isEqualTo(7); + + assertThat(handler.getEvents().get(0)).isEqualTo(new Event(PUT, foo, bar)); + assertThat(handler.getEvents().get(1)).isEqualTo(new Event(DELETE, box, null)); + assertThat(handler.getEvents().get(2)).isEqualTo(new Event(PUT, baz, boo)); + assertThat(handler.getEvents().get(3)).isEqualTo(new Event(MERGE, baz, hoo)); + assertThat(handler.getEvents().get(4)).isEqualTo(new Event(SINGLE_DELETE, foo, null)); + assertThat(handler.getEvents().get(5)).isEqualTo(new Event(DELETE_RANGE, baz, foo)); + assertThat(handler.getEvents().get(6)).isEqualTo(new Event(LOG, null, hello)); + } } } @Test public void testAppendOperation() - throws UnsupportedEncodingException { + throws RocksDBException { try (final WriteBatch b1 = new WriteBatch(); final WriteBatch b2 = new WriteBatch()) { WriteBatchTestInternalHelper.setSequence(b1, 200); @@ -72,67 +87,66 @@ public void testAppendOperation() WriteBatchTestInternalHelper.append(b1, b2); assertThat(getContents(b1).length).isEqualTo(0); assertThat(b1.count()).isEqualTo(0); - b2.put("a".getBytes("US-ASCII"), "va".getBytes("US-ASCII")); + b2.put("a".getBytes(UTF_8), "va".getBytes(UTF_8)); WriteBatchTestInternalHelper.append(b1, b2); assertThat("Put(a, va)@200".equals(new String(getContents(b1), - "US-ASCII"))); + UTF_8))); assertThat(b1.count()).isEqualTo(1); b2.clear(); - b2.put("b".getBytes("US-ASCII"), "vb".getBytes("US-ASCII")); + b2.put("b".getBytes(UTF_8), "vb".getBytes(UTF_8)); WriteBatchTestInternalHelper.append(b1, b2); assertThat(("Put(a, va)@200" + "Put(b, vb)@201") - .equals(new String(getContents(b1), "US-ASCII"))); + .equals(new String(getContents(b1), UTF_8))); assertThat(b1.count()).isEqualTo(2); - b2.remove("foo".getBytes("US-ASCII")); + b2.delete("foo".getBytes(UTF_8)); WriteBatchTestInternalHelper.append(b1, b2); assertThat(("Put(a, va)@200" + "Put(b, vb)@202" + "Put(b, vb)@201" + "Delete(foo)@203") - .equals(new String(getContents(b1), "US-ASCII"))); + .equals(new String(getContents(b1), UTF_8))); assertThat(b1.count()).isEqualTo(4); } } @Test public void blobOperation() - throws UnsupportedEncodingException { + throws RocksDBException { try (final WriteBatch batch = new WriteBatch()) { - batch.put("k1".getBytes("US-ASCII"), "v1".getBytes("US-ASCII")); - batch.put("k2".getBytes("US-ASCII"), "v2".getBytes("US-ASCII")); - batch.put("k3".getBytes("US-ASCII"), "v3".getBytes("US-ASCII")); - batch.putLogData("blob1".getBytes("US-ASCII")); - batch.remove("k2".getBytes("US-ASCII")); - batch.putLogData("blob2".getBytes("US-ASCII")); - batch.merge("foo".getBytes("US-ASCII"), "bar".getBytes("US-ASCII")); + batch.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); + batch.put("k2".getBytes(UTF_8), "v2".getBytes(UTF_8)); + batch.put("k3".getBytes(UTF_8), "v3".getBytes(UTF_8)); + batch.putLogData("blob1".getBytes(UTF_8)); + batch.delete("k2".getBytes(UTF_8)); + batch.putLogData("blob2".getBytes(UTF_8)); + batch.merge("foo".getBytes(UTF_8), "bar".getBytes(UTF_8)); assertThat(batch.count()).isEqualTo(5); assertThat(("Merge(foo, bar)@4" + "Put(k1, v1)@0" + "Delete(k2)@3" + "Put(k2, v2)@1" + "Put(k3, v3)@2") - .equals(new String(getContents(batch), "US-ASCII"))); + .equals(new String(getContents(batch), UTF_8))); } } @Test public void savePoints() - throws UnsupportedEncodingException, RocksDBException { + throws RocksDBException { try (final WriteBatch batch = new WriteBatch()) { - batch.put("k1".getBytes("US-ASCII"), "v1".getBytes("US-ASCII")); - batch.put("k2".getBytes("US-ASCII"), "v2".getBytes("US-ASCII")); - batch.put("k3".getBytes("US-ASCII"), "v3".getBytes("US-ASCII")); + batch.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); + batch.put("k2".getBytes(UTF_8), "v2".getBytes(UTF_8)); + batch.put("k3".getBytes(UTF_8), "v3".getBytes(UTF_8)); assertThat(getFromWriteBatch(batch, "k1")).isEqualTo("v1"); assertThat(getFromWriteBatch(batch, "k2")).isEqualTo("v2"); assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3"); - batch.setSavePoint(); - batch.remove("k2".getBytes("US-ASCII")); - batch.put("k3".getBytes("US-ASCII"), "v3-2".getBytes("US-ASCII")); + batch.delete("k2".getBytes(UTF_8)); + batch.put("k3".getBytes(UTF_8), "v3-2".getBytes(UTF_8)); assertThat(getFromWriteBatch(batch, "k2")).isNull(); assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3-2"); @@ -140,8 +154,8 @@ public void savePoints() batch.setSavePoint(); - batch.put("k3".getBytes("US-ASCII"), "v3-3".getBytes("US-ASCII")); - batch.put("k4".getBytes("US-ASCII"), "v4".getBytes("US-ASCII")); + batch.put("k3".getBytes(UTF_8), "v3-3".getBytes(UTF_8)); + batch.put("k4".getBytes(UTF_8), "v4".getBytes(UTF_8)); assertThat(getFromWriteBatch(batch, "k3")).isEqualTo("v3-3"); assertThat(getFromWriteBatch(batch, "k4")).isEqualTo("v4"); @@ -166,6 +180,7 @@ public void savePoints() @Test public void deleteRange() throws RocksDBException { try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final WriteBatch batch = new WriteBatch(); final WriteOptions wOpt = new WriteOptions()) { db.put("key1".getBytes(), "value".getBytes()); db.put("key2".getBytes(), "12345678".getBytes()); @@ -176,9 +191,8 @@ public void deleteRange() throws RocksDBException { assertThat(db.get("key3".getBytes())).isEqualTo("abcdefg".getBytes()); assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - WriteBatch batch = new WriteBatch(); batch.deleteRange("key2".getBytes(), "key4".getBytes()); - db.write(new WriteOptions(), batch); + db.write(wOpt, batch); assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); assertThat(db.get("key2".getBytes())).isNull(); @@ -187,6 +201,30 @@ public void deleteRange() throws RocksDBException { } } + @Test + public void restorePoints() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + + batch.put("k1".getBytes(), "v1".getBytes()); + batch.put("k2".getBytes(), "v2".getBytes()); + + batch.setSavePoint(); + + batch.put("k1".getBytes(), "123456789".getBytes()); + batch.delete("k2".getBytes()); + + batch.rollbackToSavePoint(); + + try(final CapturingWriteBatchHandler handler = new CapturingWriteBatchHandler()) { + batch.iterate(handler); + + assertThat(handler.getEvents().size()).isEqualTo(2); + assertThat(handler.getEvents().get(0)).isEqualTo(new Event(PUT, "k1".getBytes(), "v1".getBytes())); + assertThat(handler.getEvents().get(1)).isEqualTo(new Event(PUT, "k2".getBytes(), "v2".getBytes())); + } + } + } + @Test(expected = RocksDBException.class) public void restorePoints_withoutSavePoints() throws RocksDBException { try (final WriteBatch batch = new WriteBatch()) { @@ -206,67 +244,222 @@ public void restorePoints_withoutSavePoints_nested() throws RocksDBException { } } - static byte[] getContents(final WriteBatch wb) { - return getContents(wb.nativeHandle_); + @Test + public void popSavePoint() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + + batch.put("k1".getBytes(), "v1".getBytes()); + batch.put("k2".getBytes(), "v2".getBytes()); + + batch.setSavePoint(); + + batch.put("k1".getBytes(), "123456789".getBytes()); + batch.delete("k2".getBytes()); + + batch.setSavePoint(); + + batch.popSavePoint(); + + batch.rollbackToSavePoint(); + + try(final CapturingWriteBatchHandler handler = new CapturingWriteBatchHandler()) { + batch.iterate(handler); + + assertThat(handler.getEvents().size()).isEqualTo(2); + assertThat(handler.getEvents().get(0)).isEqualTo(new Event(PUT, "k1".getBytes(), "v1".getBytes())); + assertThat(handler.getEvents().get(1)).isEqualTo(new Event(PUT, "k2".getBytes(), "v2".getBytes())); + } + } } - static String getFromWriteBatch(final WriteBatch wb, final String key) - throws RocksDBException, UnsupportedEncodingException { - final WriteBatchGetter getter = - new WriteBatchGetter(key.getBytes("US-ASCII")); - wb.iterate(getter); - if(getter.getValue() != null) { - return new String(getter.getValue(), "US-ASCII"); - } else { - return null; + @Test(expected = RocksDBException.class) + public void popSavePoint_withoutSavePoints() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + batch.popSavePoint(); } } - private static native byte[] getContents(final long writeBatchHandle); + @Test(expected = RocksDBException.class) + public void popSavePoint_withoutSavePoints_nested() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { - private static class WriteBatchGetter extends WriteBatch.Handler { + batch.setSavePoint(); + batch.popSavePoint(); + + // without previous corresponding setSavePoint + batch.popSavePoint(); + } + } - private final byte[] key; - private byte[] value; + @Test + public void maxBytes() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + batch.setMaxBytes(19); - public WriteBatchGetter(final byte[] key) { - this.key = key; + batch.put("k1".getBytes(), "v1".getBytes()); } + } - public byte[] getValue() { - return value; + @Test(expected = RocksDBException.class) + public void maxBytes_over() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + batch.setMaxBytes(1); + + batch.put("k1".getBytes(), "v1".getBytes()); } + } - @Override - public void put(final byte[] key, final byte[] value) { - if(Arrays.equals(this.key, key)) { - this.value = value; + @Test + public void data() throws RocksDBException { + try (final WriteBatch batch1 = new WriteBatch()) { + batch1.delete("k0".getBytes()); + batch1.put("k1".getBytes(), "v1".getBytes()); + batch1.put("k2".getBytes(), "v2".getBytes()); + batch1.put("k3".getBytes(), "v3".getBytes()); + batch1.putLogData("log1".getBytes()); + batch1.merge("k2".getBytes(), "v22".getBytes()); + batch1.delete("k3".getBytes()); + + final byte[] serialized = batch1.data(); + + try(final WriteBatch batch2 = new WriteBatch(serialized)) { + assertThat(batch2.count()).isEqualTo(batch1.count()); + + try(final CapturingWriteBatchHandler handler1 = new CapturingWriteBatchHandler()) { + batch1.iterate(handler1); + + try (final CapturingWriteBatchHandler handler2 = new CapturingWriteBatchHandler()) { + batch2.iterate(handler2); + + assertThat(handler1.getEvents().equals(handler2.getEvents())).isTrue(); + } + } } } + } - @Override - public void merge(final byte[] key, final byte[] value) { - if(Arrays.equals(this.key, key)) { - throw new UnsupportedOperationException(); - } + @Test + public void dataSize() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + batch.put("k1".getBytes(), "v1".getBytes()); + + assertThat(batch.getDataSize()).isEqualTo(19); } + } - @Override - public void delete(final byte[] key) { - if(Arrays.equals(this.key, key)) { - this.value = null; - } + @Test + public void hasPut() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasPut()).isFalse(); + + batch.put("k1".getBytes(), "v1".getBytes()); + + assertThat(batch.hasPut()).isTrue(); + } + } + + @Test + public void hasDelete() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasDelete()).isFalse(); + + batch.delete("k1".getBytes()); + + assertThat(batch.hasDelete()).isTrue(); + } + } + + @Test + public void hasSingleDelete() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasSingleDelete()).isFalse(); + + batch.singleDelete("k1".getBytes()); + + assertThat(batch.hasSingleDelete()).isTrue(); + } + } + + @Test + public void hasDeleteRange() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasDeleteRange()).isFalse(); + + batch.deleteRange("k1".getBytes(), "k2".getBytes()); + + assertThat(batch.hasDeleteRange()).isTrue(); + } + } + + @Test + public void hasBeginPrepareRange() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasBeginPrepare()).isFalse(); + } + } + + @Test + public void hasEndPrepareRange() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasEndPrepare()).isFalse(); } + } + + @Test + public void hasCommit() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasCommit()).isFalse(); + } + } + + @Test + public void hasRollback() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.hasRollback()).isFalse(); + } + } + + @Test + public void walTerminationPoint() throws RocksDBException { + try (final WriteBatch batch = new WriteBatch()) { + WriteBatch.SavePoint walTerminationPoint = batch.getWalTerminationPoint(); + assertThat(walTerminationPoint.isCleared()).isTrue(); + + batch.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); + + batch.markWalTerminationPoint(); + + walTerminationPoint = batch.getWalTerminationPoint(); + assertThat(walTerminationPoint.getSize()).isEqualTo(19); + assertThat(walTerminationPoint.getCount()).isEqualTo(1); + assertThat(walTerminationPoint.getContentFlags()).isEqualTo(2); + } + } - @Override - public void deleteRange(final byte[] beginKey, final byte[] endKey) { - throw new UnsupportedOperationException(); + @Test + public void getWriteBatch() { + try (final WriteBatch batch = new WriteBatch()) { + assertThat(batch.getWriteBatch()).isEqualTo(batch); } + } + + static byte[] getContents(final WriteBatch wb) { + return getContents(wb.nativeHandle_); + } - @Override - public void logData(final byte[] blob) { + static String getFromWriteBatch(final WriteBatch wb, final String key) + throws RocksDBException { + final WriteBatchGetter getter = + new WriteBatchGetter(key.getBytes(UTF_8)); + wb.iterate(getter); + if(getter.getValue() != null) { + return new String(getter.getValue(), UTF_8); + } else { + return null; } } + + private static native byte[] getContents(final long writeBatchHandle); } /** diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java index 1c5e34234e..fcef00a39f 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteBatchWithIndexTest.java @@ -14,11 +14,11 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; public class WriteBatchWithIndexTest { @@ -47,7 +47,6 @@ public void readYourOwnWrites() throws RocksDBException { try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); final RocksIterator base = db.newIterator(); final RocksIterator it = wbwi.newIteratorWithBase(base)) { - it.seek(k1); assertThat(it.isValid()).isTrue(); assertThat(it.key()).isEqualTo(k1); @@ -75,8 +74,8 @@ public void readYourOwnWrites() throws RocksDBException { assertThat(it.key()).isEqualTo(k2); assertThat(it.value()).isEqualTo(v2Other); - //remove k1 and make sure we can read back the write - wbwi.remove(k1); + //delete k1 and make sure we can read back the write + wbwi.delete(k1); it.seek(k1); assertThat(it.key()).isNotEqualTo(k1); @@ -87,12 +86,25 @@ public void readYourOwnWrites() throws RocksDBException { assertThat(it.isValid()).isTrue(); assertThat(it.key()).isEqualTo(k1); assertThat(it.value()).isEqualTo(v1Other); + + //single remove k3 and make sure we can read back the write + wbwi.singleDelete(k3); + it.seek(k3); + assertThat(it.isValid()).isEqualTo(false); + + //reinsert k3 and make sure we see the new value + final byte[] v3Other = "otherValue3".getBytes(); + wbwi.put(k3, v3Other); + it.seek(k3); + assertThat(it.isValid()).isTrue(); + assertThat(it.key()).isEqualTo(k3); + assertThat(it.value()).isEqualTo(v3Other); } } } @Test - public void write_writeBatchWithIndex() throws RocksDBException { + public void writeBatchWithIndex() throws RocksDBException { try (final Options options = new Options().setCreateIfMissing(true); final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { @@ -102,11 +114,12 @@ public void write_writeBatchWithIndex() throws RocksDBException { final byte[] k2 = "key2".getBytes(); final byte[] v2 = "value2".getBytes(); - try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(); + final WriteOptions wOpt = new WriteOptions()) { wbwi.put(k1, v1); wbwi.put(k2, v2); - db.write(new WriteOptions(), wbwi); + db.write(wOpt, wbwi); } assertThat(db.get(k1)).isEqualTo(v1); @@ -124,22 +137,39 @@ public void iterator() throws RocksDBException { final String v2 = "value2"; final String k3 = "key3"; final String v3 = "value3"; - final byte[] k1b = k1.getBytes(); - final byte[] v1b = v1.getBytes(); - final byte[] k2b = k2.getBytes(); - final byte[] v2b = v2.getBytes(); - final byte[] k3b = k3.getBytes(); - final byte[] v3b = v3.getBytes(); - - //add put records + final String k4 = "key4"; + final String k5 = "key5"; + final String k6 = "key6"; + final String k7 = "key7"; + final String v8 = "value8"; + final byte[] k1b = k1.getBytes(UTF_8); + final byte[] v1b = v1.getBytes(UTF_8); + final byte[] k2b = k2.getBytes(UTF_8); + final byte[] v2b = v2.getBytes(UTF_8); + final byte[] k3b = k3.getBytes(UTF_8); + final byte[] v3b = v3.getBytes(UTF_8); + final byte[] k4b = k4.getBytes(UTF_8); + final byte[] k5b = k5.getBytes(UTF_8); + final byte[] k6b = k6.getBytes(UTF_8); + final byte[] k7b = k7.getBytes(UTF_8); + final byte[] v8b = v8.getBytes(UTF_8); + + // add put records wbwi.put(k1b, v1b); wbwi.put(k2b, v2b); wbwi.put(k3b, v3b); - //add a deletion record - final String k4 = "key4"; - final byte[] k4b = k4.getBytes(); - wbwi.remove(k4b); + // add a deletion record + wbwi.delete(k4b); + + // add a single deletion record + wbwi.singleDelete(k5b); + + // add a delete range record + wbwi.deleteRange(k6b, k7b); + + // add a log record + wbwi.putLogData(v8b); final WBWIRocksIterator.WriteEntry[] expected = { new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, @@ -149,12 +179,16 @@ public void iterator() throws RocksDBException { new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, new DirectSlice(k3), new DirectSlice(v3)), new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.DELETE, - new DirectSlice(k4), DirectSlice.NONE) + new DirectSlice(k4), DirectSlice.NONE), + new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.SINGLE_DELETE, + new DirectSlice(k5), DirectSlice.NONE), + new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.DELETE_RANGE, + new DirectSlice(k6), new DirectSlice(k7)), }; try (final WBWIRocksIterator it = wbwi.newIterator()) { //direct access - seek to key offsets - final int[] testOffsets = {2, 0, 1, 3}; + final int[] testOffsets = {2, 0, 3, 4, 1, 5}; for (int i = 0; i < testOffsets.length; i++) { final int testOffset = testOffsets[i]; @@ -164,26 +198,26 @@ public void iterator() throws RocksDBException { assertThat(it.isValid()).isTrue(); final WBWIRocksIterator.WriteEntry entry = it.entry(); - assertThat(entry.equals(expected[testOffset])).isTrue(); + assertThat(entry).isEqualTo(expected[testOffset]); } //forward iterative access int i = 0; for (it.seekToFirst(); it.isValid(); it.next()) { - assertThat(it.entry().equals(expected[i++])).isTrue(); + assertThat(it.entry()).isEqualTo(expected[i++]); } //reverse iterative access i = expected.length - 1; for (it.seekToLast(); it.isValid(); it.prev()) { - assertThat(it.entry().equals(expected[i--])).isTrue(); + assertThat(it.entry()).isEqualTo(expected[i--]); } } } } @Test - public void zeroByteTests() { + public void zeroByteTests() throws RocksDBException { try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true)) { final byte[] zeroByteValue = new byte[]{0, 0}; //add zero byte value @@ -207,8 +241,7 @@ public void zeroByteTests() { } @Test - public void savePoints() - throws UnsupportedEncodingException, RocksDBException { + public void savePoints() throws RocksDBException { try (final Options options = new Options().setCreateIfMissing(true); final RocksDB db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath())) { @@ -228,7 +261,7 @@ public void savePoints() wbwi.setSavePoint(); - wbwi.remove("k2".getBytes()); + wbwi.delete("k2".getBytes()); wbwi.put("k3".getBytes(), "v3-2".getBytes()); assertThat(getFromWriteBatchWithIndex(db, readOptions, wbwi, "k2")) @@ -272,6 +305,27 @@ public void savePoints() } } + @Test + public void restorePoints() throws RocksDBException { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + + wbwi.put("k1".getBytes(UTF_8), "v1".getBytes(UTF_8)); + wbwi.put("k2".getBytes(UTF_8), "v2".getBytes(UTF_8)); + + wbwi.setSavePoint(); + + wbwi.put("k1".getBytes(UTF_8), "123456789".getBytes(UTF_8)); + wbwi.delete("k2".getBytes(UTF_8)); + + wbwi.rollbackToSavePoint(); + + try(final DBOptions options = new DBOptions()) { + assertThat(wbwi.getFromBatch(options,"k1".getBytes(UTF_8))).isEqualTo("v1".getBytes()); + assertThat(wbwi.getFromBatch(options,"k2".getBytes(UTF_8))).isEqualTo("v2".getBytes()); + } + } + } + @Test(expected = RocksDBException.class) public void restorePoints_withoutSavePoints() throws RocksDBException { try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { @@ -291,12 +345,84 @@ public void restorePoints_withoutSavePoints_nested() throws RocksDBException { } } + @Test + public void popSavePoint() throws RocksDBException { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + + wbwi.put("k1".getBytes(), "v1".getBytes()); + wbwi.put("k2".getBytes(), "v2".getBytes()); + + wbwi.setSavePoint(); + + wbwi.put("k1".getBytes(), "123456789".getBytes()); + wbwi.delete("k2".getBytes()); + + wbwi.setSavePoint(); + + wbwi.popSavePoint(); + + wbwi.rollbackToSavePoint(); + + try(final DBOptions options = new DBOptions()) { + assertThat(wbwi.getFromBatch(options,"k1".getBytes(UTF_8))).isEqualTo("v1".getBytes()); + assertThat(wbwi.getFromBatch(options,"k2".getBytes(UTF_8))).isEqualTo("v2".getBytes()); + } + } + } + + @Test(expected = RocksDBException.class) + public void popSavePoint_withoutSavePoints() throws RocksDBException { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + wbwi.popSavePoint(); + } + } + + @Test(expected = RocksDBException.class) + public void popSavePoint_withoutSavePoints_nested() throws RocksDBException { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + + wbwi.setSavePoint(); + wbwi.popSavePoint(); + + // without previous corresponding setSavePoint + wbwi.popSavePoint(); + } + } + + @Test + public void maxBytes() throws RocksDBException { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + wbwi.setMaxBytes(19); + + wbwi.put("k1".getBytes(), "v1".getBytes()); + } + } + + @Test(expected = RocksDBException.class) + public void maxBytes_over() throws RocksDBException { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + wbwi.setMaxBytes(1); + + wbwi.put("k1".getBytes(), "v1".getBytes()); + } + } + + @Test + public void getWriteBatch() { + try (final WriteBatchWithIndex wbwi = new WriteBatchWithIndex()) { + + final WriteBatch wb = wbwi.getWriteBatch(); + assertThat(wb).isNotNull(); + assertThat(wb.isOwningHandle()).isFalse(); + } + } + private static String getFromWriteBatchWithIndex(final RocksDB db, final ReadOptions readOptions, final WriteBatchWithIndex wbwi, final String skey) { final byte[] key = skey.getBytes(); - try(final RocksIterator baseIterator = db.newIterator(readOptions); - final RocksIterator iterator = wbwi.newIteratorWithBase(baseIterator)) { + try (final RocksIterator baseIterator = db.newIterator(readOptions); + final RocksIterator iterator = wbwi.newIteratorWithBase(baseIterator)) { iterator.seek(key); // Arrays.equals(key, iterator.key()) ensures an exact match in Rocks, @@ -329,7 +455,7 @@ public void getFromBatch() throws RocksDBException { assertThat(wbwi.getFromBatch(dbOptions, k3)).isEqualTo(v3); assertThat(wbwi.getFromBatch(dbOptions, k4)).isNull(); - wbwi.remove(k2); + wbwi.delete(k2); assertThat(wbwi.getFromBatch(dbOptions, k2)).isNull(); } @@ -372,7 +498,7 @@ public void getFromBatchAndDB() throws RocksDBException { assertThat(wbwi.getFromBatchAndDB(db, readOptions, k3)).isEqualTo(v3); assertThat(wbwi.getFromBatchAndDB(db, readOptions, k4)).isEqualTo(v4); - wbwi.remove(k4); + wbwi.delete(k4); assertThat(wbwi.getFromBatchAndDB(db, readOptions, k4)).isNull(); } @@ -387,6 +513,7 @@ private byte[] toArray(final ByteBuffer buf) { @Test public void deleteRange() throws RocksDBException { try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final WriteBatch batch = new WriteBatch(); final WriteOptions wOpt = new WriteOptions()) { db.put("key1".getBytes(), "value".getBytes()); db.put("key2".getBytes(), "12345678".getBytes()); @@ -397,9 +524,8 @@ public void deleteRange() throws RocksDBException { assertThat(db.get("key3".getBytes())).isEqualTo("abcdefg".getBytes()); assertThat(db.get("key4".getBytes())).isEqualTo("xyz".getBytes()); - WriteBatch batch = new WriteBatch(); batch.deleteRange("key2".getBytes(), "key4".getBytes()); - db.write(new WriteOptions(), batch); + db.write(wOpt, batch); assertThat(db.get("key1".getBytes())).isEqualTo("value".getBytes()); assertThat(db.get("key2".getBytes())).isNull(); diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java index 72a0687866..00c1d72396 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/WriteOptionsTest.java @@ -8,6 +8,8 @@ import org.junit.ClassRule; import org.junit.Test; +import java.util.Random; + import static org.assertj.core.api.Assertions.assertThat; public class WriteOptionsTest { @@ -16,6 +18,9 @@ public class WriteOptionsTest { public static final RocksMemoryResource rocksMemoryResource = new RocksMemoryResource(); + public static final Random rand = PlatformRandomHelper. + getPlatformSpecificRandomFactory(); + @Test public void writeOptions() { try (final WriteOptions writeOptions = new WriteOptions()) { @@ -40,6 +45,25 @@ public void writeOptions() { assertThat(writeOptions.noSlowdown()).isTrue(); writeOptions.setNoSlowdown(false); assertThat(writeOptions.noSlowdown()).isFalse(); + + writeOptions.setLowPri(true); + assertThat(writeOptions.lowPri()).isTrue(); + writeOptions.setLowPri(false); + assertThat(writeOptions.lowPri()).isFalse(); } } + + @Test + public void copyConstructor() { + WriteOptions origOpts = new WriteOptions(); + origOpts.setDisableWAL(rand.nextBoolean()); + origOpts.setIgnoreMissingColumnFamilies(rand.nextBoolean()); + origOpts.setSync(rand.nextBoolean()); + WriteOptions copyOpts = new WriteOptions(origOpts); + assertThat(origOpts.disableWAL()).isEqualTo(copyOpts.disableWAL()); + assertThat(origOpts.ignoreMissingColumnFamilies()).isEqualTo( + copyOpts.ignoreMissingColumnFamilies()); + assertThat(origOpts.sync()).isEqualTo(copyOpts.sync()); + } + } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java new file mode 100644 index 0000000000..11ffedf312 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/test/RemoveEmptyValueCompactionFilterFactory.java @@ -0,0 +1,20 @@ +package org.rocksdb.test; + +import org.rocksdb.AbstractCompactionFilter; +import org.rocksdb.AbstractCompactionFilterFactory; +import org.rocksdb.RemoveEmptyValueCompactionFilter; + +/** + * Simple CompactionFilterFactory class used in tests. Generates RemoveEmptyValueCompactionFilters. + */ +public class RemoveEmptyValueCompactionFilterFactory extends AbstractCompactionFilterFactory { + @Override + public RemoveEmptyValueCompactionFilter createCompactionFilter(final AbstractCompactionFilter.Context context) { + return new RemoveEmptyValueCompactionFilter(); + } + + @Override + public String name() { + return "RemoveEmptyValueCompactionFilterFactory"; + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java index 02ad0380ee..42d3148ef2 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/test/RocksJunitRunner.java @@ -10,10 +10,17 @@ import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +import org.rocksdb.RocksDB; +import java.io.PrintStream; +import java.text.DecimalFormat; +import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; +import static org.rocksdb.test.RocksJunitRunner.RocksJunitListener.Status.*; + /** * Custom Junit Runner to print also Test classes * and executed methods to command prompt. @@ -26,20 +33,117 @@ public class RocksJunitRunner { */ static class RocksJunitListener extends TextListener { + private final static NumberFormat secsFormat = + new DecimalFormat("###,###.###"); + + private final PrintStream writer; + + private String currentClassName = null; + private String currentMethodName = null; + private Status currentStatus = null; + private long currentTestsStartTime; + private int currentTestsCount = 0; + private int currentTestsIgnoredCount = 0; + private int currentTestsFailureCount = 0; + private int currentTestsErrorCount = 0; + + enum Status { + IGNORED, + FAILURE, + ERROR, + OK + } + /** * RocksJunitListener constructor * * @param system JUnitSystem */ public RocksJunitListener(final JUnitSystem system) { - super(system); + this(system.out()); + } + + public RocksJunitListener(final PrintStream writer) { + super(writer); + this.writer = writer; + } + + @Override + public void testRunStarted(final Description description) { + writer.format("Starting RocksJava Tests...%n"); + } @Override public void testStarted(final Description description) { - System.out.format("Run: %s testing now -> %s \n", - description.getClassName(), - description.getMethodName()); + if(currentClassName == null + || !currentClassName.equals(description.getClassName())) { + if(currentClassName != null) { + printTestsSummary(); + } else { + currentTestsStartTime = System.currentTimeMillis(); + } + writer.format("%nRunning: %s%n", description.getClassName()); + currentClassName = description.getClassName(); + } + currentMethodName = description.getMethodName(); + currentStatus = OK; + currentTestsCount++; + } + + private void printTestsSummary() { + // print summary of last test set + writer.format("Tests run: %d, Failures: %d, Errors: %d, Ignored: %d, Time elapsed: %s sec%n", + currentTestsCount, + currentTestsFailureCount, + currentTestsErrorCount, + currentTestsIgnoredCount, + formatSecs(System.currentTimeMillis() - currentTestsStartTime)); + + // reset counters + currentTestsCount = 0; + currentTestsFailureCount = 0; + currentTestsErrorCount = 0; + currentTestsIgnoredCount = 0; + currentTestsStartTime = System.currentTimeMillis(); + } + + private static String formatSecs(final double milliseconds) { + final double seconds = milliseconds / 1000; + return secsFormat.format(seconds); + } + + @Override + public void testFailure(final Failure failure) { + if (failure.getException() != null + && failure.getException() instanceof AssertionError) { + currentStatus = FAILURE; + currentTestsFailureCount++; + } else { + currentStatus = ERROR; + currentTestsErrorCount++; + } + } + + @Override + public void testIgnored(final Description description) { + currentStatus = IGNORED; + currentTestsIgnoredCount++; + } + + @Override + public void testFinished(final Description description) { + if(currentStatus == OK) { + writer.format("\t%s OK%n",currentMethodName); + } else { + writer.format(" [%s] %s%n", currentStatus.name(), currentMethodName); + } + } + + @Override + public void testRunFinished(final Result result) { + printTestsSummary(); + super.testRunFinished(result); } } diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java index 42508bc118..8149a4800a 100644 --- a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/BytewiseComparatorTest.java @@ -27,6 +27,9 @@ */ public class BytewiseComparatorTest { + private List source_strings = Arrays.asList("b", "d", "f", "h", "j", "l"); + private List interleaving_strings = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"); + /** * Open the database using the C++ BytewiseComparatorImpl * and test the results against our Java BytewiseComparator @@ -38,14 +41,18 @@ public void java_vs_cpp_bytewiseComparator() final Path dbDir = Files.createTempDirectory("comparator_db_test"); try(final RocksDB db = openDatabase(dbDir, BuiltinComparator.BYTEWISE_COMPARATOR)) { + final Random rnd = new Random(rand_seed); - doRandomIterationTest( - db, - toJavaComparator(new BytewiseComparator(new ComparatorOptions())), - Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i"), - rnd, - 8, 100, 3 - ); + try(final ComparatorOptions copt2 = new ComparatorOptions(); + final Comparator comparator2 = new BytewiseComparator(copt2)) { + final java.util.Comparator jComparator = toJavaComparator(comparator2); + doRandomIterationTest( + db, + jComparator, + rnd, + 8, 100, 3 + ); + } } finally { removeData(dbDir); } @@ -61,16 +68,21 @@ public void java_vs_java_bytewiseComparator() throws IOException, RocksDBException { for(int rand_seed = 301; rand_seed < 306; rand_seed++) { final Path dbDir = Files.createTempDirectory("comparator_db_test"); - try(final RocksDB db = openDatabase(dbDir, new BytewiseComparator( - new ComparatorOptions()))) { + try(final ComparatorOptions copt = new ComparatorOptions(); + final Comparator comparator = new BytewiseComparator(copt); + final RocksDB db = openDatabase(dbDir, comparator)) { + final Random rnd = new Random(rand_seed); - doRandomIterationTest( - db, - toJavaComparator(new BytewiseComparator(new ComparatorOptions())), - Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i"), - rnd, - 8, 100, 3 - ); + try(final ComparatorOptions copt2 = new ComparatorOptions(); + final Comparator comparator2 = new BytewiseComparator(copt2)) { + final java.util.Comparator jComparator = toJavaComparator(comparator2); + doRandomIterationTest( + db, + jComparator, + rnd, + 8, 100, 3 + ); + } } finally { removeData(dbDir); } @@ -88,16 +100,18 @@ public void java_vs_cpp_directBytewiseComparator() final Path dbDir = Files.createTempDirectory("comparator_db_test"); try(final RocksDB db = openDatabase(dbDir, BuiltinComparator.BYTEWISE_COMPARATOR)) { + final Random rnd = new Random(rand_seed); - doRandomIterationTest( - db, - toJavaComparator(new DirectBytewiseComparator( - new ComparatorOptions()) - ), - Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i"), - rnd, - 8, 100, 3 - ); + try(final ComparatorOptions copt2 = new ComparatorOptions(); + final DirectComparator comparator2 = new DirectBytewiseComparator(copt2)) { + final java.util.Comparator jComparator = toJavaComparator(comparator2); + doRandomIterationTest( + db, + jComparator, + rnd, + 8, 100, 3 + ); + } } finally { removeData(dbDir); } @@ -113,18 +127,21 @@ public void java_vs_java_directBytewiseComparator() throws IOException, RocksDBException { for(int rand_seed = 301; rand_seed < 306; rand_seed++) { final Path dbDir = Files.createTempDirectory("comparator_db_test"); - try(final RocksDB db = openDatabase(dbDir, new DirectBytewiseComparator( - new ComparatorOptions()))) { + try (final ComparatorOptions copt = new ComparatorOptions(); + final DirectComparator comparator = new DirectBytewiseComparator(copt); + final RocksDB db = openDatabase(dbDir, comparator)) { + final Random rnd = new Random(rand_seed); - doRandomIterationTest( - db, - toJavaComparator(new DirectBytewiseComparator( - new ComparatorOptions()) - ), - Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i"), - rnd, - 8, 100, 3 - ); + try(final ComparatorOptions copt2 = new ComparatorOptions(); + final DirectComparator comparator2 = new DirectBytewiseComparator(copt2)) { + final java.util.Comparator jComparator = toJavaComparator(comparator2); + doRandomIterationTest( + db, + jComparator, + rnd, + 8, 100, 3 + ); + } } finally { removeData(dbDir); } @@ -142,16 +159,18 @@ public void java_vs_cpp_reverseBytewiseComparator() final Path dbDir = Files.createTempDirectory("comparator_db_test"); try(final RocksDB db = openDatabase(dbDir, BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR)) { + final Random rnd = new Random(rand_seed); - doRandomIterationTest( - db, - toJavaComparator( - new ReverseBytewiseComparator(new ComparatorOptions()) - ), - Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i"), - rnd, - 8, 100, 3 - ); + try(final ComparatorOptions copt2 = new ComparatorOptions(); + final Comparator comparator2 = new ReverseBytewiseComparator(copt2)) { + final java.util.Comparator jComparator = toJavaComparator(comparator2); + doRandomIterationTest( + db, + jComparator, + rnd, + 8, 100, 3 + ); + } } finally { removeData(dbDir); } @@ -165,21 +184,23 @@ public void java_vs_cpp_reverseBytewiseComparator() @Test public void java_vs_java_reverseBytewiseComparator() throws IOException, RocksDBException { - for(int rand_seed = 301; rand_seed < 306; rand_seed++) { final Path dbDir = Files.createTempDirectory("comparator_db_test"); - try(final RocksDB db = openDatabase(dbDir, new ReverseBytewiseComparator( - new ComparatorOptions()))) { + try (final ComparatorOptions copt = new ComparatorOptions(); + final Comparator comparator = new ReverseBytewiseComparator(copt); + final RocksDB db = openDatabase(dbDir, comparator)) { + final Random rnd = new Random(rand_seed); - doRandomIterationTest( - db, - toJavaComparator( - new ReverseBytewiseComparator(new ComparatorOptions()) - ), - Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i"), - rnd, - 8, 100, 3 - ); + try(final ComparatorOptions copt2 = new ComparatorOptions(); + final Comparator comparator2 = new ReverseBytewiseComparator(copt2)) { + final java.util.Comparator jComparator = toJavaComparator(comparator2); + doRandomIterationTest( + db, + jComparator, + rnd, + 8, 100, 3 + ); + } } finally { removeData(dbDir); } @@ -188,7 +209,7 @@ public void java_vs_java_reverseBytewiseComparator() private void doRandomIterationTest( final RocksDB db, final java.util.Comparator javaComparator, - final List source_strings, final Random rnd, + final Random rnd, final int num_writes, final int num_iter_ops, final int num_trigger_flush) throws RocksDBException { @@ -228,7 +249,7 @@ private void doRandomIterationTest( for (int i = 0; i < num_iter_ops; i++) { // Random walk and make sure iter and result_iter returns the // same key and value - final int type = rnd.nextInt(6); + final int type = rnd.nextInt(7); iter.status(); switch (type) { case 0: @@ -242,14 +263,22 @@ private void doRandomIterationTest( result_iter.seekToLast(); break; case 2: { - // Seek to random key - final int key_idx = rnd.nextInt(source_strings.size()); - final String key = source_strings.get(key_idx); + // Seek to random (existing or non-existing) key + final int key_idx = rnd.nextInt(interleaving_strings.size()); + final String key = interleaving_strings.get(key_idx); iter.seek(bytes(key)); result_iter.seek(bytes(key)); break; } - case 3: + case 3: { + // SeekForPrev to random (existing or non-existing) key + final int key_idx = rnd.nextInt(interleaving_strings.size()); + final String key = interleaving_strings.get(key_idx); + iter.seekForPrev(bytes(key)); + result_iter.seekForPrev(bytes(key)); + break; + } + case 4: // Next if (is_valid) { iter.next(); @@ -258,7 +287,7 @@ private void doRandomIterationTest( continue; } break; - case 4: + case 5: // Prev if (is_valid) { iter.prev(); @@ -268,7 +297,7 @@ private void doRandomIterationTest( } break; default: { - assert (type == 5); + assert (type == 6); final int key_idx = rnd.nextInt(source_strings.size()); final String key = source_strings.get(key_idx); final byte[] result = db.get(new ReadOptions(), bytes(key)); @@ -413,6 +442,16 @@ public void seek(final byte[] target) { } } + @Override + public void seekForPrev(final byte[] target) { + for(offset = entries.size()-1; offset >= 0; offset--) { + if(comparator.compare(entries.get(offset).getKey(), + (K)new String(target, StandardCharsets.UTF_8)) <= 0) { + return; + } + } + } + /** * Is `a` a prefix of `b` * diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java new file mode 100644 index 0000000000..83ac5d3d27 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/CapturingWriteBatchHandler.java @@ -0,0 +1,171 @@ +package org.rocksdb.util; + +import org.rocksdb.RocksDBException; +import org.rocksdb.WriteBatch; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * A simple WriteBatch Handler which adds a record + * of each event that it receives to a list + */ +public class CapturingWriteBatchHandler extends WriteBatch.Handler { + + private final List events = new ArrayList<>(); + + /** + * Returns a copy of the current events list + * + * @return a list of the events which have happened upto now + */ + public List getEvents() { + return new ArrayList<>(events); + } + + @Override + public void put(final int columnFamilyId, final byte[] key, + final byte[] value) { + events.add(new Event(Action.PUT, columnFamilyId, key, value)); + } + + @Override + public void put(final byte[] key, final byte[] value) { + events.add(new Event(Action.PUT, key, value)); + } + + @Override + public void merge(final int columnFamilyId, final byte[] key, + final byte[] value) { + events.add(new Event(Action.MERGE, columnFamilyId, key, value)); + } + + @Override + public void merge(final byte[] key, final byte[] value) { + events.add(new Event(Action.MERGE, key, value)); + } + + @Override + public void delete(final int columnFamilyId, final byte[] key) { + events.add(new Event(Action.DELETE, columnFamilyId, key, (byte[])null)); + } + + @Override + public void delete(final byte[] key) { + events.add(new Event(Action.DELETE, key, (byte[])null)); + } + + @Override + public void singleDelete(final int columnFamilyId, final byte[] key) { + events.add(new Event(Action.SINGLE_DELETE, + columnFamilyId, key, (byte[])null)); + } + + @Override + public void singleDelete(final byte[] key) { + events.add(new Event(Action.SINGLE_DELETE, key, (byte[])null)); + } + + @Override + public void deleteRange(final int columnFamilyId, final byte[] beginKey, + final byte[] endKey) { + events.add(new Event(Action.DELETE_RANGE, columnFamilyId, beginKey, + endKey)); + } + + @Override + public void deleteRange(final byte[] beginKey, final byte[] endKey) { + events.add(new Event(Action.DELETE_RANGE, beginKey, endKey)); + } + + @Override + public void logData(final byte[] blob) { + events.add(new Event(Action.LOG, (byte[])null, blob)); + } + + @Override + public void putBlobIndex(final int columnFamilyId, final byte[] key, + final byte[] value) { + events.add(new Event(Action.PUT_BLOB_INDEX, key, value)); + } + + @Override + public void markBeginPrepare() throws RocksDBException { + events.add(new Event(Action.MARK_BEGIN_PREPARE, (byte[])null, + (byte[])null)); + } + + @Override + public void markEndPrepare(final byte[] xid) throws RocksDBException { + events.add(new Event(Action.MARK_END_PREPARE, (byte[])null, + (byte[])null)); + } + + @Override + public void markNoop(final boolean emptyBatch) throws RocksDBException { + events.add(new Event(Action.MARK_NOOP, (byte[])null, (byte[])null)); + } + + @Override + public void markRollback(final byte[] xid) throws RocksDBException { + events.add(new Event(Action.MARK_ROLLBACK, (byte[])null, (byte[])null)); + } + + @Override + public void markCommit(final byte[] xid) throws RocksDBException { + events.add(new Event(Action.MARK_COMMIT, (byte[])null, (byte[])null)); + } + + public static class Event { + public final Action action; + public final int columnFamilyId; + public final byte[] key; + public final byte[] value; + + public Event(final Action action, final byte[] key, final byte[] value) { + this(action, 0, key, value); + } + + public Event(final Action action, final int columnFamilyId, final byte[] key, + final byte[] value) { + this.action = action; + this.columnFamilyId = columnFamilyId; + this.key = key; + this.value = value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final Event event = (Event) o; + return columnFamilyId == event.columnFamilyId && + action == event.action && + ((key == null && event.key == null) + || Arrays.equals(key, event.key)) && + ((value == null && event.value == null) + || Arrays.equals(value, event.value)); + } + + @Override + public int hashCode() { + + return Objects.hash(action, columnFamilyId, key, value); + } + } + + /** + * Enumeration of Write Batch + * event actions + */ + public enum Action { + PUT, MERGE, DELETE, SINGLE_DELETE, DELETE_RANGE, LOG, PUT_BLOB_INDEX, + MARK_BEGIN_PREPARE, MARK_END_PREPARE, MARK_NOOP, MARK_COMMIT, + MARK_ROLLBACK } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/TestUtil.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/TestUtil.java new file mode 100644 index 0000000000..12b3bbbbdc --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/TestUtil.java @@ -0,0 +1,72 @@ +// Copyright (c) 2011-present, Facebook, 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). + +package org.rocksdb.util; + +import org.rocksdb.CompactionPriority; +import org.rocksdb.Options; +import org.rocksdb.WALRecoveryMode; + +import java.util.Random; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * General test utilities. + */ +public class TestUtil { + + /** + * Get the options for log iteration tests. + * + * @return the options + */ + public static Options optionsForLogIterTest() { + return defaultOptions() + .setCreateIfMissing(true) + .setWalTtlSeconds(1000); + } + + /** + * Get the default options. + * + * @return the options + */ + public static Options defaultOptions() { + return new Options() + .setWriteBufferSize(4090 * 4096) + .setTargetFileSizeBase(2 * 1024 * 1024) + .setMaxBytesForLevelBase(10 * 1024 * 1024) + .setMaxOpenFiles(5000) + .setWalRecoveryMode(WALRecoveryMode.TolerateCorruptedTailRecords) + .setCompactionPriority(CompactionPriority.ByCompensatedSize); + } + + private static final Random random = new Random(); + + /** + * Generate a random string of bytes. + * + * @param len the length of the string to generate. + * + * @return the random string of bytes + */ + public static byte[] dummyString(final int len) { + final byte[] str = new byte[len]; + random.nextBytes(str); + return str; + } + + /** + * Convert a UTF-8 String to a byte array. + * + * @param str the string + * + * @return the byte array. + */ + public static byte[] u(final String str) { + return str.getBytes(UTF_8); + } +} diff --git a/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java new file mode 100644 index 0000000000..a0d8d669d2 --- /dev/null +++ b/thirdparty/rocksdb/java/src/test/java/org/rocksdb/util/WriteBatchGetter.java @@ -0,0 +1,133 @@ +package org.rocksdb.util; + +import org.rocksdb.RocksDBException; +import org.rocksdb.WriteBatch; + +import java.util.Arrays; + +public class WriteBatchGetter extends WriteBatch.Handler { + + private int columnFamilyId = -1; + private final byte[] key; + private byte[] value; + + public WriteBatchGetter(final byte[] key) { + this.key = key; + } + + public byte[] getValue() { + return value; + } + + @Override + public void put(final int columnFamilyId, final byte[] key, + final byte[] value) { + if(Arrays.equals(this.key, key)) { + this.columnFamilyId = columnFamilyId; + this.value = value; + } + } + + @Override + public void put(final byte[] key, final byte[] value) { + if(Arrays.equals(this.key, key)) { + this.value = value; + } + } + + @Override + public void merge(final int columnFamilyId, final byte[] key, + final byte[] value) { + if(Arrays.equals(this.key, key)) { + this.columnFamilyId = columnFamilyId; + this.value = value; + } + } + + @Override + public void merge(final byte[] key, final byte[] value) { + if(Arrays.equals(this.key, key)) { + this.value = value; + } + } + + @Override + public void delete(final int columnFamilyId, final byte[] key) { + if(Arrays.equals(this.key, key)) { + this.columnFamilyId = columnFamilyId; + this.value = null; + } + } + + @Override + public void delete(final byte[] key) { + if(Arrays.equals(this.key, key)) { + this.value = null; + } + } + + @Override + public void singleDelete(final int columnFamilyId, final byte[] key) { + if(Arrays.equals(this.key, key)) { + this.columnFamilyId = columnFamilyId; + this.value = null; + } + } + + @Override + public void singleDelete(final byte[] key) { + if(Arrays.equals(this.key, key)) { + this.value = null; + } + } + + @Override + public void deleteRange(final int columnFamilyId, final byte[] beginKey, + final byte[] endKey) { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteRange(final byte[] beginKey, final byte[] endKey) { + throw new UnsupportedOperationException(); + } + + @Override + public void logData(final byte[] blob) { + throw new UnsupportedOperationException(); + } + + @Override + public void putBlobIndex(final int columnFamilyId, final byte[] key, + final byte[] value) { + if(Arrays.equals(this.key, key)) { + this.columnFamilyId = columnFamilyId; + this.value = value; + } + } + + @Override + public void markBeginPrepare() throws RocksDBException { + throw new UnsupportedOperationException(); + } + + @Override + public void markEndPrepare(final byte[] xid) throws RocksDBException { + throw new UnsupportedOperationException(); + } + + @Override + public void markNoop(final boolean emptyBatch) throws RocksDBException { + throw new UnsupportedOperationException(); + } + + @Override + public void markRollback(final byte[] xid) throws RocksDBException { + throw new UnsupportedOperationException(); + } + + @Override + public void markCommit(final byte[] xid) throws RocksDBException { + throw new UnsupportedOperationException(); + } +} diff --git a/thirdparty/rocksdb/memtable/alloc_tracker.cc b/thirdparty/rocksdb/memtable/alloc_tracker.cc index 9889cc4230..a1fa4938c5 100644 --- a/thirdparty/rocksdb/memtable/alloc_tracker.cc +++ b/thirdparty/rocksdb/memtable/alloc_tracker.cc @@ -24,7 +24,8 @@ AllocTracker::~AllocTracker() { FreeMem(); } void AllocTracker::Allocate(size_t bytes) { assert(write_buffer_manager_ != nullptr); - if (write_buffer_manager_->enabled()) { + if (write_buffer_manager_->enabled() || + write_buffer_manager_->cost_to_cache()) { bytes_allocated_.fetch_add(bytes, std::memory_order_relaxed); write_buffer_manager_->ReserveMem(bytes); } @@ -32,7 +33,8 @@ void AllocTracker::Allocate(size_t bytes) { void AllocTracker::DoneAllocating() { if (write_buffer_manager_ != nullptr && !done_allocating_) { - if (write_buffer_manager_->enabled()) { + if (write_buffer_manager_->enabled() || + write_buffer_manager_->cost_to_cache()) { write_buffer_manager_->ScheduleFreeMem( bytes_allocated_.load(std::memory_order_relaxed)); } else { @@ -47,7 +49,8 @@ void AllocTracker::FreeMem() { DoneAllocating(); } if (write_buffer_manager_ != nullptr && !freed_) { - if (write_buffer_manager_->enabled()) { + if (write_buffer_manager_->enabled() || + write_buffer_manager_->cost_to_cache()) { write_buffer_manager_->FreeMem( bytes_allocated_.load(std::memory_order_relaxed)); } else { diff --git a/thirdparty/rocksdb/memtable/hash_cuckoo_rep.cc b/thirdparty/rocksdb/memtable/hash_cuckoo_rep.cc deleted file mode 100644 index 034bf5858b..0000000000 --- a/thirdparty/rocksdb/memtable/hash_cuckoo_rep.cc +++ /dev/null @@ -1,660 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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 "memtable/hash_cuckoo_rep.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "db/memtable.h" -#include "memtable/skiplist.h" -#include "memtable/stl_wrappers.h" -#include "port/port.h" -#include "rocksdb/memtablerep.h" -#include "util/murmurhash.h" - -namespace rocksdb { -namespace { - -// the default maximum size of the cuckoo path searching queue -static const int kCuckooPathMaxSearchSteps = 100; - -struct CuckooStep { - static const int kNullStep = -1; - // the bucket id in the cuckoo array. - int bucket_id_; - // index of cuckoo-step array that points to its previous step, - // -1 if it the beginning step. - int prev_step_id_; - // the depth of the current step. - unsigned int depth_; - - CuckooStep() : bucket_id_(-1), prev_step_id_(kNullStep), depth_(1) {} - - CuckooStep(CuckooStep&& o) = default; - - CuckooStep& operator=(CuckooStep&& rhs) { - bucket_id_ = std::move(rhs.bucket_id_); - prev_step_id_ = std::move(rhs.prev_step_id_); - depth_ = std::move(rhs.depth_); - return *this; - } - - CuckooStep(const CuckooStep&) = delete; - CuckooStep& operator=(const CuckooStep&) = delete; - - CuckooStep(int bucket_id, int prev_step_id, int depth) - : bucket_id_(bucket_id), prev_step_id_(prev_step_id), depth_(depth) {} -}; - -class HashCuckooRep : public MemTableRep { - public: - explicit HashCuckooRep(const MemTableRep::KeyComparator& compare, - Allocator* allocator, const size_t bucket_count, - const unsigned int hash_func_count, - const size_t approximate_entry_size) - : MemTableRep(allocator), - compare_(compare), - allocator_(allocator), - bucket_count_(bucket_count), - approximate_entry_size_(approximate_entry_size), - cuckoo_path_max_depth_(kDefaultCuckooPathMaxDepth), - occupied_count_(0), - hash_function_count_(hash_func_count), - backup_table_(nullptr) { - char* mem = reinterpret_cast( - allocator_->Allocate(sizeof(std::atomic) * bucket_count_)); - cuckoo_array_ = new (mem) std::atomic[bucket_count_]; - for (unsigned int bid = 0; bid < bucket_count_; ++bid) { - cuckoo_array_[bid].store(nullptr, std::memory_order_relaxed); - } - - cuckoo_path_ = reinterpret_cast( - allocator_->Allocate(sizeof(int) * (cuckoo_path_max_depth_ + 1))); - is_nearly_full_ = false; - } - - // return false, indicating HashCuckooRep does not support merge operator. - virtual bool IsMergeOperatorSupported() const override { return false; } - - // return false, indicating HashCuckooRep does not support snapshot. - virtual bool IsSnapshotSupported() const override { return false; } - - // Returns true iff an entry that compares equal to key is in the collection. - virtual bool Contains(const char* internal_key) const override; - - virtual ~HashCuckooRep() override {} - - // Insert the specified key (internal_key) into the mem-table. Assertion - // fails if - // the current mem-table already contains the specified key. - virtual void Insert(KeyHandle handle) override; - - // This function returns bucket_count_ * approximate_entry_size_ when any - // of the followings happen to disallow further write operations: - // 1. when the fullness reaches kMaxFullnes. - // 2. when the backup_table_ is used. - // - // otherwise, this function will always return 0. - virtual size_t ApproximateMemoryUsage() override { - if (is_nearly_full_) { - return bucket_count_ * approximate_entry_size_; - } - return 0; - } - - virtual void Get(const LookupKey& k, void* callback_args, - bool (*callback_func)(void* arg, - const char* entry)) override; - - class Iterator : public MemTableRep::Iterator { - std::shared_ptr> bucket_; - std::vector::const_iterator mutable cit_; - const KeyComparator& compare_; - std::string tmp_; // For passing to EncodeKey - bool mutable sorted_; - void DoSort() const; - - public: - explicit Iterator(std::shared_ptr> bucket, - const KeyComparator& compare); - - // Initialize an iterator over the specified collection. - // The returned iterator is not valid. - // explicit Iterator(const MemTableRep* collection); - virtual ~Iterator() override{}; - - // Returns true iff the iterator is positioned at a valid node. - virtual bool Valid() const override; - - // Returns the key at the current position. - // REQUIRES: Valid() - virtual const char* key() const override; - - // Advances to the next position. - // REQUIRES: Valid() - virtual void Next() override; - - // Advances to the previous position. - // REQUIRES: Valid() - virtual void Prev() override; - - // Advance to the first entry with a key >= target - virtual void Seek(const Slice& user_key, const char* memtable_key) override; - - // Retreat to the last entry with a key <= target - virtual void SeekForPrev(const Slice& user_key, - const char* memtable_key) override; - - // Position at the first entry in collection. - // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToFirst() override; - - // Position at the last entry in collection. - // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToLast() override; - }; - - struct CuckooStepBuffer { - CuckooStepBuffer() : write_index_(0), read_index_(0) {} - ~CuckooStepBuffer() {} - - int write_index_; - int read_index_; - CuckooStep steps_[kCuckooPathMaxSearchSteps]; - - CuckooStep& NextWriteBuffer() { return steps_[write_index_++]; } - - inline const CuckooStep& ReadNext() { return steps_[read_index_++]; } - - inline bool HasNewWrite() { return write_index_ > read_index_; } - - inline void reset() { - write_index_ = 0; - read_index_ = 0; - } - - inline bool IsFull() { return write_index_ >= kCuckooPathMaxSearchSteps; } - - // returns the number of steps that has been read - inline int ReadCount() { return read_index_; } - - // returns the number of steps that has been written to the buffer. - inline int WriteCount() { return write_index_; } - }; - - private: - const MemTableRep::KeyComparator& compare_; - // the pointer to Allocator to allocate memory, immutable after construction. - Allocator* const allocator_; - // the number of hash bucket in the hash table. - const size_t bucket_count_; - // approximate size of each entry - const size_t approximate_entry_size_; - // the maxinum depth of the cuckoo path. - const unsigned int cuckoo_path_max_depth_; - // the current number of entries in cuckoo_array_ which has been occupied. - size_t occupied_count_; - // the current number of hash functions used in the cuckoo hash. - unsigned int hash_function_count_; - // the backup MemTableRep to handle the case where cuckoo hash cannot find - // a vacant bucket for inserting the key of a put request. - std::shared_ptr backup_table_; - // the array to store pointers, pointing to the actual data. - std::atomic* cuckoo_array_; - // a buffer to store cuckoo path - int* cuckoo_path_; - // a boolean flag indicating whether the fullness of bucket array - // reaches the point to make the current memtable immutable. - bool is_nearly_full_; - - // the default maximum depth of the cuckoo path. - static const unsigned int kDefaultCuckooPathMaxDepth = 10; - - CuckooStepBuffer step_buffer_; - - // returns the bucket id assogied to the input slice based on the - unsigned int GetHash(const Slice& slice, const int hash_func_id) const { - // the seeds used in the Murmur hash to produce different hash functions. - static const int kMurmurHashSeeds[HashCuckooRepFactory::kMaxHashCount] = { - 545609244, 1769731426, 763324157, 13099088, 592422103, - 1899789565, 248369300, 1984183468, 1613664382, 1491157517}; - return static_cast( - MurmurHash(slice.data(), static_cast(slice.size()), - kMurmurHashSeeds[hash_func_id]) % - bucket_count_); - } - - // A cuckoo path is a sequence of bucket ids, where each id points to a - // location of cuckoo_array_. This path describes the displacement sequence - // of entries in order to store the desired data specified by the input user - // key. The path starts from one of the locations associated with the - // specified user key and ends at a vacant space in the cuckoo array. This - // function will update the cuckoo_path. - // - // @return true if it found a cuckoo path. - bool FindCuckooPath(const char* internal_key, const Slice& user_key, - int* cuckoo_path, size_t* cuckoo_path_length, - int initial_hash_id = 0); - - // Perform quick insert by checking whether there is a vacant bucket in one - // of the possible locations of the input key. If so, then the function will - // return true and the key will be stored in that vacant bucket. - // - // This function is a helper function of FindCuckooPath that discovers the - // first possible steps of a cuckoo path. It begins by first computing - // the possible locations of the input keys (and stores them in bucket_ids.) - // Then, if one of its possible locations is vacant, then the input key will - // be stored in that vacant space and the function will return true. - // Otherwise, the function will return false indicating a complete search - // of cuckoo-path is needed. - bool QuickInsert(const char* internal_key, const Slice& user_key, - int bucket_ids[], const int initial_hash_id); - - // Returns the pointer to the internal iterator to the buckets where buckets - // are sorted according to the user specified KeyComparator. Note that - // any insert after this function call may affect the sorted nature of - // the returned iterator. - virtual MemTableRep::Iterator* GetIterator(Arena* arena) override { - std::vector compact_buckets; - for (unsigned int bid = 0; bid < bucket_count_; ++bid) { - const char* bucket = cuckoo_array_[bid].load(std::memory_order_relaxed); - if (bucket != nullptr) { - compact_buckets.push_back(bucket); - } - } - MemTableRep* backup_table = backup_table_.get(); - if (backup_table != nullptr) { - std::unique_ptr iter(backup_table->GetIterator()); - for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { - compact_buckets.push_back(iter->key()); - } - } - if (arena == nullptr) { - return new Iterator( - std::shared_ptr>( - new std::vector(std::move(compact_buckets))), - compare_); - } else { - auto mem = arena->AllocateAligned(sizeof(Iterator)); - return new (mem) Iterator( - std::shared_ptr>( - new std::vector(std::move(compact_buckets))), - compare_); - } - } -}; - -void HashCuckooRep::Get(const LookupKey& key, void* callback_args, - bool (*callback_func)(void* arg, const char* entry)) { - Slice user_key = key.user_key(); - for (unsigned int hid = 0; hid < hash_function_count_; ++hid) { - const char* bucket = - cuckoo_array_[GetHash(user_key, hid)].load(std::memory_order_acquire); - if (bucket != nullptr) { - Slice bucket_user_key = UserKey(bucket); - if (user_key == bucket_user_key) { - callback_func(callback_args, bucket); - break; - } - } else { - // as Put() always stores at the vacant bucket located by the - // hash function with the smallest possible id, when we first - // find a vacant bucket in Get(), that means a miss. - break; - } - } - MemTableRep* backup_table = backup_table_.get(); - if (backup_table != nullptr) { - backup_table->Get(key, callback_args, callback_func); - } -} - -void HashCuckooRep::Insert(KeyHandle handle) { - static const float kMaxFullness = 0.90f; - - auto* key = static_cast(handle); - int initial_hash_id = 0; - size_t cuckoo_path_length = 0; - auto user_key = UserKey(key); - // find cuckoo path - if (FindCuckooPath(key, user_key, cuckoo_path_, &cuckoo_path_length, - initial_hash_id) == false) { - // if true, then we can't find a vacant bucket for this key even we - // have used up all the hash functions. Then use a backup memtable to - // store such key, which will further make this mem-table become - // immutable. - if (backup_table_.get() == nullptr) { - VectorRepFactory factory(10); - backup_table_.reset( - factory.CreateMemTableRep(compare_, allocator_, nullptr, nullptr)); - is_nearly_full_ = true; - } - backup_table_->Insert(key); - return; - } - // when reaching this point, means the insert can be done successfully. - occupied_count_++; - if (occupied_count_ >= bucket_count_ * kMaxFullness) { - is_nearly_full_ = true; - } - - // perform kickout process if the length of cuckoo path > 1. - if (cuckoo_path_length == 0) return; - - // the cuckoo path stores the kickout path in reverse order. - // so the kickout or displacement is actually performed - // in reverse order, which avoids false-negatives on read - // by moving each key involved in the cuckoo path to the new - // location before replacing it. - for (size_t i = 1; i < cuckoo_path_length; ++i) { - int kicked_out_bid = cuckoo_path_[i - 1]; - int current_bid = cuckoo_path_[i]; - // since we only allow one writer at a time, it is safe to do relaxed read. - cuckoo_array_[kicked_out_bid] - .store(cuckoo_array_[current_bid].load(std::memory_order_relaxed), - std::memory_order_release); - } - int insert_key_bid = cuckoo_path_[cuckoo_path_length - 1]; - cuckoo_array_[insert_key_bid].store(key, std::memory_order_release); -} - -bool HashCuckooRep::Contains(const char* internal_key) const { - auto user_key = UserKey(internal_key); - for (unsigned int hid = 0; hid < hash_function_count_; ++hid) { - const char* stored_key = - cuckoo_array_[GetHash(user_key, hid)].load(std::memory_order_acquire); - if (stored_key != nullptr) { - if (compare_(internal_key, stored_key) == 0) { - return true; - } - } - } - return false; -} - -bool HashCuckooRep::QuickInsert(const char* internal_key, const Slice& user_key, - int bucket_ids[], const int initial_hash_id) { - int cuckoo_bucket_id = -1; - - // Below does the followings: - // 0. Calculate all possible locations of the input key. - // 1. Check if there is a bucket having same user_key as the input does. - // 2. If there exists such bucket, then replace this bucket by the newly - // insert data and return. This step also performs duplication check. - // 3. If no such bucket exists but exists a vacant bucket, then insert the - // input data into it. - // 4. If step 1 to 3 all fail, then return false. - for (unsigned int hid = initial_hash_id; hid < hash_function_count_; ++hid) { - bucket_ids[hid] = GetHash(user_key, hid); - // since only one PUT is allowed at a time, and this is part of the PUT - // operation, so we can safely perform relaxed load. - const char* stored_key = - cuckoo_array_[bucket_ids[hid]].load(std::memory_order_relaxed); - if (stored_key == nullptr) { - if (cuckoo_bucket_id == -1) { - cuckoo_bucket_id = bucket_ids[hid]; - } - } else { - const auto bucket_user_key = UserKey(stored_key); - if (bucket_user_key.compare(user_key) == 0) { - cuckoo_bucket_id = bucket_ids[hid]; - break; - } - } - } - - if (cuckoo_bucket_id != -1) { - cuckoo_array_[cuckoo_bucket_id].store(const_cast(internal_key), - std::memory_order_release); - return true; - } - - return false; -} - -// Perform pre-check and find the shortest cuckoo path. A cuckoo path -// is a displacement sequence for inserting the specified input key. -// -// @return true if it successfully found a vacant space or cuckoo-path. -// If the return value is true but the length of cuckoo_path is zero, -// then it indicates that a vacant bucket or an bucket with matched user -// key with the input is found, and a quick insertion is done. -bool HashCuckooRep::FindCuckooPath(const char* internal_key, - const Slice& user_key, int* cuckoo_path, - size_t* cuckoo_path_length, - const int initial_hash_id) { - int bucket_ids[HashCuckooRepFactory::kMaxHashCount]; - *cuckoo_path_length = 0; - - if (QuickInsert(internal_key, user_key, bucket_ids, initial_hash_id)) { - return true; - } - // If this step is reached, then it means: - // 1. no vacant bucket in any of the possible locations of the input key. - // 2. none of the possible locations of the input key has the same user - // key as the input `internal_key`. - - // the front and back indices for the step_queue_ - step_buffer_.reset(); - - for (unsigned int hid = initial_hash_id; hid < hash_function_count_; ++hid) { - /// CuckooStep& current_step = step_queue_[front_pos++]; - CuckooStep& current_step = step_buffer_.NextWriteBuffer(); - current_step.bucket_id_ = bucket_ids[hid]; - current_step.prev_step_id_ = CuckooStep::kNullStep; - current_step.depth_ = 1; - } - - while (step_buffer_.HasNewWrite()) { - int step_id = step_buffer_.read_index_; - const CuckooStep& step = step_buffer_.ReadNext(); - // Since it's a BFS process, then the first step with its depth deeper - // than the maximum allowed depth indicates all the remaining steps - // in the step buffer queue will all exceed the maximum depth. - // Return false immediately indicating we can't find a vacant bucket - // for the input key before the maximum allowed depth. - if (step.depth_ >= cuckoo_path_max_depth_) { - return false; - } - // again, we can perform no barrier load safely here as the current - // thread is the only writer. - Slice bucket_user_key = - UserKey(cuckoo_array_[step.bucket_id_].load(std::memory_order_relaxed)); - if (step.prev_step_id_ != CuckooStep::kNullStep) { - if (bucket_user_key == user_key) { - // then there is a loop in the current path, stop discovering this path. - continue; - } - } - // if the current bucket stores at its nth location, then we only consider - // its mth location where m > n. This property makes sure that all reads - // will not miss if we do have data associated to the query key. - // - // The n and m in the above statement is the start_hid and hid in the code. - unsigned int start_hid = hash_function_count_; - for (unsigned int hid = 0; hid < hash_function_count_; ++hid) { - bucket_ids[hid] = GetHash(bucket_user_key, hid); - if (step.bucket_id_ == bucket_ids[hid]) { - start_hid = hid; - } - } - // must found a bucket which is its current "home". - assert(start_hid != hash_function_count_); - - // explore all possible next steps from the current step. - for (unsigned int hid = start_hid + 1; hid < hash_function_count_; ++hid) { - CuckooStep& next_step = step_buffer_.NextWriteBuffer(); - next_step.bucket_id_ = bucket_ids[hid]; - next_step.prev_step_id_ = step_id; - next_step.depth_ = step.depth_ + 1; - // once a vacant bucket is found, trace back all its previous steps - // to generate a cuckoo path. - if (cuckoo_array_[next_step.bucket_id_].load(std::memory_order_relaxed) == - nullptr) { - // store the last step in the cuckoo path. Note that cuckoo_path - // stores steps in reverse order. This allows us to move keys along - // the cuckoo path by storing each key to the new place first before - // removing it from the old place. This property ensures reads will - // not missed due to moving keys along the cuckoo path. - cuckoo_path[(*cuckoo_path_length)++] = next_step.bucket_id_; - int depth; - for (depth = step.depth_; depth > 0 && step_id != CuckooStep::kNullStep; - depth--) { - const CuckooStep& prev_step = step_buffer_.steps_[step_id]; - cuckoo_path[(*cuckoo_path_length)++] = prev_step.bucket_id_; - step_id = prev_step.prev_step_id_; - } - assert(depth == 0 && step_id == CuckooStep::kNullStep); - return true; - } - if (step_buffer_.IsFull()) { - // if true, then it reaches maxinum number of cuckoo search steps. - return false; - } - } - } - - // tried all possible paths but still not unable to find a cuckoo path - // which path leads to a vacant bucket. - return false; -} - -HashCuckooRep::Iterator::Iterator( - std::shared_ptr> bucket, - const KeyComparator& compare) - : bucket_(bucket), - cit_(bucket_->end()), - compare_(compare), - sorted_(false) {} - -void HashCuckooRep::Iterator::DoSort() const { - if (!sorted_) { - std::sort(bucket_->begin(), bucket_->end(), - stl_wrappers::Compare(compare_)); - cit_ = bucket_->begin(); - sorted_ = true; - } -} - -// Returns true iff the iterator is positioned at a valid node. -bool HashCuckooRep::Iterator::Valid() const { - DoSort(); - return cit_ != bucket_->end(); -} - -// Returns the key at the current position. -// REQUIRES: Valid() -const char* HashCuckooRep::Iterator::key() const { - assert(Valid()); - return *cit_; -} - -// Advances to the next position. -// REQUIRES: Valid() -void HashCuckooRep::Iterator::Next() { - assert(Valid()); - if (cit_ == bucket_->end()) { - return; - } - ++cit_; -} - -// Advances to the previous position. -// REQUIRES: Valid() -void HashCuckooRep::Iterator::Prev() { - assert(Valid()); - if (cit_ == bucket_->begin()) { - // If you try to go back from the first element, the iterator should be - // invalidated. So we set it to past-the-end. This means that you can - // treat the container circularly. - cit_ = bucket_->end(); - } else { - --cit_; - } -} - -// Advance to the first entry with a key >= target -void HashCuckooRep::Iterator::Seek(const Slice& user_key, - const char* memtable_key) { - DoSort(); - // Do binary search to find first value not less than the target - const char* encoded_key = - (memtable_key != nullptr) ? memtable_key : EncodeKey(&tmp_, user_key); - cit_ = std::equal_range(bucket_->begin(), bucket_->end(), encoded_key, - [this](const char* a, const char* b) { - return compare_(a, b) < 0; - }).first; -} - -// Retreat to the last entry with a key <= target -void HashCuckooRep::Iterator::SeekForPrev(const Slice& user_key, - const char* memtable_key) { - assert(false); -} - -// Position at the first entry in collection. -// Final state of iterator is Valid() iff collection is not empty. -void HashCuckooRep::Iterator::SeekToFirst() { - DoSort(); - cit_ = bucket_->begin(); -} - -// Position at the last entry in collection. -// Final state of iterator is Valid() iff collection is not empty. -void HashCuckooRep::Iterator::SeekToLast() { - DoSort(); - cit_ = bucket_->end(); - if (bucket_->size() != 0) { - --cit_; - } -} - -} // anom namespace - -MemTableRep* HashCuckooRepFactory::CreateMemTableRep( - const MemTableRep::KeyComparator& compare, Allocator* allocator, - const SliceTransform* transform, Logger* logger) { - // The estimated average fullness. The write performance of any close hash - // degrades as the fullness of the mem-table increases. Setting kFullness - // to a value around 0.7 can better avoid write performance degradation while - // keeping efficient memory usage. - static const float kFullness = 0.7f; - size_t pointer_size = sizeof(std::atomic); - assert(write_buffer_size_ >= (average_data_size_ + pointer_size)); - size_t bucket_count = - static_cast( - (write_buffer_size_ / (average_data_size_ + pointer_size)) / kFullness + - 1); - unsigned int hash_function_count = hash_function_count_; - if (hash_function_count < 2) { - hash_function_count = 2; - } - if (hash_function_count > kMaxHashCount) { - hash_function_count = kMaxHashCount; - } - return new HashCuckooRep(compare, allocator, bucket_count, - hash_function_count, - static_cast( - (average_data_size_ + pointer_size) / kFullness) - ); -} - -MemTableRepFactory* NewHashCuckooRepFactory(size_t write_buffer_size, - size_t average_data_size, - unsigned int hash_function_count) { - return new HashCuckooRepFactory(write_buffer_size, average_data_size, - hash_function_count); -} - -} // namespace rocksdb -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/memtable/hash_cuckoo_rep.h b/thirdparty/rocksdb/memtable/hash_cuckoo_rep.h deleted file mode 100644 index 800696e931..0000000000 --- a/thirdparty/rocksdb/memtable/hash_cuckoo_rep.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). -// 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. - -#pragma once -#ifndef ROCKSDB_LITE -#include "port/port.h" -#include "rocksdb/slice_transform.h" -#include "rocksdb/memtablerep.h" - -namespace rocksdb { - -class HashCuckooRepFactory : public MemTableRepFactory { - public: - // maxinum number of hash functions used in the cuckoo hash. - static const unsigned int kMaxHashCount = 10; - - explicit HashCuckooRepFactory(size_t write_buffer_size, - size_t average_data_size, - unsigned int hash_function_count) - : write_buffer_size_(write_buffer_size), - average_data_size_(average_data_size), - hash_function_count_(hash_function_count) {} - - virtual ~HashCuckooRepFactory() {} - - using MemTableRepFactory::CreateMemTableRep; - virtual MemTableRep* CreateMemTableRep( - const MemTableRep::KeyComparator& compare, Allocator* allocator, - const SliceTransform* transform, Logger* logger) override; - - virtual const char* Name() const override { return "HashCuckooRepFactory"; } - - private: - size_t write_buffer_size_; - size_t average_data_size_; - const unsigned int hash_function_count_; -}; -} // namespace rocksdb -#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/memtable/hash_linklist_rep.cc b/thirdparty/rocksdb/memtable/hash_linklist_rep.cc index 932b62a346..878d233835 100644 --- a/thirdparty/rocksdb/memtable/hash_linklist_rep.cc +++ b/thirdparty/rocksdb/memtable/hash_linklist_rep.cc @@ -17,7 +17,7 @@ #include "rocksdb/slice.h" #include "rocksdb/slice_transform.h" #include "util/arena.h" -#include "util/murmurhash.h" +#include "util/hash.h" namespace rocksdb { namespace { @@ -168,24 +168,23 @@ class HashLinkListRep : public MemTableRep { int bucket_entries_logging_threshold, bool if_log_bucket_dist_when_flash); - virtual KeyHandle Allocate(const size_t len, char** buf) override; + KeyHandle Allocate(const size_t len, char** buf) override; - virtual void Insert(KeyHandle handle) override; + void Insert(KeyHandle handle) override; - virtual bool Contains(const char* key) const override; + bool Contains(const char* key) const override; - virtual size_t ApproximateMemoryUsage() override; + size_t ApproximateMemoryUsage() override; - virtual void Get(const LookupKey& k, void* callback_args, - bool (*callback_func)(void* arg, - const char* entry)) override; + void Get(const LookupKey& k, void* callback_args, + bool (*callback_func)(void* arg, const char* entry)) override; - virtual ~HashLinkListRep(); + ~HashLinkListRep() override; - virtual MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override; + MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override; - virtual MemTableRep::Iterator* GetDynamicPrefixIterator( - Arena* arena = nullptr) override; + MemTableRep::Iterator* GetDynamicPrefixIterator( + Arena* arena = nullptr) override; private: friend class DynamicIterator; @@ -219,7 +218,7 @@ class HashLinkListRep : public MemTableRep { } size_t GetHash(const Slice& slice) const { - return MurmurHash(slice.data(), static_cast(slice.size()), 0) % + return NPHash64(slice.data(), static_cast(slice.size()), 0) % bucket_size_; } @@ -265,36 +264,34 @@ class HashLinkListRep : public MemTableRep { explicit FullListIterator(MemtableSkipList* list, Allocator* allocator) : iter_(list), full_list_(list), allocator_(allocator) {} - virtual ~FullListIterator() { - } + ~FullListIterator() override {} // Returns true iff the iterator is positioned at a valid node. - virtual bool Valid() const override { return iter_.Valid(); } + bool Valid() const override { return iter_.Valid(); } // Returns the key at the current position. // REQUIRES: Valid() - virtual const char* key() const override { + const char* key() const override { assert(Valid()); return iter_.key(); } // Advances to the next position. // REQUIRES: Valid() - virtual void Next() override { + void Next() override { assert(Valid()); iter_.Next(); } // Advances to the previous position. // REQUIRES: Valid() - virtual void Prev() override { + void Prev() override { assert(Valid()); iter_.Prev(); } // Advance to the first entry with a key >= target - virtual void Seek(const Slice& internal_key, - const char* memtable_key) override { + void Seek(const Slice& internal_key, const char* memtable_key) override { const char* encoded_key = (memtable_key != nullptr) ? memtable_key : EncodeKey(&tmp_, internal_key); @@ -302,8 +299,8 @@ class HashLinkListRep : public MemTableRep { } // Retreat to the last entry with a key <= target - virtual void SeekForPrev(const Slice& internal_key, - const char* memtable_key) override { + void SeekForPrev(const Slice& internal_key, + const char* memtable_key) override { const char* encoded_key = (memtable_key != nullptr) ? memtable_key : EncodeKey(&tmp_, internal_key); @@ -312,11 +309,12 @@ class HashLinkListRep : public MemTableRep { // Position at the first entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToFirst() override { iter_.SeekToFirst(); } + void SeekToFirst() override { iter_.SeekToFirst(); } // Position at the last entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToLast() override { iter_.SeekToLast(); } + void SeekToLast() override { iter_.SeekToLast(); } + private: MemtableSkipList::Iterator iter_; // To destruct with the iterator. @@ -333,43 +331,43 @@ class HashLinkListRep : public MemTableRep { head_(head), node_(nullptr) {} - virtual ~LinkListIterator() {} + ~LinkListIterator() override {} // Returns true iff the iterator is positioned at a valid node. - virtual bool Valid() const override { return node_ != nullptr; } + bool Valid() const override { return node_ != nullptr; } // Returns the key at the current position. // REQUIRES: Valid() - virtual const char* key() const override { + const char* key() const override { assert(Valid()); return node_->key; } // Advances to the next position. // REQUIRES: Valid() - virtual void Next() override { + void Next() override { assert(Valid()); node_ = node_->Next(); } // Advances to the previous position. // REQUIRES: Valid() - virtual void Prev() override { + void Prev() override { // Prefix iterator does not support total order. // We simply set the iterator to invalid state Reset(nullptr); } // Advance to the first entry with a key >= target - virtual void Seek(const Slice& internal_key, - const char* memtable_key) override { + void Seek(const Slice& internal_key, + const char* /*memtable_key*/) override { node_ = hash_link_list_rep_->FindGreaterOrEqualInBucket(head_, internal_key); } // Retreat to the last entry with a key <= target - virtual void SeekForPrev(const Slice& internal_key, - const char* memtable_key) override { + void SeekForPrev(const Slice& /*internal_key*/, + const char* /*memtable_key*/) override { // Since we do not support Prev() // We simply do not support SeekForPrev Reset(nullptr); @@ -377,7 +375,7 @@ class HashLinkListRep : public MemTableRep { // Position at the first entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToFirst() override { + void SeekToFirst() override { // Prefix iterator does not support total order. // We simply set the iterator to invalid state Reset(nullptr); @@ -385,7 +383,7 @@ class HashLinkListRep : public MemTableRep { // Position at the last entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToLast() override { + void SeekToLast() override { // Prefix iterator does not support total order. // We simply set the iterator to invalid state Reset(nullptr); @@ -414,7 +412,7 @@ class HashLinkListRep : public MemTableRep { memtable_rep_(memtable_rep) {} // Advance to the first entry with a key >= target - virtual void Seek(const Slice& k, const char* memtable_key) override { + void Seek(const Slice& k, const char* memtable_key) override { auto transformed = memtable_rep_.GetPrefix(k); auto* bucket = memtable_rep_.GetBucket(transformed); @@ -443,21 +441,21 @@ class HashLinkListRep : public MemTableRep { } } - virtual bool Valid() const override { + bool Valid() const override { if (skip_list_iter_) { return skip_list_iter_->Valid(); } return HashLinkListRep::LinkListIterator::Valid(); } - virtual const char* key() const override { + const char* key() const override { if (skip_list_iter_) { return skip_list_iter_->key(); } return HashLinkListRep::LinkListIterator::key(); } - virtual void Next() override { + void Next() override { if (skip_list_iter_) { skip_list_iter_->Next(); } else { @@ -476,19 +474,19 @@ class HashLinkListRep : public MemTableRep { // instantiating an empty bucket over which to iterate. public: EmptyIterator() { } - virtual bool Valid() const override { return false; } - virtual const char* key() const override { + bool Valid() const override { return false; } + const char* key() const override { assert(false); return nullptr; } - virtual void Next() override {} - virtual void Prev() override {} - virtual void Seek(const Slice& user_key, - const char* memtable_key) override {} - virtual void SeekForPrev(const Slice& user_key, - const char* memtable_key) override {} - virtual void SeekToFirst() override {} - virtual void SeekToLast() override {} + void Next() override {} + void Prev() override {} + void Seek(const Slice& /*user_key*/, + const char* /*memtable_key*/) override {} + void SeekForPrev(const Slice& /*user_key*/, + const char* /*memtable_key*/) override {} + void SeekToFirst() override {} + void SeekToLast() override {} private: }; diff --git a/thirdparty/rocksdb/memtable/hash_skiplist_rep.cc b/thirdparty/rocksdb/memtable/hash_skiplist_rep.cc index e34743eb2c..d02919cd4e 100644 --- a/thirdparty/rocksdb/memtable/hash_skiplist_rep.cc +++ b/thirdparty/rocksdb/memtable/hash_skiplist_rep.cc @@ -28,21 +28,20 @@ class HashSkipListRep : public MemTableRep { size_t bucket_size, int32_t skiplist_height, int32_t skiplist_branching_factor); - virtual void Insert(KeyHandle handle) override; + void Insert(KeyHandle handle) override; - virtual bool Contains(const char* key) const override; + bool Contains(const char* key) const override; - virtual size_t ApproximateMemoryUsage() override; + size_t ApproximateMemoryUsage() override; - virtual void Get(const LookupKey& k, void* callback_args, - bool (*callback_func)(void* arg, - const char* entry)) override; + void Get(const LookupKey& k, void* callback_args, + bool (*callback_func)(void* arg, const char* entry)) override; - virtual ~HashSkipListRep(); + ~HashSkipListRep() override; - virtual MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override; + MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override; - virtual MemTableRep::Iterator* GetDynamicPrefixIterator( + MemTableRep::Iterator* GetDynamicPrefixIterator( Arena* arena = nullptr) override; private: @@ -85,7 +84,7 @@ class HashSkipListRep : public MemTableRep { Arena* arena = nullptr) : list_(list), iter_(list), own_list_(own_list), arena_(arena) {} - virtual ~Iterator() { + ~Iterator() override { // if we own the list, we should also delete it if (own_list_) { assert(list_ != nullptr); @@ -94,34 +93,31 @@ class HashSkipListRep : public MemTableRep { } // Returns true iff the iterator is positioned at a valid node. - virtual bool Valid() const override { - return list_ != nullptr && iter_.Valid(); - } + bool Valid() const override { return list_ != nullptr && iter_.Valid(); } // Returns the key at the current position. // REQUIRES: Valid() - virtual const char* key() const override { + const char* key() const override { assert(Valid()); return iter_.key(); } // Advances to the next position. // REQUIRES: Valid() - virtual void Next() override { + void Next() override { assert(Valid()); iter_.Next(); } // Advances to the previous position. // REQUIRES: Valid() - virtual void Prev() override { + void Prev() override { assert(Valid()); iter_.Prev(); } // Advance to the first entry with a key >= target - virtual void Seek(const Slice& internal_key, - const char* memtable_key) override { + void Seek(const Slice& internal_key, const char* memtable_key) override { if (list_ != nullptr) { const char* encoded_key = (memtable_key != nullptr) ? @@ -131,15 +127,15 @@ class HashSkipListRep : public MemTableRep { } // Retreat to the last entry with a key <= target - virtual void SeekForPrev(const Slice& internal_key, - const char* memtable_key) override { + void SeekForPrev(const Slice& /*internal_key*/, + const char* /*memtable_key*/) override { // not supported assert(false); } // Position at the first entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToFirst() override { + void SeekToFirst() override { if (list_ != nullptr) { iter_.SeekToFirst(); } @@ -147,11 +143,12 @@ class HashSkipListRep : public MemTableRep { // Position at the last entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToLast() override { + void SeekToLast() override { if (list_ != nullptr) { iter_.SeekToLast(); } } + protected: void Reset(Bucket* list) { if (own_list_) { @@ -168,7 +165,7 @@ class HashSkipListRep : public MemTableRep { Bucket* list_; Bucket::Iterator iter_; // here we track if we own list_. If we own it, we are also - // responsible for it's cleaning. This is a poor man's shared_ptr + // responsible for it's cleaning. This is a poor man's std::shared_ptr bool own_list_; std::unique_ptr arena_; std::string tmp_; // For passing to EncodeKey @@ -181,7 +178,7 @@ class HashSkipListRep : public MemTableRep { memtable_rep_(memtable_rep) {} // Advance to the first entry with a key >= target - virtual void Seek(const Slice& k, const char* memtable_key) override { + void Seek(const Slice& k, const char* memtable_key) override { auto transformed = memtable_rep_.transform_->Transform(ExtractUserKey(k)); Reset(memtable_rep_.GetBucket(transformed)); HashSkipListRep::Iterator::Seek(k, memtable_key); @@ -189,7 +186,7 @@ class HashSkipListRep : public MemTableRep { // Position at the first entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToFirst() override { + void SeekToFirst() override { // Prefix iterator does not support total order. // We simply set the iterator to invalid state Reset(nullptr); @@ -197,11 +194,12 @@ class HashSkipListRep : public MemTableRep { // Position at the last entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToLast() override { + void SeekToLast() override { // Prefix iterator does not support total order. // We simply set the iterator to invalid state Reset(nullptr); } + private: // the underlying memtable const HashSkipListRep& memtable_rep_; @@ -212,19 +210,19 @@ class HashSkipListRep : public MemTableRep { // instantiating an empty bucket over which to iterate. public: EmptyIterator() { } - virtual bool Valid() const override { return false; } - virtual const char* key() const override { + bool Valid() const override { return false; } + const char* key() const override { assert(false); return nullptr; } - virtual void Next() override {} - virtual void Prev() override {} - virtual void Seek(const Slice& internal_key, - const char* memtable_key) override {} - virtual void SeekForPrev(const Slice& internal_key, - const char* memtable_key) override {} - virtual void SeekToFirst() override {} - virtual void SeekToLast() override {} + void Next() override {} + void Prev() override {} + void Seek(const Slice& /*internal_key*/, + const char* /*memtable_key*/) override {} + void SeekForPrev(const Slice& /*internal_key*/, + const char* /*memtable_key*/) override {} + void SeekToFirst() override {} + void SeekToLast() override {} private: }; @@ -335,7 +333,7 @@ MemTableRep::Iterator* HashSkipListRep::GetDynamicPrefixIterator(Arena* arena) { MemTableRep* HashSkipListRepFactory::CreateMemTableRep( const MemTableRep::KeyComparator& compare, Allocator* allocator, - const SliceTransform* transform, Logger* logger) { + const SliceTransform* transform, Logger* /*logger*/) { return new HashSkipListRep(compare, allocator, transform, bucket_count_, skiplist_height_, skiplist_branching_factor_); } diff --git a/thirdparty/rocksdb/memtable/inlineskiplist.h b/thirdparty/rocksdb/memtable/inlineskiplist.h index 5cf6c57d57..1ef8f2b6db 100644 --- a/thirdparty/rocksdb/memtable/inlineskiplist.h +++ b/thirdparty/rocksdb/memtable/inlineskiplist.h @@ -45,8 +45,12 @@ #include #include #include +#include +#include "port/likely.h" #include "port/port.h" +#include "rocksdb/slice.h" #include "util/allocator.h" +#include "util/coding.h" #include "util/random.h" namespace rocksdb { @@ -58,6 +62,9 @@ class InlineSkipList { struct Splice; public: + using DecodedKey = \ + typename std::remove_reference::type::DecodedType; + static const uint16_t kMaxPossibleHeight = 32; // Create a new InlineSkipList object that will use "cmp" for comparing @@ -81,7 +88,7 @@ class InlineSkipList { // // REQUIRES: nothing that compares equal to key is currently in the list. // REQUIRES: no concurrent calls to any of inserts. - void Insert(const char* key); + bool Insert(const char* key); // Inserts a key allocated by AllocateKey with a hint of last insert // position in the skip-list. If hint points to nullptr, a new hint will be @@ -93,10 +100,10 @@ class InlineSkipList { // // REQUIRES: nothing that compares equal to key is currently in the list. // REQUIRES: no concurrent calls to any of inserts. - void InsertWithHint(const char* key, void** hint); + bool InsertWithHint(const char* key, void** hint); // Like Insert, but external synchronization is not required. - void InsertConcurrently(const char* key); + bool InsertConcurrently(const char* key); // Inserts a node into the skip list. key must have been allocated by // AllocateKey and then filled in by the caller. If UseCAS is true, @@ -114,7 +121,7 @@ class InlineSkipList { // false has worse running time for the non-sequential case O(log N), // but a better constant factor. template - void Insert(const char* key, Splice* splice, bool allow_partial_splice_fix); + bool Insert(const char* key, Splice* splice, bool allow_partial_splice_fix); // Returns true iff an entry that compares equal to key is in the list. bool Contains(const char* key) const; @@ -177,10 +184,9 @@ class InlineSkipList { const uint16_t kBranching_; const uint32_t kScaledInverseBranching_; + Allocator* const allocator_; // Allocator used for allocations of nodes // Immutable after construction Comparator const compare_; - Allocator* const allocator_; // Allocator used for allocations of nodes - Node* const head_; // Modified only by Insert(). Read racily by readers, but stale @@ -211,6 +217,7 @@ class InlineSkipList { // Return true if key is greater than the data stored in "n". Null n // is considered infinite. n should not be head_. bool KeyIsAfterNode(const char* key, Node* n) const; + bool KeyIsAfterNode(const DecodedKey& key, Node* n) const; // Returns the earliest node with a key >= key. // Return nullptr if there is no such node. @@ -239,12 +246,13 @@ class InlineSkipList { // point to a node that is before the key, and after should point to // a node that is after the key. after should be nullptr if a good after // node isn't conveniently available. - void FindSpliceForLevel(const char* key, Node* before, Node* after, int level, + template + void FindSpliceForLevel(const DecodedKey& key, Node* before, Node* after, int level, Node** out_prev, Node** out_next); // Recomputes Splice levels from highest_level (inclusive) down to // lowest_level (inclusive). - void RecomputeSpliceLevels(const char* key, Splice* splice, + void RecomputeSpliceLevels(const DecodedKey& key, Splice* splice, int recompute_level); // No copying allowed @@ -278,7 +286,7 @@ struct InlineSkipList::Node { // next_[0]. This is used for passing data from AllocateKey to Insert. void StashHeight(const int height) { assert(sizeof(int) <= sizeof(next_[0])); - memcpy(&next_[0], &height, sizeof(int)); + memcpy(static_cast(&next_[0]), &height, sizeof(int)); } // Retrieves the value passed to StashHeight. Undefined after a call @@ -298,30 +306,30 @@ struct InlineSkipList::Node { assert(n >= 0); // Use an 'acquire load' so that we observe a fully initialized // version of the returned Node. - return (next_[-n].load(std::memory_order_acquire)); + return ((&next_[0] - n)->load(std::memory_order_acquire)); } void SetNext(int n, Node* x) { assert(n >= 0); // Use a 'release store' so that anybody who reads through this // pointer observes a fully initialized version of the inserted node. - next_[-n].store(x, std::memory_order_release); + (&next_[0] - n)->store(x, std::memory_order_release); } bool CASNext(int n, Node* expected, Node* x) { assert(n >= 0); - return next_[-n].compare_exchange_strong(expected, x); + return (&next_[0] - n)->compare_exchange_strong(expected, x); } // No-barrier variants that can be safely used in a few locations. Node* NoBarrier_Next(int n) { assert(n >= 0); - return next_[-n].load(std::memory_order_relaxed); + return (&next_[0] - n)->load(std::memory_order_relaxed); } void NoBarrier_SetNext(int n, Node* x) { assert(n >= 0); - next_[-n].store(x, std::memory_order_relaxed); + (&next_[0] - n)->store(x, std::memory_order_relaxed); } // Insert node after prev on specific level. @@ -433,6 +441,14 @@ bool InlineSkipList::KeyIsAfterNode(const char* key, return (n != nullptr) && (compare_(n->Key(), key) < 0); } +template +bool InlineSkipList::KeyIsAfterNode(const DecodedKey& key, + Node* n) const { + // nullptr n is considered infinite + assert(n != head_); + return (n != nullptr) && (compare_(n->Key(), key) < 0); +} + template typename InlineSkipList::Node* InlineSkipList::FindGreaterOrEqual(const char* key) const { @@ -444,15 +460,19 @@ InlineSkipList::FindGreaterOrEqual(const char* key) const { Node* x = head_; int level = GetMaxHeight() - 1; Node* last_bigger = nullptr; + const DecodedKey key_decoded = compare_.decode_key(key); while (true) { Node* next = x->Next(level); + if (next != nullptr) { + PREFETCH(next->Next(level), 0, 1); + } // Make sure the lists are sorted assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x)); // Make sure we haven't overshot during our search - assert(x == head_ || KeyIsAfterNode(key, x)); + assert(x == head_ || KeyIsAfterNode(key_decoded, x)); int cmp = (next == nullptr || next == last_bigger) ? 1 - : compare_(next->Key(), key); + : compare_(next->Key(), key_decoded); if (cmp == 0 || (cmp > 0 && level == 0)) { return next; } else if (cmp < 0) { @@ -482,12 +502,18 @@ InlineSkipList::FindLessThan(const char* key, Node** prev, Node* x = root; // KeyIsAfter(key, last_not_after) is definitely false Node* last_not_after = nullptr; + const DecodedKey key_decoded = compare_.decode_key(key); while (true) { + assert(x != nullptr); Node* next = x->Next(level); + if (next != nullptr) { + PREFETCH(next->Next(level), 0, 1); + } assert(x == head_ || next == nullptr || KeyIsAfterNode(next->Key(), x)); - assert(x == head_ || KeyIsAfterNode(key, x)); - if (next != last_not_after && KeyIsAfterNode(key, next)) { + assert(x == head_ || KeyIsAfterNode(key_decoded, x)); + if (next != last_not_after && KeyIsAfterNode(key_decoded, next)) { // Keep searching in this list + assert(next != nullptr); x = next; } else { if (prev != nullptr) { @@ -530,10 +556,14 @@ uint64_t InlineSkipList::EstimateCount(const char* key) const { Node* x = head_; int level = GetMaxHeight() - 1; + const DecodedKey key_decoded = compare_.decode_key(key); while (true) { - assert(x == head_ || compare_(x->Key(), key) < 0); + assert(x == head_ || compare_(x->Key(), key_decoded) < 0); Node* next = x->Next(level); - if (next == nullptr || compare_(next->Key(), key) >= 0) { + if (next != nullptr) { + PREFETCH(next->Next(level), 0, 1); + } + if (next == nullptr || compare_(next->Key(), key_decoded) >= 0) { if (level == 0) { return count; } else { @@ -553,11 +583,11 @@ InlineSkipList::InlineSkipList(const Comparator cmp, Allocator* allocator, int32_t max_height, int32_t branching_factor) - : kMaxHeight_(max_height), - kBranching_(branching_factor), + : kMaxHeight_(static_cast(max_height)), + kBranching_(static_cast(branching_factor)), kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_), - compare_(cmp), allocator_(allocator), + compare_(cmp), head_(AllocateNode(0, max_height)), max_height_(1), seq_splice_(AllocateSplice()) { @@ -614,38 +644,47 @@ InlineSkipList::AllocateSplice() { } template -void InlineSkipList::Insert(const char* key) { - Insert(key, seq_splice_, false); +bool InlineSkipList::Insert(const char* key) { + return Insert(key, seq_splice_, false); } template -void InlineSkipList::InsertConcurrently(const char* key) { +bool InlineSkipList::InsertConcurrently(const char* key) { Node* prev[kMaxPossibleHeight]; Node* next[kMaxPossibleHeight]; Splice splice; splice.prev_ = prev; splice.next_ = next; - Insert(key, &splice, false); + return Insert(key, &splice, false); } template -void InlineSkipList::InsertWithHint(const char* key, void** hint) { +bool InlineSkipList::InsertWithHint(const char* key, void** hint) { assert(hint != nullptr); Splice* splice = reinterpret_cast(*hint); if (splice == nullptr) { splice = AllocateSplice(); *hint = reinterpret_cast(splice); } - Insert(key, splice, true); + return Insert(key, splice, true); } template -void InlineSkipList::FindSpliceForLevel(const char* key, +template +void InlineSkipList::FindSpliceForLevel(const DecodedKey& key, Node* before, Node* after, int level, Node** out_prev, Node** out_next) { while (true) { Node* next = before->Next(level); + if (next != nullptr) { + PREFETCH(next->Next(level), 0, 1); + } + if (prefetch_before == true) { + if (next != nullptr && level>0) { + PREFETCH(next->Next(level-1), 0, 1); + } + } assert(before == head_ || next == nullptr || KeyIsAfterNode(next->Key(), before)); assert(before == head_ || KeyIsAfterNode(key, before)); @@ -660,22 +699,23 @@ void InlineSkipList::FindSpliceForLevel(const char* key, } template -void InlineSkipList::RecomputeSpliceLevels(const char* key, +void InlineSkipList::RecomputeSpliceLevels(const DecodedKey& key, Splice* splice, int recompute_level) { assert(recompute_level > 0); assert(recompute_level <= splice->height_); for (int i = recompute_level - 1; i >= 0; --i) { - FindSpliceForLevel(key, splice->prev_[i + 1], splice->next_[i + 1], i, + FindSpliceForLevel(key, splice->prev_[i + 1], splice->next_[i + 1], i, &splice->prev_[i], &splice->next_[i]); } } template template -void InlineSkipList::Insert(const char* key, Splice* splice, +bool InlineSkipList::Insert(const char* key, Splice* splice, bool allow_partial_splice_fix) { Node* x = reinterpret_cast(const_cast(key)) - 1; + const DecodedKey key_decoded = compare_.decode_key(key); int height = x->UnstashHeight(); assert(height >= 1 && height <= kMaxHeight_); @@ -743,7 +783,8 @@ void InlineSkipList::Insert(const char* key, Splice* splice, // our chances of success. ++recompute_height; } else if (splice->prev_[recompute_height] != head_ && - !KeyIsAfterNode(key, splice->prev_[recompute_height])) { + !KeyIsAfterNode(key_decoded, + splice->prev_[recompute_height])) { // key is from before splice if (allow_partial_splice_fix) { // skip all levels with the same node without more comparisons @@ -755,7 +796,8 @@ void InlineSkipList::Insert(const char* key, Splice* splice, // we're pessimistic, recompute everything recompute_height = max_height; } - } else if (KeyIsAfterNode(key, splice->next_[recompute_height])) { + } else if (KeyIsAfterNode(key_decoded, + splice->next_[recompute_height])) { // key is from after splice if (allow_partial_splice_fix) { Node* bad = splice->next_[recompute_height]; @@ -773,13 +815,24 @@ void InlineSkipList::Insert(const char* key, Splice* splice, } assert(recompute_height <= max_height); if (recompute_height > 0) { - RecomputeSpliceLevels(key, splice, recompute_height); + RecomputeSpliceLevels(key_decoded, splice, recompute_height); } bool splice_is_valid = true; if (UseCAS) { for (int i = 0; i < height; ++i) { while (true) { + // Checking for duplicate keys on the level 0 is sufficient + if (UNLIKELY(i == 0 && splice->next_[i] != nullptr && + compare_(x->Key(), splice->next_[i]->Key()) >= 0)) { + // duplicate key + return false; + } + if (UNLIKELY(i == 0 && splice->prev_[i] != head_ && + compare_(splice->prev_[i]->Key(), x->Key()) >= 0)) { + // duplicate key + return false; + } assert(splice->next_[i] == nullptr || compare_(x->Key(), splice->next_[i]->Key()) < 0); assert(splice->prev_[i] == head_ || @@ -794,8 +847,8 @@ void InlineSkipList::Insert(const char* key, Splice* splice, // search, because it should be unlikely that lots of nodes have // been inserted between prev[i] and next[i]. No point in using // next[i] as the after hint, because we know it is stale. - FindSpliceForLevel(key, splice->prev_[i], nullptr, i, &splice->prev_[i], - &splice->next_[i]); + FindSpliceForLevel(key_decoded, splice->prev_[i], nullptr, i, + &splice->prev_[i], &splice->next_[i]); // Since we've narrowed the bracket for level i, we might have // violated the Splice constraint between i and i-1. Make sure @@ -809,8 +862,19 @@ void InlineSkipList::Insert(const char* key, Splice* splice, for (int i = 0; i < height; ++i) { if (i >= recompute_height && splice->prev_[i]->Next(i) != splice->next_[i]) { - FindSpliceForLevel(key, splice->prev_[i], nullptr, i, &splice->prev_[i], - &splice->next_[i]); + FindSpliceForLevel(key_decoded, splice->prev_[i], nullptr, i, + &splice->prev_[i], &splice->next_[i]); + } + // Checking for duplicate keys on the level 0 is sufficient + if (UNLIKELY(i == 0 && splice->next_[i] != nullptr && + compare_(x->Key(), splice->next_[i]->Key()) >= 0)) { + // duplicate key + return false; + } + if (UNLIKELY(i == 0 && splice->prev_[i] != head_ && + compare_(splice->prev_[i]->Key(), x->Key()) >= 0)) { + // duplicate key + return false; } assert(splice->next_[i] == nullptr || compare_(x->Key(), splice->next_[i]->Key()) < 0); @@ -844,6 +908,7 @@ void InlineSkipList::Insert(const char* key, Splice* splice, } else { splice->height_ = 0; } + return true; } template @@ -863,6 +928,7 @@ void InlineSkipList::TEST_Validate() const { // levels. Node* nodes[kMaxPossibleHeight]; int max_height = GetMaxHeight(); + assert(max_height > 0); for (int i = 0; i < max_height; i++) { nodes[i] = head_; } @@ -892,7 +958,7 @@ void InlineSkipList::TEST_Validate() const { } } for (int i = 1; i < max_height; i++) { - assert(nodes[i]->Next(i) == nullptr); + assert(nodes[i] != nullptr && nodes[i]->Next(i) == nullptr); } } diff --git a/thirdparty/rocksdb/memtable/inlineskiplist_test.cc b/thirdparty/rocksdb/memtable/inlineskiplist_test.cc index 5803e5b0f5..b416ef7c55 100644 --- a/thirdparty/rocksdb/memtable/inlineskiplist_test.cc +++ b/thirdparty/rocksdb/memtable/inlineskiplist_test.cc @@ -32,6 +32,12 @@ static Key Decode(const char* key) { } struct TestComparator { + typedef Key DecodedType; + + static DecodedType decode_key(const char* b) { + return Decode(b); + } + int operator()(const char* a, const char* b) const { if (Decode(a) < Decode(b)) { return -1; @@ -41,6 +47,16 @@ struct TestComparator { return 0; } } + + int operator()(const char* a, const DecodedType b) const { + if (Decode(a) < b) { + return -1; + } else if (Decode(a) > b) { + return +1; + } else { + return 0; + } + } }; typedef InlineSkipList TestInlineSkipList; @@ -54,11 +70,12 @@ class InlineSkipTest : public testing::Test { keys_.insert(key); } - void InsertWithHint(TestInlineSkipList* list, Key key, void** hint) { + bool InsertWithHint(TestInlineSkipList* list, Key key, void** hint) { char* buf = list->AllocateKey(sizeof(Key)); memcpy(buf, &key, sizeof(Key)); - list->InsertWithHint(buf, hint); + bool res = list->InsertWithHint(buf, hint); keys_.insert(key); + return res; } void Validate(TestInlineSkipList* list) { @@ -292,6 +309,7 @@ TEST_F(InlineSkipTest, InsertWithHint_CompatibleWithInsertWithoutHint) { Validate(&list); } +#ifndef ROCKSDB_VALGRIND_RUN // We want to make sure that with a single writer and multiple // concurrent readers (with no synchronization other than when a // reader's iterator is created), the reader always observes all the @@ -618,6 +636,7 @@ TEST_F(InlineSkipTest, ConcurrentInsert1) { RunConcurrentInsert(1); } TEST_F(InlineSkipTest, ConcurrentInsert2) { RunConcurrentInsert(2); } TEST_F(InlineSkipTest, ConcurrentInsert3) { RunConcurrentInsert(3); } +#endif // ROCKSDB_VALGRIND_RUN } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/memtable/memtablerep_bench.cc b/thirdparty/rocksdb/memtable/memtablerep_bench.cc index 63a0201ce8..51ff11a015 100644 --- a/thirdparty/rocksdb/memtable/memtablerep_bench.cc +++ b/thirdparty/rocksdb/memtable/memtablerep_bench.cc @@ -19,8 +19,6 @@ int main() { } #else -#include - #include #include #include @@ -38,13 +36,14 @@ int main() { #include "rocksdb/slice_transform.h" #include "rocksdb/write_buffer_manager.h" #include "util/arena.h" +#include "util/gflags_compat.h" #include "util/mutexlock.h" #include "util/stop_watch.h" #include "util/testutil.h" -using GFLAGS::ParseCommandLineFlags; -using GFLAGS::RegisterFlagValidator; -using GFLAGS::SetUsageMessage; +using GFLAGS_NAMESPACE::ParseCommandLineFlags; +using GFLAGS_NAMESPACE::RegisterFlagValidator; +using GFLAGS_NAMESPACE::SetUsageMessage; DEFINE_string(benchmarks, "fillrandom", "Comma-separated list of benchmarks to run. Options:\n" @@ -96,17 +95,8 @@ DEFINE_int32( threshold_use_skiplist, 256, "threshold_use_skiplist parameter to pass into NewHashLinkListRepFactory"); -DEFINE_int64( - write_buffer_size, 256, - "write_buffer_size parameter to pass into NewHashCuckooRepFactory"); - -DEFINE_int64( - average_data_size, 64, - "average_data_size parameter to pass into NewHashCuckooRepFactory"); - -DEFINE_int64( - hash_function_count, 4, - "hash_function_count parameter to pass into NewHashCuckooRepFactory"); +DEFINE_int64(write_buffer_size, 256, + "write_buffer_size parameter to pass into WriteBufferManager"); DEFINE_int32( num_threads, 1, @@ -480,8 +470,8 @@ class FillBenchmark : public Benchmark { num_write_ops_per_thread_ = FLAGS_num_operations; } - void RunThreads(std::vector* threads, uint64_t* bytes_written, - uint64_t* bytes_read, bool write, + void RunThreads(std::vector* /*threads*/, uint64_t* bytes_written, + uint64_t* bytes_read, bool /*write*/, uint64_t* read_hits) override { FillBenchmarkThread(table_, key_gen_, bytes_written, bytes_read, sequence_, num_write_ops_per_thread_, read_hits)(); @@ -497,7 +487,7 @@ class ReadBenchmark : public Benchmark { } void RunThreads(std::vector* threads, uint64_t* bytes_written, - uint64_t* bytes_read, bool write, + uint64_t* bytes_read, bool /*write*/, uint64_t* read_hits) override { for (int i = 0; i < FLAGS_num_threads; ++i) { threads->emplace_back( @@ -521,7 +511,7 @@ class SeqReadBenchmark : public Benchmark { } void RunThreads(std::vector* threads, uint64_t* bytes_written, - uint64_t* bytes_read, bool write, + uint64_t* bytes_read, bool /*write*/, uint64_t* read_hits) override { for (int i = 0; i < FLAGS_num_threads; ++i) { threads->emplace_back(SeqReadBenchmarkThread( @@ -548,7 +538,7 @@ class ReadWriteBenchmark : public Benchmark { } void RunThreads(std::vector* threads, uint64_t* bytes_written, - uint64_t* bytes_read, bool write, + uint64_t* bytes_read, bool /*write*/, uint64_t* read_hits) override { std::atomic_int threads_done; threads_done.store(0); @@ -608,12 +598,6 @@ int main(int argc, char** argv) { FLAGS_if_log_bucket_dist_when_flash, FLAGS_threshold_use_skiplist)); options.prefix_extractor.reset( rocksdb::NewFixedPrefixTransform(FLAGS_prefix_length)); - } else if (FLAGS_memtablerep == "cuckoo") { - factory.reset(rocksdb::NewHashCuckooRepFactory( - FLAGS_write_buffer_size, FLAGS_average_data_size, - static_cast(FLAGS_hash_function_count))); - options.prefix_extractor.reset( - rocksdb::NewFixedPrefixTransform(FLAGS_prefix_length)); #endif // ROCKSDB_LITE } else { fprintf(stdout, "Unknown memtablerep: %s\n", FLAGS_memtablerep.c_str()); diff --git a/thirdparty/rocksdb/memtable/skiplist.h b/thirdparty/rocksdb/memtable/skiplist.h index 0162dccb78..47a89034eb 100644 --- a/thirdparty/rocksdb/memtable/skiplist.h +++ b/thirdparty/rocksdb/memtable/skiplist.h @@ -310,6 +310,7 @@ typename SkipList::Node* SkipList:: int level = GetMaxHeight() - 1; Node* last_bigger = nullptr; while (true) { + assert(x != nullptr); Node* next = x->Next(level); // Make sure the lists are sorted assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x)); @@ -338,6 +339,7 @@ SkipList::FindLessThan(const Key& key, Node** prev) const { // KeyIsAfter(key, last_not_after) is definitely false Node* last_not_after = nullptr; while (true) { + assert(x != nullptr); Node* next = x->Next(level); assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x)); assert(x == head_ || KeyIsAfterNode(key, x)); @@ -407,8 +409,8 @@ template SkipList::SkipList(const Comparator cmp, Allocator* allocator, int32_t max_height, int32_t branching_factor) - : kMaxHeight_(max_height), - kBranching_(branching_factor), + : kMaxHeight_(static_cast(max_height)), + kBranching_(static_cast(branching_factor)), kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_), compare_(cmp), allocator_(allocator), diff --git a/thirdparty/rocksdb/memtable/skiplistrep.cc b/thirdparty/rocksdb/memtable/skiplistrep.cc index f56be5dcb6..32870b127d 100644 --- a/thirdparty/rocksdb/memtable/skiplistrep.cc +++ b/thirdparty/rocksdb/memtable/skiplistrep.cc @@ -27,45 +27,55 @@ class SkipListRep : public MemTableRep { transform_(transform), lookahead_(lookahead) {} - virtual KeyHandle Allocate(const size_t len, char** buf) override { + KeyHandle Allocate(const size_t len, char** buf) override { *buf = skip_list_.AllocateKey(len); return static_cast(*buf); - } + } // Insert key into the list. // REQUIRES: nothing that compares equal to key is currently in the list. - virtual void Insert(KeyHandle handle) override { - skip_list_.Insert(static_cast(handle)); - } + void Insert(KeyHandle handle) override { + skip_list_.Insert(static_cast(handle)); + } - virtual void InsertWithHint(KeyHandle handle, void** hint) override { - skip_list_.InsertWithHint(static_cast(handle), hint); - } + bool InsertKey(KeyHandle handle) override { + return skip_list_.Insert(static_cast(handle)); + } - virtual void InsertConcurrently(KeyHandle handle) override { - skip_list_.InsertConcurrently(static_cast(handle)); - } + void InsertWithHint(KeyHandle handle, void** hint) override { + skip_list_.InsertWithHint(static_cast(handle), hint); + } - // Returns true iff an entry that compares equal to key is in the list. - virtual bool Contains(const char* key) const override { - return skip_list_.Contains(key); - } + bool InsertKeyWithHint(KeyHandle handle, void** hint) override { + return skip_list_.InsertWithHint(static_cast(handle), hint); + } - virtual size_t ApproximateMemoryUsage() override { - // All memory is allocated through allocator; nothing to report here - return 0; - } + void InsertConcurrently(KeyHandle handle) override { + skip_list_.InsertConcurrently(static_cast(handle)); + } - virtual void Get(const LookupKey& k, void* callback_args, - bool (*callback_func)(void* arg, - const char* entry)) override { - SkipListRep::Iterator iter(&skip_list_); - Slice dummy_slice; - for (iter.Seek(dummy_slice, k.memtable_key().data()); - iter.Valid() && callback_func(callback_args, iter.key()); - iter.Next()) { - } - } + bool InsertKeyConcurrently(KeyHandle handle) override { + return skip_list_.InsertConcurrently(static_cast(handle)); + } + + // Returns true iff an entry that compares equal to key is in the list. + bool Contains(const char* key) const override { + return skip_list_.Contains(key); + } + + size_t ApproximateMemoryUsage() override { + // All memory is allocated through allocator; nothing to report here + return 0; + } + + void Get(const LookupKey& k, void* callback_args, + bool (*callback_func)(void* arg, const char* entry)) override { + SkipListRep::Iterator iter(&skip_list_); + Slice dummy_slice; + for (iter.Seek(dummy_slice, k.memtable_key().data()); + iter.Valid() && callback_func(callback_args, iter.key()); iter.Next()) { + } + } uint64_t ApproximateNumEntries(const Slice& start_ikey, const Slice& end_ikey) override { @@ -76,7 +86,7 @@ class SkipListRep : public MemTableRep { return (end_count >= start_count) ? (end_count - start_count) : 0; } - virtual ~SkipListRep() override { } + ~SkipListRep() override {} // Iteration over the contents of a skip list class Iterator : public MemTableRep::Iterator { @@ -89,34 +99,25 @@ class SkipListRep : public MemTableRep { const InlineSkipList* list) : iter_(list) {} - virtual ~Iterator() override { } + ~Iterator() override {} // Returns true iff the iterator is positioned at a valid node. - virtual bool Valid() const override { - return iter_.Valid(); - } + bool Valid() const override { return iter_.Valid(); } // Returns the key at the current position. // REQUIRES: Valid() - virtual const char* key() const override { - return iter_.key(); - } + const char* key() const override { return iter_.key(); } // Advances to the next position. // REQUIRES: Valid() - virtual void Next() override { - iter_.Next(); - } + void Next() override { iter_.Next(); } // Advances to the previous position. // REQUIRES: Valid() - virtual void Prev() override { - iter_.Prev(); - } + void Prev() override { iter_.Prev(); } // Advance to the first entry with a key >= target - virtual void Seek(const Slice& user_key, const char* memtable_key) - override { + void Seek(const Slice& user_key, const char* memtable_key) override { if (memtable_key != nullptr) { iter_.Seek(memtable_key); } else { @@ -125,8 +126,7 @@ class SkipListRep : public MemTableRep { } // Retreat to the last entry with a key <= target - virtual void SeekForPrev(const Slice& user_key, - const char* memtable_key) override { + void SeekForPrev(const Slice& user_key, const char* memtable_key) override { if (memtable_key != nullptr) { iter_.SeekForPrev(memtable_key); } else { @@ -136,15 +136,12 @@ class SkipListRep : public MemTableRep { // Position at the first entry in list. // Final state of iterator is Valid() iff list is not empty. - virtual void SeekToFirst() override { - iter_.SeekToFirst(); - } + void SeekToFirst() override { iter_.SeekToFirst(); } // Position at the last entry in list. // Final state of iterator is Valid() iff list is not empty. - virtual void SeekToLast() override { - iter_.SeekToLast(); - } + void SeekToLast() override { iter_.SeekToLast(); } + protected: std::string tmp_; // For passing to EncodeKey }; @@ -158,18 +155,16 @@ class SkipListRep : public MemTableRep { explicit LookaheadIterator(const SkipListRep& rep) : rep_(rep), iter_(&rep_.skip_list_), prev_(iter_) {} - virtual ~LookaheadIterator() override {} + ~LookaheadIterator() override {} - virtual bool Valid() const override { - return iter_.Valid(); - } + bool Valid() const override { return iter_.Valid(); } - virtual const char *key() const override { + const char* key() const override { assert(Valid()); return iter_.key(); } - virtual void Next() override { + void Next() override { assert(Valid()); bool advance_prev = true; @@ -194,14 +189,13 @@ class SkipListRep : public MemTableRep { iter_.Next(); } - virtual void Prev() override { + void Prev() override { assert(Valid()); iter_.Prev(); prev_ = iter_; } - virtual void Seek(const Slice& internal_key, const char *memtable_key) - override { + void Seek(const Slice& internal_key, const char* memtable_key) override { const char *encoded_key = (memtable_key != nullptr) ? memtable_key : EncodeKey(&tmp_, internal_key); @@ -224,8 +218,8 @@ class SkipListRep : public MemTableRep { prev_ = iter_; } - virtual void SeekForPrev(const Slice& internal_key, - const char* memtable_key) override { + void SeekForPrev(const Slice& internal_key, + const char* memtable_key) override { const char* encoded_key = (memtable_key != nullptr) ? memtable_key : EncodeKey(&tmp_, internal_key); @@ -233,12 +227,12 @@ class SkipListRep : public MemTableRep { prev_ = iter_; } - virtual void SeekToFirst() override { + void SeekToFirst() override { iter_.SeekToFirst(); prev_ = iter_; } - virtual void SeekToLast() override { + void SeekToLast() override { iter_.SeekToLast(); prev_ = iter_; } @@ -252,7 +246,7 @@ class SkipListRep : public MemTableRep { InlineSkipList::Iterator prev_; }; - virtual MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override { + MemTableRep::Iterator* GetIterator(Arena* arena = nullptr) override { if (lookahead_ > 0) { void *mem = arena ? arena->AllocateAligned(sizeof(SkipListRep::LookaheadIterator)) @@ -270,7 +264,7 @@ class SkipListRep : public MemTableRep { MemTableRep* SkipListFactory::CreateMemTableRep( const MemTableRep::KeyComparator& compare, Allocator* allocator, - const SliceTransform* transform, Logger* logger) { + const SliceTransform* transform, Logger* /*logger*/) { return new SkipListRep(compare, allocator, transform, lookahead_); } diff --git a/thirdparty/rocksdb/memtable/stl_wrappers.h b/thirdparty/rocksdb/memtable/stl_wrappers.h index 19fa151488..0287f4f8fe 100644 --- a/thirdparty/rocksdb/memtable/stl_wrappers.h +++ b/thirdparty/rocksdb/memtable/stl_wrappers.h @@ -11,7 +11,6 @@ #include "rocksdb/memtablerep.h" #include "rocksdb/slice.h" #include "util/coding.h" -#include "util/murmurhash.h" namespace rocksdb { namespace stl_wrappers { diff --git a/thirdparty/rocksdb/memtable/vectorrep.cc b/thirdparty/rocksdb/memtable/vectorrep.cc index e54025c2d3..827ab8a5d2 100644 --- a/thirdparty/rocksdb/memtable/vectorrep.cc +++ b/thirdparty/rocksdb/memtable/vectorrep.cc @@ -31,20 +31,19 @@ class VectorRep : public MemTableRep { // single buffer and pass that in as the parameter to Insert) // REQUIRES: nothing that compares equal to key is currently in the // collection. - virtual void Insert(KeyHandle handle) override; + void Insert(KeyHandle handle) override; // Returns true iff an entry that compares equal to key is in the collection. - virtual bool Contains(const char* key) const override; + bool Contains(const char* key) const override; - virtual void MarkReadOnly() override; + void MarkReadOnly() override; - virtual size_t ApproximateMemoryUsage() override; + size_t ApproximateMemoryUsage() override; - virtual void Get(const LookupKey& k, void* callback_args, - bool (*callback_func)(void* arg, - const char* entry)) override; + void Get(const LookupKey& k, void* callback_args, + bool (*callback_func)(void* arg, const char* entry)) override; - virtual ~VectorRep() override { } + ~VectorRep() override {} class Iterator : public MemTableRep::Iterator { class VectorRep* vrep_; @@ -62,41 +61,40 @@ class VectorRep : public MemTableRep { // Initialize an iterator over the specified collection. // The returned iterator is not valid. // explicit Iterator(const MemTableRep* collection); - virtual ~Iterator() override { }; + ~Iterator() override{}; // Returns true iff the iterator is positioned at a valid node. - virtual bool Valid() const override; + bool Valid() const override; // Returns the key at the current position. // REQUIRES: Valid() - virtual const char* key() const override; + const char* key() const override; // Advances to the next position. // REQUIRES: Valid() - virtual void Next() override; + void Next() override; // Advances to the previous position. // REQUIRES: Valid() - virtual void Prev() override; + void Prev() override; // Advance to the first entry with a key >= target - virtual void Seek(const Slice& user_key, const char* memtable_key) override; + void Seek(const Slice& user_key, const char* memtable_key) override; // Advance to the first entry with a key <= target - virtual void SeekForPrev(const Slice& user_key, - const char* memtable_key) override; + void SeekForPrev(const Slice& user_key, const char* memtable_key) override; // Position at the first entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToFirst() override; + void SeekToFirst() override; // Position at the last entry in collection. // Final state of iterator is Valid() iff collection is not empty. - virtual void SeekToLast() override; + void SeekToLast() override; }; // Return an iterator over the keys in this representation. - virtual MemTableRep::Iterator* GetIterator(Arena* arena) override; + MemTableRep::Iterator* GetIterator(Arena* arena) override; private: friend class Iterator; @@ -227,8 +225,8 @@ void VectorRep::Iterator::Seek(const Slice& user_key, } // Advance to the first entry with a key <= target -void VectorRep::Iterator::SeekForPrev(const Slice& user_key, - const char* memtable_key) { +void VectorRep::Iterator::SeekForPrev(const Slice& /*user_key*/, + const char* /*memtable_key*/) { assert(false); } @@ -296,7 +294,7 @@ MemTableRep::Iterator* VectorRep::GetIterator(Arena* arena) { MemTableRep* VectorRepFactory::CreateMemTableRep( const MemTableRep::KeyComparator& compare, Allocator* allocator, - const SliceTransform*, Logger* logger) { + const SliceTransform*, Logger* /*logger*/) { return new VectorRep(compare, allocator, count_); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/memtable/write_buffer_manager.cc b/thirdparty/rocksdb/memtable/write_buffer_manager.cc index bac0fdd8fb..7f2e664ab5 100644 --- a/thirdparty/rocksdb/memtable/write_buffer_manager.cc +++ b/thirdparty/rocksdb/memtable/write_buffer_manager.cc @@ -60,6 +60,8 @@ WriteBufferManager::WriteBufferManager(size_t _buffer_size, // Construct the cache key using the pointer to this. cache_rep_.reset(new CacheRep(cache)); } +#else + (void)cache; #endif // ROCKSDB_LITE } @@ -77,7 +79,7 @@ WriteBufferManager::~WriteBufferManager() { void WriteBufferManager::ReserveMemWithCache(size_t mem) { #ifndef ROCKSDB_LITE assert(cache_rep_ != nullptr); - // Use a mutex to protect various data structures. Can be optimzied to a + // Use a mutex to protect various data structures. Can be optimized to a // lock-free solution if it ends up with a performance bottleneck. std::lock_guard lock(cache_rep_->cache_mutex_); @@ -92,20 +94,22 @@ void WriteBufferManager::ReserveMemWithCache(size_t mem) { cache_rep_->dummy_handles_.push_back(handle); cache_rep_->cache_allocated_size_ += kSizeDummyEntry; } +#else + (void)mem; #endif // ROCKSDB_LITE } void WriteBufferManager::FreeMemWithCache(size_t mem) { #ifndef ROCKSDB_LITE assert(cache_rep_ != nullptr); - // Use a mutex to protect various data structures. Can be optimzied to a + // Use a mutex to protect various data structures. Can be optimized to a // lock-free solution if it ends up with a performance bottleneck. std::lock_guard lock(cache_rep_->cache_mutex_); size_t new_mem_used = memory_used_.load(std::memory_order_relaxed) - mem; memory_used_.store(new_mem_used, std::memory_order_relaxed); // Gradually shrink memory costed in the block cache if the actual // usage is less than 3/4 of what we reserve from the block cache. - // We do this becausse: + // We do this because: // 1. we don't pay the cost of the block cache immediately a memtable is // freed, as block cache insert is expensive; // 2. eventually, if we walk away from a temporary memtable size increase, @@ -119,6 +123,8 @@ void WriteBufferManager::FreeMemWithCache(size_t mem) { cache_rep_->dummy_handles_.pop_back(); cache_rep_->cache_allocated_size_ -= kSizeDummyEntry; } +#else + (void)mem; #endif // ROCKSDB_LITE } } // namespace rocksdb diff --git a/thirdparty/rocksdb/monitoring/histogram.cc b/thirdparty/rocksdb/monitoring/histogram.cc index b3c01a78e0..4bc7139d30 100644 --- a/thirdparty/rocksdb/monitoring/histogram.cc +++ b/thirdparty/rocksdb/monitoring/histogram.cc @@ -202,6 +202,7 @@ std::string HistogramStat::ToString() const { Percentile(99.99)); r.append(buf); r.append("------------------------------------------------------\n"); + if (cur_num == 0) return r; // all buckets are empty const double mult = 100.0 / cur_num; uint64_t cumulative_sum = 0; for (unsigned int b = 0; b < num_buckets_; b++) { @@ -209,7 +210,8 @@ std::string HistogramStat::ToString() const { if (bucket_value <= 0.0) continue; cumulative_sum += bucket_value; snprintf(buf, sizeof(buf), - "[ %7" PRIu64 ", %7" PRIu64 " ) %8" PRIu64 " %7.3f%% %7.3f%% ", + "%c %7" PRIu64 ", %7" PRIu64 " ] %8" PRIu64 " %7.3f%% %7.3f%% ", + (b == 0) ? '[' : '(', (b == 0) ? 0 : bucketMapper.BucketLimit(b-1), // left bucketMapper.BucketLimit(b), // right bucket_value, // count @@ -233,6 +235,9 @@ void HistogramStat::Data(HistogramData * const data) const { data->max = static_cast(max()); data->average = Average(); data->standard_deviation = StandardDeviation(); + data->count = num(); + data->sum = sum(); + data->min = static_cast(min()); } void HistogramImpl::Clear() { diff --git a/thirdparty/rocksdb/monitoring/histogram_test.cc b/thirdparty/rocksdb/monitoring/histogram_test.cc index b4e3c981c8..df58822fc2 100644 --- a/thirdparty/rocksdb/monitoring/histogram_test.cc +++ b/thirdparty/rocksdb/monitoring/histogram_test.cc @@ -85,6 +85,19 @@ TEST_F(HistogramTest, BasicOperation) { BasicOperation(histogramWindowing); } +TEST_F(HistogramTest, BoundaryValue) { + HistogramImpl histogram; + // - both should be in [0, 1] bucket because we place values on bucket + // boundaries in the lower bucket. + // - all points are in [0, 1] bucket, so p50 will be 0.5 + // - the test cannot be written with a single point since histogram won't + // report percentiles lower than the min or greater than the max. + histogram.Add(0); + histogram.Add(1); + + ASSERT_LE(fabs(histogram.Percentile(50.0) - 0.5), kIota); +} + TEST_F(HistogramTest, MergeHistogram) { HistogramImpl histogram; HistogramImpl other; diff --git a/thirdparty/rocksdb/monitoring/histogram_windowing.cc b/thirdparty/rocksdb/monitoring/histogram_windowing.cc index 28d8265f26..ecd6f090a5 100644 --- a/thirdparty/rocksdb/monitoring/histogram_windowing.cc +++ b/thirdparty/rocksdb/monitoring/histogram_windowing.cc @@ -17,7 +17,7 @@ namespace rocksdb { HistogramWindowingImpl::HistogramWindowingImpl() { env_ = Env::Default(); - window_stats_.reset(new HistogramStat[num_windows_]); + window_stats_.reset(new HistogramStat[static_cast(num_windows_)]); Clear(); } @@ -29,7 +29,7 @@ HistogramWindowingImpl::HistogramWindowingImpl( micros_per_window_(micros_per_window), min_num_per_window_(min_num_per_window) { env_ = Env::Default(); - window_stats_.reset(new HistogramStat[num_windows_]); + window_stats_.reset(new HistogramStat[static_cast(num_windows_)]); Clear(); } @@ -60,7 +60,7 @@ void HistogramWindowingImpl::Add(uint64_t value){ stats_.Add(value); // Current window update - window_stats_[current_window()].Add(value); + window_stats_[static_cast(current_window())].Add(value); } void HistogramWindowingImpl::Merge(const Histogram& other) { @@ -89,8 +89,11 @@ void HistogramWindowingImpl::Merge(const HistogramWindowingImpl& other) { (cur_window + num_windows_ - i) % num_windows_; uint64_t other_window_index = (other_cur_window + other.num_windows_ - i) % other.num_windows_; + size_t windex = static_cast(window_index); + size_t other_windex = static_cast(other_window_index); - window_stats_[window_index].Merge(other.window_stats_[other_window_index]); + window_stats_[windex].Merge( + other.window_stats_[other_windex]); } } @@ -129,8 +132,9 @@ void HistogramWindowingImpl::Data(HistogramData * const data) const { void HistogramWindowingImpl::TimerTick() { uint64_t curr_time = env_->NowMicros(); + size_t curr_window_ = static_cast(current_window()); if (curr_time - last_swap_time() > micros_per_window_ && - window_stats_[current_window()].num() >= min_num_per_window_) { + window_stats_[curr_window_].num() >= min_num_per_window_) { SwapHistoryBucket(); } } @@ -149,7 +153,8 @@ void HistogramWindowingImpl::SwapHistoryBucket() { 0 : curr_window + 1; // subtract next buckets from totals and swap to next buckets - HistogramStat& stats_to_drop = window_stats_[next_window]; + HistogramStat& stats_to_drop = + window_stats_[static_cast(next_window)]; if (!stats_to_drop.Empty()) { for (size_t b = 0; b < stats_.num_buckets_; b++){ diff --git a/thirdparty/rocksdb/monitoring/histogram_windowing.h b/thirdparty/rocksdb/monitoring/histogram_windowing.h index 2a6d0dd158..6532aa248e 100644 --- a/thirdparty/rocksdb/monitoring/histogram_windowing.h +++ b/thirdparty/rocksdb/monitoring/histogram_windowing.h @@ -22,8 +22,8 @@ class HistogramWindowingImpl : public Histogram uint64_t micros_per_window, uint64_t min_num_per_window); - HistogramWindowingImpl(const HistogramImpl&) = delete; - HistogramWindowingImpl& operator=(const HistogramImpl&) = delete; + HistogramWindowingImpl(const HistogramWindowingImpl&) = delete; + HistogramWindowingImpl& operator=(const HistogramWindowingImpl&) = delete; ~HistogramWindowingImpl(); @@ -77,4 +77,4 @@ class HistogramWindowingImpl : public Histogram uint64_t min_num_per_window_ = 0; }; -} // namespace rocksdb \ No newline at end of file +} // namespace rocksdb diff --git a/thirdparty/rocksdb/monitoring/instrumented_mutex.cc b/thirdparty/rocksdb/monitoring/instrumented_mutex.cc index c07a5a17a8..7b61bcf4fb 100644 --- a/thirdparty/rocksdb/monitoring/instrumented_mutex.cc +++ b/thirdparty/rocksdb/monitoring/instrumented_mutex.cc @@ -10,25 +10,21 @@ namespace rocksdb { namespace { -bool ShouldReportToStats(Env* env, Statistics* stats) { - return env != nullptr && stats != nullptr && - stats->stats_level_ > kExceptTimeForMutex; +Statistics* stats_for_report(Env* env, Statistics* stats) { + if (env != nullptr && stats != nullptr && + stats->get_stats_level() > kExceptTimeForMutex) { + return stats; + } else { + return nullptr; + } } } // namespace void InstrumentedMutex::Lock() { - PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(db_mutex_lock_nanos, - stats_code_ == DB_MUTEX_WAIT_MICROS); - uint64_t wait_time_micros = 0; - if (ShouldReportToStats(env_, stats_)) { - { - StopWatch sw(env_, nullptr, 0, &wait_time_micros); - LockInternal(); - } - RecordTick(stats_, stats_code_, wait_time_micros); - } else { - LockInternal(); - } + PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD( + db_mutex_lock_nanos, stats_code_ == DB_MUTEX_WAIT_MICROS, + stats_for_report(env_, stats_), stats_code_); + LockInternal(); } void InstrumentedMutex::LockInternal() { @@ -39,18 +35,10 @@ void InstrumentedMutex::LockInternal() { } void InstrumentedCondVar::Wait() { - PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(db_condition_wait_nanos, - stats_code_ == DB_MUTEX_WAIT_MICROS); - uint64_t wait_time_micros = 0; - if (ShouldReportToStats(env_, stats_)) { - { - StopWatch sw(env_, nullptr, 0, &wait_time_micros); - WaitInternal(); - } - RecordTick(stats_, stats_code_, wait_time_micros); - } else { - WaitInternal(); - } + PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD( + db_condition_wait_nanos, stats_code_ == DB_MUTEX_WAIT_MICROS, + stats_for_report(env_, stats_), stats_code_); + WaitInternal(); } void InstrumentedCondVar::WaitInternal() { @@ -61,20 +49,10 @@ void InstrumentedCondVar::WaitInternal() { } bool InstrumentedCondVar::TimedWait(uint64_t abs_time_us) { - PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(db_condition_wait_nanos, - stats_code_ == DB_MUTEX_WAIT_MICROS); - uint64_t wait_time_micros = 0; - bool result = false; - if (ShouldReportToStats(env_, stats_)) { - { - StopWatch sw(env_, nullptr, 0, &wait_time_micros); - result = TimedWaitInternal(abs_time_us); - } - RecordTick(stats_, stats_code_, wait_time_micros); - } else { - result = TimedWaitInternal(abs_time_us); - } - return result; + PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD( + db_condition_wait_nanos, stats_code_ == DB_MUTEX_WAIT_MICROS, + stats_for_report(env_, stats_), stats_code_); + return TimedWaitInternal(abs_time_us); } bool InstrumentedCondVar::TimedWaitInternal(uint64_t abs_time_us) { diff --git a/thirdparty/rocksdb/monitoring/iostats_context.cc b/thirdparty/rocksdb/monitoring/iostats_context.cc index 8aa131a704..3d102f9120 100644 --- a/thirdparty/rocksdb/monitoring/iostats_context.cc +++ b/thirdparty/rocksdb/monitoring/iostats_context.cc @@ -6,7 +6,6 @@ #include #include "monitoring/iostats_context_imp.h" #include "rocksdb/env.h" -#include "util/thread_local.h" namespace rocksdb { diff --git a/thirdparty/rocksdb/monitoring/iostats_context_imp.h b/thirdparty/rocksdb/monitoring/iostats_context_imp.h index 88538297a6..23c2088cab 100644 --- a/thirdparty/rocksdb/monitoring/iostats_context_imp.h +++ b/thirdparty/rocksdb/monitoring/iostats_context_imp.h @@ -8,35 +8,40 @@ #include "rocksdb/iostats_context.h" #ifdef ROCKSDB_SUPPORT_THREAD_LOCAL +namespace rocksdb { +extern __thread IOStatsContext iostats_context; +} // namespace rocksdb // increment a specific counter by the specified value -#define IOSTATS_ADD(metric, value) \ - (get_iostats_context()->metric += value) +#define IOSTATS_ADD(metric, value) (iostats_context.metric += value) // Increase metric value only when it is positive #define IOSTATS_ADD_IF_POSITIVE(metric, value) \ if (value > 0) { IOSTATS_ADD(metric, value); } // reset a specific counter to zero -#define IOSTATS_RESET(metric) \ - (get_iostats_context()->metric = 0) +#define IOSTATS_RESET(metric) (iostats_context.metric = 0) // reset all counters to zero -#define IOSTATS_RESET_ALL() \ - (get_iostats_context()->Reset()) +#define IOSTATS_RESET_ALL() (iostats_context.Reset()) -#define IOSTATS_SET_THREAD_POOL_ID(value) \ - (get_iostats_context()->thread_pool_id = value) +#define IOSTATS_SET_THREAD_POOL_ID(value) \ + (iostats_context.thread_pool_id = value) -#define IOSTATS_THREAD_POOL_ID() \ - (get_iostats_context()->thread_pool_id) +#define IOSTATS_THREAD_POOL_ID() (iostats_context.thread_pool_id) -#define IOSTATS(metric) \ - (get_iostats_context()->metric) +#define IOSTATS(metric) (iostats_context.metric) // Declare and set start time of the timer -#define IOSTATS_TIMER_GUARD(metric) \ - PerfStepTimer iostats_step_timer_##metric(&(get_iostats_context()->metric)); \ +#define IOSTATS_TIMER_GUARD(metric) \ + PerfStepTimer iostats_step_timer_##metric(&(iostats_context.metric)); \ + iostats_step_timer_##metric.Start(); + +// Declare and set start time of the timer +#define IOSTATS_CPU_TIMER_GUARD(metric, env) \ + PerfStepTimer iostats_step_timer_##metric( \ + &(iostats_context.metric), env, true, \ + PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); \ iostats_step_timer_##metric.Start(); #else // ROCKSDB_SUPPORT_THREAD_LOCAL @@ -50,5 +55,6 @@ #define IOSTATS(metric) 0 #define IOSTATS_TIMER_GUARD(metric) +#define IOSTATS_CPU_TIMER_GUARD(metric, env) #endif // ROCKSDB_SUPPORT_THREAD_LOCAL diff --git a/thirdparty/rocksdb/monitoring/perf_context.cc b/thirdparty/rocksdb/monitoring/perf_context.cc index 791f4bdbe4..40b0b215c4 100644 --- a/thirdparty/rocksdb/monitoring/perf_context.cc +++ b/thirdparty/rocksdb/monitoring/perf_context.cc @@ -15,7 +15,7 @@ PerfContext perf_context; #if defined(OS_SOLARIS) __thread PerfContext perf_context_; #else -__thread PerfContext perf_context; +thread_local PerfContext perf_context; #endif #endif @@ -31,6 +31,300 @@ PerfContext* get_perf_context() { #endif } +PerfContext::~PerfContext() { +#if !defined(NPERF_CONTEXT) && defined(ROCKSDB_SUPPORT_THREAD_LOCAL) && !defined(OS_SOLARIS) + ClearPerLevelPerfContext(); +#endif +} + +PerfContext::PerfContext(const PerfContext& other) { +#ifndef NPERF_CONTEXT + user_key_comparison_count = other.user_key_comparison_count; + block_cache_hit_count = other.block_cache_hit_count; + block_read_count = other.block_read_count; + block_read_byte = other.block_read_byte; + block_read_time = other.block_read_time; + block_cache_index_hit_count = other.block_cache_index_hit_count; + index_block_read_count = other.index_block_read_count; + block_cache_filter_hit_count = other.block_cache_filter_hit_count; + filter_block_read_count = other.filter_block_read_count; + compression_dict_block_read_count = other.compression_dict_block_read_count; + block_checksum_time = other.block_checksum_time; + block_decompress_time = other.block_decompress_time; + get_read_bytes = other.get_read_bytes; + multiget_read_bytes = other.multiget_read_bytes; + iter_read_bytes = other.iter_read_bytes; + internal_key_skipped_count = other.internal_key_skipped_count; + internal_delete_skipped_count = other.internal_delete_skipped_count; + internal_recent_skipped_count = other.internal_recent_skipped_count; + internal_merge_count = other.internal_merge_count; + write_wal_time = other.write_wal_time; + get_snapshot_time = other.get_snapshot_time; + get_from_memtable_time = other.get_from_memtable_time; + get_from_memtable_count = other.get_from_memtable_count; + get_post_process_time = other.get_post_process_time; + get_from_output_files_time = other.get_from_output_files_time; + seek_on_memtable_time = other.seek_on_memtable_time; + seek_on_memtable_count = other.seek_on_memtable_count; + next_on_memtable_count = other.next_on_memtable_count; + prev_on_memtable_count = other.prev_on_memtable_count; + seek_child_seek_time = other.seek_child_seek_time; + seek_child_seek_count = other.seek_child_seek_count; + seek_min_heap_time = other.seek_min_heap_time; + seek_internal_seek_time = other.seek_internal_seek_time; + find_next_user_entry_time = other.find_next_user_entry_time; + write_pre_and_post_process_time = other.write_pre_and_post_process_time; + write_memtable_time = other.write_memtable_time; + write_delay_time = other.write_delay_time; + write_thread_wait_nanos = other.write_thread_wait_nanos; + write_scheduling_flushes_compactions_time = + other.write_scheduling_flushes_compactions_time; + db_mutex_lock_nanos = other.db_mutex_lock_nanos; + db_condition_wait_nanos = other.db_condition_wait_nanos; + merge_operator_time_nanos = other.merge_operator_time_nanos; + read_index_block_nanos = other.read_index_block_nanos; + read_filter_block_nanos = other.read_filter_block_nanos; + new_table_block_iter_nanos = other.new_table_block_iter_nanos; + new_table_iterator_nanos = other.new_table_iterator_nanos; + block_seek_nanos = other.block_seek_nanos; + find_table_nanos = other.find_table_nanos; + bloom_memtable_hit_count = other.bloom_memtable_hit_count; + bloom_memtable_miss_count = other.bloom_memtable_miss_count; + bloom_sst_hit_count = other.bloom_sst_hit_count; + bloom_sst_miss_count = other.bloom_sst_miss_count; + key_lock_wait_time = other.key_lock_wait_time; + key_lock_wait_count = other.key_lock_wait_count; + + env_new_sequential_file_nanos = other.env_new_sequential_file_nanos; + env_new_random_access_file_nanos = other.env_new_random_access_file_nanos; + env_new_writable_file_nanos = other.env_new_writable_file_nanos; + env_reuse_writable_file_nanos = other.env_reuse_writable_file_nanos; + env_new_random_rw_file_nanos = other.env_new_random_rw_file_nanos; + env_new_directory_nanos = other.env_new_directory_nanos; + env_file_exists_nanos = other.env_file_exists_nanos; + env_get_children_nanos = other.env_get_children_nanos; + env_get_children_file_attributes_nanos = + other.env_get_children_file_attributes_nanos; + env_delete_file_nanos = other.env_delete_file_nanos; + env_create_dir_nanos = other.env_create_dir_nanos; + env_create_dir_if_missing_nanos = other.env_create_dir_if_missing_nanos; + env_delete_dir_nanos = other.env_delete_dir_nanos; + env_get_file_size_nanos = other.env_get_file_size_nanos; + env_get_file_modification_time_nanos = + other.env_get_file_modification_time_nanos; + env_rename_file_nanos = other.env_rename_file_nanos; + env_link_file_nanos = other.env_link_file_nanos; + env_lock_file_nanos = other.env_lock_file_nanos; + env_unlock_file_nanos = other.env_unlock_file_nanos; + env_new_logger_nanos = other.env_new_logger_nanos; + get_cpu_nanos = other.get_cpu_nanos; + iter_next_cpu_nanos = other.iter_next_cpu_nanos; + iter_prev_cpu_nanos = other.iter_prev_cpu_nanos; + iter_seek_cpu_nanos = other.iter_seek_cpu_nanos; + if (per_level_perf_context_enabled && level_to_perf_context != nullptr) { + ClearPerLevelPerfContext(); + } + if (other.level_to_perf_context != nullptr) { + level_to_perf_context = new std::map(); + *level_to_perf_context = *other.level_to_perf_context; + } + per_level_perf_context_enabled = other.per_level_perf_context_enabled; +#endif +} + +PerfContext::PerfContext(PerfContext&& other) noexcept { +#ifndef NPERF_CONTEXT + user_key_comparison_count = other.user_key_comparison_count; + block_cache_hit_count = other.block_cache_hit_count; + block_read_count = other.block_read_count; + block_read_byte = other.block_read_byte; + block_read_time = other.block_read_time; + block_cache_index_hit_count = other.block_cache_index_hit_count; + index_block_read_count = other.index_block_read_count; + block_cache_filter_hit_count = other.block_cache_filter_hit_count; + filter_block_read_count = other.filter_block_read_count; + compression_dict_block_read_count = other.compression_dict_block_read_count; + block_checksum_time = other.block_checksum_time; + block_decompress_time = other.block_decompress_time; + get_read_bytes = other.get_read_bytes; + multiget_read_bytes = other.multiget_read_bytes; + iter_read_bytes = other.iter_read_bytes; + internal_key_skipped_count = other.internal_key_skipped_count; + internal_delete_skipped_count = other.internal_delete_skipped_count; + internal_recent_skipped_count = other.internal_recent_skipped_count; + internal_merge_count = other.internal_merge_count; + write_wal_time = other.write_wal_time; + get_snapshot_time = other.get_snapshot_time; + get_from_memtable_time = other.get_from_memtable_time; + get_from_memtable_count = other.get_from_memtable_count; + get_post_process_time = other.get_post_process_time; + get_from_output_files_time = other.get_from_output_files_time; + seek_on_memtable_time = other.seek_on_memtable_time; + seek_on_memtable_count = other.seek_on_memtable_count; + next_on_memtable_count = other.next_on_memtable_count; + prev_on_memtable_count = other.prev_on_memtable_count; + seek_child_seek_time = other.seek_child_seek_time; + seek_child_seek_count = other.seek_child_seek_count; + seek_min_heap_time = other.seek_min_heap_time; + seek_internal_seek_time = other.seek_internal_seek_time; + find_next_user_entry_time = other.find_next_user_entry_time; + write_pre_and_post_process_time = other.write_pre_and_post_process_time; + write_memtable_time = other.write_memtable_time; + write_delay_time = other.write_delay_time; + write_thread_wait_nanos = other.write_thread_wait_nanos; + write_scheduling_flushes_compactions_time = + other.write_scheduling_flushes_compactions_time; + db_mutex_lock_nanos = other.db_mutex_lock_nanos; + db_condition_wait_nanos = other.db_condition_wait_nanos; + merge_operator_time_nanos = other.merge_operator_time_nanos; + read_index_block_nanos = other.read_index_block_nanos; + read_filter_block_nanos = other.read_filter_block_nanos; + new_table_block_iter_nanos = other.new_table_block_iter_nanos; + new_table_iterator_nanos = other.new_table_iterator_nanos; + block_seek_nanos = other.block_seek_nanos; + find_table_nanos = other.find_table_nanos; + bloom_memtable_hit_count = other.bloom_memtable_hit_count; + bloom_memtable_miss_count = other.bloom_memtable_miss_count; + bloom_sst_hit_count = other.bloom_sst_hit_count; + bloom_sst_miss_count = other.bloom_sst_miss_count; + key_lock_wait_time = other.key_lock_wait_time; + key_lock_wait_count = other.key_lock_wait_count; + + env_new_sequential_file_nanos = other.env_new_sequential_file_nanos; + env_new_random_access_file_nanos = other.env_new_random_access_file_nanos; + env_new_writable_file_nanos = other.env_new_writable_file_nanos; + env_reuse_writable_file_nanos = other.env_reuse_writable_file_nanos; + env_new_random_rw_file_nanos = other.env_new_random_rw_file_nanos; + env_new_directory_nanos = other.env_new_directory_nanos; + env_file_exists_nanos = other.env_file_exists_nanos; + env_get_children_nanos = other.env_get_children_nanos; + env_get_children_file_attributes_nanos = + other.env_get_children_file_attributes_nanos; + env_delete_file_nanos = other.env_delete_file_nanos; + env_create_dir_nanos = other.env_create_dir_nanos; + env_create_dir_if_missing_nanos = other.env_create_dir_if_missing_nanos; + env_delete_dir_nanos = other.env_delete_dir_nanos; + env_get_file_size_nanos = other.env_get_file_size_nanos; + env_get_file_modification_time_nanos = + other.env_get_file_modification_time_nanos; + env_rename_file_nanos = other.env_rename_file_nanos; + env_link_file_nanos = other.env_link_file_nanos; + env_lock_file_nanos = other.env_lock_file_nanos; + env_unlock_file_nanos = other.env_unlock_file_nanos; + env_new_logger_nanos = other.env_new_logger_nanos; + get_cpu_nanos = other.get_cpu_nanos; + iter_next_cpu_nanos = other.iter_next_cpu_nanos; + iter_prev_cpu_nanos = other.iter_prev_cpu_nanos; + iter_seek_cpu_nanos = other.iter_seek_cpu_nanos; + if (per_level_perf_context_enabled && level_to_perf_context != nullptr) { + ClearPerLevelPerfContext(); + } + if (other.level_to_perf_context != nullptr) { + level_to_perf_context = other.level_to_perf_context; + other.level_to_perf_context = nullptr; + } + per_level_perf_context_enabled = other.per_level_perf_context_enabled; +#endif +} + +// TODO(Zhongyi): reduce code duplication between copy constructor and +// assignment operator +PerfContext& PerfContext::operator=(const PerfContext& other) { +#ifndef NPERF_CONTEXT + user_key_comparison_count = other.user_key_comparison_count; + block_cache_hit_count = other.block_cache_hit_count; + block_read_count = other.block_read_count; + block_read_byte = other.block_read_byte; + block_read_time = other.block_read_time; + block_cache_index_hit_count = other.block_cache_index_hit_count; + index_block_read_count = other.index_block_read_count; + block_cache_filter_hit_count = other.block_cache_filter_hit_count; + filter_block_read_count = other.filter_block_read_count; + compression_dict_block_read_count = other.compression_dict_block_read_count; + block_checksum_time = other.block_checksum_time; + block_decompress_time = other.block_decompress_time; + get_read_bytes = other.get_read_bytes; + multiget_read_bytes = other.multiget_read_bytes; + iter_read_bytes = other.iter_read_bytes; + internal_key_skipped_count = other.internal_key_skipped_count; + internal_delete_skipped_count = other.internal_delete_skipped_count; + internal_recent_skipped_count = other.internal_recent_skipped_count; + internal_merge_count = other.internal_merge_count; + write_wal_time = other.write_wal_time; + get_snapshot_time = other.get_snapshot_time; + get_from_memtable_time = other.get_from_memtable_time; + get_from_memtable_count = other.get_from_memtable_count; + get_post_process_time = other.get_post_process_time; + get_from_output_files_time = other.get_from_output_files_time; + seek_on_memtable_time = other.seek_on_memtable_time; + seek_on_memtable_count = other.seek_on_memtable_count; + next_on_memtable_count = other.next_on_memtable_count; + prev_on_memtable_count = other.prev_on_memtable_count; + seek_child_seek_time = other.seek_child_seek_time; + seek_child_seek_count = other.seek_child_seek_count; + seek_min_heap_time = other.seek_min_heap_time; + seek_internal_seek_time = other.seek_internal_seek_time; + find_next_user_entry_time = other.find_next_user_entry_time; + write_pre_and_post_process_time = other.write_pre_and_post_process_time; + write_memtable_time = other.write_memtable_time; + write_delay_time = other.write_delay_time; + write_thread_wait_nanos = other.write_thread_wait_nanos; + write_scheduling_flushes_compactions_time = + other.write_scheduling_flushes_compactions_time; + db_mutex_lock_nanos = other.db_mutex_lock_nanos; + db_condition_wait_nanos = other.db_condition_wait_nanos; + merge_operator_time_nanos = other.merge_operator_time_nanos; + read_index_block_nanos = other.read_index_block_nanos; + read_filter_block_nanos = other.read_filter_block_nanos; + new_table_block_iter_nanos = other.new_table_block_iter_nanos; + new_table_iterator_nanos = other.new_table_iterator_nanos; + block_seek_nanos = other.block_seek_nanos; + find_table_nanos = other.find_table_nanos; + bloom_memtable_hit_count = other.bloom_memtable_hit_count; + bloom_memtable_miss_count = other.bloom_memtable_miss_count; + bloom_sst_hit_count = other.bloom_sst_hit_count; + bloom_sst_miss_count = other.bloom_sst_miss_count; + key_lock_wait_time = other.key_lock_wait_time; + key_lock_wait_count = other.key_lock_wait_count; + + env_new_sequential_file_nanos = other.env_new_sequential_file_nanos; + env_new_random_access_file_nanos = other.env_new_random_access_file_nanos; + env_new_writable_file_nanos = other.env_new_writable_file_nanos; + env_reuse_writable_file_nanos = other.env_reuse_writable_file_nanos; + env_new_random_rw_file_nanos = other.env_new_random_rw_file_nanos; + env_new_directory_nanos = other.env_new_directory_nanos; + env_file_exists_nanos = other.env_file_exists_nanos; + env_get_children_nanos = other.env_get_children_nanos; + env_get_children_file_attributes_nanos = + other.env_get_children_file_attributes_nanos; + env_delete_file_nanos = other.env_delete_file_nanos; + env_create_dir_nanos = other.env_create_dir_nanos; + env_create_dir_if_missing_nanos = other.env_create_dir_if_missing_nanos; + env_delete_dir_nanos = other.env_delete_dir_nanos; + env_get_file_size_nanos = other.env_get_file_size_nanos; + env_get_file_modification_time_nanos = + other.env_get_file_modification_time_nanos; + env_rename_file_nanos = other.env_rename_file_nanos; + env_link_file_nanos = other.env_link_file_nanos; + env_lock_file_nanos = other.env_lock_file_nanos; + env_unlock_file_nanos = other.env_unlock_file_nanos; + env_new_logger_nanos = other.env_new_logger_nanos; + get_cpu_nanos = other.get_cpu_nanos; + iter_next_cpu_nanos = other.iter_next_cpu_nanos; + iter_prev_cpu_nanos = other.iter_prev_cpu_nanos; + iter_seek_cpu_nanos = other.iter_seek_cpu_nanos; + if (per_level_perf_context_enabled && level_to_perf_context != nullptr) { + ClearPerLevelPerfContext(); + } + if (other.level_to_perf_context != nullptr) { + level_to_perf_context = new std::map(); + *level_to_perf_context = *other.level_to_perf_context; + } + per_level_perf_context_enabled = other.per_level_perf_context_enabled; +#endif + return *this; +} + void PerfContext::Reset() { #ifndef NPERF_CONTEXT user_key_comparison_count = 0; @@ -38,6 +332,11 @@ void PerfContext::Reset() { block_read_count = 0; block_read_byte = 0; block_read_time = 0; + block_cache_index_hit_count = 0; + index_block_read_count = 0; + block_cache_filter_hit_count = 0; + filter_block_read_count = 0; + compression_dict_block_read_count = 0; block_checksum_time = 0; block_decompress_time = 0; get_read_bytes = 0; @@ -66,6 +365,8 @@ void PerfContext::Reset() { write_pre_and_post_process_time = 0; write_memtable_time = 0; write_delay_time = 0; + write_thread_wait_nanos = 0; + write_scheduling_flushes_compactions_time = 0; db_mutex_lock_nanos = 0; db_condition_wait_nanos = 0; merge_operator_time_nanos = 0; @@ -79,6 +380,8 @@ void PerfContext::Reset() { bloom_memtable_miss_count = 0; bloom_sst_hit_count = 0; bloom_sst_miss_count = 0; + key_lock_wait_time = 0; + key_lock_wait_count = 0; env_new_sequential_file_nanos = 0; env_new_random_access_file_nanos = 0; @@ -100,6 +403,15 @@ void PerfContext::Reset() { env_lock_file_nanos = 0; env_unlock_file_nanos = 0; env_new_logger_nanos = 0; + get_cpu_nanos = 0; + iter_next_cpu_nanos = 0; + iter_prev_cpu_nanos = 0; + iter_seek_cpu_nanos = 0; + if (per_level_perf_context_enabled && level_to_perf_context) { + for (auto& kv : *level_to_perf_context) { + kv.second.Reset(); + } + } #endif } @@ -108,6 +420,27 @@ void PerfContext::Reset() { ss << #counter << " = " << counter << ", "; \ } +#define PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(counter) \ + if (per_level_perf_context_enabled && \ + level_to_perf_context) { \ + ss << #counter << " = "; \ + for (auto& kv : *level_to_perf_context) { \ + if (!exclude_zero_counters || (kv.second.counter > 0)) { \ + ss << kv.second.counter << "@level" << kv.first << ", "; \ + } \ + } \ + } + +void PerfContextByLevel::Reset() { +#ifndef NPERF_CONTEXT + bloom_filter_useful = 0; + bloom_filter_full_positive = 0; + bloom_filter_full_true_positive = 0; + block_cache_hit_count = 0; + block_cache_miss_count = 0; +#endif +} + std::string PerfContext::ToString(bool exclude_zero_counters) const { #ifdef NPERF_CONTEXT return ""; @@ -118,6 +451,11 @@ std::string PerfContext::ToString(bool exclude_zero_counters) const { PERF_CONTEXT_OUTPUT(block_read_count); PERF_CONTEXT_OUTPUT(block_read_byte); PERF_CONTEXT_OUTPUT(block_read_time); + PERF_CONTEXT_OUTPUT(block_cache_index_hit_count); + PERF_CONTEXT_OUTPUT(index_block_read_count); + PERF_CONTEXT_OUTPUT(block_cache_filter_hit_count); + PERF_CONTEXT_OUTPUT(filter_block_read_count); + PERF_CONTEXT_OUTPUT(compression_dict_block_read_count); PERF_CONTEXT_OUTPUT(block_checksum_time); PERF_CONTEXT_OUTPUT(block_decompress_time); PERF_CONTEXT_OUTPUT(get_read_bytes); @@ -144,6 +482,8 @@ std::string PerfContext::ToString(bool exclude_zero_counters) const { PERF_CONTEXT_OUTPUT(find_next_user_entry_time); PERF_CONTEXT_OUTPUT(write_pre_and_post_process_time); PERF_CONTEXT_OUTPUT(write_memtable_time); + PERF_CONTEXT_OUTPUT(write_thread_wait_nanos); + PERF_CONTEXT_OUTPUT(write_scheduling_flushes_compactions_time); PERF_CONTEXT_OUTPUT(db_mutex_lock_nanos); PERF_CONTEXT_OUTPUT(db_condition_wait_nanos); PERF_CONTEXT_OUTPUT(merge_operator_time_nanos); @@ -158,6 +498,8 @@ std::string PerfContext::ToString(bool exclude_zero_counters) const { PERF_CONTEXT_OUTPUT(bloom_memtable_miss_count); PERF_CONTEXT_OUTPUT(bloom_sst_hit_count); PERF_CONTEXT_OUTPUT(bloom_sst_miss_count); + PERF_CONTEXT_OUTPUT(key_lock_wait_time); + PERF_CONTEXT_OUTPUT(key_lock_wait_count); PERF_CONTEXT_OUTPUT(env_new_sequential_file_nanos); PERF_CONTEXT_OUTPUT(env_new_random_access_file_nanos); PERF_CONTEXT_OUTPUT(env_new_writable_file_nanos); @@ -178,8 +520,37 @@ std::string PerfContext::ToString(bool exclude_zero_counters) const { PERF_CONTEXT_OUTPUT(env_lock_file_nanos); PERF_CONTEXT_OUTPUT(env_unlock_file_nanos); PERF_CONTEXT_OUTPUT(env_new_logger_nanos); + PERF_CONTEXT_OUTPUT(get_cpu_nanos); + PERF_CONTEXT_OUTPUT(iter_next_cpu_nanos); + PERF_CONTEXT_OUTPUT(iter_prev_cpu_nanos); + PERF_CONTEXT_OUTPUT(iter_seek_cpu_nanos); + PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(bloom_filter_useful); + PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(bloom_filter_full_positive); + PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(bloom_filter_full_true_positive); + PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(block_cache_hit_count); + PERF_CONTEXT_BY_LEVEL_OUTPUT_ONE_COUNTER(block_cache_miss_count); return ss.str(); #endif } +void PerfContext::EnablePerLevelPerfContext() { + if (level_to_perf_context == nullptr) { + level_to_perf_context = new std::map(); + } + per_level_perf_context_enabled = true; +} + +void PerfContext::DisablePerLevelPerfContext(){ + per_level_perf_context_enabled = false; +} + +void PerfContext::ClearPerLevelPerfContext(){ + if (level_to_perf_context != nullptr) { + level_to_perf_context->clear(); + delete level_to_perf_context; + level_to_perf_context = nullptr; + } + per_level_perf_context_enabled = false; +} + } diff --git a/thirdparty/rocksdb/monitoring/perf_context_imp.h b/thirdparty/rocksdb/monitoring/perf_context_imp.h index 421a8cea15..e0ff8afc58 100644 --- a/thirdparty/rocksdb/monitoring/perf_context_imp.h +++ b/thirdparty/rocksdb/monitoring/perf_context_imp.h @@ -9,6 +9,16 @@ #include "util/stop_watch.h" namespace rocksdb { +#if defined(NPERF_CONTEXT) || !defined(ROCKSDB_SUPPORT_THREAD_LOCAL) +extern PerfContext perf_context; +#else +#if defined(OS_SOLARIS) +extern __thread PerfContext perf_context_; +#define perf_context (*get_perf_context()) +#else +extern thread_local PerfContext perf_context; +#endif +#endif #if defined(NPERF_CONTEXT) @@ -27,14 +37,29 @@ namespace rocksdb { #define PERF_TIMER_START(metric) perf_step_timer_##metric.Start(); // Declare and set start time of the timer -#define PERF_TIMER_GUARD(metric) \ - PerfStepTimer perf_step_timer_##metric(&(get_perf_context()->metric)); \ +#define PERF_TIMER_GUARD(metric) \ + PerfStepTimer perf_step_timer_##metric(&(perf_context.metric)); \ + perf_step_timer_##metric.Start(); + +// Declare and set start time of the timer +#define PERF_TIMER_GUARD_WITH_ENV(metric, env) \ + PerfStepTimer perf_step_timer_##metric(&(perf_context.metric), env); \ + perf_step_timer_##metric.Start(); + +// Declare and set start time of the timer +#define PERF_CPU_TIMER_GUARD(metric, env) \ + PerfStepTimer perf_step_timer_##metric( \ + &(perf_context.metric), env, true, \ + PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); \ perf_step_timer_##metric.Start(); -#define PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(metric, condition) \ - PerfStepTimer perf_step_timer_##metric(&(get_perf_context()->metric), true); \ - if ((condition)) { \ - perf_step_timer_##metric.Start(); \ +#define PERF_CONDITIONAL_TIMER_FOR_MUTEX_GUARD(metric, condition, stats, \ + ticker_type) \ + PerfStepTimer perf_step_timer_##metric(&(perf_context.metric), nullptr, \ + false, PerfLevel::kEnableTime, stats, \ + ticker_type); \ + if (condition) { \ + perf_step_timer_##metric.Start(); \ } // Update metric with time elapsed since last START. start time is reset @@ -44,9 +69,25 @@ namespace rocksdb { // Increase metric value #define PERF_COUNTER_ADD(metric, value) \ if (perf_level >= PerfLevel::kEnableCount) { \ - get_perf_context()->metric += value; \ + perf_context.metric += value; \ } +// Increase metric value +#define PERF_COUNTER_BY_LEVEL_ADD(metric, value, level) \ + if (perf_level >= PerfLevel::kEnableCount && \ + perf_context.per_level_perf_context_enabled && \ + perf_context.level_to_perf_context) { \ + if ((*(perf_context.level_to_perf_context)).find(level) != \ + (*(perf_context.level_to_perf_context)).end()) { \ + (*(perf_context.level_to_perf_context))[level].metric += value; \ + } \ + else { \ + PerfContextByLevel empty_context; \ + (*(perf_context.level_to_perf_context))[level] = empty_context; \ + (*(perf_context.level_to_perf_context))[level].metric += value; \ + } \ + } \ + #endif } diff --git a/thirdparty/rocksdb/monitoring/perf_step_timer.h b/thirdparty/rocksdb/monitoring/perf_step_timer.h index 4cb48b1256..6501bd54ab 100644 --- a/thirdparty/rocksdb/monitoring/perf_step_timer.h +++ b/thirdparty/rocksdb/monitoring/perf_step_timer.h @@ -12,26 +12,41 @@ namespace rocksdb { class PerfStepTimer { public: - explicit PerfStepTimer(uint64_t* metric, bool for_mutex = false) - : enabled_(perf_level >= PerfLevel::kEnableTime || - (!for_mutex && perf_level >= kEnableTimeExceptForMutex)), - env_(enabled_ ? Env::Default() : nullptr), + explicit PerfStepTimer( + uint64_t* metric, Env* env = nullptr, bool use_cpu_time = false, + PerfLevel enable_level = PerfLevel::kEnableTimeExceptForMutex, + Statistics* statistics = nullptr, uint32_t ticker_type = 0) + : perf_counter_enabled_(perf_level >= enable_level), + use_cpu_time_(use_cpu_time), + env_((perf_counter_enabled_ || statistics != nullptr) + ? ((env != nullptr) ? env : Env::Default()) + : nullptr), start_(0), - metric_(metric) {} + metric_(metric), + statistics_(statistics), + ticker_type_(ticker_type) {} ~PerfStepTimer() { Stop(); } void Start() { - if (enabled_) { - start_ = env_->NowNanos(); + if (perf_counter_enabled_ || statistics_ != nullptr) { + start_ = time_now(); + } + } + + uint64_t time_now() { + if (!use_cpu_time_) { + return env_->NowNanos(); + } else { + return env_->NowCPUNanos(); } } void Measure() { if (start_) { - uint64_t now = env_->NowNanos(); + uint64_t now = time_now(); *metric_ += now - start_; start_ = now; } @@ -39,16 +54,26 @@ class PerfStepTimer { void Stop() { if (start_) { - *metric_ += env_->NowNanos() - start_; + uint64_t duration = time_now() - start_; + if (perf_counter_enabled_) { + *metric_ += duration; + } + + if (statistics_ != nullptr) { + RecordTick(statistics_, ticker_type_, duration); + } start_ = 0; } } private: - const bool enabled_; + const bool perf_counter_enabled_; + const bool use_cpu_time_; Env* const env_; uint64_t start_; uint64_t* metric_; + Statistics* statistics_; + uint32_t ticker_type_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/monitoring/statistics.cc b/thirdparty/rocksdb/monitoring/statistics.cc index 9387043127..adb8cbfed8 100644 --- a/thirdparty/rocksdb/monitoring/statistics.cc +++ b/thirdparty/rocksdb/monitoring/statistics.cc @@ -17,13 +17,225 @@ namespace rocksdb { +// The order of items listed in Tickers should be the same as +// the order listed in TickersNameMap +const std::vector> TickersNameMap = { + {BLOCK_CACHE_MISS, "rocksdb.block.cache.miss"}, + {BLOCK_CACHE_HIT, "rocksdb.block.cache.hit"}, + {BLOCK_CACHE_ADD, "rocksdb.block.cache.add"}, + {BLOCK_CACHE_ADD_FAILURES, "rocksdb.block.cache.add.failures"}, + {BLOCK_CACHE_INDEX_MISS, "rocksdb.block.cache.index.miss"}, + {BLOCK_CACHE_INDEX_HIT, "rocksdb.block.cache.index.hit"}, + {BLOCK_CACHE_INDEX_ADD, "rocksdb.block.cache.index.add"}, + {BLOCK_CACHE_INDEX_BYTES_INSERT, "rocksdb.block.cache.index.bytes.insert"}, + {BLOCK_CACHE_INDEX_BYTES_EVICT, "rocksdb.block.cache.index.bytes.evict"}, + {BLOCK_CACHE_FILTER_MISS, "rocksdb.block.cache.filter.miss"}, + {BLOCK_CACHE_FILTER_HIT, "rocksdb.block.cache.filter.hit"}, + {BLOCK_CACHE_FILTER_ADD, "rocksdb.block.cache.filter.add"}, + {BLOCK_CACHE_FILTER_BYTES_INSERT, + "rocksdb.block.cache.filter.bytes.insert"}, + {BLOCK_CACHE_FILTER_BYTES_EVICT, "rocksdb.block.cache.filter.bytes.evict"}, + {BLOCK_CACHE_DATA_MISS, "rocksdb.block.cache.data.miss"}, + {BLOCK_CACHE_DATA_HIT, "rocksdb.block.cache.data.hit"}, + {BLOCK_CACHE_DATA_ADD, "rocksdb.block.cache.data.add"}, + {BLOCK_CACHE_DATA_BYTES_INSERT, "rocksdb.block.cache.data.bytes.insert"}, + {BLOCK_CACHE_BYTES_READ, "rocksdb.block.cache.bytes.read"}, + {BLOCK_CACHE_BYTES_WRITE, "rocksdb.block.cache.bytes.write"}, + {BLOOM_FILTER_USEFUL, "rocksdb.bloom.filter.useful"}, + {BLOOM_FILTER_FULL_POSITIVE, "rocksdb.bloom.filter.full.positive"}, + {BLOOM_FILTER_FULL_TRUE_POSITIVE, + "rocksdb.bloom.filter.full.true.positive"}, + {PERSISTENT_CACHE_HIT, "rocksdb.persistent.cache.hit"}, + {PERSISTENT_CACHE_MISS, "rocksdb.persistent.cache.miss"}, + {SIM_BLOCK_CACHE_HIT, "rocksdb.sim.block.cache.hit"}, + {SIM_BLOCK_CACHE_MISS, "rocksdb.sim.block.cache.miss"}, + {MEMTABLE_HIT, "rocksdb.memtable.hit"}, + {MEMTABLE_MISS, "rocksdb.memtable.miss"}, + {GET_HIT_L0, "rocksdb.l0.hit"}, + {GET_HIT_L1, "rocksdb.l1.hit"}, + {GET_HIT_L2_AND_UP, "rocksdb.l2andup.hit"}, + {COMPACTION_KEY_DROP_NEWER_ENTRY, "rocksdb.compaction.key.drop.new"}, + {COMPACTION_KEY_DROP_OBSOLETE, "rocksdb.compaction.key.drop.obsolete"}, + {COMPACTION_KEY_DROP_RANGE_DEL, "rocksdb.compaction.key.drop.range_del"}, + {COMPACTION_KEY_DROP_USER, "rocksdb.compaction.key.drop.user"}, + {COMPACTION_RANGE_DEL_DROP_OBSOLETE, + "rocksdb.compaction.range_del.drop.obsolete"}, + {COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE, + "rocksdb.compaction.optimized.del.drop.obsolete"}, + {COMPACTION_CANCELLED, "rocksdb.compaction.cancelled"}, + {NUMBER_KEYS_WRITTEN, "rocksdb.number.keys.written"}, + {NUMBER_KEYS_READ, "rocksdb.number.keys.read"}, + {NUMBER_KEYS_UPDATED, "rocksdb.number.keys.updated"}, + {BYTES_WRITTEN, "rocksdb.bytes.written"}, + {BYTES_READ, "rocksdb.bytes.read"}, + {NUMBER_DB_SEEK, "rocksdb.number.db.seek"}, + {NUMBER_DB_NEXT, "rocksdb.number.db.next"}, + {NUMBER_DB_PREV, "rocksdb.number.db.prev"}, + {NUMBER_DB_SEEK_FOUND, "rocksdb.number.db.seek.found"}, + {NUMBER_DB_NEXT_FOUND, "rocksdb.number.db.next.found"}, + {NUMBER_DB_PREV_FOUND, "rocksdb.number.db.prev.found"}, + {ITER_BYTES_READ, "rocksdb.db.iter.bytes.read"}, + {NO_FILE_CLOSES, "rocksdb.no.file.closes"}, + {NO_FILE_OPENS, "rocksdb.no.file.opens"}, + {NO_FILE_ERRORS, "rocksdb.no.file.errors"}, + {STALL_L0_SLOWDOWN_MICROS, "rocksdb.l0.slowdown.micros"}, + {STALL_MEMTABLE_COMPACTION_MICROS, "rocksdb.memtable.compaction.micros"}, + {STALL_L0_NUM_FILES_MICROS, "rocksdb.l0.num.files.stall.micros"}, + {STALL_MICROS, "rocksdb.stall.micros"}, + {DB_MUTEX_WAIT_MICROS, "rocksdb.db.mutex.wait.micros"}, + {RATE_LIMIT_DELAY_MILLIS, "rocksdb.rate.limit.delay.millis"}, + {NO_ITERATORS, "rocksdb.num.iterators"}, + {NUMBER_MULTIGET_CALLS, "rocksdb.number.multiget.get"}, + {NUMBER_MULTIGET_KEYS_READ, "rocksdb.number.multiget.keys.read"}, + {NUMBER_MULTIGET_BYTES_READ, "rocksdb.number.multiget.bytes.read"}, + {NUMBER_FILTERED_DELETES, "rocksdb.number.deletes.filtered"}, + {NUMBER_MERGE_FAILURES, "rocksdb.number.merge.failures"}, + {BLOOM_FILTER_PREFIX_CHECKED, "rocksdb.bloom.filter.prefix.checked"}, + {BLOOM_FILTER_PREFIX_USEFUL, "rocksdb.bloom.filter.prefix.useful"}, + {NUMBER_OF_RESEEKS_IN_ITERATION, "rocksdb.number.reseeks.iteration"}, + {GET_UPDATES_SINCE_CALLS, "rocksdb.getupdatessince.calls"}, + {BLOCK_CACHE_COMPRESSED_MISS, "rocksdb.block.cachecompressed.miss"}, + {BLOCK_CACHE_COMPRESSED_HIT, "rocksdb.block.cachecompressed.hit"}, + {BLOCK_CACHE_COMPRESSED_ADD, "rocksdb.block.cachecompressed.add"}, + {BLOCK_CACHE_COMPRESSED_ADD_FAILURES, + "rocksdb.block.cachecompressed.add.failures"}, + {WAL_FILE_SYNCED, "rocksdb.wal.synced"}, + {WAL_FILE_BYTES, "rocksdb.wal.bytes"}, + {WRITE_DONE_BY_SELF, "rocksdb.write.self"}, + {WRITE_DONE_BY_OTHER, "rocksdb.write.other"}, + {WRITE_TIMEDOUT, "rocksdb.write.timeout"}, + {WRITE_WITH_WAL, "rocksdb.write.wal"}, + {COMPACT_READ_BYTES, "rocksdb.compact.read.bytes"}, + {COMPACT_WRITE_BYTES, "rocksdb.compact.write.bytes"}, + {FLUSH_WRITE_BYTES, "rocksdb.flush.write.bytes"}, + {NUMBER_DIRECT_LOAD_TABLE_PROPERTIES, + "rocksdb.number.direct.load.table.properties"}, + {NUMBER_SUPERVERSION_ACQUIRES, "rocksdb.number.superversion_acquires"}, + {NUMBER_SUPERVERSION_RELEASES, "rocksdb.number.superversion_releases"}, + {NUMBER_SUPERVERSION_CLEANUPS, "rocksdb.number.superversion_cleanups"}, + {NUMBER_BLOCK_COMPRESSED, "rocksdb.number.block.compressed"}, + {NUMBER_BLOCK_DECOMPRESSED, "rocksdb.number.block.decompressed"}, + {NUMBER_BLOCK_NOT_COMPRESSED, "rocksdb.number.block.not_compressed"}, + {MERGE_OPERATION_TOTAL_TIME, "rocksdb.merge.operation.time.nanos"}, + {FILTER_OPERATION_TOTAL_TIME, "rocksdb.filter.operation.time.nanos"}, + {ROW_CACHE_HIT, "rocksdb.row.cache.hit"}, + {ROW_CACHE_MISS, "rocksdb.row.cache.miss"}, + {READ_AMP_ESTIMATE_USEFUL_BYTES, "rocksdb.read.amp.estimate.useful.bytes"}, + {READ_AMP_TOTAL_READ_BYTES, "rocksdb.read.amp.total.read.bytes"}, + {NUMBER_RATE_LIMITER_DRAINS, "rocksdb.number.rate_limiter.drains"}, + {NUMBER_ITER_SKIP, "rocksdb.number.iter.skip"}, + {BLOB_DB_NUM_PUT, "rocksdb.blobdb.num.put"}, + {BLOB_DB_NUM_WRITE, "rocksdb.blobdb.num.write"}, + {BLOB_DB_NUM_GET, "rocksdb.blobdb.num.get"}, + {BLOB_DB_NUM_MULTIGET, "rocksdb.blobdb.num.multiget"}, + {BLOB_DB_NUM_SEEK, "rocksdb.blobdb.num.seek"}, + {BLOB_DB_NUM_NEXT, "rocksdb.blobdb.num.next"}, + {BLOB_DB_NUM_PREV, "rocksdb.blobdb.num.prev"}, + {BLOB_DB_NUM_KEYS_WRITTEN, "rocksdb.blobdb.num.keys.written"}, + {BLOB_DB_NUM_KEYS_READ, "rocksdb.blobdb.num.keys.read"}, + {BLOB_DB_BYTES_WRITTEN, "rocksdb.blobdb.bytes.written"}, + {BLOB_DB_BYTES_READ, "rocksdb.blobdb.bytes.read"}, + {BLOB_DB_WRITE_INLINED, "rocksdb.blobdb.write.inlined"}, + {BLOB_DB_WRITE_INLINED_TTL, "rocksdb.blobdb.write.inlined.ttl"}, + {BLOB_DB_WRITE_BLOB, "rocksdb.blobdb.write.blob"}, + {BLOB_DB_WRITE_BLOB_TTL, "rocksdb.blobdb.write.blob.ttl"}, + {BLOB_DB_BLOB_FILE_BYTES_WRITTEN, "rocksdb.blobdb.blob.file.bytes.written"}, + {BLOB_DB_BLOB_FILE_BYTES_READ, "rocksdb.blobdb.blob.file.bytes.read"}, + {BLOB_DB_BLOB_FILE_SYNCED, "rocksdb.blobdb.blob.file.synced"}, + {BLOB_DB_BLOB_INDEX_EXPIRED_COUNT, + "rocksdb.blobdb.blob.index.expired.count"}, + {BLOB_DB_BLOB_INDEX_EXPIRED_SIZE, "rocksdb.blobdb.blob.index.expired.size"}, + {BLOB_DB_BLOB_INDEX_EVICTED_COUNT, + "rocksdb.blobdb.blob.index.evicted.count"}, + {BLOB_DB_BLOB_INDEX_EVICTED_SIZE, "rocksdb.blobdb.blob.index.evicted.size"}, + {BLOB_DB_GC_NUM_FILES, "rocksdb.blobdb.gc.num.files"}, + {BLOB_DB_GC_NUM_NEW_FILES, "rocksdb.blobdb.gc.num.new.files"}, + {BLOB_DB_GC_FAILURES, "rocksdb.blobdb.gc.failures"}, + {BLOB_DB_GC_NUM_KEYS_OVERWRITTEN, "rocksdb.blobdb.gc.num.keys.overwritten"}, + {BLOB_DB_GC_NUM_KEYS_EXPIRED, "rocksdb.blobdb.gc.num.keys.expired"}, + {BLOB_DB_GC_NUM_KEYS_RELOCATED, "rocksdb.blobdb.gc.num.keys.relocated"}, + {BLOB_DB_GC_BYTES_OVERWRITTEN, "rocksdb.blobdb.gc.bytes.overwritten"}, + {BLOB_DB_GC_BYTES_EXPIRED, "rocksdb.blobdb.gc.bytes.expired"}, + {BLOB_DB_GC_BYTES_RELOCATED, "rocksdb.blobdb.gc.bytes.relocated"}, + {BLOB_DB_FIFO_NUM_FILES_EVICTED, "rocksdb.blobdb.fifo.num.files.evicted"}, + {BLOB_DB_FIFO_NUM_KEYS_EVICTED, "rocksdb.blobdb.fifo.num.keys.evicted"}, + {BLOB_DB_FIFO_BYTES_EVICTED, "rocksdb.blobdb.fifo.bytes.evicted"}, + {TXN_PREPARE_MUTEX_OVERHEAD, "rocksdb.txn.overhead.mutex.prepare"}, + {TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD, + "rocksdb.txn.overhead.mutex.old.commit.map"}, + {TXN_DUPLICATE_KEY_OVERHEAD, "rocksdb.txn.overhead.duplicate.key"}, + {TXN_SNAPSHOT_MUTEX_OVERHEAD, "rocksdb.txn.overhead.mutex.snapshot"}, + {NUMBER_MULTIGET_KEYS_FOUND, "rocksdb.number.multiget.keys.found"}, + {NO_ITERATOR_CREATED, "rocksdb.num.iterator.created"}, + {NO_ITERATOR_DELETED, "rocksdb.num.iterator.deleted"}, + {BLOCK_CACHE_COMPRESSION_DICT_MISS, + "rocksdb.block.cache.compression.dict.miss"}, + {BLOCK_CACHE_COMPRESSION_DICT_HIT, + "rocksdb.block.cache.compression.dict.hit"}, + {BLOCK_CACHE_COMPRESSION_DICT_ADD, + "rocksdb.block.cache.compression.dict.add"}, + {BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT, + "rocksdb.block.cache.compression.dict.bytes.insert"}, + {BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT, + "rocksdb.block.cache.compression.dict.bytes.evict"}, +}; + +const std::vector> HistogramsNameMap = { + {DB_GET, "rocksdb.db.get.micros"}, + {DB_WRITE, "rocksdb.db.write.micros"}, + {COMPACTION_TIME, "rocksdb.compaction.times.micros"}, + {COMPACTION_CPU_TIME, "rocksdb.compaction.times.cpu_micros"}, + {SUBCOMPACTION_SETUP_TIME, "rocksdb.subcompaction.setup.times.micros"}, + {TABLE_SYNC_MICROS, "rocksdb.table.sync.micros"}, + {COMPACTION_OUTFILE_SYNC_MICROS, "rocksdb.compaction.outfile.sync.micros"}, + {WAL_FILE_SYNC_MICROS, "rocksdb.wal.file.sync.micros"}, + {MANIFEST_FILE_SYNC_MICROS, "rocksdb.manifest.file.sync.micros"}, + {TABLE_OPEN_IO_MICROS, "rocksdb.table.open.io.micros"}, + {DB_MULTIGET, "rocksdb.db.multiget.micros"}, + {READ_BLOCK_COMPACTION_MICROS, "rocksdb.read.block.compaction.micros"}, + {READ_BLOCK_GET_MICROS, "rocksdb.read.block.get.micros"}, + {WRITE_RAW_BLOCK_MICROS, "rocksdb.write.raw.block.micros"}, + {STALL_L0_SLOWDOWN_COUNT, "rocksdb.l0.slowdown.count"}, + {STALL_MEMTABLE_COMPACTION_COUNT, "rocksdb.memtable.compaction.count"}, + {STALL_L0_NUM_FILES_COUNT, "rocksdb.num.files.stall.count"}, + {HARD_RATE_LIMIT_DELAY_COUNT, "rocksdb.hard.rate.limit.delay.count"}, + {SOFT_RATE_LIMIT_DELAY_COUNT, "rocksdb.soft.rate.limit.delay.count"}, + {NUM_FILES_IN_SINGLE_COMPACTION, "rocksdb.numfiles.in.singlecompaction"}, + {DB_SEEK, "rocksdb.db.seek.micros"}, + {WRITE_STALL, "rocksdb.db.write.stall"}, + {SST_READ_MICROS, "rocksdb.sst.read.micros"}, + {NUM_SUBCOMPACTIONS_SCHEDULED, "rocksdb.num.subcompactions.scheduled"}, + {BYTES_PER_READ, "rocksdb.bytes.per.read"}, + {BYTES_PER_WRITE, "rocksdb.bytes.per.write"}, + {BYTES_PER_MULTIGET, "rocksdb.bytes.per.multiget"}, + {BYTES_COMPRESSED, "rocksdb.bytes.compressed"}, + {BYTES_DECOMPRESSED, "rocksdb.bytes.decompressed"}, + {COMPRESSION_TIMES_NANOS, "rocksdb.compression.times.nanos"}, + {DECOMPRESSION_TIMES_NANOS, "rocksdb.decompression.times.nanos"}, + {READ_NUM_MERGE_OPERANDS, "rocksdb.read.num.merge_operands"}, + {BLOB_DB_KEY_SIZE, "rocksdb.blobdb.key.size"}, + {BLOB_DB_VALUE_SIZE, "rocksdb.blobdb.value.size"}, + {BLOB_DB_WRITE_MICROS, "rocksdb.blobdb.write.micros"}, + {BLOB_DB_GET_MICROS, "rocksdb.blobdb.get.micros"}, + {BLOB_DB_MULTIGET_MICROS, "rocksdb.blobdb.multiget.micros"}, + {BLOB_DB_SEEK_MICROS, "rocksdb.blobdb.seek.micros"}, + {BLOB_DB_NEXT_MICROS, "rocksdb.blobdb.next.micros"}, + {BLOB_DB_PREV_MICROS, "rocksdb.blobdb.prev.micros"}, + {BLOB_DB_BLOB_FILE_WRITE_MICROS, "rocksdb.blobdb.blob.file.write.micros"}, + {BLOB_DB_BLOB_FILE_READ_MICROS, "rocksdb.blobdb.blob.file.read.micros"}, + {BLOB_DB_BLOB_FILE_SYNC_MICROS, "rocksdb.blobdb.blob.file.sync.micros"}, + {BLOB_DB_GC_MICROS, "rocksdb.blobdb.gc.micros"}, + {BLOB_DB_COMPRESSION_MICROS, "rocksdb.blobdb.compression.micros"}, + {BLOB_DB_DECOMPRESSION_MICROS, "rocksdb.blobdb.decompression.micros"}, + {FLUSH_TIME, "rocksdb.db.flush.micros"}, +}; + std::shared_ptr CreateDBStatistics() { - return std::make_shared(nullptr, false); + return std::make_shared(nullptr); } -StatisticsImpl::StatisticsImpl(std::shared_ptr stats, - bool enable_internal_stats) - : stats_(std::move(stats)), enable_internal_stats_(enable_internal_stats) {} +StatisticsImpl::StatisticsImpl(std::shared_ptr stats) + : stats_(std::move(stats)) {} StatisticsImpl::~StatisticsImpl() {} @@ -33,10 +245,7 @@ uint64_t StatisticsImpl::getTickerCount(uint32_t tickerType) const { } uint64_t StatisticsImpl::getTickerCountLocked(uint32_t tickerType) const { - assert( - enable_internal_stats_ ? - tickerType < INTERNAL_TICKER_ENUM_MAX : - tickerType < TICKER_ENUM_MAX); + assert(tickerType < TICKER_ENUM_MAX); uint64_t res = 0; for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) { res += per_core_stats_.AccessAtCore(core_idx)->tickers_[tickerType]; @@ -52,10 +261,7 @@ void StatisticsImpl::histogramData(uint32_t histogramType, std::unique_ptr StatisticsImpl::getHistogramImplLocked( uint32_t histogramType) const { - assert( - enable_internal_stats_ ? - histogramType < INTERNAL_HISTOGRAM_ENUM_MAX : - histogramType < HISTOGRAM_ENUM_MAX); + assert(histogramType < HISTOGRAM_ENUM_MAX); std::unique_ptr res_hist(new HistogramImpl()); for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) { res_hist->Merge( @@ -80,8 +286,7 @@ void StatisticsImpl::setTickerCount(uint32_t tickerType, uint64_t count) { } void StatisticsImpl::setTickerCountLocked(uint32_t tickerType, uint64_t count) { - assert(enable_internal_stats_ ? tickerType < INTERNAL_TICKER_ENUM_MAX - : tickerType < TICKER_ENUM_MAX); + assert(tickerType < TICKER_ENUM_MAX); for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) { if (core_idx == 0) { per_core_stats_.AccessAtCore(core_idx)->tickers_[tickerType] = count; @@ -95,8 +300,7 @@ uint64_t StatisticsImpl::getAndResetTickerCount(uint32_t tickerType) { uint64_t sum = 0; { MutexLock lock(&aggregate_lock_); - assert(enable_internal_stats_ ? tickerType < INTERNAL_TICKER_ENUM_MAX - : tickerType < TICKER_ENUM_MAX); + assert(tickerType < TICKER_ENUM_MAX); for (size_t core_idx = 0; core_idx < per_core_stats_.Size(); ++core_idx) { sum += per_core_stats_.AccessAtCore(core_idx)->tickers_[tickerType].exchange( @@ -110,10 +314,7 @@ uint64_t StatisticsImpl::getAndResetTickerCount(uint32_t tickerType) { } void StatisticsImpl::recordTick(uint32_t tickerType, uint64_t count) { - assert( - enable_internal_stats_ ? - tickerType < INTERNAL_TICKER_ENUM_MAX : - tickerType < TICKER_ENUM_MAX); + assert(tickerType < TICKER_ENUM_MAX); per_core_stats_.Access()->tickers_[tickerType].fetch_add( count, std::memory_order_relaxed); if (stats_ && tickerType < TICKER_ENUM_MAX) { @@ -121,14 +322,14 @@ void StatisticsImpl::recordTick(uint32_t tickerType, uint64_t count) { } } -void StatisticsImpl::measureTime(uint32_t histogramType, uint64_t value) { - assert( - enable_internal_stats_ ? - histogramType < INTERNAL_HISTOGRAM_ENUM_MAX : - histogramType < HISTOGRAM_ENUM_MAX); +void StatisticsImpl::recordInHistogram(uint32_t histogramType, uint64_t value) { + assert(histogramType < HISTOGRAM_ENUM_MAX); + if (get_stats_level() <= StatsLevel::kExceptHistogramOrTimers) { + return; + } per_core_stats_.Access()->histograms_[histogramType].Add(value); if (stats_ && histogramType < HISTOGRAM_ENUM_MAX) { - stats_->measureTime(histogramType, value); + stats_->recordInHistogram(histogramType, value); } } @@ -157,35 +358,50 @@ std::string StatisticsImpl::ToString() const { std::string res; res.reserve(20000); for (const auto& t : TickersNameMap) { - if (t.first < TICKER_ENUM_MAX || enable_internal_stats_) { - char buffer[kTmpStrBufferSize]; - snprintf(buffer, kTmpStrBufferSize, "%s COUNT : %" PRIu64 "\n", - t.second.c_str(), getTickerCountLocked(t.first)); - res.append(buffer); - } + assert(t.first < TICKER_ENUM_MAX); + char buffer[kTmpStrBufferSize]; + snprintf(buffer, kTmpStrBufferSize, "%s COUNT : %" PRIu64 "\n", + t.second.c_str(), getTickerCountLocked(t.first)); + res.append(buffer); } for (const auto& h : HistogramsNameMap) { - if (h.first < HISTOGRAM_ENUM_MAX || enable_internal_stats_) { - char buffer[kTmpStrBufferSize]; - HistogramData hData; - getHistogramImplLocked(h.first)->Data(&hData); - snprintf( - buffer, kTmpStrBufferSize, - "%s statistics Percentiles :=> 50 : %f 95 : %f 99 : %f 100 : %f\n", - h.second.c_str(), hData.median, hData.percentile95, - hData.percentile99, hData.max); - res.append(buffer); + assert(h.first < HISTOGRAM_ENUM_MAX); + char buffer[kTmpStrBufferSize]; + HistogramData hData; + getHistogramImplLocked(h.first)->Data(&hData); + // don't handle failures - buffer should always be big enough and arguments + // should be provided correctly + int ret = + snprintf(buffer, kTmpStrBufferSize, + "%s P50 : %f P95 : %f P99 : %f P100 : %f COUNT : %" PRIu64 + " SUM : %" PRIu64 "\n", + h.second.c_str(), hData.median, hData.percentile95, + hData.percentile99, hData.max, hData.count, hData.sum); + if (ret < 0 || ret >= kTmpStrBufferSize) { + assert(false); + continue; } + res.append(buffer); } res.shrink_to_fit(); return res; } -bool StatisticsImpl::HistEnabledForType(uint32_t type) const { - if (LIKELY(!enable_internal_stats_)) { - return type < HISTOGRAM_ENUM_MAX; +bool StatisticsImpl::getTickerMap( + std::map* stats_map) const { + assert(stats_map); + if (!stats_map) return false; + stats_map->clear(); + MutexLock lock(&aggregate_lock_); + for (const auto& t : TickersNameMap) { + assert(t.first < TICKER_ENUM_MAX); + (*stats_map)[t.second.c_str()] = getTickerCountLocked(t.first); } return true; } +bool StatisticsImpl::HistEnabledForType(uint32_t type) const { + return type < HISTOGRAM_ENUM_MAX; +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/monitoring/statistics.h b/thirdparty/rocksdb/monitoring/statistics.h index 6e915215de..952bf8cb41 100644 --- a/thirdparty/rocksdb/monitoring/statistics.h +++ b/thirdparty/rocksdb/monitoring/statistics.h @@ -6,9 +6,10 @@ #pragma once #include "rocksdb/statistics.h" -#include #include +#include #include +#include #include "monitoring/histogram.h" #include "port/likely.h" @@ -22,6 +23,11 @@ #define ROCKSDB_FIELD_UNUSED #endif // __clang__ +#ifndef STRINGIFY +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#endif + namespace rocksdb { enum TickersInternal : uint32_t { @@ -34,11 +40,9 @@ enum HistogramsInternal : uint32_t { INTERNAL_HISTOGRAM_ENUM_MAX }; - class StatisticsImpl : public Statistics { public: - StatisticsImpl(std::shared_ptr stats, - bool enable_internal_stats); + StatisticsImpl(std::shared_ptr stats); virtual ~StatisticsImpl(); virtual uint64_t getTickerCount(uint32_t ticker_type) const override; @@ -49,17 +53,24 @@ class StatisticsImpl : public Statistics { virtual void setTickerCount(uint32_t ticker_type, uint64_t count) override; virtual uint64_t getAndResetTickerCount(uint32_t ticker_type) override; virtual void recordTick(uint32_t ticker_type, uint64_t count) override; - virtual void measureTime(uint32_t histogram_type, uint64_t value) override; + // The function is implemented for now for backward compatibility reason. + // In case a user explictly calls it, for example, they may have a wrapped + // Statistics object, passing the call to recordTick() into here, nothing + // will break. + void measureTime(uint32_t histogramType, uint64_t time) override { + recordInHistogram(histogramType, time); + } + virtual void recordInHistogram(uint32_t histogram_type, + uint64_t value) override; virtual Status Reset() override; virtual std::string ToString() const override; + virtual bool getTickerMap(std::map*) const override; virtual bool HistEnabledForType(uint32_t type) const override; private: // If non-nullptr, forwards updates to the object pointed to by `stats_`. std::shared_ptr stats_; - // TODO(ajkr): clean this up since there are no internal stats anymore - bool enable_internal_stats_; // Synchronizes anything that operates across other cores' local data, // such that operations like Reset() can be performed atomically. mutable port::Mutex aggregate_lock_; @@ -69,18 +80,23 @@ class StatisticsImpl : public Statistics { // cores can never share the same cache line. // // Alignment attributes expand to nothing depending on the platform - struct StatisticsData { + struct ALIGN_AS(CACHE_LINE_SIZE) StatisticsData { std::atomic_uint_fast64_t tickers_[INTERNAL_TICKER_ENUM_MAX] = {{0}}; HistogramImpl histograms_[INTERNAL_HISTOGRAM_ENUM_MAX]; +#ifndef HAVE_ALIGNED_NEW char padding[(CACHE_LINE_SIZE - (INTERNAL_TICKER_ENUM_MAX * sizeof(std::atomic_uint_fast64_t) + INTERNAL_HISTOGRAM_ENUM_MAX * sizeof(HistogramImpl)) % - CACHE_LINE_SIZE) % - CACHE_LINE_SIZE] ROCKSDB_FIELD_UNUSED; + CACHE_LINE_SIZE)] ROCKSDB_FIELD_UNUSED; +#endif + void *operator new(size_t s) { return port::cacheline_aligned_alloc(s); } + void *operator new[](size_t s) { return port::cacheline_aligned_alloc(s); } + void operator delete(void *p) { port::cacheline_aligned_free(p); } + void operator delete[](void *p) { port::cacheline_aligned_free(p); } }; - static_assert(sizeof(StatisticsData) % 64 == 0, "Expected 64-byte aligned"); + static_assert(sizeof(StatisticsData) % CACHE_LINE_SIZE == 0, "Expected " TOSTRING(CACHE_LINE_SIZE) "-byte aligned"); CoreLocalArray per_core_stats_; @@ -91,10 +107,17 @@ class StatisticsImpl : public Statistics { }; // Utility functions -inline void MeasureTime(Statistics* statistics, uint32_t histogram_type, - uint64_t value) { +inline void RecordInHistogram(Statistics* statistics, uint32_t histogram_type, + uint64_t value) { + if (statistics) { + statistics->recordInHistogram(histogram_type, value); + } +} + +inline void RecordTimeToHistogram(Statistics* statistics, + uint32_t histogram_type, uint64_t value) { if (statistics) { - statistics->measureTime(histogram_type, value); + statistics->reportTimeToHistogram(histogram_type, value); } } diff --git a/thirdparty/rocksdb/monitoring/statistics_test.cc b/thirdparty/rocksdb/monitoring/statistics_test.cc index 43aacde9c1..a77022bfb3 100644 --- a/thirdparty/rocksdb/monitoring/statistics_test.cc +++ b/thirdparty/rocksdb/monitoring/statistics_test.cc @@ -16,7 +16,7 @@ class StatisticsTest : public testing::Test {}; // Sanity check to make sure that contents and order of TickersNameMap // match Tickers enum -TEST_F(StatisticsTest, Sanity) { +TEST_F(StatisticsTest, SanityTickers) { EXPECT_EQ(static_cast(Tickers::TICKER_ENUM_MAX), TickersNameMap.size()); @@ -26,6 +26,18 @@ TEST_F(StatisticsTest, Sanity) { } } +// Sanity check to make sure that contents and order of HistogramsNameMap +// match Tickers enum +TEST_F(StatisticsTest, SanityHistograms) { + EXPECT_EQ(static_cast(Histograms::HISTOGRAM_ENUM_MAX), + HistogramsNameMap.size()); + + for (uint32_t h = 0; h < Histograms::HISTOGRAM_ENUM_MAX; h++) { + auto pair = HistogramsNameMap[static_cast(h)]; + ASSERT_EQ(pair.first, h) << "Miss match at " << pair.second; + } +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/monitoring/thread_status_impl.cc b/thirdparty/rocksdb/monitoring/thread_status_impl.cc index e263ce661e..f4531ce75c 100644 --- a/thirdparty/rocksdb/monitoring/thread_status_impl.cc +++ b/thirdparty/rocksdb/monitoring/thread_status_impl.cc @@ -14,14 +14,21 @@ namespace rocksdb { #ifdef ROCKSDB_USING_THREAD_STATUS -const std::string& ThreadStatus::GetThreadTypeName( +std::string ThreadStatus::GetThreadTypeName( ThreadStatus::ThreadType thread_type) { - static std::string thread_type_names[NUM_THREAD_TYPES + 1] = { - "High Pri", "Low Pri", "User", "Unknown"}; - if (thread_type < 0 || thread_type >= NUM_THREAD_TYPES) { - return thread_type_names[NUM_THREAD_TYPES]; // "Unknown" + switch (thread_type) { + case ThreadStatus::ThreadType::HIGH_PRIORITY: + return "High Pri"; + case ThreadStatus::ThreadType::LOW_PRIORITY: + return "Low Pri"; + case ThreadStatus::ThreadType::USER: + return "User"; + case ThreadStatus::ThreadType::BOTTOM_PRIORITY: + return "Bottom Pri"; + case ThreadStatus::ThreadType::NUM_THREAD_TYPES: + assert(false); } - return thread_type_names[thread_type]; + return "Unknown"; } const std::string& ThreadStatus::GetOperationName( @@ -77,10 +84,8 @@ const std::string& ThreadStatus::GetOperationPropertyName( } } -std::map - ThreadStatus::InterpretOperationProperties( - ThreadStatus::OperationType op_type, - const uint64_t* op_properties) { +std::map ThreadStatus::InterpretOperationProperties( + ThreadStatus::OperationType op_type, const uint64_t* op_properties) { int num_properties; switch (op_type) { case OP_COMPACTION: @@ -95,20 +100,14 @@ std::map std::map property_map; for (int i = 0; i < num_properties; ++i) { - if (op_type == OP_COMPACTION && - i == COMPACTION_INPUT_OUTPUT_LEVEL) { - property_map.insert( - {"BaseInputLevel", op_properties[i] >> 32}); + if (op_type == OP_COMPACTION && i == COMPACTION_INPUT_OUTPUT_LEVEL) { + property_map.insert({"BaseInputLevel", op_properties[i] >> 32}); property_map.insert( {"OutputLevel", op_properties[i] % (uint64_t(1) << 32U)}); - } else if (op_type == OP_COMPACTION && - i == COMPACTION_PROP_FLAGS) { - property_map.insert( - {"IsManual", ((op_properties[i] & 2) >> 1)}); - property_map.insert( - {"IsDeletion", ((op_properties[i] & 4) >> 2)}); - property_map.insert( - {"IsTrivialMove", ((op_properties[i] & 8) >> 3)}); + } else if (op_type == OP_COMPACTION && i == COMPACTION_PROP_FLAGS) { + property_map.insert({"IsManual", ((op_properties[i] & 2) >> 1)}); + property_map.insert({"IsDeletion", ((op_properties[i] & 4) >> 2)}); + property_map.insert({"IsTrivialMove", ((op_properties[i] & 8) >> 3)}); } else { property_map.insert( {GetOperationPropertyName(op_type, i), op_properties[i]}); @@ -117,49 +116,46 @@ std::map return property_map; } - #else -const std::string& ThreadStatus::GetThreadTypeName( - ThreadStatus::ThreadType thread_type) { +std::string ThreadStatus::GetThreadTypeName( + ThreadStatus::ThreadType /*thread_type*/) { static std::string dummy_str = ""; return dummy_str; } const std::string& ThreadStatus::GetOperationName( - ThreadStatus::OperationType op_type) { + ThreadStatus::OperationType /*op_type*/) { static std::string dummy_str = ""; return dummy_str; } const std::string& ThreadStatus::GetOperationStageName( - ThreadStatus::OperationStage stage) { + ThreadStatus::OperationStage /*stage*/) { static std::string dummy_str = ""; return dummy_str; } const std::string& ThreadStatus::GetStateName( - ThreadStatus::StateType state_type) { + ThreadStatus::StateType /*state_type*/) { static std::string dummy_str = ""; return dummy_str; } -const std::string ThreadStatus::MicrosToString( - uint64_t op_elapsed_time) { +const std::string ThreadStatus::MicrosToString(uint64_t /*op_elapsed_time*/) { static std::string dummy_str = ""; return dummy_str; } const std::string& ThreadStatus::GetOperationPropertyName( - ThreadStatus::OperationType op_type, int i) { + ThreadStatus::OperationType /*op_type*/, int /*i*/) { static std::string dummy_str = ""; return dummy_str; } -std::map - ThreadStatus::InterpretOperationProperties( - ThreadStatus::OperationType op_type, - const uint64_t* op_properties) { +std::map ThreadStatus::InterpretOperationProperties( + ThreadStatus::OperationType /*op_type*/, + const uint64_t* /*op_properties*/) { return std::map(); } diff --git a/thirdparty/rocksdb/monitoring/thread_status_updater.cc b/thirdparty/rocksdb/monitoring/thread_status_updater.cc index 7441c35f8b..cde44928b6 100644 --- a/thirdparty/rocksdb/monitoring/thread_status_updater.cc +++ b/thirdparty/rocksdb/monitoring/thread_status_updater.cc @@ -15,8 +15,8 @@ namespace rocksdb { __thread ThreadStatusData* ThreadStatusUpdater::thread_status_data_ = nullptr; -void ThreadStatusUpdater::RegisterThread( - ThreadStatus::ThreadType ttype, uint64_t thread_id) { +void ThreadStatusUpdater::RegisterThread(ThreadStatus::ThreadType ttype, + uint64_t thread_id) { if (UNLIKELY(thread_status_data_ == nullptr)) { thread_status_data_ = new ThreadStatusData(); thread_status_data_->thread_type = ttype; @@ -43,8 +43,7 @@ void ThreadStatusUpdater::ResetThreadStatus() { SetColumnFamilyInfoKey(nullptr); } -void ThreadStatusUpdater::SetColumnFamilyInfoKey( - const void* cf_key) { +void ThreadStatusUpdater::SetColumnFamilyInfoKey(const void* cf_key) { auto* data = Get(); if (data == nullptr) { return; @@ -78,13 +77,12 @@ void ThreadStatusUpdater::SetThreadOperation( data->operation_type.store(type, std::memory_order_release); if (type == ThreadStatus::OP_UNKNOWN) { data->operation_stage.store(ThreadStatus::STAGE_UNKNOWN, - std::memory_order_relaxed); + std::memory_order_relaxed); ClearThreadOperationProperties(); } } -void ThreadStatusUpdater::SetThreadOperationProperty( - int i, uint64_t value) { +void ThreadStatusUpdater::SetThreadOperationProperty(int i, uint64_t value) { auto* data = GetLocalThreadStatus(); if (data == nullptr) { return; @@ -92,8 +90,8 @@ void ThreadStatusUpdater::SetThreadOperationProperty( data->op_properties[i].store(value, std::memory_order_relaxed); } -void ThreadStatusUpdater::IncreaseThreadOperationProperty( - int i, uint64_t delta) { +void ThreadStatusUpdater::IncreaseThreadOperationProperty(int i, + uint64_t delta) { auto* data = GetLocalThreadStatus(); if (data == nullptr) { return; @@ -115,9 +113,9 @@ void ThreadStatusUpdater::ClearThreadOperation() { return; } data->operation_stage.store(ThreadStatus::STAGE_UNKNOWN, - std::memory_order_relaxed); - data->operation_type.store( - ThreadStatus::OP_UNKNOWN, std::memory_order_relaxed); + std::memory_order_relaxed); + data->operation_type.store(ThreadStatus::OP_UNKNOWN, + std::memory_order_relaxed); ClearThreadOperationProperties(); } @@ -137,12 +135,10 @@ ThreadStatus::OperationStage ThreadStatusUpdater::SetThreadOperationStage( if (data == nullptr) { return ThreadStatus::STAGE_UNKNOWN; } - return data->operation_stage.exchange( - stage, std::memory_order_relaxed); + return data->operation_stage.exchange(stage, std::memory_order_relaxed); } -void ThreadStatusUpdater::SetThreadState( - const ThreadStatus::StateType type) { +void ThreadStatusUpdater::SetThreadState(const ThreadStatus::StateType type) { auto* data = GetLocalThreadStatus(); if (data == nullptr) { return; @@ -155,8 +151,8 @@ void ThreadStatusUpdater::ClearThreadState() { if (data == nullptr) { return; } - data->state_type.store( - ThreadStatus::STATE_UNKNOWN, std::memory_order_relaxed); + data->state_type.store(ThreadStatus::STATE_UNKNOWN, + std::memory_order_relaxed); } Status ThreadStatusUpdater::GetThreadList( @@ -168,50 +164,40 @@ Status ThreadStatusUpdater::GetThreadList( std::lock_guard lck(thread_list_mutex_); for (auto* thread_data : thread_data_set_) { assert(thread_data); - auto thread_id = thread_data->thread_id.load( - std::memory_order_relaxed); - auto thread_type = thread_data->thread_type.load( - std::memory_order_relaxed); + auto thread_id = thread_data->thread_id.load(std::memory_order_relaxed); + auto thread_type = thread_data->thread_type.load(std::memory_order_relaxed); // Since any change to cf_info_map requires thread_list_mutex, // which is currently held by GetThreadList(), here we can safely // use "memory_order_relaxed" to load the cf_key. - auto cf_key = thread_data->cf_key.load( - std::memory_order_relaxed); - auto iter = cf_info_map_.find(cf_key); - auto* cf_info = iter != cf_info_map_.end() ? - iter->second.get() : nullptr; - const std::string* db_name = nullptr; - const std::string* cf_name = nullptr; + auto cf_key = thread_data->cf_key.load(std::memory_order_relaxed); + ThreadStatus::OperationType op_type = ThreadStatus::OP_UNKNOWN; ThreadStatus::OperationStage op_stage = ThreadStatus::STAGE_UNKNOWN; ThreadStatus::StateType state_type = ThreadStatus::STATE_UNKNOWN; uint64_t op_elapsed_micros = 0; uint64_t op_props[ThreadStatus::kNumOperationProperties] = {0}; - if (cf_info != nullptr) { - db_name = &cf_info->db_name; - cf_name = &cf_info->cf_name; - op_type = thread_data->operation_type.load( - std::memory_order_acquire); + + auto iter = cf_info_map_.find(cf_key); + if (iter != cf_info_map_.end()) { + op_type = thread_data->operation_type.load(std::memory_order_acquire); // display lower-level info only when higher-level info is available. if (op_type != ThreadStatus::OP_UNKNOWN) { op_elapsed_micros = now_micros - thread_data->op_start_time.load( - std::memory_order_relaxed); - op_stage = thread_data->operation_stage.load( - std::memory_order_relaxed); - state_type = thread_data->state_type.load( - std::memory_order_relaxed); + std::memory_order_relaxed); + op_stage = thread_data->operation_stage.load(std::memory_order_relaxed); + state_type = thread_data->state_type.load(std::memory_order_relaxed); for (int i = 0; i < ThreadStatus::kNumOperationProperties; ++i) { - op_props[i] = thread_data->op_properties[i].load( - std::memory_order_relaxed); + op_props[i] = + thread_data->op_properties[i].load(std::memory_order_relaxed); } } } + thread_list->emplace_back( thread_id, thread_type, - db_name ? *db_name : "", - cf_name ? *cf_name : "", - op_type, op_elapsed_micros, op_stage, op_props, - state_type); + iter != cf_info_map_.end() ? iter->second.db_name : "", + iter != cf_info_map_.end() ? iter->second.cf_name : "", op_type, + op_elapsed_micros, op_stage, op_props, state_type); } return Status::OK(); @@ -222,22 +208,23 @@ ThreadStatusData* ThreadStatusUpdater::GetLocalThreadStatus() { return nullptr; } if (!thread_status_data_->enable_tracking) { - assert(thread_status_data_->cf_key.load( - std::memory_order_relaxed) == nullptr); + assert(thread_status_data_->cf_key.load(std::memory_order_relaxed) == + nullptr); return nullptr; } return thread_status_data_; } -void ThreadStatusUpdater::NewColumnFamilyInfo( - const void* db_key, const std::string& db_name, - const void* cf_key, const std::string& cf_name) { +void ThreadStatusUpdater::NewColumnFamilyInfo(const void* db_key, + const std::string& db_name, + const void* cf_key, + const std::string& cf_name) { // Acquiring same lock as GetThreadList() to guarantee // a consistent view of global column family table (cf_info_map). std::lock_guard lck(thread_list_mutex_); - cf_info_map_[cf_key].reset( - new ConstantColumnFamilyInfo(db_key, db_name, cf_name)); + cf_info_map_.emplace(std::piecewise_construct, std::make_tuple(cf_key), + std::make_tuple(db_key, db_name, cf_name)); db_key_map_[db_key].insert(cf_key); } @@ -245,25 +232,20 @@ void ThreadStatusUpdater::EraseColumnFamilyInfo(const void* cf_key) { // Acquiring same lock as GetThreadList() to guarantee // a consistent view of global column family table (cf_info_map). std::lock_guard lck(thread_list_mutex_); + auto cf_pair = cf_info_map_.find(cf_key); - if (cf_pair == cf_info_map_.end()) { - return; + if (cf_pair != cf_info_map_.end()) { + // Remove its entry from db_key_map_ by the following steps: + // 1. Obtain the entry in db_key_map_ whose set contains cf_key + // 2. Remove it from the set. + ConstantColumnFamilyInfo& cf_info = cf_pair->second; + auto db_pair = db_key_map_.find(cf_info.db_key); + assert(db_pair != db_key_map_.end()); + size_t result __attribute__((__unused__)); + result = db_pair->second.erase(cf_key); + assert(result); + cf_info_map_.erase(cf_pair); } - - auto* cf_info = cf_pair->second.get(); - assert(cf_info); - - // Remove its entry from db_key_map_ by the following steps: - // 1. Obtain the entry in db_key_map_ whose set contains cf_key - // 2. Remove it from the set. - auto db_pair = db_key_map_.find(cf_info->db_key); - assert(db_pair != db_key_map_.end()); - size_t result __attribute__((unused)) = db_pair->second.erase(cf_key); - assert(result); - - cf_pair->second.reset(); - result = cf_info_map_.erase(cf_key); - assert(result); } void ThreadStatusUpdater::EraseDatabaseInfo(const void* db_key) { @@ -277,73 +259,56 @@ void ThreadStatusUpdater::EraseDatabaseInfo(const void* db_key) { return; } - size_t result __attribute__((unused)) = 0; for (auto cf_key : db_pair->second) { auto cf_pair = cf_info_map_.find(cf_key); - if (cf_pair == cf_info_map_.end()) { - continue; + if (cf_pair != cf_info_map_.end()) { + cf_info_map_.erase(cf_pair); } - cf_pair->second.reset(); - result = cf_info_map_.erase(cf_key); - assert(result); } db_key_map_.erase(db_key); } #else -void ThreadStatusUpdater::RegisterThread( - ThreadStatus::ThreadType ttype, uint64_t thread_id) { -} +void ThreadStatusUpdater::RegisterThread(ThreadStatus::ThreadType /*ttype*/, + uint64_t /*thread_id*/) {} -void ThreadStatusUpdater::UnregisterThread() { -} +void ThreadStatusUpdater::UnregisterThread() {} -void ThreadStatusUpdater::ResetThreadStatus() { -} +void ThreadStatusUpdater::ResetThreadStatus() {} -void ThreadStatusUpdater::SetColumnFamilyInfoKey( - const void* cf_key) { -} +void ThreadStatusUpdater::SetColumnFamilyInfoKey(const void* /*cf_key*/) {} void ThreadStatusUpdater::SetThreadOperation( - const ThreadStatus::OperationType type) { -} + const ThreadStatus::OperationType /*type*/) {} -void ThreadStatusUpdater::ClearThreadOperation() { -} +void ThreadStatusUpdater::ClearThreadOperation() {} void ThreadStatusUpdater::SetThreadState( - const ThreadStatus::StateType type) { -} + const ThreadStatus::StateType /*type*/) {} -void ThreadStatusUpdater::ClearThreadState() { -} +void ThreadStatusUpdater::ClearThreadState() {} Status ThreadStatusUpdater::GetThreadList( - std::vector* thread_list) { + std::vector* /*thread_list*/) { return Status::NotSupported( "GetThreadList is not supported in the current running environment."); } -void ThreadStatusUpdater::NewColumnFamilyInfo( - const void* db_key, const std::string& db_name, - const void* cf_key, const std::string& cf_name) { -} +void ThreadStatusUpdater::NewColumnFamilyInfo(const void* /*db_key*/, + const std::string& /*db_name*/, + const void* /*cf_key*/, + const std::string& /*cf_name*/) {} -void ThreadStatusUpdater::EraseColumnFamilyInfo(const void* cf_key) { -} +void ThreadStatusUpdater::EraseColumnFamilyInfo(const void* /*cf_key*/) {} -void ThreadStatusUpdater::EraseDatabaseInfo(const void* db_key) { -} +void ThreadStatusUpdater::EraseDatabaseInfo(const void* /*db_key*/) {} -void ThreadStatusUpdater::SetThreadOperationProperty( - int i, uint64_t value) { -} +void ThreadStatusUpdater::SetThreadOperationProperty(int /*i*/, + uint64_t /*value*/) {} -void ThreadStatusUpdater::IncreaseThreadOperationProperty( - int i, uint64_t delta) { -} +void ThreadStatusUpdater::IncreaseThreadOperationProperty(int /*i*/, + uint64_t /*delta*/) {} #endif // ROCKSDB_USING_THREAD_STATUS } // namespace rocksdb diff --git a/thirdparty/rocksdb/monitoring/thread_status_updater.h b/thirdparty/rocksdb/monitoring/thread_status_updater.h index 69b4d4f7ec..6706d159df 100644 --- a/thirdparty/rocksdb/monitoring/thread_status_updater.h +++ b/thirdparty/rocksdb/monitoring/thread_status_updater.h @@ -218,8 +218,7 @@ class ThreadStatusUpdater { // globally instead of inside DB is to avoid the situation where DB is // closing while GetThreadList function already get the pointer to its // CopnstantColumnFamilyInfo. - std::unordered_map< - const void*, std::unique_ptr> cf_info_map_; + std::unordered_map cf_info_map_; // A db_key to cf_key map that allows erasing elements in cf_info_map // associated to the same db_key faster. diff --git a/thirdparty/rocksdb/monitoring/thread_status_updater_debug.cc b/thirdparty/rocksdb/monitoring/thread_status_updater_debug.cc index eec52e1887..8dc0fe6fd9 100644 --- a/thirdparty/rocksdb/monitoring/thread_status_updater_debug.cc +++ b/thirdparty/rocksdb/monitoring/thread_status_updater_debug.cc @@ -13,19 +13,17 @@ namespace rocksdb { #ifndef NDEBUG #ifdef ROCKSDB_USING_THREAD_STATUS void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap( - const std::vector& handles, - bool check_exist) { + const std::vector& handles, bool check_exist) { std::unique_lock lock(thread_list_mutex_); if (check_exist) { assert(cf_info_map_.size() == handles.size()); } for (auto* handle : handles) { auto* cfd = reinterpret_cast(handle)->cfd(); - auto iter __attribute__((unused)) = cf_info_map_.find(cfd); + auto iter __attribute__((__unused__)) = cf_info_map_.find(cfd); if (check_exist) { assert(iter != cf_info_map_.end()); - assert(iter->second); - assert(iter->second->cf_name == cfd->GetName()); + assert(iter->second.cf_name == cfd->GetName()); } else { assert(iter == cf_info_map_.end()); } @@ -35,12 +33,10 @@ void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap( #else void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap( - const std::vector& handles, - bool check_exist) { + const std::vector& /*handles*/, bool /*check_exist*/) { } #endif // ROCKSDB_USING_THREAD_STATUS #endif // !NDEBUG - } // namespace rocksdb diff --git a/thirdparty/rocksdb/monitoring/thread_status_util.cc b/thirdparty/rocksdb/monitoring/thread_status_util.cc index 50692dfe55..c2af0a5745 100644 --- a/thirdparty/rocksdb/monitoring/thread_status_util.cc +++ b/thirdparty/rocksdb/monitoring/thread_status_util.cc @@ -10,20 +10,18 @@ namespace rocksdb { - #ifdef ROCKSDB_USING_THREAD_STATUS -__thread ThreadStatusUpdater* - ThreadStatusUtil::thread_updater_local_cache_ = nullptr; +__thread ThreadStatusUpdater* ThreadStatusUtil::thread_updater_local_cache_ = + nullptr; __thread bool ThreadStatusUtil::thread_updater_initialized_ = false; -void ThreadStatusUtil::RegisterThread( - const Env* env, ThreadStatus::ThreadType thread_type) { +void ThreadStatusUtil::RegisterThread(const Env* env, + ThreadStatus::ThreadType thread_type) { if (!MaybeInitThreadLocalUpdater(env)) { return; } assert(thread_updater_local_cache_); - thread_updater_local_cache_->RegisterThread( - thread_type, env->GetThreadID()); + thread_updater_local_cache_->RegisterThread(thread_type, env->GetThreadID()); } void ThreadStatusUtil::UnregisterThread() { @@ -80,28 +78,25 @@ ThreadStatus::OperationStage ThreadStatusUtil::SetThreadOperationStage( return thread_updater_local_cache_->SetThreadOperationStage(stage); } -void ThreadStatusUtil::SetThreadOperationProperty( - int code, uint64_t value) { +void ThreadStatusUtil::SetThreadOperationProperty(int code, uint64_t value) { if (thread_updater_local_cache_ == nullptr) { // thread_updater_local_cache_ must be set in SetColumnFamily // or other ThreadStatusUtil functions. return; } - thread_updater_local_cache_->SetThreadOperationProperty( - code, value); + thread_updater_local_cache_->SetThreadOperationProperty(code, value); } -void ThreadStatusUtil::IncreaseThreadOperationProperty( - int code, uint64_t delta) { +void ThreadStatusUtil::IncreaseThreadOperationProperty(int code, + uint64_t delta) { if (thread_updater_local_cache_ == nullptr) { // thread_updater_local_cache_ must be set in SetColumnFamily // or other ThreadStatusUtil functions. return; } - thread_updater_local_cache_->IncreaseThreadOperationProperty( - code, delta); + thread_updater_local_cache_->IncreaseThreadOperationProperty(code, delta); } void ThreadStatusUtil::SetThreadState(ThreadStatus::StateType state) { @@ -135,8 +130,7 @@ void ThreadStatusUtil::NewColumnFamilyInfo(const DB* db, } } -void ThreadStatusUtil::EraseColumnFamilyInfo( - const ColumnFamilyData* cfd) { +void ThreadStatusUtil::EraseColumnFamilyInfo(const ColumnFamilyData* cfd) { if (thread_updater_local_cache_ == nullptr) { return; } @@ -173,49 +167,39 @@ AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() { ThreadStatusUpdater* ThreadStatusUtil::thread_updater_local_cache_ = nullptr; bool ThreadStatusUtil::thread_updater_initialized_ = false; -bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* env) { +bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* /*env*/) { return false; } -void ThreadStatusUtil::SetColumnFamily(const ColumnFamilyData* cfd, - const Env* env, - bool enable_thread_tracking) {} +void ThreadStatusUtil::SetColumnFamily(const ColumnFamilyData* /*cfd*/, + const Env* /*env*/, + bool /*enable_thread_tracking*/) {} -void ThreadStatusUtil::SetThreadOperation(ThreadStatus::OperationType op) { -} +void ThreadStatusUtil::SetThreadOperation(ThreadStatus::OperationType /*op*/) {} -void ThreadStatusUtil::SetThreadOperationProperty( - int code, uint64_t value) { -} +void ThreadStatusUtil::SetThreadOperationProperty(int /*code*/, + uint64_t /*value*/) {} -void ThreadStatusUtil::IncreaseThreadOperationProperty( - int code, uint64_t delta) { -} +void ThreadStatusUtil::IncreaseThreadOperationProperty(int /*code*/, + uint64_t /*delta*/) {} -void ThreadStatusUtil::SetThreadState(ThreadStatus::StateType state) { -} +void ThreadStatusUtil::SetThreadState(ThreadStatus::StateType /*state*/) {} -void ThreadStatusUtil::NewColumnFamilyInfo(const DB* db, - const ColumnFamilyData* cfd, - const std::string& cf_name, - const Env* env) {} +void ThreadStatusUtil::NewColumnFamilyInfo(const DB* /*db*/, + const ColumnFamilyData* /*cfd*/, + const std::string& /*cf_name*/, + const Env* /*env*/) {} -void ThreadStatusUtil::EraseColumnFamilyInfo( - const ColumnFamilyData* cfd) { -} +void ThreadStatusUtil::EraseColumnFamilyInfo(const ColumnFamilyData* /*cfd*/) {} -void ThreadStatusUtil::EraseDatabaseInfo(const DB* db) { -} +void ThreadStatusUtil::EraseDatabaseInfo(const DB* /*db*/) {} -void ThreadStatusUtil::ResetThreadStatus() { -} +void ThreadStatusUtil::ResetThreadStatus() {} AutoThreadOperationStageUpdater::AutoThreadOperationStageUpdater( - ThreadStatus::OperationStage stage) { -} + ThreadStatus::OperationStage /*stage*/) {} -AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() { -} +AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() {} #endif // ROCKSDB_USING_THREAD_STATUS diff --git a/thirdparty/rocksdb/options/cf_options.cc b/thirdparty/rocksdb/options/cf_options.cc index 67cbef68f6..6957e150f1 100644 --- a/thirdparty/rocksdb/options/cf_options.cc +++ b/thirdparty/rocksdb/options/cf_options.cc @@ -17,6 +17,7 @@ #include "port/port.h" #include "rocksdb/env.h" #include "rocksdb/options.h" +#include "rocksdb/concurrent_task_limiter.h" namespace rocksdb { @@ -27,9 +28,6 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options, const ColumnFamilyOptions& cf_options) : compaction_style(cf_options.compaction_style), compaction_pri(cf_options.compaction_pri), - compaction_options_universal(cf_options.compaction_options_universal), - compaction_options_fifo(cf_options.compaction_options_fifo), - prefix_extractor(cf_options.prefix_extractor.get()), user_comparator(cf_options.comparator), internal_comparator(InternalKeyComparator(cf_options.comparator)), merge_operator(cf_options.merge_operator.get()), @@ -44,6 +42,7 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options, info_log(db_options.info_log.get()), statistics(db_options.statistics.get()), rate_limiter(db_options.rate_limiter.get()), + info_log_level(db_options.info_log_level), env(db_options.env), allow_mmap_reads(db_options.allow_mmap_reads), allow_mmap_writes(db_options.allow_mmap_writes), @@ -59,6 +58,7 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options, use_fsync(db_options.use_fsync), compression_per_level(cf_options.compression_per_level), bottommost_compression(cf_options.bottommost_compression), + bottommost_compression_opts(cf_options.bottommost_compression_opts), compression_opts(cf_options.compression_opts), level_compaction_dynamic_level_bytes( cf_options.level_compaction_dynamic_level_bytes), @@ -66,16 +66,18 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options, db_options.access_hint_on_compaction_start), new_table_reader_for_compaction_inputs( db_options.new_table_reader_for_compaction_inputs), - compaction_readahead_size(db_options.compaction_readahead_size), num_levels(cf_options.num_levels), optimize_filters_for_hits(cf_options.optimize_filters_for_hits), force_consistency_checks(cf_options.force_consistency_checks), allow_ingest_behind(db_options.allow_ingest_behind), + preserve_deletes(db_options.preserve_deletes), listeners(db_options.listeners), row_cache(db_options.row_cache), max_subcompactions(db_options.max_subcompactions), memtable_insert_with_hint_prefix_extractor( - cf_options.memtable_insert_with_hint_prefix_extractor.get()) {} + cf_options.memtable_insert_with_hint_prefix_extractor.get()), + cf_paths(cf_options.cf_paths), + compaction_thread_limiter(cf_options.compaction_thread_limiter) {} // Multiple two operands. If they overflow, return op1. uint64_t MultiplyCheckOverflow(uint64_t op1, double op2) { @@ -88,6 +90,24 @@ uint64_t MultiplyCheckOverflow(uint64_t op1, double op2) { return static_cast(op1 * op2); } +// when level_compaction_dynamic_level_bytes is true and leveled compaction +// is used, the base level is not always L1, so precomupted max_file_size can +// no longer be used. Recompute file_size_for_level from base level. +uint64_t MaxFileSizeForLevel(const MutableCFOptions& cf_options, + int level, CompactionStyle compaction_style, int base_level, + bool level_compaction_dynamic_level_bytes) { + if (!level_compaction_dynamic_level_bytes || level < base_level || + compaction_style != kCompactionStyleLevel) { + assert(level >= 0); + assert(level < (int)cf_options.max_file_size.size()); + return cf_options.max_file_size[level]; + } else { + assert(level >= 0 && base_level >= 0); + assert(level - base_level < (int)cf_options.max_file_size.size()); + return cf_options.max_file_size[level - base_level]; + } +} + void MutableCFOptions::RefreshDerivedOptions(int num_levels, CompactionStyle compaction_style) { max_file_size.resize(num_levels); @@ -103,12 +123,6 @@ void MutableCFOptions::RefreshDerivedOptions(int num_levels, } } -uint64_t MutableCFOptions::MaxFileSizeForLevel(int level) const { - assert(level >= 0); - assert(level < (int)max_file_size.size()); - return max_file_size[level]; -} - void MutableCFOptions::Dump(Logger* log) const { // Memtable related options ROCKS_LOG_INFO(log, @@ -121,6 +135,8 @@ void MutableCFOptions::Dump(Logger* log) const { arena_block_size); ROCKS_LOG_INFO(log, " memtable_prefix_bloom_ratio: %f", memtable_prefix_bloom_size_ratio); + ROCKS_LOG_INFO(log, " memtable_whole_key_filtering: %d", + memtable_whole_key_filtering); ROCKS_LOG_INFO(log, " memtable_huge_page_size: %" ROCKSDB_PRIszt, memtable_huge_page_size); @@ -130,6 +146,9 @@ void MutableCFOptions::Dump(Logger* log) const { ROCKS_LOG_INFO(log, " inplace_update_num_locks: %" ROCKSDB_PRIszt, inplace_update_num_locks); + ROCKS_LOG_INFO( + log, " prefix_extractor: %s", + prefix_extractor == nullptr ? "nullptr" : prefix_extractor->Name()); ROCKS_LOG_INFO(log, " disable_auto_compactions: %d", disable_auto_compactions); ROCKS_LOG_INFO(log, " soft_pending_compaction_bytes_limit: %" PRIu64, @@ -152,6 +171,8 @@ void MutableCFOptions::Dump(Logger* log) const { max_bytes_for_level_base); ROCKS_LOG_INFO(log, " max_bytes_for_level_multiplier: %f", max_bytes_for_level_multiplier); + ROCKS_LOG_INFO(log, " ttl: %" PRIu64, + ttl); std::string result; char buf[10]; for (const auto m : max_bytes_for_level_multiplier_additional) { @@ -174,6 +195,34 @@ void MutableCFOptions::Dump(Logger* log) const { report_bg_io_stats); ROCKS_LOG_INFO(log, " compression: %d", static_cast(compression)); + + // Universal Compaction Options + ROCKS_LOG_INFO(log, "compaction_options_universal.size_ratio : %d", + compaction_options_universal.size_ratio); + ROCKS_LOG_INFO(log, "compaction_options_universal.min_merge_width : %d", + compaction_options_universal.min_merge_width); + ROCKS_LOG_INFO(log, "compaction_options_universal.max_merge_width : %d", + compaction_options_universal.max_merge_width); + ROCKS_LOG_INFO( + log, "compaction_options_universal.max_size_amplification_percent : %d", + compaction_options_universal.max_size_amplification_percent); + ROCKS_LOG_INFO(log, + "compaction_options_universal.compression_size_percent : %d", + compaction_options_universal.compression_size_percent); + ROCKS_LOG_INFO(log, "compaction_options_universal.stop_style : %d", + compaction_options_universal.stop_style); + ROCKS_LOG_INFO( + log, "compaction_options_universal.allow_trivial_move : %d", + static_cast(compaction_options_universal.allow_trivial_move)); + + // FIFO Compaction Options + ROCKS_LOG_INFO(log, "compaction_options_fifo.max_table_files_size : %" PRIu64, + compaction_options_fifo.max_table_files_size); + ROCKS_LOG_INFO(log, "compaction_options_fifo.allow_compaction : %d", + compaction_options_fifo.allow_compaction); } +MutableCFOptions::MutableCFOptions(const Options& options) + : MutableCFOptions(ColumnFamilyOptions(options)) {} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/options/cf_options.h b/thirdparty/rocksdb/options/cf_options.h index f376729f85..fed144e4c3 100644 --- a/thirdparty/rocksdb/options/cf_options.h +++ b/thirdparty/rocksdb/options/cf_options.h @@ -18,7 +18,7 @@ namespace rocksdb { // ImmutableCFOptions is a data struct used by RocksDB internal. It contains a // subset of Options that should not be changed during the entire lifetime // of DB. Raw pointers defined in this struct do not have ownership to the data -// they point to. Options contains shared_ptr to these data. +// they point to. Options contains std::shared_ptr to these data. struct ImmutableCFOptions { ImmutableCFOptions(); explicit ImmutableCFOptions(const Options& options); @@ -30,11 +30,6 @@ struct ImmutableCFOptions { CompactionPri compaction_pri; - CompactionOptionsUniversal compaction_options_universal; - CompactionOptionsFIFO compaction_options_fifo; - - const SliceTransform* prefix_extractor; - const Comparator* user_comparator; InternalKeyComparator internal_comparator; @@ -94,6 +89,8 @@ struct ImmutableCFOptions { CompressionType bottommost_compression; + CompressionOptions bottommost_compression_opts; + CompressionOptions compression_opts; bool level_compaction_dynamic_level_bytes; @@ -102,8 +99,6 @@ struct ImmutableCFOptions { bool new_table_reader_for_compaction_inputs; - size_t compaction_readahead_size; - int num_levels; bool optimize_filters_for_hits; @@ -112,7 +107,9 @@ struct ImmutableCFOptions { bool allow_ingest_behind; - // A vector of EventListeners which call-back functions will be called + bool preserve_deletes; + + // A vector of EventListeners which callback functions will be called // when specific RocksDB event happens. std::vector> listeners; @@ -121,6 +118,10 @@ struct ImmutableCFOptions { uint32_t max_subcompactions; const SliceTransform* memtable_insert_with_hint_prefix_extractor; + + std::vector cf_paths; + + std::shared_ptr compaction_thread_limiter; }; struct MutableCFOptions { @@ -130,9 +131,11 @@ struct MutableCFOptions { arena_block_size(options.arena_block_size), memtable_prefix_bloom_size_ratio( options.memtable_prefix_bloom_size_ratio), + memtable_whole_key_filtering(options.memtable_whole_key_filtering), memtable_huge_page_size(options.memtable_huge_page_size), max_successive_merges(options.max_successive_merges), inplace_update_num_locks(options.inplace_update_num_locks), + prefix_extractor(options.prefix_extractor), disable_auto_compactions(options.disable_auto_compactions), soft_pending_compaction_bytes_limit( options.soft_pending_compaction_bytes_limit), @@ -147,13 +150,17 @@ struct MutableCFOptions { target_file_size_multiplier(options.target_file_size_multiplier), max_bytes_for_level_base(options.max_bytes_for_level_base), max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier), + ttl(options.ttl), max_bytes_for_level_multiplier_additional( options.max_bytes_for_level_multiplier_additional), + compaction_options_fifo(options.compaction_options_fifo), + compaction_options_universal(options.compaction_options_universal), max_sequential_skip_in_iterations( options.max_sequential_skip_in_iterations), paranoid_file_checks(options.paranoid_file_checks), report_bg_io_stats(options.report_bg_io_stats), - compression(options.compression) { + compression(options.compression), + sample_for_compression(options.sample_for_compression) { RefreshDerivedOptions(options.num_levels, options.compaction_style); } @@ -162,9 +169,11 @@ struct MutableCFOptions { max_write_buffer_number(0), arena_block_size(0), memtable_prefix_bloom_size_ratio(0), + memtable_whole_key_filtering(false), memtable_huge_page_size(0), max_successive_merges(0), inplace_update_num_locks(0), + prefix_extractor(nullptr), disable_auto_compactions(false), soft_pending_compaction_bytes_limit(0), hard_pending_compaction_bytes_limit(0), @@ -176,10 +185,15 @@ struct MutableCFOptions { target_file_size_multiplier(0), max_bytes_for_level_base(0), max_bytes_for_level_multiplier(0), + ttl(0), + compaction_options_fifo(), max_sequential_skip_in_iterations(0), paranoid_file_checks(false), report_bg_io_stats(false), - compression(Snappy_Supported() ? kSnappyCompression : kNoCompression) {} + compression(Snappy_Supported() ? kSnappyCompression : kNoCompression), + sample_for_compression(0) {} + + explicit MutableCFOptions(const Options& options); // Must be called after any change to MutableCFOptions void RefreshDerivedOptions(int num_levels, CompactionStyle compaction_style); @@ -188,8 +202,6 @@ struct MutableCFOptions { RefreshDerivedOptions(ioptions.num_levels, ioptions.compaction_style); } - // Get the max file size in a given level. - uint64_t MaxFileSizeForLevel(int level) const; int MaxBytesMultiplerAdditional(int level) const { if (level >= static_cast(max_bytes_for_level_multiplier_additional.size())) { @@ -205,9 +217,11 @@ struct MutableCFOptions { int max_write_buffer_number; size_t arena_block_size; double memtable_prefix_bloom_size_ratio; + bool memtable_whole_key_filtering; size_t memtable_huge_page_size; size_t max_successive_merges; size_t inplace_update_num_locks; + std::shared_ptr prefix_extractor; // Compaction related options bool disable_auto_compactions; @@ -221,13 +235,17 @@ struct MutableCFOptions { int target_file_size_multiplier; uint64_t max_bytes_for_level_base; double max_bytes_for_level_multiplier; + uint64_t ttl; std::vector max_bytes_for_level_multiplier_additional; + CompactionOptionsFIFO compaction_options_fifo; + CompactionOptionsUniversal compaction_options_universal; // Misc options uint64_t max_sequential_skip_in_iterations; bool paranoid_file_checks; bool report_bg_io_stats; CompressionType compression; + uint64_t sample_for_compression; // Derived options // Per-level target file size. @@ -236,4 +254,8 @@ struct MutableCFOptions { uint64_t MultiplyCheckOverflow(uint64_t op1, double op2); +// Get the max file size in a given level. +uint64_t MaxFileSizeForLevel(const MutableCFOptions& cf_options, + int level, CompactionStyle compaction_style, int base_level = 1, + bool level_compaction_dynamic_level_bytes = false); } // namespace rocksdb diff --git a/thirdparty/rocksdb/options/db_options.cc b/thirdparty/rocksdb/options/db_options.cc index 61775757d5..f24705cb75 100644 --- a/thirdparty/rocksdb/options/db_options.cc +++ b/thirdparty/rocksdb/options/db_options.cc @@ -62,12 +62,8 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options) access_hint_on_compaction_start(options.access_hint_on_compaction_start), new_table_reader_for_compaction_inputs( options.new_table_reader_for_compaction_inputs), - compaction_readahead_size(options.compaction_readahead_size), random_access_max_buffer_size(options.random_access_max_buffer_size), - writable_file_max_buffer_size(options.writable_file_max_buffer_size), use_adaptive_mutex(options.use_adaptive_mutex), - bytes_per_sync(options.bytes_per_sync), - wal_bytes_per_sync(options.wal_bytes_per_sync), listeners(options.listeners), enable_thread_tracking(options.enable_thread_tracking), enable_pipelined_write(options.enable_pipelined_write), @@ -87,8 +83,11 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options) dump_malloc_stats(options.dump_malloc_stats), avoid_flush_during_recovery(options.avoid_flush_during_recovery), allow_ingest_behind(options.allow_ingest_behind), - concurrent_prepare(options.concurrent_prepare), - manual_wal_flush(options.manual_wal_flush) { + preserve_deletes(options.preserve_deletes), + two_write_queues(options.two_write_queues), + manual_wal_flush(options.manual_wal_flush), + atomic_flush(options.atomic_flush), + avoid_unnecessary_blocking_io(options.avoid_unnecessary_blocking_io) { } void ImmutableDBOptions::Dump(Logger* log) const { @@ -104,6 +103,8 @@ void ImmutableDBOptions::Dump(Logger* log) const { info_log.get()); ROCKS_LOG_HEADER(log, " Options.max_file_opening_threads: %d", max_file_opening_threads); + ROCKS_LOG_HEADER(log, " Options.statistics: %p", + statistics.get()); ROCKS_LOG_HEADER(log, " Options.use_fsync: %d", use_fsync); ROCKS_LOG_HEADER( @@ -168,15 +169,9 @@ void ImmutableDBOptions::Dump(Logger* log) const { static_cast(access_hint_on_compaction_start)); ROCKS_LOG_HEADER(log, " Options.new_table_reader_for_compaction_inputs: %d", new_table_reader_for_compaction_inputs); - ROCKS_LOG_HEADER( - log, " Options.compaction_readahead_size: %" ROCKSDB_PRIszt, - compaction_readahead_size); ROCKS_LOG_HEADER( log, " Options.random_access_max_buffer_size: %" ROCKSDB_PRIszt, random_access_max_buffer_size); - ROCKS_LOG_HEADER( - log, " Options.writable_file_max_buffer_size: %" ROCKSDB_PRIszt, - writable_file_max_buffer_size); ROCKS_LOG_HEADER(log, " Options.use_adaptive_mutex: %d", use_adaptive_mutex); ROCKS_LOG_HEADER(log, " Options.rate_limiter: %p", @@ -184,14 +179,8 @@ void ImmutableDBOptions::Dump(Logger* log) const { Header( log, " Options.sst_file_manager.rate_bytes_per_sec: %" PRIi64, sst_file_manager ? sst_file_manager->GetDeleteRateBytesPerSecond() : 0); - ROCKS_LOG_HEADER(log, - " Options.bytes_per_sync: %" PRIu64, - bytes_per_sync); - ROCKS_LOG_HEADER(log, - " Options.wal_bytes_per_sync: %" PRIu64, - wal_bytes_per_sync); ROCKS_LOG_HEADER(log, " Options.wal_recovery_mode: %d", - wal_recovery_mode); + static_cast(wal_recovery_mode)); ROCKS_LOG_HEADER(log, " Options.enable_thread_tracking: %d", enable_thread_tracking); ROCKS_LOG_HEADER(log, " Options.enable_pipelined_write: %d", @@ -208,7 +197,8 @@ void ImmutableDBOptions::Dump(Logger* log) const { write_thread_slow_yield_usec); if (row_cache) { ROCKS_LOG_HEADER( - log, " Options.row_cache: %" PRIu64, + log, + " Options.row_cache: %" ROCKSDB_PRIszt, row_cache->GetCapacity()); } else { ROCKS_LOG_HEADER(log, @@ -223,10 +213,16 @@ void ImmutableDBOptions::Dump(Logger* log) const { avoid_flush_during_recovery); ROCKS_LOG_HEADER(log, " Options.allow_ingest_behind: %d", allow_ingest_behind); - ROCKS_LOG_HEADER(log, " Options.concurrent_prepare: %d", - concurrent_prepare); + ROCKS_LOG_HEADER(log, " Options.preserve_deletes: %d", + preserve_deletes); + ROCKS_LOG_HEADER(log, " Options.two_write_queues: %d", + two_write_queues); ROCKS_LOG_HEADER(log, " Options.manual_wal_flush: %d", manual_wal_flush); + ROCKS_LOG_HEADER(log, " Options.atomic_flush: %d", atomic_flush); + ROCKS_LOG_HEADER(log, + " Options.avoid_unnecessary_blocking_io: %d", + avoid_unnecessary_blocking_io); } MutableDBOptions::MutableDBOptions() @@ -234,23 +230,35 @@ MutableDBOptions::MutableDBOptions() base_background_compactions(-1), max_background_compactions(-1), avoid_flush_during_shutdown(false), + writable_file_max_buffer_size(1024 * 1024), delayed_write_rate(2 * 1024U * 1024U), max_total_wal_size(0), delete_obsolete_files_period_micros(6ULL * 60 * 60 * 1000000), stats_dump_period_sec(600), - max_open_files(-1) {} + stats_persist_period_sec(600), + stats_history_buffer_size(1024 * 1024), + max_open_files(-1), + bytes_per_sync(0), + wal_bytes_per_sync(0), + compaction_readahead_size(0) {} MutableDBOptions::MutableDBOptions(const DBOptions& options) : max_background_jobs(options.max_background_jobs), base_background_compactions(options.base_background_compactions), max_background_compactions(options.max_background_compactions), avoid_flush_during_shutdown(options.avoid_flush_during_shutdown), + writable_file_max_buffer_size(options.writable_file_max_buffer_size), delayed_write_rate(options.delayed_write_rate), max_total_wal_size(options.max_total_wal_size), delete_obsolete_files_period_micros( options.delete_obsolete_files_period_micros), stats_dump_period_sec(options.stats_dump_period_sec), - max_open_files(options.max_open_files) {} + stats_persist_period_sec(options.stats_persist_period_sec), + stats_history_buffer_size(options.stats_history_buffer_size), + max_open_files(options.max_open_files), + bytes_per_sync(options.bytes_per_sync), + wal_bytes_per_sync(options.wal_bytes_per_sync), + compaction_readahead_size(options.compaction_readahead_size) {} void MutableDBOptions::Dump(Logger* log) const { ROCKS_LOG_HEADER(log, " Options.max_background_jobs: %d", @@ -259,6 +267,9 @@ void MutableDBOptions::Dump(Logger* log) const { max_background_compactions); ROCKS_LOG_HEADER(log, " Options.avoid_flush_during_shutdown: %d", avoid_flush_during_shutdown); + ROCKS_LOG_HEADER( + log, " Options.writable_file_max_buffer_size: %" ROCKSDB_PRIszt, + writable_file_max_buffer_size); ROCKS_LOG_HEADER(log, " Options.delayed_write_rate : %" PRIu64, delayed_write_rate); ROCKS_LOG_HEADER(log, " Options.max_total_wal_size: %" PRIu64, @@ -268,8 +279,23 @@ void MutableDBOptions::Dump(Logger* log) const { delete_obsolete_files_period_micros); ROCKS_LOG_HEADER(log, " Options.stats_dump_period_sec: %u", stats_dump_period_sec); + ROCKS_LOG_HEADER(log, " Options.stats_persist_period_sec: %d", + stats_persist_period_sec); + ROCKS_LOG_HEADER( + log, + " Options.stats_history_buffer_size: %" ROCKSDB_PRIszt, + stats_history_buffer_size); ROCKS_LOG_HEADER(log, " Options.max_open_files: %d", max_open_files); + ROCKS_LOG_HEADER(log, + " Options.bytes_per_sync: %" PRIu64, + bytes_per_sync); + ROCKS_LOG_HEADER(log, + " Options.wal_bytes_per_sync: %" PRIu64, + wal_bytes_per_sync); + ROCKS_LOG_HEADER(log, + " Options.compaction_readahead_size: %" ROCKSDB_PRIszt, + compaction_readahead_size); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/options/db_options.h b/thirdparty/rocksdb/options/db_options.h index 18d1a5fb67..283cf7d352 100644 --- a/thirdparty/rocksdb/options/db_options.h +++ b/thirdparty/rocksdb/options/db_options.h @@ -55,12 +55,8 @@ struct ImmutableDBOptions { std::shared_ptr write_buffer_manager; DBOptions::AccessHint access_hint_on_compaction_start; bool new_table_reader_for_compaction_inputs; - size_t compaction_readahead_size; size_t random_access_max_buffer_size; - size_t writable_file_max_buffer_size; bool use_adaptive_mutex; - uint64_t bytes_per_sync; - uint64_t wal_bytes_per_sync; std::vector> listeners; bool enable_thread_tracking; bool enable_pipelined_write; @@ -79,8 +75,11 @@ struct ImmutableDBOptions { bool dump_malloc_stats; bool avoid_flush_during_recovery; bool allow_ingest_behind; - bool concurrent_prepare; + bool preserve_deletes; + bool two_write_queues; bool manual_wal_flush; + bool atomic_flush; + bool avoid_unnecessary_blocking_io; }; struct MutableDBOptions { @@ -94,11 +93,17 @@ struct MutableDBOptions { int base_background_compactions; int max_background_compactions; bool avoid_flush_during_shutdown; + size_t writable_file_max_buffer_size; uint64_t delayed_write_rate; uint64_t max_total_wal_size; uint64_t delete_obsolete_files_period_micros; unsigned int stats_dump_period_sec; + unsigned int stats_persist_period_sec; + size_t stats_history_buffer_size; int max_open_files; + uint64_t bytes_per_sync; + uint64_t wal_bytes_per_sync; + size_t compaction_readahead_size; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/options/options.cc b/thirdparty/rocksdb/options/options.cc index 7bd2c9582f..2c99545811 100644 --- a/thirdparty/rocksdb/options/options.cc +++ b/thirdparty/rocksdb/options/options.cc @@ -51,6 +51,7 @@ AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options) inplace_callback(options.inplace_callback), memtable_prefix_bloom_size_ratio( options.memtable_prefix_bloom_size_ratio), + memtable_whole_key_filtering(options.memtable_whole_key_filtering), memtable_huge_page_size(options.memtable_huge_page_size), memtable_insert_with_hint_prefix_extractor( options.memtable_insert_with_hint_prefix_extractor), @@ -85,7 +86,9 @@ AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options) optimize_filters_for_hits(options.optimize_filters_for_hits), paranoid_file_checks(options.paranoid_file_checks), force_consistency_checks(options.force_consistency_checks), - report_bg_io_stats(options.report_bg_io_stats) { + report_bg_io_stats(options.report_bg_io_stats), + ttl(options.ttl), + sample_for_compression(options.sample_for_compression) { assert(memtable_factory.get() != nullptr); if (max_bytes_for_level_multiplier_additional.size() < static_cast(num_levels)) { @@ -99,100 +102,11 @@ ColumnFamilyOptions::ColumnFamilyOptions() std::shared_ptr(new BlockBasedTableFactory())) {} ColumnFamilyOptions::ColumnFamilyOptions(const Options& options) - : AdvancedColumnFamilyOptions(options), - comparator(options.comparator), - merge_operator(options.merge_operator), - compaction_filter(options.compaction_filter), - compaction_filter_factory(options.compaction_filter_factory), - write_buffer_size(options.write_buffer_size), - compression(options.compression), - bottommost_compression(options.bottommost_compression), - compression_opts(options.compression_opts), - level0_file_num_compaction_trigger( - options.level0_file_num_compaction_trigger), - prefix_extractor(options.prefix_extractor), - max_bytes_for_level_base(options.max_bytes_for_level_base), - disable_auto_compactions(options.disable_auto_compactions), - table_factory(options.table_factory) {} + : ColumnFamilyOptions(*static_cast(&options)) {} DBOptions::DBOptions() {} - DBOptions::DBOptions(const Options& options) - : create_if_missing(options.create_if_missing), - create_missing_column_families(options.create_missing_column_families), - error_if_exists(options.error_if_exists), - paranoid_checks(options.paranoid_checks), - env(options.env), - rate_limiter(options.rate_limiter), - sst_file_manager(options.sst_file_manager), - info_log(options.info_log), - info_log_level(options.info_log_level), - max_open_files(options.max_open_files), - max_file_opening_threads(options.max_file_opening_threads), - max_total_wal_size(options.max_total_wal_size), - statistics(options.statistics), - use_fsync(options.use_fsync), - db_paths(options.db_paths), - db_log_dir(options.db_log_dir), - wal_dir(options.wal_dir), - delete_obsolete_files_period_micros( - options.delete_obsolete_files_period_micros), - max_background_jobs(options.max_background_jobs), - base_background_compactions(options.base_background_compactions), - max_background_compactions(options.max_background_compactions), - max_subcompactions(options.max_subcompactions), - max_background_flushes(options.max_background_flushes), - max_log_file_size(options.max_log_file_size), - log_file_time_to_roll(options.log_file_time_to_roll), - keep_log_file_num(options.keep_log_file_num), - recycle_log_file_num(options.recycle_log_file_num), - max_manifest_file_size(options.max_manifest_file_size), - table_cache_numshardbits(options.table_cache_numshardbits), - WAL_ttl_seconds(options.WAL_ttl_seconds), - WAL_size_limit_MB(options.WAL_size_limit_MB), - manifest_preallocation_size(options.manifest_preallocation_size), - allow_mmap_reads(options.allow_mmap_reads), - allow_mmap_writes(options.allow_mmap_writes), - use_direct_reads(options.use_direct_reads), - use_direct_io_for_flush_and_compaction( - options.use_direct_io_for_flush_and_compaction), - allow_fallocate(options.allow_fallocate), - is_fd_close_on_exec(options.is_fd_close_on_exec), - skip_log_error_on_recovery(options.skip_log_error_on_recovery), - stats_dump_period_sec(options.stats_dump_period_sec), - advise_random_on_open(options.advise_random_on_open), - db_write_buffer_size(options.db_write_buffer_size), - write_buffer_manager(options.write_buffer_manager), - access_hint_on_compaction_start(options.access_hint_on_compaction_start), - new_table_reader_for_compaction_inputs( - options.new_table_reader_for_compaction_inputs), - compaction_readahead_size(options.compaction_readahead_size), - random_access_max_buffer_size(options.random_access_max_buffer_size), - writable_file_max_buffer_size(options.writable_file_max_buffer_size), - use_adaptive_mutex(options.use_adaptive_mutex), - bytes_per_sync(options.bytes_per_sync), - wal_bytes_per_sync(options.wal_bytes_per_sync), - listeners(options.listeners), - enable_thread_tracking(options.enable_thread_tracking), - delayed_write_rate(options.delayed_write_rate), - enable_pipelined_write(options.enable_pipelined_write), - allow_concurrent_memtable_write(options.allow_concurrent_memtable_write), - enable_write_thread_adaptive_yield( - options.enable_write_thread_adaptive_yield), - write_thread_max_yield_usec(options.write_thread_max_yield_usec), - write_thread_slow_yield_usec(options.write_thread_slow_yield_usec), - skip_stats_update_on_db_open(options.skip_stats_update_on_db_open), - wal_recovery_mode(options.wal_recovery_mode), - row_cache(options.row_cache), -#ifndef ROCKSDB_LITE - wal_filter(options.wal_filter), -#endif // ROCKSDB_LITE - fail_if_options_file_error(options.fail_if_options_file_error), - dump_malloc_stats(options.dump_malloc_stats), - avoid_flush_during_recovery(options.avoid_flush_during_recovery), - avoid_flush_during_shutdown(options.avoid_flush_during_shutdown), - allow_ingest_behind(options.allow_ingest_behind) { -} + : DBOptions(*static_cast(&options)) {} void DBOptions::Dump(Logger* log) const { ImmutableDBOptions(*this).Dump(log); @@ -247,6 +161,28 @@ void ColumnFamilyOptions::Dump(Logger* log) const { min_write_buffer_number_to_merge); ROCKS_LOG_HEADER(log, " Options.max_write_buffer_number_to_maintain: %d", max_write_buffer_number_to_maintain); + ROCKS_LOG_HEADER( + log, " Options.bottommost_compression_opts.window_bits: %d", + bottommost_compression_opts.window_bits); + ROCKS_LOG_HEADER( + log, " Options.bottommost_compression_opts.level: %d", + bottommost_compression_opts.level); + ROCKS_LOG_HEADER( + log, " Options.bottommost_compression_opts.strategy: %d", + bottommost_compression_opts.strategy); + ROCKS_LOG_HEADER( + log, + " Options.bottommost_compression_opts.max_dict_bytes: " + "%" PRIu32, + bottommost_compression_opts.max_dict_bytes); + ROCKS_LOG_HEADER( + log, + " Options.bottommost_compression_opts.zstd_max_train_bytes: " + "%" PRIu32, + bottommost_compression_opts.zstd_max_train_bytes); + ROCKS_LOG_HEADER( + log, " Options.bottommost_compression_opts.enabled: %s", + bottommost_compression_opts.enabled ? "true" : "false"); ROCKS_LOG_HEADER(log, " Options.compression_opts.window_bits: %d", compression_opts.window_bits); ROCKS_LOG_HEADER(log, " Options.compression_opts.level: %d", @@ -255,8 +191,15 @@ void ColumnFamilyOptions::Dump(Logger* log) const { compression_opts.strategy); ROCKS_LOG_HEADER( log, - " Options.compression_opts.max_dict_bytes: %" ROCKSDB_PRIszt, + " Options.compression_opts.max_dict_bytes: %" PRIu32, compression_opts.max_dict_bytes); + ROCKS_LOG_HEADER(log, + " Options.compression_opts.zstd_max_train_bytes: " + "%" PRIu32, + compression_opts.zstd_max_train_bytes); + ROCKS_LOG_HEADER(log, + " Options.compression_opts.enabled: %s", + compression_opts.enabled ? "true" : "false"); ROCKS_LOG_HEADER(log, " Options.level0_file_num_compaction_trigger: %d", level0_file_num_compaction_trigger); ROCKS_LOG_HEADER(log, " Options.level0_slowdown_writes_trigger: %d", @@ -365,8 +308,6 @@ void ColumnFamilyOptions::Dump(Logger* log) const { ROCKS_LOG_HEADER(log, "Options.compaction_options_fifo.allow_compaction: %d", compaction_options_fifo.allow_compaction); - ROCKS_LOG_HEADER(log, "Options.compaction_options_fifo.ttl: %" PRIu64, - compaction_options_fifo.ttl); std::string collector_names; for (const auto& collector_factory : table_properties_collector_factories) { collector_names.append(collector_factory->Name()); @@ -386,6 +327,9 @@ void ColumnFamilyOptions::Dump(Logger* log) const { ROCKS_LOG_HEADER( log, " Options.memtable_prefix_bloom_size_ratio: %f", memtable_prefix_bloom_size_ratio); + ROCKS_LOG_HEADER(log, + " Options.memtable_whole_key_filtering: %d", + memtable_whole_key_filtering); ROCKS_LOG_HEADER(log, " Options.memtable_huge_page_size: %" ROCKSDB_PRIszt, memtable_huge_page_size); @@ -406,6 +350,8 @@ void ColumnFamilyOptions::Dump(Logger* log) const { force_consistency_checks); ROCKS_LOG_HEADER(log, " Options.report_bg_io_stats: %d", report_bg_io_stats); + ROCKS_LOG_HEADER(log, " Options.ttl: %" PRIu64, + ttl); } // ColumnFamilyOptions::Dump void Options::Dump(Logger* log) const { @@ -497,6 +443,10 @@ DBOptions* DBOptions::OldDefaults(int rocksdb_major_version, ColumnFamilyOptions* ColumnFamilyOptions::OldDefaults( int rocksdb_major_version, int rocksdb_minor_version) { + if (rocksdb_major_version < 5 || + (rocksdb_major_version == 5 && rocksdb_minor_version <= 18)) { + compaction_pri = CompactionPri::kByCompensatedSize; + } if (rocksdb_major_version < 4 || (rocksdb_major_version == 4 && rocksdb_minor_version < 7)) { write_buffer_size = 4 << 20; @@ -510,7 +460,6 @@ ColumnFamilyOptions* ColumnFamilyOptions::OldDefaults( } else if (rocksdb_major_version == 5 && rocksdb_minor_version < 2) { level0_stop_writes_trigger = 30; } - compaction_pri = CompactionPri::kByCompensatedSize; return this; } @@ -537,6 +486,9 @@ ColumnFamilyOptions* ColumnFamilyOptions::OptimizeForPointLookup( prefix_extractor.reset(NewNoopTransform()); BlockBasedTableOptions block_based_options; block_based_options.index_type = BlockBasedTableOptions::kHashSearch; + block_based_options.data_block_index_type = + BlockBasedTableOptions::kDataBlockBinaryAndHash; + block_based_options.data_block_hash_table_util_ratio = 0.75; block_based_options.filter_policy.reset(NewBloomFilterPolicy(10)); block_based_options.block_cache = NewLRUCache(static_cast(block_cache_size_mb * 1024 * 1024)); @@ -592,8 +544,7 @@ ColumnFamilyOptions* ColumnFamilyOptions::OptimizeUniversalStyleCompaction( } DBOptions* DBOptions::IncreaseParallelism(int total_threads) { - max_background_compactions = total_threads - 1; - max_background_flushes = 1; + max_background_jobs = total_threads; env->SetBackgroundThreads(total_threads, Env::LOW); env->SetBackgroundThreads(1, Env::HIGH); return this; @@ -603,6 +554,7 @@ DBOptions* DBOptions::IncreaseParallelism(int total_threads) { ReadOptions::ReadOptions() : snapshot(nullptr), + iterate_lower_bound(nullptr), iterate_upper_bound(nullptr), readahead_size(0), max_skippable_internal_keys(0), @@ -615,10 +567,12 @@ ReadOptions::ReadOptions() prefix_same_as_start(false), pin_data(false), background_purge_on_iterator_cleanup(false), - ignore_range_deletions(false) {} + ignore_range_deletions(false), + iter_start_seqnum(0) {} ReadOptions::ReadOptions(bool cksum, bool cache) : snapshot(nullptr), + iterate_lower_bound(nullptr), iterate_upper_bound(nullptr), readahead_size(0), max_skippable_internal_keys(0), @@ -631,6 +585,7 @@ ReadOptions::ReadOptions(bool cksum, bool cache) prefix_same_as_start(false), pin_data(false), background_purge_on_iterator_cleanup(false), - ignore_range_deletions(false) {} + ignore_range_deletions(false), + iter_start_seqnum(0) {} } // namespace rocksdb diff --git a/thirdparty/rocksdb/options/options_helper.cc b/thirdparty/rocksdb/options/options_helper.cc index 5cf548fb9e..9facf6e946 100644 --- a/thirdparty/rocksdb/options/options_helper.cc +++ b/thirdparty/rocksdb/options/options_helper.cc @@ -19,6 +19,7 @@ #include "rocksdb/rate_limiter.h" #include "rocksdb/slice_transform.h" #include "rocksdb/table.h" +#include "rocksdb/utilities/object_registry.h" #include "table/block_based_table_factory.h" #include "table/plain_table_factory.h" #include "util/cast_util.h" @@ -56,6 +57,8 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options, mutable_db_options.base_background_compactions; options.max_background_compactions = mutable_db_options.max_background_compactions; + options.bytes_per_sync = mutable_db_options.bytes_per_sync; + options.wal_bytes_per_sync = mutable_db_options.wal_bytes_per_sync; options.max_subcompactions = immutable_db_options.max_subcompactions; options.max_background_flushes = immutable_db_options.max_background_flushes; options.max_log_file_size = immutable_db_options.max_log_file_size; @@ -77,6 +80,10 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options, options.allow_fallocate = immutable_db_options.allow_fallocate; options.is_fd_close_on_exec = immutable_db_options.is_fd_close_on_exec; options.stats_dump_period_sec = mutable_db_options.stats_dump_period_sec; + options.stats_persist_period_sec = + mutable_db_options.stats_persist_period_sec; + options.stats_history_buffer_size = + mutable_db_options.stats_history_buffer_size; options.advise_random_on_open = immutable_db_options.advise_random_on_open; options.db_write_buffer_size = immutable_db_options.db_write_buffer_size; options.write_buffer_manager = immutable_db_options.write_buffer_manager; @@ -85,17 +92,16 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options, options.new_table_reader_for_compaction_inputs = immutable_db_options.new_table_reader_for_compaction_inputs; options.compaction_readahead_size = - immutable_db_options.compaction_readahead_size; + mutable_db_options.compaction_readahead_size; options.random_access_max_buffer_size = immutable_db_options.random_access_max_buffer_size; options.writable_file_max_buffer_size = - immutable_db_options.writable_file_max_buffer_size; + mutable_db_options.writable_file_max_buffer_size; options.use_adaptive_mutex = immutable_db_options.use_adaptive_mutex; - options.bytes_per_sync = immutable_db_options.bytes_per_sync; - options.wal_bytes_per_sync = immutable_db_options.wal_bytes_per_sync; options.listeners = immutable_db_options.listeners; options.enable_thread_tracking = immutable_db_options.enable_thread_tracking; options.delayed_write_rate = mutable_db_options.delayed_write_rate; + options.enable_pipelined_write = immutable_db_options.enable_pipelined_write; options.allow_concurrent_memtable_write = immutable_db_options.allow_concurrent_memtable_write; options.enable_write_thread_adaptive_yield = @@ -121,6 +127,13 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options, mutable_db_options.avoid_flush_during_shutdown; options.allow_ingest_behind = immutable_db_options.allow_ingest_behind; + options.preserve_deletes = + immutable_db_options.preserve_deletes; + options.two_write_queues = immutable_db_options.two_write_queues; + options.manual_wal_flush = immutable_db_options.manual_wal_flush; + options.atomic_flush = immutable_db_options.atomic_flush; + options.avoid_unnecessary_blocking_io = + immutable_db_options.avoid_unnecessary_blocking_io; return options; } @@ -136,14 +149,21 @@ ColumnFamilyOptions BuildColumnFamilyOptions( cf_opts.arena_block_size = mutable_cf_options.arena_block_size; cf_opts.memtable_prefix_bloom_size_ratio = mutable_cf_options.memtable_prefix_bloom_size_ratio; + cf_opts.memtable_whole_key_filtering = + mutable_cf_options.memtable_whole_key_filtering; cf_opts.memtable_huge_page_size = mutable_cf_options.memtable_huge_page_size; cf_opts.max_successive_merges = mutable_cf_options.max_successive_merges; cf_opts.inplace_update_num_locks = mutable_cf_options.inplace_update_num_locks; + cf_opts.prefix_extractor = mutable_cf_options.prefix_extractor; // Compaction related options cf_opts.disable_auto_compactions = mutable_cf_options.disable_auto_compactions; + cf_opts.soft_pending_compaction_bytes_limit = + mutable_cf_options.soft_pending_compaction_bytes_limit; + cf_opts.hard_pending_compaction_bytes_limit = + mutable_cf_options.hard_pending_compaction_bytes_limit; cf_opts.level0_file_num_compaction_trigger = mutable_cf_options.level0_file_num_compaction_trigger; cf_opts.level0_slowdown_writes_trigger = @@ -158,6 +178,7 @@ ColumnFamilyOptions BuildColumnFamilyOptions( mutable_cf_options.max_bytes_for_level_base; cf_opts.max_bytes_for_level_multiplier = mutable_cf_options.max_bytes_for_level_multiplier; + cf_opts.ttl = mutable_cf_options.ttl; cf_opts.max_bytes_for_level_multiplier_additional.clear(); for (auto value : @@ -165,12 +186,17 @@ ColumnFamilyOptions BuildColumnFamilyOptions( cf_opts.max_bytes_for_level_multiplier_additional.emplace_back(value); } + cf_opts.compaction_options_fifo = mutable_cf_options.compaction_options_fifo; + cf_opts.compaction_options_universal = + mutable_cf_options.compaction_options_universal; + // Misc options cf_opts.max_sequential_skip_in_iterations = mutable_cf_options.max_sequential_skip_in_iterations; cf_opts.paranoid_file_checks = mutable_cf_options.paranoid_file_checks; cf_opts.report_bg_io_stats = mutable_cf_options.report_bg_io_stats; cf_opts.compression = mutable_cf_options.compression; + cf_opts.sample_for_compression = mutable_cf_options.sample_for_compression; cf_opts.table_factory = options.table_factory; // TODO(yhchiang): find some way to handle the following derived options @@ -179,8 +205,53 @@ ColumnFamilyOptions BuildColumnFamilyOptions( return cf_opts; } +std::map + OptionsHelper::compaction_style_to_string = { + {kCompactionStyleLevel, "kCompactionStyleLevel"}, + {kCompactionStyleUniversal, "kCompactionStyleUniversal"}, + {kCompactionStyleFIFO, "kCompactionStyleFIFO"}, + {kCompactionStyleNone, "kCompactionStyleNone"}}; + +std::map OptionsHelper::compaction_pri_to_string = { + {kByCompensatedSize, "kByCompensatedSize"}, + {kOldestLargestSeqFirst, "kOldestLargestSeqFirst"}, + {kOldestSmallestSeqFirst, "kOldestSmallestSeqFirst"}, + {kMinOverlappingRatio, "kMinOverlappingRatio"}}; + +std::map + OptionsHelper::compaction_stop_style_to_string = { + {kCompactionStopStyleSimilarSize, "kCompactionStopStyleSimilarSize"}, + {kCompactionStopStyleTotalSize, "kCompactionStopStyleTotalSize"}}; + +std::unordered_map + OptionsHelper::checksum_type_string_map = {{"kNoChecksum", kNoChecksum}, + {"kCRC32c", kCRC32c}, + {"kxxHash", kxxHash}, + {"kxxHash64", kxxHash64}}; + +std::unordered_map + OptionsHelper::compression_type_string_map = { + {"kNoCompression", kNoCompression}, + {"kSnappyCompression", kSnappyCompression}, + {"kZlibCompression", kZlibCompression}, + {"kBZip2Compression", kBZip2Compression}, + {"kLZ4Compression", kLZ4Compression}, + {"kLZ4HCCompression", kLZ4HCCompression}, + {"kXpressCompression", kXpressCompression}, + {"kZSTD", kZSTD}, + {"kZSTDNotFinalCompression", kZSTDNotFinalCompression}, + {"kDisableCompressionOption", kDisableCompressionOption}}; #ifndef ROCKSDB_LITE +const std::string kNameComparator = "comparator"; +const std::string kNameMergeOperator = "merge_operator"; + +template +Status GetStringFromStruct( + std::string* opt_string, const T& options, + const std::unordered_map type_info, + const std::string& delimiter); + namespace { template bool ParseEnum(const std::unordered_map& type_map, @@ -255,11 +326,83 @@ bool ParseVectorCompressionType( return true; } +// This is to handle backward compatibility, where compaction_options_fifo +// could be assigned a single scalar value, say, like "23", which would be +// assigned to max_table_files_size. +bool FIFOCompactionOptionsSpecialCase(const std::string& opt_str, + CompactionOptionsFIFO* options) { + if (opt_str.find("=") != std::string::npos) { + // New format. Go do your new parsing using ParseStructOptions. + return false; + } + + // Old format. Parse just a single uint64_t value. + options->max_table_files_size = ParseUint64(opt_str); + return true; +} + +template +bool SerializeStruct( + const T& options, std::string* value, + std::unordered_map type_info_map) { + std::string opt_str; + Status s = GetStringFromStruct(&opt_str, options, type_info_map, ";"); + if (!s.ok()) { + return false; + } + *value = "{" + opt_str + "}"; + return true; +} + +template +bool ParseSingleStructOption( + const std::string& opt_val_str, T* options, + std::unordered_map type_info_map) { + size_t end = opt_val_str.find('='); + std::string key = opt_val_str.substr(0, end); + std::string value = opt_val_str.substr(end + 1); + auto iter = type_info_map.find(key); + if (iter == type_info_map.end()) { + return false; + } + const auto& opt_info = iter->second; + return ParseOptionHelper( + reinterpret_cast(options) + opt_info.mutable_offset, opt_info.type, + value); +} + +template +bool ParseStructOptions( + const std::string& opt_str, T* options, + std::unordered_map type_info_map) { + assert(!opt_str.empty()); + + size_t start = 0; + if (opt_str[0] == '{') { + start++; + } + while ((start != std::string::npos) && (start < opt_str.size())) { + if (opt_str[start] == '}') { + break; + } + size_t end = opt_str.find(';', start); + size_t len = (end == std::string::npos) ? end : end - start; + if (!ParseSingleStructOption(opt_str.substr(start, len), options, + type_info_map)) { + return false; + } + start = (end == std::string::npos) ? end : end + 1; + } + return true; +} +} // anonymouse namespace + bool ParseSliceTransformHelper( const std::string& kFixedPrefixName, const std::string& kCappedPrefixName, const std::string& value, std::shared_ptr* slice_transform) { - + const char* no_op_name = "rocksdb.Noop"; + size_t no_op_length = strlen(no_op_name); auto& pe_value = value; if (pe_value.size() > kFixedPrefixName.size() && pe_value.compare(0, kFixedPrefixName.size(), kFixedPrefixName) == 0) { @@ -271,6 +414,10 @@ bool ParseSliceTransformHelper( int prefix_length = ParseInt(trim(pe_value.substr(kCappedPrefixName.size()))); slice_transform->reset(NewCappedPrefixTransform(prefix_length)); + } else if (pe_value.size() == no_op_length && + pe_value.compare(0, no_op_length, no_op_name) == 0) { + const SliceTransform* no_op_transform = NewNoopTransform(); + slice_transform->reset(no_op_transform); } else if (value == kNullptrString) { slice_transform->reset(); } else { @@ -304,7 +451,6 @@ bool ParseSliceTransform( // SliceTransforms here. return false; } -} // anonymouse namespace bool ParseOptionHelper(char* opt_address, const OptionType& opt_type, const std::string& value) { @@ -315,6 +461,12 @@ bool ParseOptionHelper(char* opt_address, const OptionType& opt_type, case OptionType::kInt: *reinterpret_cast(opt_address) = ParseInt(value); break; + case OptionType::kInt32T: + *reinterpret_cast(opt_address) = ParseInt32(value); + break; + case OptionType::kInt64T: + PutUnaligned(reinterpret_cast(opt_address), ParseInt64(value)); + break; case OptionType::kVectorInt: *reinterpret_cast*>(opt_address) = ParseVectorInt(value); break; @@ -363,6 +515,11 @@ bool ParseOptionHelper(char* opt_address, const OptionType& opt_type, return ParseEnum( block_base_table_index_type_string_map, value, reinterpret_cast(opt_address)); + case OptionType::kBlockBasedTableDataBlockIndexType: + return ParseEnum( + block_base_table_data_block_index_type_string_map, value, + reinterpret_cast( + opt_address)); case OptionType::kEncodingType: return ParseEnum( encoding_type_string_map, value, @@ -379,6 +536,28 @@ bool ParseOptionHelper(char* opt_address, const OptionType& opt_type, return ParseEnum( info_log_level_string_map, value, reinterpret_cast(opt_address)); + case OptionType::kCompactionOptionsFIFO: { + if (!FIFOCompactionOptionsSpecialCase( + value, reinterpret_cast(opt_address))) { + return ParseStructOptions( + value, reinterpret_cast(opt_address), + fifo_compaction_options_type_info); + } + return true; + } + case OptionType::kLRUCacheOptions: { + return ParseStructOptions(value, + reinterpret_cast(opt_address), + lru_cache_options_type_info); + } + case OptionType::kCompactionOptionsUniversal: + return ParseStructOptions( + value, reinterpret_cast(opt_address), + universal_compaction_options_type_info); + case OptionType::kCompactionStopStyle: + return ParseEnum( + compaction_stop_style_string_map, value, + reinterpret_cast(opt_address)); default: return false; } @@ -397,6 +576,16 @@ bool SerializeSingleOptionHelper(const char* opt_address, case OptionType::kInt: *value = ToString(*(reinterpret_cast(opt_address))); break; + case OptionType::kInt32T: + *value = ToString(*(reinterpret_cast(opt_address))); + break; + case OptionType::kInt64T: + { + int64_t v; + GetUnaligned(reinterpret_cast(opt_address), &v); + *value = ToString(v); + } + break; case OptionType::kVectorInt: return SerializeIntVector( *reinterpret_cast*>(opt_address), value); @@ -520,6 +709,12 @@ bool SerializeSingleOptionHelper(const char* opt_address, *reinterpret_cast( opt_address), value); + case OptionType::kBlockBasedTableDataBlockIndexType: + return SerializeEnum( + block_base_table_data_block_index_type_string_map, + *reinterpret_cast( + opt_address), + value); case OptionType::kFlushBlockPolicyFactory: { const auto* ptr = reinterpret_cast*>( @@ -543,6 +738,18 @@ bool SerializeSingleOptionHelper(const char* opt_address, return SerializeEnum( info_log_level_string_map, *reinterpret_cast(opt_address), value); + case OptionType::kCompactionOptionsFIFO: + return SerializeStruct( + *reinterpret_cast(opt_address), value, + fifo_compaction_options_type_info); + case OptionType::kCompactionOptionsUniversal: + return SerializeStruct( + *reinterpret_cast(opt_address), + value, universal_compaction_options_type_info); + case OptionType::kCompactionStopStyle: + return SerializeEnum( + compaction_stop_style_string_map, + *reinterpret_cast(opt_address), value); default: return false; } @@ -552,7 +759,7 @@ bool SerializeSingleOptionHelper(const char* opt_address, Status GetMutableOptionsFromStrings( const MutableCFOptions& base_options, const std::unordered_map& options_map, - MutableCFOptions* new_options) { + Logger* info_log, MutableCFOptions* new_options) { assert(new_options); *new_options = base_options; for (const auto& o : options_map) { @@ -565,6 +772,13 @@ Status GetMutableOptionsFromStrings( if (!opt_info.is_mutable) { return Status::InvalidArgument("Option not changeable: " + o.first); } + if (opt_info.verification == OptionVerificationType::kDeprecated) { + // log warning when user tries to set a deprecated option but don't fail + // the call for compatibility. + ROCKS_LOG_WARN(info_log, "%s is a deprecated option and cannot be set", + o.first.c_str()); + continue; + } bool is_ok = ParseOptionHelper( reinterpret_cast(new_options) + opt_info.mutable_offset, opt_info.type, o.second); @@ -685,6 +899,65 @@ Status StringToMap(const std::string& opts_str, return Status::OK(); } +Status ParseCompressionOptions(const std::string& value, const std::string& name, + CompressionOptions& compression_opts) { + size_t start = 0; + size_t end = value.find(':'); + if (end == std::string::npos) { + return Status::InvalidArgument("unable to parse the specified CF option " + + name); + } + compression_opts.window_bits = ParseInt(value.substr(start, end - start)); + start = end + 1; + end = value.find(':', start); + if (end == std::string::npos) { + return Status::InvalidArgument("unable to parse the specified CF option " + + name); + } + compression_opts.level = ParseInt(value.substr(start, end - start)); + start = end + 1; + if (start >= value.size()) { + return Status::InvalidArgument("unable to parse the specified CF option " + + name); + } + end = value.find(':', start); + compression_opts.strategy = + ParseInt(value.substr(start, value.size() - start)); + // max_dict_bytes is optional for backwards compatibility + if (end != std::string::npos) { + start = end + 1; + if (start >= value.size()) { + return Status::InvalidArgument( + "unable to parse the specified CF option " + name); + } + compression_opts.max_dict_bytes = + ParseInt(value.substr(start, value.size() - start)); + end = value.find(':', start); + } + // zstd_max_train_bytes is optional for backwards compatibility + if (end != std::string::npos) { + start = end + 1; + if (start >= value.size()) { + return Status::InvalidArgument( + "unable to parse the specified CF option " + name); + } + compression_opts.zstd_max_train_bytes = + ParseInt(value.substr(start, value.size() - start)); + end = value.find(':', start); + } + // enabled is optional for backwards compatibility + if (end != std::string::npos) { + start = end + 1; + if (start >= value.size()) { + return Status::InvalidArgument( + "unable to parse the specified CF option " + name); + } + compression_opts.enabled = + ParseBoolean("", value.substr(start, value.size() - start)); + } + return Status::OK(); +} + Status ParseColumnFamilyOption(const std::string& name, const std::string& org_value, ColumnFamilyOptions* new_options, @@ -733,45 +1006,39 @@ Status ParseColumnFamilyOption(const std::string& name, "unable to parse the specified CF option " + name); } new_options->memtable_factory.reset(new_mem_factory.release()); - } else if (name == "compression_opts") { - size_t start = 0; - size_t end = value.find(':'); - if (end == std::string::npos) { - return Status::InvalidArgument( - "unable to parse the specified CF option " + name); - } - new_options->compression_opts.window_bits = - ParseInt(value.substr(start, end - start)); - start = end + 1; - end = value.find(':', start); - if (end == std::string::npos) { - return Status::InvalidArgument( - "unable to parse the specified CF option " + name); + } else if (name == "bottommost_compression_opts") { + Status s = ParseCompressionOptions( + value, name, new_options->bottommost_compression_opts); + if (!s.ok()) { + return s; } - new_options->compression_opts.level = - ParseInt(value.substr(start, end - start)); - start = end + 1; - if (start >= value.size()) { - return Status::InvalidArgument( - "unable to parse the specified CF option " + name); + } else if (name == "compression_opts") { + Status s = + ParseCompressionOptions(value, name, new_options->compression_opts); + if (!s.ok()) { + return s; } - end = value.find(':', start); - new_options->compression_opts.strategy = - ParseInt(value.substr(start, value.size() - start)); - // max_dict_bytes is optional for backwards compatibility - if (end != std::string::npos) { - start = end + 1; - if (start >= value.size()) { - return Status::InvalidArgument( - "unable to parse the specified CF option " + name); + } else { + if (name == kNameComparator) { + // Try to get comparator from object registry first. + std::unique_ptr comp_guard; + const Comparator* comp = + NewCustomObject(value, &comp_guard); + // Only support static comparator for now. + if (comp != nullptr && !comp_guard) { + new_options->comparator = comp; + } + } else if (name == kNameMergeOperator) { + // Try to get merge operator from object registry first. + std::unique_ptr> mo_guard; + std::shared_ptr* mo = + NewCustomObject>(value, &mo_guard); + // Only support static comparator for now. + if (mo != nullptr) { + new_options->merge_operator = *mo; } - new_options->compression_opts.max_dict_bytes = - ParseInt(value.substr(start, value.size() - start)); } - } else if (name == "compaction_options_fifo") { - new_options->compaction_options_fifo.max_table_files_size = - ParseUint64(value); - } else { + auto iter = cf_options_type_info.find(name); if (iter == cf_options_type_info.end()) { return Status::InvalidArgument( @@ -787,6 +1054,7 @@ Status ParseColumnFamilyOption(const std::string& name, switch (opt_info.verification) { case OptionVerificationType::kByName: case OptionVerificationType::kByNameAllowNull: + case OptionVerificationType::kByNameAllowFromNull: return Status::NotSupported( "Deserializing the specified CF option " + name + " is not supported"); @@ -804,17 +1072,18 @@ Status ParseColumnFamilyOption(const std::string& name, return Status::OK(); } -bool SerializeSingleDBOption(std::string* opt_string, - const DBOptions& db_options, - const std::string& name, - const std::string& delimiter) { - auto iter = db_options_type_info.find(name); - if (iter == db_options_type_info.end()) { +template +bool SerializeSingleStructOption( + std::string* opt_string, const T& options, + const std::unordered_map type_info, + const std::string& name, const std::string& delimiter) { + auto iter = type_info.find(name); + if (iter == type_info.end()) { return false; } auto& opt_info = iter->second; const char* opt_address = - reinterpret_cast(&db_options) + opt_info.offset; + reinterpret_cast(&options) + opt_info.offset; std::string value; bool result = SerializeSingleOptionHelper(opt_address, opt_info.type, &value); if (result) { @@ -823,72 +1092,45 @@ bool SerializeSingleDBOption(std::string* opt_string, return result; } -Status GetStringFromDBOptions(std::string* opt_string, - const DBOptions& db_options, - const std::string& delimiter) { +template +Status GetStringFromStruct( + std::string* opt_string, const T& options, + const std::unordered_map type_info, + const std::string& delimiter) { assert(opt_string); opt_string->clear(); - for (auto iter = db_options_type_info.begin(); - iter != db_options_type_info.end(); ++iter) { + for (auto iter = type_info.begin(); iter != type_info.end(); ++iter) { if (iter->second.verification == OptionVerificationType::kDeprecated) { // If the option is no longer used in rocksdb and marked as deprecated, // we skip it in the serialization. continue; } std::string single_output; - bool result = SerializeSingleDBOption(&single_output, db_options, - iter->first, delimiter); - assert(result); + bool result = SerializeSingleStructOption( + &single_output, options, type_info, iter->first, delimiter); if (result) { opt_string->append(single_output); + } else { + return Status::InvalidArgument("failed to serialize %s\n", + iter->first.c_str()); } + assert(result); } return Status::OK(); } -bool SerializeSingleColumnFamilyOption(std::string* opt_string, - const ColumnFamilyOptions& cf_options, - const std::string& name, - const std::string& delimiter) { - auto iter = cf_options_type_info.find(name); - if (iter == cf_options_type_info.end()) { - return false; - } - auto& opt_info = iter->second; - const char* opt_address = - reinterpret_cast(&cf_options) + opt_info.offset; - std::string value; - bool result = SerializeSingleOptionHelper(opt_address, opt_info.type, &value); - if (result) { - *opt_string = name + "=" + value + delimiter; - } - return result; +Status GetStringFromDBOptions(std::string* opt_string, + const DBOptions& db_options, + const std::string& delimiter) { + return GetStringFromStruct(opt_string, db_options, + db_options_type_info, delimiter); } Status GetStringFromColumnFamilyOptions(std::string* opt_string, const ColumnFamilyOptions& cf_options, const std::string& delimiter) { - assert(opt_string); - opt_string->clear(); - for (auto iter = cf_options_type_info.begin(); - iter != cf_options_type_info.end(); ++iter) { - if (iter->second.verification == OptionVerificationType::kDeprecated) { - // If the option is no longer used in rocksdb and marked as deprecated, - // we skip it in the serialization. - continue; - } - std::string single_output; - bool result = SerializeSingleColumnFamilyOption(&single_output, cf_options, - iter->first, delimiter); - if (result) { - opt_string->append(single_output); - } else { - return Status::InvalidArgument("failed to serialize %s\n", - iter->first.c_str()); - } - assert(result); - } - return Status::OK(); + return GetStringFromStruct( + opt_string, cf_options, cf_options_type_info, delimiter); } Status GetStringFromCompressionType(std::string* compression_str, @@ -1128,6 +1370,674 @@ Status GetTableFactoryFromMap( return Status::OK(); } +std::unordered_map + OptionsHelper::db_options_type_info = { + /* + // not yet supported + Env* env; + std::shared_ptr row_cache; + std::shared_ptr delete_scheduler; + std::shared_ptr info_log; + std::shared_ptr rate_limiter; + std::shared_ptr statistics; + std::vector db_paths; + std::vector> listeners; + */ + {"advise_random_on_open", + {offsetof(struct DBOptions, advise_random_on_open), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"allow_mmap_reads", + {offsetof(struct DBOptions, allow_mmap_reads), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"allow_fallocate", + {offsetof(struct DBOptions, allow_fallocate), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"allow_mmap_writes", + {offsetof(struct DBOptions, allow_mmap_writes), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"use_direct_reads", + {offsetof(struct DBOptions, use_direct_reads), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"use_direct_writes", + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, + 0}}, + {"use_direct_io_for_flush_and_compaction", + {offsetof(struct DBOptions, use_direct_io_for_flush_and_compaction), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"allow_2pc", + {offsetof(struct DBOptions, allow_2pc), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"allow_os_buffer", + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true, + 0}}, + {"create_if_missing", + {offsetof(struct DBOptions, create_if_missing), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"create_missing_column_families", + {offsetof(struct DBOptions, create_missing_column_families), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"disableDataSync", + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, + 0}}, + {"disable_data_sync", // for compatibility + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, + 0}}, + {"enable_thread_tracking", + {offsetof(struct DBOptions, enable_thread_tracking), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"error_if_exists", + {offsetof(struct DBOptions, error_if_exists), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"is_fd_close_on_exec", + {offsetof(struct DBOptions, is_fd_close_on_exec), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"paranoid_checks", + {offsetof(struct DBOptions, paranoid_checks), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"skip_log_error_on_recovery", + {offsetof(struct DBOptions, skip_log_error_on_recovery), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"skip_stats_update_on_db_open", + {offsetof(struct DBOptions, skip_stats_update_on_db_open), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"new_table_reader_for_compaction_inputs", + {offsetof(struct DBOptions, new_table_reader_for_compaction_inputs), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"compaction_readahead_size", + {offsetof(struct DBOptions, compaction_readahead_size), + OptionType::kSizeT, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, compaction_readahead_size)}}, + {"random_access_max_buffer_size", + {offsetof(struct DBOptions, random_access_max_buffer_size), + OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}, + {"use_adaptive_mutex", + {offsetof(struct DBOptions, use_adaptive_mutex), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"use_fsync", + {offsetof(struct DBOptions, use_fsync), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"max_background_jobs", + {offsetof(struct DBOptions, max_background_jobs), OptionType::kInt, + OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, max_background_jobs)}}, + {"max_background_compactions", + {offsetof(struct DBOptions, max_background_compactions), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, max_background_compactions)}}, + {"base_background_compactions", + {offsetof(struct DBOptions, base_background_compactions), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, base_background_compactions)}}, + {"max_background_flushes", + {offsetof(struct DBOptions, max_background_flushes), OptionType::kInt, + OptionVerificationType::kNormal, false, 0}}, + {"max_file_opening_threads", + {offsetof(struct DBOptions, max_file_opening_threads), + OptionType::kInt, OptionVerificationType::kNormal, false, 0}}, + {"max_open_files", + {offsetof(struct DBOptions, max_open_files), OptionType::kInt, + OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, max_open_files)}}, + {"table_cache_numshardbits", + {offsetof(struct DBOptions, table_cache_numshardbits), + OptionType::kInt, OptionVerificationType::kNormal, false, 0}}, + {"db_write_buffer_size", + {offsetof(struct DBOptions, db_write_buffer_size), OptionType::kSizeT, + OptionVerificationType::kNormal, false, 0}}, + {"keep_log_file_num", + {offsetof(struct DBOptions, keep_log_file_num), OptionType::kSizeT, + OptionVerificationType::kNormal, false, 0}}, + {"recycle_log_file_num", + {offsetof(struct DBOptions, recycle_log_file_num), OptionType::kSizeT, + OptionVerificationType::kNormal, false, 0}}, + {"log_file_time_to_roll", + {offsetof(struct DBOptions, log_file_time_to_roll), OptionType::kSizeT, + OptionVerificationType::kNormal, false, 0}}, + {"manifest_preallocation_size", + {offsetof(struct DBOptions, manifest_preallocation_size), + OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}, + {"max_log_file_size", + {offsetof(struct DBOptions, max_log_file_size), OptionType::kSizeT, + OptionVerificationType::kNormal, false, 0}}, + {"db_log_dir", + {offsetof(struct DBOptions, db_log_dir), OptionType::kString, + OptionVerificationType::kNormal, false, 0}}, + {"wal_dir", + {offsetof(struct DBOptions, wal_dir), OptionType::kString, + OptionVerificationType::kNormal, false, 0}}, + {"max_subcompactions", + {offsetof(struct DBOptions, max_subcompactions), OptionType::kUInt32T, + OptionVerificationType::kNormal, false, 0}}, + {"WAL_size_limit_MB", + {offsetof(struct DBOptions, WAL_size_limit_MB), OptionType::kUInt64T, + OptionVerificationType::kNormal, false, 0}}, + {"WAL_ttl_seconds", + {offsetof(struct DBOptions, WAL_ttl_seconds), OptionType::kUInt64T, + OptionVerificationType::kNormal, false, 0}}, + {"bytes_per_sync", + {offsetof(struct DBOptions, bytes_per_sync), OptionType::kUInt64T, + OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, bytes_per_sync)}}, + {"delayed_write_rate", + {offsetof(struct DBOptions, delayed_write_rate), OptionType::kUInt64T, + OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, delayed_write_rate)}}, + {"delete_obsolete_files_period_micros", + {offsetof(struct DBOptions, delete_obsolete_files_period_micros), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, + delete_obsolete_files_period_micros)}}, + {"max_manifest_file_size", + {offsetof(struct DBOptions, max_manifest_file_size), + OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}}, + {"max_total_wal_size", + {offsetof(struct DBOptions, max_total_wal_size), OptionType::kUInt64T, + OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, max_total_wal_size)}}, + {"wal_bytes_per_sync", + {offsetof(struct DBOptions, wal_bytes_per_sync), OptionType::kUInt64T, + OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, wal_bytes_per_sync)}}, + {"stats_dump_period_sec", + {offsetof(struct DBOptions, stats_dump_period_sec), OptionType::kUInt, + OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, stats_dump_period_sec)}}, + {"stats_persist_period_sec", + {offsetof(struct DBOptions, stats_persist_period_sec), + OptionType::kUInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, stats_persist_period_sec)}}, + {"stats_history_buffer_size", + {offsetof(struct DBOptions, stats_history_buffer_size), + OptionType::kSizeT, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, stats_history_buffer_size)}}, + {"fail_if_options_file_error", + {offsetof(struct DBOptions, fail_if_options_file_error), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"enable_pipelined_write", + {offsetof(struct DBOptions, enable_pipelined_write), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"allow_concurrent_memtable_write", + {offsetof(struct DBOptions, allow_concurrent_memtable_write), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"wal_recovery_mode", + {offsetof(struct DBOptions, wal_recovery_mode), + OptionType::kWALRecoveryMode, OptionVerificationType::kNormal, false, + 0}}, + {"enable_write_thread_adaptive_yield", + {offsetof(struct DBOptions, enable_write_thread_adaptive_yield), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"write_thread_slow_yield_usec", + {offsetof(struct DBOptions, write_thread_slow_yield_usec), + OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}}, + {"write_thread_max_yield_usec", + {offsetof(struct DBOptions, write_thread_max_yield_usec), + OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}}, + {"access_hint_on_compaction_start", + {offsetof(struct DBOptions, access_hint_on_compaction_start), + OptionType::kAccessHint, OptionVerificationType::kNormal, false, 0}}, + {"info_log_level", + {offsetof(struct DBOptions, info_log_level), OptionType::kInfoLogLevel, + OptionVerificationType::kNormal, false, 0}}, + {"dump_malloc_stats", + {offsetof(struct DBOptions, dump_malloc_stats), OptionType::kBoolean, + OptionVerificationType::kNormal, false, 0}}, + {"avoid_flush_during_recovery", + {offsetof(struct DBOptions, avoid_flush_during_recovery), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"avoid_flush_during_shutdown", + {offsetof(struct DBOptions, avoid_flush_during_shutdown), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, avoid_flush_during_shutdown)}}, + {"writable_file_max_buffer_size", + {offsetof(struct DBOptions, writable_file_max_buffer_size), + OptionType::kSizeT, OptionVerificationType::kNormal, true, + offsetof(struct MutableDBOptions, writable_file_max_buffer_size)}}, + {"allow_ingest_behind", + {offsetof(struct DBOptions, allow_ingest_behind), OptionType::kBoolean, + OptionVerificationType::kNormal, false, + offsetof(struct ImmutableDBOptions, allow_ingest_behind)}}, + {"preserve_deletes", + {offsetof(struct DBOptions, preserve_deletes), OptionType::kBoolean, + OptionVerificationType::kNormal, false, + offsetof(struct ImmutableDBOptions, preserve_deletes)}}, + {"concurrent_prepare", // Deprecated by two_write_queues + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, + 0}}, + {"two_write_queues", + {offsetof(struct DBOptions, two_write_queues), OptionType::kBoolean, + OptionVerificationType::kNormal, false, + offsetof(struct ImmutableDBOptions, two_write_queues)}}, + {"manual_wal_flush", + {offsetof(struct DBOptions, manual_wal_flush), OptionType::kBoolean, + OptionVerificationType::kNormal, false, + offsetof(struct ImmutableDBOptions, manual_wal_flush)}}, + {"seq_per_batch", + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, + 0}}, + {"atomic_flush", + {offsetof(struct DBOptions, atomic_flush), OptionType::kBoolean, + OptionVerificationType::kNormal, false, + offsetof(struct ImmutableDBOptions, atomic_flush)}}, + {"avoid_unnecessary_blocking_io", + {offsetof(struct DBOptions, avoid_unnecessary_blocking_io), + OptionType::kBoolean, OptionVerificationType::kNormal, false, + offsetof(struct ImmutableDBOptions, avoid_unnecessary_blocking_io)}} + }; + +std::unordered_map + OptionsHelper::block_base_table_index_type_string_map = { + {"kBinarySearch", BlockBasedTableOptions::IndexType::kBinarySearch}, + {"kHashSearch", BlockBasedTableOptions::IndexType::kHashSearch}, + {"kTwoLevelIndexSearch", + BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch}}; + +std::unordered_map + OptionsHelper::block_base_table_data_block_index_type_string_map = { + {"kDataBlockBinarySearch", + BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinarySearch}, + {"kDataBlockBinaryAndHash", + BlockBasedTableOptions::DataBlockIndexType::kDataBlockBinaryAndHash}}; + +std::unordered_map + OptionsHelper::encoding_type_string_map = {{"kPlain", kPlain}, + {"kPrefix", kPrefix}}; + +std::unordered_map + OptionsHelper::compaction_style_string_map = { + {"kCompactionStyleLevel", kCompactionStyleLevel}, + {"kCompactionStyleUniversal", kCompactionStyleUniversal}, + {"kCompactionStyleFIFO", kCompactionStyleFIFO}, + {"kCompactionStyleNone", kCompactionStyleNone}}; + +std::unordered_map + OptionsHelper::compaction_pri_string_map = { + {"kByCompensatedSize", kByCompensatedSize}, + {"kOldestLargestSeqFirst", kOldestLargestSeqFirst}, + {"kOldestSmallestSeqFirst", kOldestSmallestSeqFirst}, + {"kMinOverlappingRatio", kMinOverlappingRatio}}; + +std::unordered_map + OptionsHelper::wal_recovery_mode_string_map = { + {"kTolerateCorruptedTailRecords", + WALRecoveryMode::kTolerateCorruptedTailRecords}, + {"kAbsoluteConsistency", WALRecoveryMode::kAbsoluteConsistency}, + {"kPointInTimeRecovery", WALRecoveryMode::kPointInTimeRecovery}, + {"kSkipAnyCorruptedRecords", + WALRecoveryMode::kSkipAnyCorruptedRecords}}; + +std::unordered_map + OptionsHelper::access_hint_string_map = { + {"NONE", DBOptions::AccessHint::NONE}, + {"NORMAL", DBOptions::AccessHint::NORMAL}, + {"SEQUENTIAL", DBOptions::AccessHint::SEQUENTIAL}, + {"WILLNEED", DBOptions::AccessHint::WILLNEED}}; + +std::unordered_map + OptionsHelper::info_log_level_string_map = { + {"DEBUG_LEVEL", InfoLogLevel::DEBUG_LEVEL}, + {"INFO_LEVEL", InfoLogLevel::INFO_LEVEL}, + {"WARN_LEVEL", InfoLogLevel::WARN_LEVEL}, + {"ERROR_LEVEL", InfoLogLevel::ERROR_LEVEL}, + {"FATAL_LEVEL", InfoLogLevel::FATAL_LEVEL}, + {"HEADER_LEVEL", InfoLogLevel::HEADER_LEVEL}}; + +ColumnFamilyOptions OptionsHelper::dummy_cf_options; +CompactionOptionsFIFO OptionsHelper::dummy_comp_options; +LRUCacheOptions OptionsHelper::dummy_lru_cache_options; +CompactionOptionsUniversal OptionsHelper::dummy_comp_options_universal; + +// offset_of is used to get the offset of a class data member +// ex: offset_of(&ColumnFamilyOptions::num_levels) +// This call will return the offset of num_levels in ColumnFamilyOptions class +// +// This is the same as offsetof() but allow us to work with non standard-layout +// classes and structures +// refs: +// http://en.cppreference.com/w/cpp/concept/StandardLayoutType +// https://gist.github.com/graphitemaster/494f21190bb2c63c5516 +template +int offset_of(T1 ColumnFamilyOptions::*member) { + return int(size_t(&(OptionsHelper::dummy_cf_options.*member)) - + size_t(&OptionsHelper::dummy_cf_options)); +} +template +int offset_of(T1 AdvancedColumnFamilyOptions::*member) { + return int(size_t(&(OptionsHelper::dummy_cf_options.*member)) - + size_t(&OptionsHelper::dummy_cf_options)); +} +template +int offset_of(T1 CompactionOptionsFIFO::*member) { + return int(size_t(&(OptionsHelper::dummy_comp_options.*member)) - + size_t(&OptionsHelper::dummy_comp_options)); +} +template +int offset_of(T1 LRUCacheOptions::*member) { + return int(size_t(&(OptionsHelper::dummy_lru_cache_options.*member)) - + size_t(&OptionsHelper::dummy_lru_cache_options)); +} +template +int offset_of(T1 CompactionOptionsUniversal::*member) { + return int(size_t(&(OptionsHelper::dummy_comp_options_universal.*member)) - + size_t(&OptionsHelper::dummy_comp_options_universal)); +} + +std::unordered_map + OptionsHelper::cf_options_type_info = { + /* not yet supported + CompressionOptions compression_opts; + TablePropertiesCollectorFactories table_properties_collector_factories; + typedef std::vector> + TablePropertiesCollectorFactories; + UpdateStatus (*inplace_callback)(char* existing_value, + uint34_t* existing_value_size, + Slice delta_value, + std::string* merged_value); + std::vector cf_paths; + */ + {"report_bg_io_stats", + {offset_of(&ColumnFamilyOptions::report_bg_io_stats), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, report_bg_io_stats)}}, + {"compaction_measure_io_stats", + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, + 0}}, + {"disable_auto_compactions", + {offset_of(&ColumnFamilyOptions::disable_auto_compactions), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, disable_auto_compactions)}}, + {"filter_deletes", + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true, + 0}}, + {"inplace_update_support", + {offset_of(&ColumnFamilyOptions::inplace_update_support), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"level_compaction_dynamic_level_bytes", + {offset_of(&ColumnFamilyOptions::level_compaction_dynamic_level_bytes), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"optimize_filters_for_hits", + {offset_of(&ColumnFamilyOptions::optimize_filters_for_hits), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"paranoid_file_checks", + {offset_of(&ColumnFamilyOptions::paranoid_file_checks), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, paranoid_file_checks)}}, + {"force_consistency_checks", + {offset_of(&ColumnFamilyOptions::force_consistency_checks), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"purge_redundant_kvs_while_flush", + {offset_of(&ColumnFamilyOptions::purge_redundant_kvs_while_flush), + OptionType::kBoolean, OptionVerificationType::kDeprecated, false, 0}}, + {"verify_checksums_in_compaction", + {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true, + 0}}, + {"soft_pending_compaction_bytes_limit", + {offset_of(&ColumnFamilyOptions::soft_pending_compaction_bytes_limit), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, + soft_pending_compaction_bytes_limit)}}, + {"hard_pending_compaction_bytes_limit", + {offset_of(&ColumnFamilyOptions::hard_pending_compaction_bytes_limit), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, + hard_pending_compaction_bytes_limit)}}, + {"hard_rate_limit", + {0, OptionType::kDouble, OptionVerificationType::kDeprecated, true, + 0}}, + {"soft_rate_limit", + {0, OptionType::kDouble, OptionVerificationType::kDeprecated, true, + 0}}, + {"max_compaction_bytes", + {offset_of(&ColumnFamilyOptions::max_compaction_bytes), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, max_compaction_bytes)}}, + {"expanded_compaction_factor", + {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}}, + {"level0_file_num_compaction_trigger", + {offset_of(&ColumnFamilyOptions::level0_file_num_compaction_trigger), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, + level0_file_num_compaction_trigger)}}, + {"level0_slowdown_writes_trigger", + {offset_of(&ColumnFamilyOptions::level0_slowdown_writes_trigger), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, level0_slowdown_writes_trigger)}}, + {"level0_stop_writes_trigger", + {offset_of(&ColumnFamilyOptions::level0_stop_writes_trigger), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, level0_stop_writes_trigger)}}, + {"max_grandparent_overlap_factor", + {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}}, + {"max_mem_compaction_level", + {0, OptionType::kInt, OptionVerificationType::kDeprecated, false, 0}}, + {"max_write_buffer_number", + {offset_of(&ColumnFamilyOptions::max_write_buffer_number), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, max_write_buffer_number)}}, + {"max_write_buffer_number_to_maintain", + {offset_of(&ColumnFamilyOptions::max_write_buffer_number_to_maintain), + OptionType::kInt, OptionVerificationType::kNormal, false, 0}}, + {"min_write_buffer_number_to_merge", + {offset_of(&ColumnFamilyOptions::min_write_buffer_number_to_merge), + OptionType::kInt, OptionVerificationType::kNormal, false, 0}}, + {"num_levels", + {offset_of(&ColumnFamilyOptions::num_levels), OptionType::kInt, + OptionVerificationType::kNormal, false, 0}}, + {"source_compaction_factor", + {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}}, + {"target_file_size_multiplier", + {offset_of(&ColumnFamilyOptions::target_file_size_multiplier), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, target_file_size_multiplier)}}, + {"arena_block_size", + {offset_of(&ColumnFamilyOptions::arena_block_size), OptionType::kSizeT, + OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, arena_block_size)}}, + {"inplace_update_num_locks", + {offset_of(&ColumnFamilyOptions::inplace_update_num_locks), + OptionType::kSizeT, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, inplace_update_num_locks)}}, + {"max_successive_merges", + {offset_of(&ColumnFamilyOptions::max_successive_merges), + OptionType::kSizeT, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, max_successive_merges)}}, + {"memtable_huge_page_size", + {offset_of(&ColumnFamilyOptions::memtable_huge_page_size), + OptionType::kSizeT, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, memtable_huge_page_size)}}, + {"memtable_prefix_bloom_huge_page_tlb_size", + {0, OptionType::kSizeT, OptionVerificationType::kDeprecated, true, 0}}, + {"write_buffer_size", + {offset_of(&ColumnFamilyOptions::write_buffer_size), + OptionType::kSizeT, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, write_buffer_size)}}, + {"bloom_locality", + {offset_of(&ColumnFamilyOptions::bloom_locality), OptionType::kUInt32T, + OptionVerificationType::kNormal, false, 0}}, + {"memtable_prefix_bloom_bits", + {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true, + 0}}, + {"memtable_prefix_bloom_size_ratio", + {offset_of(&ColumnFamilyOptions::memtable_prefix_bloom_size_ratio), + OptionType::kDouble, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, memtable_prefix_bloom_size_ratio)}}, + {"memtable_prefix_bloom_probes", + {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true, + 0}}, + {"memtable_whole_key_filtering", + {offset_of(&ColumnFamilyOptions::memtable_whole_key_filtering), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, memtable_whole_key_filtering)}}, + {"min_partial_merge_operands", + {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true, + 0}}, + {"max_bytes_for_level_base", + {offset_of(&ColumnFamilyOptions::max_bytes_for_level_base), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, max_bytes_for_level_base)}}, + {"max_bytes_for_level_multiplier", + {offset_of(&ColumnFamilyOptions::max_bytes_for_level_multiplier), + OptionType::kDouble, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, max_bytes_for_level_multiplier)}}, + {"max_bytes_for_level_multiplier_additional", + {offset_of( + &ColumnFamilyOptions::max_bytes_for_level_multiplier_additional), + OptionType::kVectorInt, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, + max_bytes_for_level_multiplier_additional)}}, + {"max_sequential_skip_in_iterations", + {offset_of(&ColumnFamilyOptions::max_sequential_skip_in_iterations), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, + max_sequential_skip_in_iterations)}}, + {"target_file_size_base", + {offset_of(&ColumnFamilyOptions::target_file_size_base), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, target_file_size_base)}}, + {"rate_limit_delay_max_milliseconds", + {0, OptionType::kUInt, OptionVerificationType::kDeprecated, false, 0}}, + {"compression", + {offset_of(&ColumnFamilyOptions::compression), + OptionType::kCompressionType, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, compression)}}, + {"compression_per_level", + {offset_of(&ColumnFamilyOptions::compression_per_level), + OptionType::kVectorCompressionType, OptionVerificationType::kNormal, + false, 0}}, + {"bottommost_compression", + {offset_of(&ColumnFamilyOptions::bottommost_compression), + OptionType::kCompressionType, OptionVerificationType::kNormal, false, + 0}}, + {kNameComparator, + {offset_of(&ColumnFamilyOptions::comparator), OptionType::kComparator, + OptionVerificationType::kByName, false, 0}}, + {"prefix_extractor", + {offset_of(&ColumnFamilyOptions::prefix_extractor), + OptionType::kSliceTransform, OptionVerificationType::kByNameAllowNull, + true, offsetof(struct MutableCFOptions, prefix_extractor)}}, + {"memtable_insert_with_hint_prefix_extractor", + {offset_of( + &ColumnFamilyOptions::memtable_insert_with_hint_prefix_extractor), + OptionType::kSliceTransform, OptionVerificationType::kByNameAllowNull, + false, 0}}, + {"memtable_factory", + {offset_of(&ColumnFamilyOptions::memtable_factory), + OptionType::kMemTableRepFactory, OptionVerificationType::kByName, + false, 0}}, + {"table_factory", + {offset_of(&ColumnFamilyOptions::table_factory), + OptionType::kTableFactory, OptionVerificationType::kByName, false, + 0}}, + {"compaction_filter", + {offset_of(&ColumnFamilyOptions::compaction_filter), + OptionType::kCompactionFilter, OptionVerificationType::kByName, false, + 0}}, + {"compaction_filter_factory", + {offset_of(&ColumnFamilyOptions::compaction_filter_factory), + OptionType::kCompactionFilterFactory, OptionVerificationType::kByName, + false, 0}}, + {kNameMergeOperator, + {offset_of(&ColumnFamilyOptions::merge_operator), + OptionType::kMergeOperator, + OptionVerificationType::kByNameAllowFromNull, false, 0}}, + {"compaction_style", + {offset_of(&ColumnFamilyOptions::compaction_style), + OptionType::kCompactionStyle, OptionVerificationType::kNormal, false, + 0}}, + {"compaction_pri", + {offset_of(&ColumnFamilyOptions::compaction_pri), + OptionType::kCompactionPri, OptionVerificationType::kNormal, false, + 0}}, + {"compaction_options_fifo", + {offset_of(&ColumnFamilyOptions::compaction_options_fifo), + OptionType::kCompactionOptionsFIFO, OptionVerificationType::kNormal, + true, offsetof(struct MutableCFOptions, compaction_options_fifo)}}, + {"compaction_options_universal", + {offset_of(&ColumnFamilyOptions::compaction_options_universal), + OptionType::kCompactionOptionsUniversal, + OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, compaction_options_universal)}}, + {"ttl", + {offset_of(&ColumnFamilyOptions::ttl), OptionType::kUInt64T, + OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, ttl)}}, + {"sample_for_compression", + {offset_of(&ColumnFamilyOptions::sample_for_compression), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct MutableCFOptions, sample_for_compression)}}}; + +std::unordered_map + OptionsHelper::fifo_compaction_options_type_info = { + {"max_table_files_size", + {offset_of(&CompactionOptionsFIFO::max_table_files_size), + OptionType::kUInt64T, OptionVerificationType::kNormal, true, + offsetof(struct CompactionOptionsFIFO, max_table_files_size)}}, + {"ttl", + {0, OptionType::kUInt64T, + OptionVerificationType::kDeprecated, false, + 0}}, + {"allow_compaction", + {offset_of(&CompactionOptionsFIFO::allow_compaction), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(struct CompactionOptionsFIFO, allow_compaction)}}}; + +std::unordered_map + OptionsHelper::universal_compaction_options_type_info = { + {"size_ratio", + {offset_of(&CompactionOptionsUniversal::size_ratio), OptionType::kUInt, + OptionVerificationType::kNormal, true, + offsetof(class CompactionOptionsUniversal, size_ratio)}}, + {"min_merge_width", + {offset_of(&CompactionOptionsUniversal::min_merge_width), + OptionType::kUInt, OptionVerificationType::kNormal, true, + offsetof(class CompactionOptionsUniversal, min_merge_width)}}, + {"max_merge_width", + {offset_of(&CompactionOptionsUniversal::max_merge_width), + OptionType::kUInt, OptionVerificationType::kNormal, true, + offsetof(class CompactionOptionsUniversal, max_merge_width)}}, + {"max_size_amplification_percent", + {offset_of( + &CompactionOptionsUniversal::max_size_amplification_percent), + OptionType::kUInt, OptionVerificationType::kNormal, true, + offsetof(class CompactionOptionsUniversal, + max_size_amplification_percent)}}, + {"compression_size_percent", + {offset_of(&CompactionOptionsUniversal::compression_size_percent), + OptionType::kInt, OptionVerificationType::kNormal, true, + offsetof(class CompactionOptionsUniversal, + compression_size_percent)}}, + {"stop_style", + {offset_of(&CompactionOptionsUniversal::stop_style), + OptionType::kCompactionStopStyle, OptionVerificationType::kNormal, + true, offsetof(class CompactionOptionsUniversal, stop_style)}}, + {"allow_trivial_move", + {offset_of(&CompactionOptionsUniversal::allow_trivial_move), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(class CompactionOptionsUniversal, allow_trivial_move)}}}; + +std::unordered_map + OptionsHelper::compaction_stop_style_string_map = { + {"kCompactionStopStyleSimilarSize", kCompactionStopStyleSimilarSize}, + {"kCompactionStopStyleTotalSize", kCompactionStopStyleTotalSize}}; + +std::unordered_map + OptionsHelper::lru_cache_options_type_info = { + {"capacity", + {offset_of(&LRUCacheOptions::capacity), OptionType::kSizeT, + OptionVerificationType::kNormal, true, + offsetof(struct LRUCacheOptions, capacity)}}, + {"num_shard_bits", + {offset_of(&LRUCacheOptions::num_shard_bits), OptionType::kInt, + OptionVerificationType::kNormal, true, + offsetof(struct LRUCacheOptions, num_shard_bits)}}, + {"strict_capacity_limit", + {offset_of(&LRUCacheOptions::strict_capacity_limit), + OptionType::kBoolean, OptionVerificationType::kNormal, true, + offsetof(struct LRUCacheOptions, strict_capacity_limit)}}, + {"high_pri_pool_ratio", + {offset_of(&LRUCacheOptions::high_pri_pool_ratio), OptionType::kDouble, + OptionVerificationType::kNormal, true, + offsetof(struct LRUCacheOptions, high_pri_pool_ratio)}}}; + #endif // !ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/options/options_helper.h b/thirdparty/rocksdb/options/options_helper.h index 67b04271ff..1d3d880a62 100644 --- a/thirdparty/rocksdb/options/options_helper.h +++ b/thirdparty/rocksdb/options/options_helper.h @@ -15,6 +15,7 @@ #include "rocksdb/options.h" #include "rocksdb/status.h" #include "rocksdb/table.h" +#include "rocksdb/universal_compaction.h" namespace rocksdb { @@ -25,32 +26,12 @@ ColumnFamilyOptions BuildColumnFamilyOptions( const ColumnFamilyOptions& ioptions, const MutableCFOptions& mutable_cf_options); -static std::map compaction_style_to_string = { - {kCompactionStyleLevel, "kCompactionStyleLevel"}, - {kCompactionStyleUniversal, "kCompactionStyleUniversal"}, - {kCompactionStyleFIFO, "kCompactionStyleFIFO"}, - {kCompactionStyleNone, "kCompactionStyleNone"}}; - -static std::map compaction_pri_to_string = { - {kByCompensatedSize, "kByCompensatedSize"}, - {kOldestLargestSeqFirst, "kOldestLargestSeqFirst"}, - {kOldestSmallestSeqFirst, "kOldestSmallestSeqFirst"}, - {kMinOverlappingRatio, "kMinOverlappingRatio"}}; - -static std::map - compaction_stop_style_to_string = { - {kCompactionStopStyleSimilarSize, "kCompactionStopStyleSimilarSize"}, - {kCompactionStopStyleTotalSize, "kCompactionStopStyleTotalSize"}}; - -static std::unordered_map checksum_type_string_map = - {{"kNoChecksum", kNoChecksum}, {"kCRC32c", kCRC32c}, {"kxxHash", kxxHash}}; - #ifndef ROCKSDB_LITE Status GetMutableOptionsFromStrings( const MutableCFOptions& base_options, const std::unordered_map& options_map, - MutableCFOptions* new_options); + Logger* info_log, MutableCFOptions* new_options); Status GetMutableDBOptionsFromStrings( const MutableDBOptions& base_options, @@ -66,6 +47,8 @@ Status GetTableFactoryFromMap( enum class OptionType { kBoolean, kInt, + kInt32T, + kInt64T, kVectorInt, kUInt, kUInt32T, @@ -82,9 +65,13 @@ enum class OptionType { kComparator, kCompactionFilter, kCompactionFilterFactory, + kCompactionOptionsFIFO, + kCompactionOptionsUniversal, + kCompactionStopStyle, kMergeOperator, kMemTableRepFactory, kBlockBasedTableIndexType, + kBlockBasedTableDataBlockIndexType, kFilterPolicy, kFlushBlockPolicyFactory, kChecksumType, @@ -92,20 +79,23 @@ enum class OptionType { kWALRecoveryMode, kAccessHint, kInfoLogLevel, + kLRUCacheOptions, kUnknown }; enum class OptionVerificationType { kNormal, - kByName, // The option is pointer typed so we can only verify - // based on it's name. - kByNameAllowNull, // Same as kByName, but it also allows the case - // where one of them is a nullptr. - kDeprecated // The option is no longer used in rocksdb. The RocksDB - // OptionsParser will still accept this option if it - // happen to exists in some Options file. However, the - // parser will not include it in serialization and - // verification processes. + kByName, // The option is pointer typed so we can only verify + // based on it's name. + kByNameAllowNull, // Same as kByName, but it also allows the case + // where one of them is a nullptr. + kByNameAllowFromNull, // Same as kByName, but it also allows the case + // where the old option is nullptr. + kDeprecated // The option is no longer used in rocksdb. The RocksDB + // OptionsParser will still accept this option if it + // happen to exists in some Options file. However, + // the parser will not include it in serialization + // and verification processes. }; // A struct for storing constant option information such as option name, @@ -143,501 +133,9 @@ Status GetColumnFamilyOptionsFromMapInternal( std::vector* unsupported_options_names = nullptr, bool ignore_unknown_options = false); -static std::unordered_map db_options_type_info = { - /* - // not yet supported - Env* env; - std::shared_ptr row_cache; - std::shared_ptr delete_scheduler; - std::shared_ptr info_log; - std::shared_ptr rate_limiter; - std::shared_ptr statistics; - std::vector db_paths; - std::vector> listeners; - */ - {"advise_random_on_open", - {offsetof(struct DBOptions, advise_random_on_open), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"allow_mmap_reads", - {offsetof(struct DBOptions, allow_mmap_reads), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"allow_fallocate", - {offsetof(struct DBOptions, allow_fallocate), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"allow_mmap_writes", - {offsetof(struct DBOptions, allow_mmap_writes), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"use_direct_reads", - {offsetof(struct DBOptions, use_direct_reads), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"use_direct_writes", - {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, 0}}, - {"use_direct_io_for_flush_and_compaction", - {offsetof(struct DBOptions, use_direct_io_for_flush_and_compaction), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"allow_2pc", - {offsetof(struct DBOptions, allow_2pc), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"allow_os_buffer", - {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true, 0}}, - {"create_if_missing", - {offsetof(struct DBOptions, create_if_missing), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"create_missing_column_families", - {offsetof(struct DBOptions, create_missing_column_families), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"disableDataSync", - {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, 0}}, - {"disable_data_sync", // for compatibility - {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, 0}}, - {"enable_thread_tracking", - {offsetof(struct DBOptions, enable_thread_tracking), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"error_if_exists", - {offsetof(struct DBOptions, error_if_exists), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"is_fd_close_on_exec", - {offsetof(struct DBOptions, is_fd_close_on_exec), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"paranoid_checks", - {offsetof(struct DBOptions, paranoid_checks), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"skip_log_error_on_recovery", - {offsetof(struct DBOptions, skip_log_error_on_recovery), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"skip_stats_update_on_db_open", - {offsetof(struct DBOptions, skip_stats_update_on_db_open), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"new_table_reader_for_compaction_inputs", - {offsetof(struct DBOptions, new_table_reader_for_compaction_inputs), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"compaction_readahead_size", - {offsetof(struct DBOptions, compaction_readahead_size), OptionType::kSizeT, - OptionVerificationType::kNormal, false, 0}}, - {"random_access_max_buffer_size", - {offsetof(struct DBOptions, random_access_max_buffer_size), - OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}, - {"writable_file_max_buffer_size", - {offsetof(struct DBOptions, writable_file_max_buffer_size), - OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}, - {"use_adaptive_mutex", - {offsetof(struct DBOptions, use_adaptive_mutex), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"use_fsync", - {offsetof(struct DBOptions, use_fsync), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"max_background_jobs", - {offsetof(struct DBOptions, max_background_jobs), OptionType::kInt, - OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, max_background_jobs)}}, - {"max_background_compactions", - {offsetof(struct DBOptions, max_background_compactions), OptionType::kInt, - OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, max_background_compactions)}}, - {"base_background_compactions", - {offsetof(struct DBOptions, base_background_compactions), OptionType::kInt, - OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, base_background_compactions)}}, - {"max_background_flushes", - {offsetof(struct DBOptions, max_background_flushes), OptionType::kInt, - OptionVerificationType::kNormal, false, 0}}, - {"max_file_opening_threads", - {offsetof(struct DBOptions, max_file_opening_threads), OptionType::kInt, - OptionVerificationType::kNormal, false, 0}}, - {"max_open_files", - {offsetof(struct DBOptions, max_open_files), OptionType::kInt, - OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, max_open_files)}}, - {"table_cache_numshardbits", - {offsetof(struct DBOptions, table_cache_numshardbits), OptionType::kInt, - OptionVerificationType::kNormal, false, 0}}, - {"db_write_buffer_size", - {offsetof(struct DBOptions, db_write_buffer_size), OptionType::kSizeT, - OptionVerificationType::kNormal, false, 0}}, - {"keep_log_file_num", - {offsetof(struct DBOptions, keep_log_file_num), OptionType::kSizeT, - OptionVerificationType::kNormal, false, 0}}, - {"recycle_log_file_num", - {offsetof(struct DBOptions, recycle_log_file_num), OptionType::kSizeT, - OptionVerificationType::kNormal, false, 0}}, - {"log_file_time_to_roll", - {offsetof(struct DBOptions, log_file_time_to_roll), OptionType::kSizeT, - OptionVerificationType::kNormal, false, 0}}, - {"manifest_preallocation_size", - {offsetof(struct DBOptions, manifest_preallocation_size), - OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}, - {"max_log_file_size", - {offsetof(struct DBOptions, max_log_file_size), OptionType::kSizeT, - OptionVerificationType::kNormal, false, 0}}, - {"db_log_dir", - {offsetof(struct DBOptions, db_log_dir), OptionType::kString, - OptionVerificationType::kNormal, false, 0}}, - {"wal_dir", - {offsetof(struct DBOptions, wal_dir), OptionType::kString, - OptionVerificationType::kNormal, false, 0}}, - {"max_subcompactions", - {offsetof(struct DBOptions, max_subcompactions), OptionType::kUInt32T, - OptionVerificationType::kNormal, false, 0}}, - {"WAL_size_limit_MB", - {offsetof(struct DBOptions, WAL_size_limit_MB), OptionType::kUInt64T, - OptionVerificationType::kNormal, false, 0}}, - {"WAL_ttl_seconds", - {offsetof(struct DBOptions, WAL_ttl_seconds), OptionType::kUInt64T, - OptionVerificationType::kNormal, false, 0}}, - {"bytes_per_sync", - {offsetof(struct DBOptions, bytes_per_sync), OptionType::kUInt64T, - OptionVerificationType::kNormal, false, 0}}, - {"delayed_write_rate", - {offsetof(struct DBOptions, delayed_write_rate), OptionType::kUInt64T, - OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, delayed_write_rate)}}, - {"delete_obsolete_files_period_micros", - {offsetof(struct DBOptions, delete_obsolete_files_period_micros), - OptionType::kUInt64T, OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, delete_obsolete_files_period_micros)}}, - {"max_manifest_file_size", - {offsetof(struct DBOptions, max_manifest_file_size), OptionType::kUInt64T, - OptionVerificationType::kNormal, false, 0}}, - {"max_total_wal_size", - {offsetof(struct DBOptions, max_total_wal_size), OptionType::kUInt64T, - OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, max_total_wal_size)}}, - {"wal_bytes_per_sync", - {offsetof(struct DBOptions, wal_bytes_per_sync), OptionType::kUInt64T, - OptionVerificationType::kNormal, false, 0}}, - {"stats_dump_period_sec", - {offsetof(struct DBOptions, stats_dump_period_sec), OptionType::kUInt, - OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, stats_dump_period_sec)}}, - {"fail_if_options_file_error", - {offsetof(struct DBOptions, fail_if_options_file_error), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"enable_pipelined_write", - {offsetof(struct DBOptions, enable_pipelined_write), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"allow_concurrent_memtable_write", - {offsetof(struct DBOptions, allow_concurrent_memtable_write), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"wal_recovery_mode", - {offsetof(struct DBOptions, wal_recovery_mode), - OptionType::kWALRecoveryMode, OptionVerificationType::kNormal, false, 0}}, - {"enable_write_thread_adaptive_yield", - {offsetof(struct DBOptions, enable_write_thread_adaptive_yield), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"write_thread_slow_yield_usec", - {offsetof(struct DBOptions, write_thread_slow_yield_usec), - OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}}, - {"write_thread_max_yield_usec", - {offsetof(struct DBOptions, write_thread_max_yield_usec), - OptionType::kUInt64T, OptionVerificationType::kNormal, false, 0}}, - {"access_hint_on_compaction_start", - {offsetof(struct DBOptions, access_hint_on_compaction_start), - OptionType::kAccessHint, OptionVerificationType::kNormal, false, 0}}, - {"info_log_level", - {offsetof(struct DBOptions, info_log_level), OptionType::kInfoLogLevel, - OptionVerificationType::kNormal, false, 0}}, - {"dump_malloc_stats", - {offsetof(struct DBOptions, dump_malloc_stats), OptionType::kBoolean, - OptionVerificationType::kNormal, false, 0}}, - {"avoid_flush_during_recovery", - {offsetof(struct DBOptions, avoid_flush_during_recovery), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"avoid_flush_during_shutdown", - {offsetof(struct DBOptions, avoid_flush_during_shutdown), - OptionType::kBoolean, OptionVerificationType::kNormal, true, - offsetof(struct MutableDBOptions, avoid_flush_during_shutdown)}}, - {"allow_ingest_behind", - {offsetof(struct DBOptions, allow_ingest_behind), OptionType::kBoolean, - OptionVerificationType::kNormal, false, - offsetof(struct ImmutableDBOptions, allow_ingest_behind)}}, - {"concurrent_prepare", - {offsetof(struct DBOptions, concurrent_prepare), OptionType::kBoolean, - OptionVerificationType::kNormal, false, - offsetof(struct ImmutableDBOptions, concurrent_prepare)}}, - {"manual_wal_flush", - {offsetof(struct DBOptions, manual_wal_flush), OptionType::kBoolean, - OptionVerificationType::kNormal, false, - offsetof(struct ImmutableDBOptions, manual_wal_flush)}}}; - -// offset_of is used to get the offset of a class data member -// ex: offset_of(&ColumnFamilyOptions::num_levels) -// This call will return the offset of num_levels in ColumnFamilyOptions class -// -// This is the same as offsetof() but allow us to work with non standard-layout -// classes and structures -// refs: -// http://en.cppreference.com/w/cpp/concept/StandardLayoutType -// https://gist.github.com/graphitemaster/494f21190bb2c63c5516 -template -inline int offset_of(T1 T2::*member) { - static T2 obj; - return int(size_t(&(obj.*member)) - size_t(&obj)); -} - -static std::unordered_map cf_options_type_info = { - /* not yet supported - CompactionOptionsFIFO compaction_options_fifo; - CompactionOptionsUniversal compaction_options_universal; - CompressionOptions compression_opts; - TablePropertiesCollectorFactories table_properties_collector_factories; - typedef std::vector> - TablePropertiesCollectorFactories; - UpdateStatus (*inplace_callback)(char* existing_value, - uint34_t* existing_value_size, - Slice delta_value, - std::string* merged_value); - */ - {"report_bg_io_stats", - {offset_of(&ColumnFamilyOptions::report_bg_io_stats), OptionType::kBoolean, - OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, report_bg_io_stats)}}, - {"compaction_measure_io_stats", - {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, false, 0}}, - {"disable_auto_compactions", - {offset_of(&ColumnFamilyOptions::disable_auto_compactions), - OptionType::kBoolean, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, disable_auto_compactions)}}, - {"filter_deletes", - {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true, 0}}, - {"inplace_update_support", - {offset_of(&ColumnFamilyOptions::inplace_update_support), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"level_compaction_dynamic_level_bytes", - {offset_of(&ColumnFamilyOptions::level_compaction_dynamic_level_bytes), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"optimize_filters_for_hits", - {offset_of(&ColumnFamilyOptions::optimize_filters_for_hits), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"paranoid_file_checks", - {offset_of(&ColumnFamilyOptions::paranoid_file_checks), - OptionType::kBoolean, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, paranoid_file_checks)}}, - {"force_consistency_checks", - {offset_of(&ColumnFamilyOptions::force_consistency_checks), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"purge_redundant_kvs_while_flush", - {offset_of(&ColumnFamilyOptions::purge_redundant_kvs_while_flush), - OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, - {"verify_checksums_in_compaction", - {0, OptionType::kBoolean, OptionVerificationType::kDeprecated, true, 0}}, - {"soft_pending_compaction_bytes_limit", - {offset_of(&ColumnFamilyOptions::soft_pending_compaction_bytes_limit), - OptionType::kUInt64T, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, soft_pending_compaction_bytes_limit)}}, - {"hard_pending_compaction_bytes_limit", - {offset_of(&ColumnFamilyOptions::hard_pending_compaction_bytes_limit), - OptionType::kUInt64T, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, hard_pending_compaction_bytes_limit)}}, - {"hard_rate_limit", - {0, OptionType::kDouble, OptionVerificationType::kDeprecated, true, 0}}, - {"soft_rate_limit", - {0, OptionType::kDouble, OptionVerificationType::kDeprecated, true, 0}}, - {"max_compaction_bytes", - {offset_of(&ColumnFamilyOptions::max_compaction_bytes), - OptionType::kUInt64T, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, max_compaction_bytes)}}, - {"expanded_compaction_factor", - {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}}, - {"level0_file_num_compaction_trigger", - {offset_of(&ColumnFamilyOptions::level0_file_num_compaction_trigger), - OptionType::kInt, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, level0_file_num_compaction_trigger)}}, - {"level0_slowdown_writes_trigger", - {offset_of(&ColumnFamilyOptions::level0_slowdown_writes_trigger), - OptionType::kInt, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, level0_slowdown_writes_trigger)}}, - {"level0_stop_writes_trigger", - {offset_of(&ColumnFamilyOptions::level0_stop_writes_trigger), - OptionType::kInt, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, level0_stop_writes_trigger)}}, - {"max_grandparent_overlap_factor", - {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}}, - {"max_mem_compaction_level", - {0, OptionType::kInt, OptionVerificationType::kDeprecated, false, 0}}, - {"max_write_buffer_number", - {offset_of(&ColumnFamilyOptions::max_write_buffer_number), - OptionType::kInt, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, max_write_buffer_number)}}, - {"max_write_buffer_number_to_maintain", - {offset_of(&ColumnFamilyOptions::max_write_buffer_number_to_maintain), - OptionType::kInt, OptionVerificationType::kNormal, false, 0}}, - {"min_write_buffer_number_to_merge", - {offset_of(&ColumnFamilyOptions::min_write_buffer_number_to_merge), - OptionType::kInt, OptionVerificationType::kNormal, false, 0}}, - {"num_levels", - {offset_of(&ColumnFamilyOptions::num_levels), OptionType::kInt, - OptionVerificationType::kNormal, false, 0}}, - {"source_compaction_factor", - {0, OptionType::kInt, OptionVerificationType::kDeprecated, true, 0}}, - {"target_file_size_multiplier", - {offset_of(&ColumnFamilyOptions::target_file_size_multiplier), - OptionType::kInt, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, target_file_size_multiplier)}}, - {"arena_block_size", - {offset_of(&ColumnFamilyOptions::arena_block_size), OptionType::kSizeT, - OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, arena_block_size)}}, - {"inplace_update_num_locks", - {offset_of(&ColumnFamilyOptions::inplace_update_num_locks), - OptionType::kSizeT, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, inplace_update_num_locks)}}, - {"max_successive_merges", - {offset_of(&ColumnFamilyOptions::max_successive_merges), - OptionType::kSizeT, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, max_successive_merges)}}, - {"memtable_huge_page_size", - {offset_of(&ColumnFamilyOptions::memtable_huge_page_size), - OptionType::kSizeT, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, memtable_huge_page_size)}}, - {"memtable_prefix_bloom_huge_page_tlb_size", - {0, OptionType::kSizeT, OptionVerificationType::kDeprecated, true, 0}}, - {"write_buffer_size", - {offset_of(&ColumnFamilyOptions::write_buffer_size), OptionType::kSizeT, - OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, write_buffer_size)}}, - {"bloom_locality", - {offset_of(&ColumnFamilyOptions::bloom_locality), OptionType::kUInt32T, - OptionVerificationType::kNormal, false, 0}}, - {"memtable_prefix_bloom_bits", - {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true, 0}}, - {"memtable_prefix_bloom_size_ratio", - {offset_of(&ColumnFamilyOptions::memtable_prefix_bloom_size_ratio), - OptionType::kDouble, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, memtable_prefix_bloom_size_ratio)}}, - {"memtable_prefix_bloom_probes", - {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true, 0}}, - {"min_partial_merge_operands", - {0, OptionType::kUInt32T, OptionVerificationType::kDeprecated, true, 0}}, - {"max_bytes_for_level_base", - {offset_of(&ColumnFamilyOptions::max_bytes_for_level_base), - OptionType::kUInt64T, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, max_bytes_for_level_base)}}, - {"max_bytes_for_level_multiplier", - {offset_of(&ColumnFamilyOptions::max_bytes_for_level_multiplier), - OptionType::kDouble, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, max_bytes_for_level_multiplier)}}, - {"max_bytes_for_level_multiplier_additional", - {offset_of( - &ColumnFamilyOptions::max_bytes_for_level_multiplier_additional), - OptionType::kVectorInt, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, - max_bytes_for_level_multiplier_additional)}}, - {"max_sequential_skip_in_iterations", - {offset_of(&ColumnFamilyOptions::max_sequential_skip_in_iterations), - OptionType::kUInt64T, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, max_sequential_skip_in_iterations)}}, - {"target_file_size_base", - {offset_of(&ColumnFamilyOptions::target_file_size_base), - OptionType::kUInt64T, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, target_file_size_base)}}, - {"rate_limit_delay_max_milliseconds", - {0, OptionType::kUInt, OptionVerificationType::kDeprecated, false, 0}}, - {"compression", - {offset_of(&ColumnFamilyOptions::compression), - OptionType::kCompressionType, OptionVerificationType::kNormal, true, - offsetof(struct MutableCFOptions, compression)}}, - {"compression_per_level", - {offset_of(&ColumnFamilyOptions::compression_per_level), - OptionType::kVectorCompressionType, OptionVerificationType::kNormal, - false, 0}}, - {"bottommost_compression", - {offset_of(&ColumnFamilyOptions::bottommost_compression), - OptionType::kCompressionType, OptionVerificationType::kNormal, false, 0}}, - {"comparator", - {offset_of(&ColumnFamilyOptions::comparator), OptionType::kComparator, - OptionVerificationType::kByName, false, 0}}, - {"prefix_extractor", - {offset_of(&ColumnFamilyOptions::prefix_extractor), - OptionType::kSliceTransform, OptionVerificationType::kByNameAllowNull, - false, 0}}, - {"memtable_insert_with_hint_prefix_extractor", - {offset_of( - &ColumnFamilyOptions::memtable_insert_with_hint_prefix_extractor), - OptionType::kSliceTransform, OptionVerificationType::kByNameAllowNull, - false, 0}}, - {"memtable_factory", - {offset_of(&ColumnFamilyOptions::memtable_factory), - OptionType::kMemTableRepFactory, OptionVerificationType::kByName, false, - 0}}, - {"table_factory", - {offset_of(&ColumnFamilyOptions::table_factory), OptionType::kTableFactory, - OptionVerificationType::kByName, false, 0}}, - {"compaction_filter", - {offset_of(&ColumnFamilyOptions::compaction_filter), - OptionType::kCompactionFilter, OptionVerificationType::kByName, false, - 0}}, - {"compaction_filter_factory", - {offset_of(&ColumnFamilyOptions::compaction_filter_factory), - OptionType::kCompactionFilterFactory, OptionVerificationType::kByName, - false, 0}}, - {"merge_operator", - {offset_of(&ColumnFamilyOptions::merge_operator), - OptionType::kMergeOperator, OptionVerificationType::kByName, false, 0}}, - {"compaction_style", - {offset_of(&ColumnFamilyOptions::compaction_style), - OptionType::kCompactionStyle, OptionVerificationType::kNormal, false, 0}}, - {"compaction_pri", - {offset_of(&ColumnFamilyOptions::compaction_pri), - OptionType::kCompactionPri, OptionVerificationType::kNormal, false, 0}}}; - -static std::unordered_map - compression_type_string_map = { - {"kNoCompression", kNoCompression}, - {"kSnappyCompression", kSnappyCompression}, - {"kZlibCompression", kZlibCompression}, - {"kBZip2Compression", kBZip2Compression}, - {"kLZ4Compression", kLZ4Compression}, - {"kLZ4HCCompression", kLZ4HCCompression}, - {"kXpressCompression", kXpressCompression}, - {"kZSTD", kZSTD}, - {"kZSTDNotFinalCompression", kZSTDNotFinalCompression}, - {"kDisableCompressionOption", kDisableCompressionOption}}; - -static std::unordered_map - block_base_table_index_type_string_map = { - {"kBinarySearch", BlockBasedTableOptions::IndexType::kBinarySearch}, - {"kHashSearch", BlockBasedTableOptions::IndexType::kHashSearch}, - {"kTwoLevelIndexSearch", - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch}}; - -static std::unordered_map encoding_type_string_map = - {{"kPlain", kPlain}, {"kPrefix", kPrefix}}; - -static std::unordered_map - compaction_style_string_map = { - {"kCompactionStyleLevel", kCompactionStyleLevel}, - {"kCompactionStyleUniversal", kCompactionStyleUniversal}, - {"kCompactionStyleFIFO", kCompactionStyleFIFO}, - {"kCompactionStyleNone", kCompactionStyleNone}}; - -static std::unordered_map - compaction_pri_string_map = { - {"kByCompensatedSize", kByCompensatedSize}, - {"kOldestLargestSeqFirst", kOldestLargestSeqFirst}, - {"kOldestSmallestSeqFirst", kOldestSmallestSeqFirst}, - {"kMinOverlappingRatio", kMinOverlappingRatio}}; - -static std::unordered_map wal_recovery_mode_string_map = { - {"kTolerateCorruptedTailRecords", - WALRecoveryMode::kTolerateCorruptedTailRecords}, - {"kAbsoluteConsistency", WALRecoveryMode::kAbsoluteConsistency}, - {"kPointInTimeRecovery", WALRecoveryMode::kPointInTimeRecovery}, - {"kSkipAnyCorruptedRecords", WALRecoveryMode::kSkipAnyCorruptedRecords}}; - -static std::unordered_map - access_hint_string_map = {{"NONE", DBOptions::AccessHint::NONE}, - {"NORMAL", DBOptions::AccessHint::NORMAL}, - {"SEQUENTIAL", DBOptions::AccessHint::SEQUENTIAL}, - {"WILLNEED", DBOptions::AccessHint::WILLNEED}}; - -static std::unordered_map info_log_level_string_map = - {{"DEBUG_LEVEL", InfoLogLevel::DEBUG_LEVEL}, - {"INFO_LEVEL", InfoLogLevel::INFO_LEVEL}, - {"WARN_LEVEL", InfoLogLevel::WARN_LEVEL}, - {"ERROR_LEVEL", InfoLogLevel::ERROR_LEVEL}, - {"FATAL_LEVEL", InfoLogLevel::FATAL_LEVEL}, - {"HEADER_LEVEL", InfoLogLevel::HEADER_LEVEL}}; +bool ParseSliceTransform( + const std::string& value, + std::shared_ptr* slice_transform); extern Status StringToMap( const std::string& opts_str, @@ -647,4 +145,82 @@ extern bool ParseOptionHelper(char* opt_address, const OptionType& opt_type, const std::string& value); #endif // !ROCKSDB_LITE +struct OptionsHelper { + static std::map compaction_style_to_string; + static std::map compaction_pri_to_string; + static std::map + compaction_stop_style_to_string; + static std::unordered_map checksum_type_string_map; + static std::unordered_map + compression_type_string_map; +#ifndef ROCKSDB_LITE + static std::unordered_map cf_options_type_info; + static std::unordered_map + fifo_compaction_options_type_info; + static std::unordered_map + universal_compaction_options_type_info; + static std::unordered_map + compaction_stop_style_string_map; + static std::unordered_map db_options_type_info; + static std::unordered_map + lru_cache_options_type_info; + static std::unordered_map + block_base_table_index_type_string_map; + static std::unordered_map + block_base_table_data_block_index_type_string_map; + static std::unordered_map encoding_type_string_map; + static std::unordered_map + compaction_style_string_map; + static std::unordered_map + compaction_pri_string_map; + static std::unordered_map + wal_recovery_mode_string_map; + static std::unordered_map + access_hint_string_map; + static std::unordered_map + info_log_level_string_map; + static ColumnFamilyOptions dummy_cf_options; + static CompactionOptionsFIFO dummy_comp_options; + static LRUCacheOptions dummy_lru_cache_options; + static CompactionOptionsUniversal dummy_comp_options_universal; +#endif // !ROCKSDB_LITE +}; + +// Some aliasing +static auto& compaction_style_to_string = + OptionsHelper::compaction_style_to_string; +static auto& compaction_pri_to_string = OptionsHelper::compaction_pri_to_string; +static auto& compaction_stop_style_to_string = + OptionsHelper::compaction_stop_style_to_string; +static auto& checksum_type_string_map = OptionsHelper::checksum_type_string_map; +#ifndef ROCKSDB_LITE +static auto& cf_options_type_info = OptionsHelper::cf_options_type_info; +static auto& fifo_compaction_options_type_info = + OptionsHelper::fifo_compaction_options_type_info; +static auto& universal_compaction_options_type_info = + OptionsHelper::universal_compaction_options_type_info; +static auto& compaction_stop_style_string_map = + OptionsHelper::compaction_stop_style_string_map; +static auto& db_options_type_info = OptionsHelper::db_options_type_info; +static auto& lru_cache_options_type_info = + OptionsHelper::lru_cache_options_type_info; +static auto& compression_type_string_map = + OptionsHelper::compression_type_string_map; +static auto& block_base_table_index_type_string_map = + OptionsHelper::block_base_table_index_type_string_map; +static auto& block_base_table_data_block_index_type_string_map = + OptionsHelper::block_base_table_data_block_index_type_string_map; +static auto& encoding_type_string_map = OptionsHelper::encoding_type_string_map; +static auto& compaction_style_string_map = + OptionsHelper::compaction_style_string_map; +static auto& compaction_pri_string_map = + OptionsHelper::compaction_pri_string_map; +static auto& wal_recovery_mode_string_map = + OptionsHelper::wal_recovery_mode_string_map; +static auto& access_hint_string_map = OptionsHelper::access_hint_string_map; +static auto& info_log_level_string_map = + OptionsHelper::info_log_level_string_map; +#endif // !ROCKSDB_LITE + } // namespace rocksdb diff --git a/thirdparty/rocksdb/options/options_parser.cc b/thirdparty/rocksdb/options/options_parser.cc index 2cb60a068c..2a85fa5343 100644 --- a/thirdparty/rocksdb/options/options_parser.cc +++ b/thirdparty/rocksdb/options/options_parser.cc @@ -17,6 +17,7 @@ #include "rocksdb/convenience.h" #include "rocksdb/db.h" #include "util/cast_util.h" +#include "util/file_reader_writer.h" #include "util/string_util.h" #include "util/sync_point.h" @@ -41,12 +42,16 @@ Status PersistRocksDBOptions(const DBOptions& db_opt, return Status::InvalidArgument( "cf_names.size() and cf_opts.size() must be the same"); } - std::unique_ptr writable; + std::unique_ptr wf; - Status s = env->NewWritableFile(file_name, &writable, EnvOptions()); + Status s = env->NewWritableFile(file_name, &wf, EnvOptions()); if (!s.ok()) { return s; } + std::unique_ptr writable; + writable.reset(new WritableFileWriter(std::move(wf), file_name, EnvOptions(), + nullptr /* statistics */)); + std::string options_file_content; writable->Append(option_file_header + "[" + @@ -93,8 +98,7 @@ Status PersistRocksDBOptions(const DBOptions& db_opt, writable->Append(options_file_content + "\n"); } } - writable->Flush(); - writable->Fsync(); + writable->Sync(true /* use_fsync */); writable->Close(); return RocksDBOptionsParser::VerifyRocksDBOptionsFromFile( @@ -196,45 +200,6 @@ Status RocksDBOptionsParser::ParseStatement(std::string* name, return Status::OK(); } -namespace { -bool ReadOneLine(std::istringstream* iss, SequentialFile* seq_file, - std::string* output, bool* has_data, Status* result) { - const int kBufferSize = 4096; - char buffer[kBufferSize + 1]; - Slice input_slice; - - std::string line; - bool has_complete_line = false; - while (!has_complete_line) { - if (std::getline(*iss, line)) { - has_complete_line = !iss->eof(); - } else { - has_complete_line = false; - } - if (!has_complete_line) { - // if we're not sure whether we have a complete line, - // further read from the file. - if (*has_data) { - *result = seq_file->Read(kBufferSize, &input_slice, buffer); - } - if (input_slice.size() == 0) { - // meaning we have read all the data - *has_data = false; - break; - } else { - iss->str(line + input_slice.ToString()); - // reset the internal state of iss so that we can keep reading it. - iss->clear(); - *has_data = (input_slice.size() == kBufferSize); - continue; - } - } - } - *output = line; - return *has_data || has_complete_line; -} -} // namespace - Status RocksDBOptionsParser::Parse(const std::string& file_name, Env* env, bool ignore_unknown_options) { Reset(); @@ -268,6 +233,16 @@ Status RocksDBOptionsParser::Parse(const std::string& file_name, Env* env, if (!s.ok()) { return s; } + + // If the option file is not generated by a higher minor version, + // there shouldn't be any unknown option. + if (ignore_unknown_options && section == kOptionSectionVersion) { + if (db_version[0] < ROCKSDB_MAJOR || (db_version[0] == ROCKSDB_MAJOR && + db_version[1] <= ROCKSDB_MINOR)) { + ignore_unknown_options = false; + } + } + s = ParseSection(§ion, &title, &argument, line, line_num); if (!s.ok()) { return s; @@ -525,6 +500,16 @@ bool AreEqualOptions( case OptionType::kInt: return (*reinterpret_cast(offset1) == *reinterpret_cast(offset2)); + case OptionType::kInt32T: + return (*reinterpret_cast(offset1) == + *reinterpret_cast(offset2)); + case OptionType::kInt64T: + { + int64_t v1, v2; + GetUnaligned(reinterpret_cast(offset1), &v1); + GetUnaligned(reinterpret_cast(offset2), &v2); + return (v1 == v2); + } case OptionType::kVectorInt: return (*reinterpret_cast*>(offset1) == *reinterpret_cast*>(offset2)); @@ -578,6 +563,12 @@ bool AreEqualOptions( *reinterpret_cast( offset1) == *reinterpret_cast(offset2)); + case OptionType::kBlockBasedTableDataBlockIndexType: + return ( + *reinterpret_cast( + offset1) == + *reinterpret_cast( + offset2)); case OptionType::kWALRecoveryMode: return (*reinterpret_cast(offset1) == *reinterpret_cast(offset2)); @@ -587,8 +578,38 @@ bool AreEqualOptions( case OptionType::kInfoLogLevel: return (*reinterpret_cast(offset1) == *reinterpret_cast(offset2)); + case OptionType::kCompactionOptionsFIFO: { + CompactionOptionsFIFO lhs = + *reinterpret_cast(offset1); + CompactionOptionsFIFO rhs = + *reinterpret_cast(offset2); + if (lhs.max_table_files_size == rhs.max_table_files_size && + lhs.allow_compaction == rhs.allow_compaction) { + return true; + } + return false; + } + case OptionType::kCompactionOptionsUniversal: { + CompactionOptionsUniversal lhs = + *reinterpret_cast(offset1); + CompactionOptionsUniversal rhs = + *reinterpret_cast(offset2); + if (lhs.size_ratio == rhs.size_ratio && + lhs.min_merge_width == rhs.min_merge_width && + lhs.max_merge_width == rhs.max_merge_width && + lhs.max_size_amplification_percent == + rhs.max_size_amplification_percent && + lhs.compression_size_percent == rhs.compression_size_percent && + lhs.stop_style == rhs.stop_style && + lhs.allow_trivial_move == rhs.allow_trivial_move) { + return true; + } + return false; + } default: if (type_info.verification == OptionVerificationType::kByName || + type_info.verification == + OptionVerificationType::kByNameAllowFromNull || type_info.verification == OptionVerificationType::kByNameAllowNull) { std::string value1; bool result = @@ -608,6 +629,11 @@ bool AreEqualOptions( if (iter->second == kNullptrString || value1 == kNullptrString) { return true; } + } else if (type_info.verification == + OptionVerificationType::kByNameAllowFromNull) { + if (iter->second == kNullptrString) { + return true; + } } return (value1 == iter->second); } @@ -690,7 +716,7 @@ Status RocksDBOptionsParser::VerifyRocksDBOptionsFromFile( Status RocksDBOptionsParser::VerifyDBOptions( const DBOptions& base_opt, const DBOptions& persisted_opt, - const std::unordered_map* opt_map, + const std::unordered_map* /*opt_map*/, OptionsSanityCheckLevel sanity_check_level) { for (auto pair : db_options_type_info) { if (pair.second.verification == OptionVerificationType::kDeprecated) { diff --git a/thirdparty/rocksdb/options/options_parser.h b/thirdparty/rocksdb/options/options_parser.h index 5545c0b0fa..5aab3e7e9b 100644 --- a/thirdparty/rocksdb/options/options_parser.h +++ b/thirdparty/rocksdb/options/options_parser.h @@ -9,7 +9,6 @@ #include #include -#include "options/options_helper.h" #include "options/options_sanity_check.h" #include "rocksdb/env.h" #include "rocksdb/options.h" diff --git a/thirdparty/rocksdb/options/options_settable_test.cc b/thirdparty/rocksdb/options/options_settable_test.cc index ab9989fb46..3a6bd6a882 100644 --- a/thirdparty/rocksdb/options/options_settable_test.cc +++ b/thirdparty/rocksdb/options/options_settable_test.cc @@ -13,15 +13,15 @@ #include -#include "options/options_parser.h" +#include "options/options_helper.h" #include "rocksdb/convenience.h" #include "util/testharness.h" #ifndef GFLAGS bool FLAGS_enable_print = false; #else -#include -using GFLAGS::ParseCommandLineFlags; +#include "util/gflags_compat.h" +using GFLAGS_NAMESPACE::ParseCommandLineFlags; DEFINE_bool(enable_print, false, "Print options generated to console."); #endif // GFLAGS @@ -140,7 +140,10 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) { "cache_index_and_filter_blocks=1;" "cache_index_and_filter_blocks_with_high_priority=true;" "pin_l0_filter_and_index_blocks_in_cache=1;" + "pin_top_level_index_and_filter=1;" "index_type=kHashSearch;" + "data_block_index_type=kDataBlockBinaryAndHash;" + "data_block_hash_table_util_ratio=0.75;" "checksum=kxxHash;hash_index_allow_collision=1;no_block_cache=1;" "block_cache=1M;block_cache_compressed=1k;block_size=1024;" "block_size_deviation=8;block_restart_interval=4; " @@ -150,7 +153,9 @@ TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) { "filter_policy=bloomfilter:4:true;whole_key_filtering=1;" "format_version=1;" "hash_index_allow_collision=false;" - "verify_compression=true;read_amp_bytes_per_bit=0", + "verify_compression=true;read_amp_bytes_per_bit=0;" + "enable_index_compression=false;" + "block_align=true", new_bbto)); ASSERT_EQ(unset_bytes_base, @@ -261,6 +266,8 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) { "manifest_preallocation_size=1222;" "allow_mmap_writes=false;" "stats_dump_period_sec=70127;" + "stats_persist_period_sec=54321;" + "stats_history_buffer_size=14159;" "allow_fallocate=true;" "allow_mmap_reads=false;" "use_direct_reads=false;" @@ -282,8 +289,13 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) { "avoid_flush_during_recovery=false;" "avoid_flush_during_shutdown=false;" "allow_ingest_behind=false;" + "preserve_deletes=false;" "concurrent_prepare=false;" - "manual_wal_flush=false;", + "two_write_queues=false;" + "manual_wal_flush=false;" + "seq_per_batch=false;" + "atomic_flush=false;" + "avoid_unnecessary_blocking_io=false", new_options)); ASSERT_EQ(unset_bytes_base, NumUnsetBytes(new_options_ptr, sizeof(DBOptions), @@ -296,6 +308,12 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) { delete[] new_options_ptr; } +template +inline int offset_of(T1 T2::*member) { + static T2 obj; + return int(size_t(&(obj.*member)) - size_t(&obj)); +} + // If the test fails, likely a new option is added to ColumnFamilyOptions // but it cannot be set through GetColumnFamilyOptionsFromString(), or the // test is not updated accordingly. @@ -334,6 +352,10 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) { sizeof(std::shared_ptr)}, {offset_of(&ColumnFamilyOptions::table_factory), sizeof(std::shared_ptr)}, + {offset_of(&ColumnFamilyOptions::cf_paths), + sizeof(std::vector)}, + {offset_of(&ColumnFamilyOptions::compaction_thread_limiter), + sizeof(std::shared_ptr)}, }; char* options_ptr = new char[sizeof(ColumnFamilyOptions)]; @@ -367,10 +389,12 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) { options->rate_limit_delay_max_milliseconds = 33; options->compaction_options_universal = CompactionOptionsUniversal(); options->compression_opts = CompressionOptions(); + options->bottommost_compression_opts = CompressionOptions(); options->hard_rate_limit = 0; options->soft_rate_limit = 0; - options->compaction_options_fifo = CompactionOptionsFIFO(); + options->purge_redundant_kvs_while_flush = false; options->max_mem_compaction_level = 0; + options->compaction_filter = nullptr; char* new_options_ptr = new char[sizeof(ColumnFamilyOptions)]; ColumnFamilyOptions* new_options = @@ -414,6 +438,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) { "max_write_buffer_number_to_maintain=84;" "merge_operator=aabcxehazrMergeOperator;" "memtable_prefix_bloom_size_ratio=0.4642;" + "memtable_whole_key_filtering=true;" "memtable_insert_with_hint_prefix_extractor=rocksdb.CappedPrefix.13;" "paranoid_file_checks=true;" "force_consistency_checks=true;" @@ -423,10 +448,13 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) { "inplace_update_support=false;" "compaction_style=kCompactionStyleFIFO;" "compaction_pri=kMinOverlappingRatio;" - "purge_redundant_kvs_while_flush=true;" "hard_pending_compaction_bytes_limit=0;" "disable_auto_compactions=false;" - "report_bg_io_stats=true;", + "report_bg_io_stats=true;" + "ttl=60;" + "sample_for_compression=0;" + "compaction_options_fifo={max_table_files_size=3;allow_" + "compaction=false;};", new_options)); ASSERT_EQ(unset_bytes_base, diff --git a/thirdparty/rocksdb/options/options_test.cc b/thirdparty/rocksdb/options/options_test.cc index fc4939beb4..586e5697cb 100644 --- a/thirdparty/rocksdb/options/options_test.cc +++ b/thirdparty/rocksdb/options/options_test.cc @@ -16,24 +16,29 @@ #include #include +#include "cache/lru_cache.h" +#include "cache/sharded_cache.h" #include "options/options_helper.h" #include "options/options_parser.h" #include "options/options_sanity_check.h" +#include "port/port.h" #include "rocksdb/cache.h" #include "rocksdb/convenience.h" #include "rocksdb/memtablerep.h" #include "rocksdb/utilities/leveldb_options.h" +#include "rocksdb/utilities/object_registry.h" #include "util/random.h" #include "util/stderr_logger.h" #include "util/string_util.h" #include "util/testharness.h" #include "util/testutil.h" +#include "utilities/merge_operators/bytesxor.h" #ifndef GFLAGS bool FLAGS_enable_print = false; #else -#include -using GFLAGS::ParseCommandLineFlags; +#include "util/gflags_compat.h" +using GFLAGS_NAMESPACE::ParseCommandLineFlags; DEFINE_bool(enable_print, false, "Print options generated to console."); #endif // GFLAGS @@ -60,7 +65,8 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) { "kZSTD:" "kZSTDNotFinalCompression"}, {"bottommost_compression", "kLZ4Compression"}, - {"compression_opts", "4:5:6:7"}, + {"bottommost_compression_opts", "5:6:7:8:9:true"}, + {"compression_opts", "4:5:6:7:8:true"}, {"num_levels", "8"}, {"level0_file_num_compaction_trigger", "8"}, {"level0_slowdown_writes_trigger", "9"}, @@ -87,6 +93,7 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) { {"compaction_measure_io_stats", "false"}, {"inplace_update_num_locks", "25"}, {"memtable_prefix_bloom_size_ratio", "0.26"}, + {"memtable_whole_key_filtering", "true"}, {"memtable_huge_page_size", "28"}, {"bloom_locality", "29"}, {"max_successive_merges", "30"}, @@ -124,6 +131,8 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) { {"is_fd_close_on_exec", "true"}, {"skip_log_error_on_recovery", "false"}, {"stats_dump_period_sec", "46"}, + {"stats_persist_period_sec", "57"}, + {"stats_history_buffer_size", "69"}, {"advise_random_on_open", "true"}, {"use_adaptive_mutex", "false"}, {"new_table_reader_for_compaction_inputs", "true"}, @@ -157,7 +166,15 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) { ASSERT_EQ(new_cf_opt.compression_opts.level, 5); ASSERT_EQ(new_cf_opt.compression_opts.strategy, 6); ASSERT_EQ(new_cf_opt.compression_opts.max_dict_bytes, 7); + ASSERT_EQ(new_cf_opt.compression_opts.zstd_max_train_bytes, 8); + ASSERT_EQ(new_cf_opt.compression_opts.enabled, true); ASSERT_EQ(new_cf_opt.bottommost_compression, kLZ4Compression); + ASSERT_EQ(new_cf_opt.bottommost_compression_opts.window_bits, 5); + ASSERT_EQ(new_cf_opt.bottommost_compression_opts.level, 6); + ASSERT_EQ(new_cf_opt.bottommost_compression_opts.strategy, 7); + ASSERT_EQ(new_cf_opt.bottommost_compression_opts.max_dict_bytes, 8); + ASSERT_EQ(new_cf_opt.bottommost_compression_opts.zstd_max_train_bytes, 9); + ASSERT_EQ(new_cf_opt.bottommost_compression_opts.enabled, true); ASSERT_EQ(new_cf_opt.num_levels, 8); ASSERT_EQ(new_cf_opt.level0_file_num_compaction_trigger, 8); ASSERT_EQ(new_cf_opt.level0_slowdown_writes_trigger, 9); @@ -184,6 +201,7 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) { ASSERT_EQ(new_cf_opt.inplace_update_support, true); ASSERT_EQ(new_cf_opt.inplace_update_num_locks, 25U); ASSERT_EQ(new_cf_opt.memtable_prefix_bloom_size_ratio, 0.26); + ASSERT_EQ(new_cf_opt.memtable_whole_key_filtering, true); ASSERT_EQ(new_cf_opt.memtable_huge_page_size, 28U); ASSERT_EQ(new_cf_opt.bloom_locality, 29U); ASSERT_EQ(new_cf_opt.max_successive_merges, 30U); @@ -249,6 +267,8 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) { ASSERT_EQ(new_db_opt.is_fd_close_on_exec, true); ASSERT_EQ(new_db_opt.skip_log_error_on_recovery, false); ASSERT_EQ(new_db_opt.stats_dump_period_sec, 46U); + ASSERT_EQ(new_db_opt.stats_persist_period_sec, 57U); + ASSERT_EQ(new_db_opt.stats_history_buffer_size, 69U); ASSERT_EQ(new_db_opt.advise_random_on_open, true); ASSERT_EQ(new_db_opt.use_adaptive_mutex, false); ASSERT_EQ(new_db_opt.new_table_reader_for_compaction_inputs, true); @@ -317,6 +337,34 @@ TEST_F(OptionsTest, GetColumnFamilyOptionsFromStringTest) { &new_cf_opt)); ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opt, new_cf_opt)); + // Comparator from object registry + std::string kCompName = "reverse_comp"; + static Registrar test_reg_a( + kCompName, [](const std::string& /*name*/, + std::unique_ptr* /*comparator_guard*/) { + return ReverseBytewiseComparator(); + }); + + ASSERT_OK(GetColumnFamilyOptionsFromString( + base_cf_opt, "comparator=" + kCompName + ";", &new_cf_opt)); + ASSERT_EQ(new_cf_opt.comparator, ReverseBytewiseComparator()); + + // MergeOperator from object registry + std::unique_ptr bxo(new BytesXOROperator()); + std::string kMoName = bxo->Name(); + static Registrar> test_reg_b( + kMoName, [](const std::string& /*name*/, + std::unique_ptr>* + merge_operator_guard) { + merge_operator_guard->reset( + new std::shared_ptr(new BytesXOROperator())); + return merge_operator_guard->get(); + }); + + ASSERT_OK(GetColumnFamilyOptionsFromString( + base_cf_opt, "merge_operator=" + kMoName + ";", &new_cf_opt)); + ASSERT_EQ(kMoName, std::string(new_cf_opt.merge_operator->Name())); + // Wrong key/value pair ASSERT_NOK(GetColumnFamilyOptionsFromString(base_cf_opt, "write_buffer_size=13;max_write_buffer_number;", &new_cf_opt)); @@ -529,6 +577,101 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) { ASSERT_EQ(table_opt.cache_index_and_filter_blocks, new_opt.cache_index_and_filter_blocks); ASSERT_EQ(table_opt.filter_policy, new_opt.filter_policy); + + // Check block cache options are overwritten when specified + // in new format as a struct. + ASSERT_OK(GetBlockBasedTableOptionsFromString(table_opt, + "block_cache={capacity=1M;num_shard_bits=4;" + "strict_capacity_limit=true;high_pri_pool_ratio=0.5;};" + "block_cache_compressed={capacity=1M;num_shard_bits=4;" + "strict_capacity_limit=true;high_pri_pool_ratio=0.5;}", + &new_opt)); + ASSERT_TRUE(new_opt.block_cache != nullptr); + ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetNumShardBits(), 4); + ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetHighPriPoolRatio(), 0.5); + ASSERT_TRUE(new_opt.block_cache_compressed != nullptr); + ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 1024UL*1024UL); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetNumShardBits(), 4); + ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), true); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetHighPriPoolRatio(), + 0.5); + + // Set only block cache capacity. Check other values are + // reset to default values. + ASSERT_OK(GetBlockBasedTableOptionsFromString(table_opt, + "block_cache={capacity=2M};" + "block_cache_compressed={capacity=2M}", + &new_opt)); + ASSERT_TRUE(new_opt.block_cache != nullptr); + ASSERT_EQ(new_opt.block_cache->GetCapacity(), 2*1024UL*1024UL); + // Default values + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetNumShardBits(), + GetDefaultCacheShardBits(new_opt.block_cache->GetCapacity())); + ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetHighPriPoolRatio(), 0.0); + ASSERT_TRUE(new_opt.block_cache_compressed != nullptr); + ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 2*1024UL*1024UL); + // Default values + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetNumShardBits(), + GetDefaultCacheShardBits( + new_opt.block_cache_compressed->GetCapacity())); + ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), false); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetHighPriPoolRatio(), + 0.0); + + // Set couple of block cache options. + ASSERT_OK(GetBlockBasedTableOptionsFromString(table_opt, + "block_cache={num_shard_bits=5;high_pri_pool_ratio=0.5;};" + "block_cache_compressed={num_shard_bits=5;" + "high_pri_pool_ratio=0.5;}", + &new_opt)); + ASSERT_EQ(new_opt.block_cache->GetCapacity(), 0); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetNumShardBits(), 5); + ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), false); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetHighPriPoolRatio(), 0.5); + ASSERT_TRUE(new_opt.block_cache_compressed != nullptr); + ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 0); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetNumShardBits(), 5); + ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), false); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetHighPriPoolRatio(), + 0.5); + + // Set couple of block cache options. + ASSERT_OK(GetBlockBasedTableOptionsFromString(table_opt, + "block_cache={capacity=1M;num_shard_bits=4;" + "strict_capacity_limit=true;};" + "block_cache_compressed={capacity=1M;num_shard_bits=4;" + "strict_capacity_limit=true;}", + &new_opt)); + ASSERT_TRUE(new_opt.block_cache != nullptr); + ASSERT_EQ(new_opt.block_cache->GetCapacity(), 1024UL*1024UL); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetNumShardBits(), 4); + ASSERT_EQ(new_opt.block_cache->HasStrictCapacityLimit(), true); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache)->GetHighPriPoolRatio(), 0.0); + ASSERT_TRUE(new_opt.block_cache_compressed != nullptr); + ASSERT_EQ(new_opt.block_cache_compressed->GetCapacity(), 1024UL*1024UL); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetNumShardBits(), 4); + ASSERT_EQ(new_opt.block_cache_compressed->HasStrictCapacityLimit(), true); + ASSERT_EQ(std::dynamic_pointer_cast( + new_opt.block_cache_compressed)->GetHighPriPoolRatio(), + 0.0); } #endif // !ROCKSDB_LITE @@ -598,8 +741,8 @@ TEST_F(OptionsTest, GetMemTableRepFactoryFromString) { &new_mem_factory)); ASSERT_NOK(GetMemTableRepFactoryFromString("cuckoo", &new_mem_factory)); - ASSERT_OK(GetMemTableRepFactoryFromString("cuckoo:1024", &new_mem_factory)); - ASSERT_EQ(std::string(new_mem_factory->Name()), "HashCuckooRepFactory"); + // CuckooHash memtable is already removed. + ASSERT_NOK(GetMemTableRepFactoryFromString("cuckoo:1024", &new_mem_factory)); ASSERT_NOK(GetMemTableRepFactoryFromString("bad_factory", &new_mem_factory)); } @@ -619,6 +762,8 @@ TEST_F(OptionsTest, GetOptionsFromStringTest) { "write_buffer_size=10;max_write_buffer_number=16;" "block_based_table_factory={block_cache=1M;block_size=4;};" "compression_opts=4:5:6;create_if_missing=true;max_open_files=1;" + "bottommost_compression_opts=5:6:7;create_if_missing=true;max_open_files=" + "1;" "rate_limiter_bytes_per_sec=1024", &new_options)); @@ -626,7 +771,15 @@ TEST_F(OptionsTest, GetOptionsFromStringTest) { ASSERT_EQ(new_options.compression_opts.level, 5); ASSERT_EQ(new_options.compression_opts.strategy, 6); ASSERT_EQ(new_options.compression_opts.max_dict_bytes, 0); + ASSERT_EQ(new_options.compression_opts.zstd_max_train_bytes, 0); + ASSERT_EQ(new_options.compression_opts.enabled, false); ASSERT_EQ(new_options.bottommost_compression, kDisableCompressionOption); + ASSERT_EQ(new_options.bottommost_compression_opts.window_bits, 5); + ASSERT_EQ(new_options.bottommost_compression_opts.level, 6); + ASSERT_EQ(new_options.bottommost_compression_opts.strategy, 7); + ASSERT_EQ(new_options.bottommost_compression_opts.max_dict_bytes, 0); + ASSERT_EQ(new_options.bottommost_compression_opts.zstd_max_train_bytes, 0); + ASSERT_EQ(new_options.bottommost_compression_opts.enabled, false); ASSERT_EQ(new_options.write_buffer_size, 10U); ASSERT_EQ(new_options.max_write_buffer_number, 16); BlockBasedTableOptions new_block_based_table_options = @@ -660,6 +813,25 @@ TEST_F(OptionsTest, DBOptionsSerialization) { ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(base_options, new_options)); } +TEST_F(OptionsTest, OptionsComposeDecompose) { + // build an Options from DBOptions + CFOptions, then decompose it to verify + // we get same constituent options. + DBOptions base_db_opts; + ColumnFamilyOptions base_cf_opts; + + Random rnd(301); + test::RandomInitDBOptions(&base_db_opts, &rnd); + test::RandomInitCFOptions(&base_cf_opts, &rnd); + + Options base_opts(base_db_opts, base_cf_opts); + DBOptions new_db_opts(base_opts); + ColumnFamilyOptions new_cf_opts(base_opts); + + ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(base_db_opts, new_db_opts)); + ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(base_cf_opts, new_cf_opts)); + delete new_cf_opts.compaction_filter; +} + TEST_F(OptionsTest, ColumnFamilyOptionsSerialization) { ColumnFamilyOptions base_opt, new_opt; Random rnd(302); @@ -1100,37 +1272,79 @@ TEST_F(OptionsParserTest, DuplicateCFOptions) { } TEST_F(OptionsParserTest, IgnoreUnknownOptions) { - DBOptions db_opt; - db_opt.max_open_files = 12345; - db_opt.max_background_flushes = 301; - db_opt.max_total_wal_size = 1024; - ColumnFamilyOptions cf_opt; + for (int case_id = 0; case_id < 5; case_id++) { + DBOptions db_opt; + db_opt.max_open_files = 12345; + db_opt.max_background_flushes = 301; + db_opt.max_total_wal_size = 1024; + ColumnFamilyOptions cf_opt; - std::string options_file_content = - "# This is a testing option string.\n" - "# Currently we only support \"#\" styled comment.\n" - "\n" - "[Version]\n" - " rocksdb_version=3.14.0\n" - " options_file_version=1\n" - "[DBOptions]\n" - " max_open_files=12345\n" - " max_background_flushes=301\n" - " max_total_wal_size=1024 # keep_log_file_num=1000\n" - " unknown_db_option1=321\n" - " unknown_db_option2=false\n" - "[CFOptions \"default\"]\n" - " unknown_cf_option1=hello\n" - "[CFOptions \"something_else\"]\n" - " unknown_cf_option2=world\n" - " # if a section is blank, we will use the default\n"; + std::string version_string; + bool should_ignore = true; + if (case_id == 0) { + // same version + should_ignore = false; + version_string = + ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR) + ".0"; + } else if (case_id == 1) { + // higher minor version + should_ignore = true; + version_string = + ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR + 1) + ".0"; + } else if (case_id == 2) { + // higher major version. + should_ignore = true; + version_string = ToString(ROCKSDB_MAJOR + 1) + ".0.0"; + } else if (case_id == 3) { + // lower minor version +#if ROCKSDB_MINOR == 0 + continue; +#else + version_string = + ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR - 1) + ".0"; + should_ignore = false; +#endif + } else { + // lower major version + should_ignore = false; + version_string = + ToString(ROCKSDB_MAJOR - 1) + "." + ToString(ROCKSDB_MINOR) + ".0"; + } - const std::string kTestFileName = "test-rocksdb-options.ini"; - env_->WriteToNewFile(kTestFileName, options_file_content); - RocksDBOptionsParser parser; - ASSERT_NOK(parser.Parse(kTestFileName, env_.get())); - ASSERT_OK(parser.Parse(kTestFileName, env_.get(), - true /* ignore_unknown_options */)); + std::string options_file_content = + "# This is a testing option string.\n" + "# Currently we only support \"#\" styled comment.\n" + "\n" + "[Version]\n" + " rocksdb_version=" + + version_string + + "\n" + " options_file_version=1\n" + "[DBOptions]\n" + " max_open_files=12345\n" + " max_background_flushes=301\n" + " max_total_wal_size=1024 # keep_log_file_num=1000\n" + " unknown_db_option1=321\n" + " unknown_db_option2=false\n" + "[CFOptions \"default\"]\n" + " unknown_cf_option1=hello\n" + "[CFOptions \"something_else\"]\n" + " unknown_cf_option2=world\n" + " # if a section is blank, we will use the default\n"; + + const std::string kTestFileName = "test-rocksdb-options.ini"; + env_->DeleteFile(kTestFileName); + env_->WriteToNewFile(kTestFileName, options_file_content); + RocksDBOptionsParser parser; + ASSERT_NOK(parser.Parse(kTestFileName, env_.get())); + if (should_ignore) { + ASSERT_OK(parser.Parse(kTestFileName, env_.get(), + true /* ignore_unknown_options */)); + } else { + ASSERT_NOK(parser.Parse(kTestFileName, env_.get(), + true /* ignore_unknown_options */)); + } + } } TEST_F(OptionsParserTest, ParseVersion) { @@ -1351,6 +1565,7 @@ TEST_F(OptionsParserTest, DifferentDefault) { const std::string kOptionsFileName = "test-persisted-options.ini"; ColumnFamilyOptions cf_level_opts; + ASSERT_EQ(CompactionPri::kMinOverlappingRatio, cf_level_opts.compaction_pri); cf_level_opts.OptimizeLevelStyleCompaction(); ColumnFamilyOptions cf_univ_opts; @@ -1420,6 +1635,14 @@ TEST_F(OptionsParserTest, DifferentDefault) { Options old_default_opts; old_default_opts.OldDefaults(5, 2); ASSERT_EQ(16 * 1024U * 1024U, old_default_opts.delayed_write_rate); + ASSERT_TRUE(old_default_opts.compaction_pri == + CompactionPri::kByCompensatedSize); + } + { + Options old_default_opts; + old_default_opts.OldDefaults(5, 18); + ASSERT_TRUE(old_default_opts.compaction_pri == + CompactionPri::kByCompensatedSize); } Options small_opts; @@ -1525,6 +1748,15 @@ TEST_F(OptionsSanityCheckTest, SanityCheck) { // merge_operator { + // Test when going from nullptr -> merge operator + opts.merge_operator.reset(test::RandomMergeOperator(&rnd)); + ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible)); + ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone)); + + // persist the change + ASSERT_OK(PersistCFOptions(opts)); + ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch)); + for (int test = 0; test < 5; ++test) { // change the merge operator opts.merge_operator.reset(test::RandomMergeOperator(&rnd)); @@ -1535,6 +1767,15 @@ TEST_F(OptionsSanityCheckTest, SanityCheck) { ASSERT_OK(PersistCFOptions(opts)); ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch)); } + + // Test when going from merge operator -> nullptr + opts.merge_operator = nullptr; + ASSERT_NOK(SanityCheckCFOptions(opts, kSanityLevelLooselyCompatible)); + ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelNone)); + + // persist the change + ASSERT_OK(PersistCFOptions(opts)); + ASSERT_OK(SanityCheckCFOptions(opts, kSanityLevelExactMatch)); } // compaction_filter @@ -1603,6 +1844,18 @@ bool IsEscapedString(const std::string& str) { } } // namespace +TEST_F(OptionsParserTest, IntegerParsing) { + ASSERT_EQ(ParseUint64("18446744073709551615"), 18446744073709551615U); + ASSERT_EQ(ParseUint32("4294967295"), 4294967295U); + ASSERT_EQ(ParseSizeT("18446744073709551615"), 18446744073709551615U); + ASSERT_EQ(ParseInt64("9223372036854775807"), 9223372036854775807U); + ASSERT_EQ(ParseInt64("-9223372036854775808"), port::kMinInt64); + ASSERT_EQ(ParseInt32("2147483647"), 2147483647U); + ASSERT_EQ(ParseInt32("-2147483648"), port::kMinInt32); + ASSERT_EQ(ParseInt("-32767"), -32767); + ASSERT_EQ(ParseDouble("-1.234567"), -1.234567); +} + TEST_F(OptionsParserTest, EscapeOptionString) { ASSERT_EQ(UnescapeOptionString( "This is a test string with \\# \\: and \\\\ escape chars."), diff --git a/thirdparty/rocksdb/port/jemalloc_helper.h b/thirdparty/rocksdb/port/jemalloc_helper.h new file mode 100644 index 0000000000..0c216face1 --- /dev/null +++ b/thirdparty/rocksdb/port/jemalloc_helper.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#ifdef ROCKSDB_JEMALLOC +#ifdef __FreeBSD__ +#include +#else +#include +#endif + +#ifndef JEMALLOC_CXX_THROW +#define JEMALLOC_CXX_THROW +#endif + +// Declare non-standard jemalloc APIs as weak symbols. We can null-check these +// symbols to detect whether jemalloc is linked with the binary. +extern "C" void* mallocx(size_t, int) __attribute__((__weak__)); +extern "C" void* rallocx(void*, size_t, int) __attribute__((__weak__)); +extern "C" size_t xallocx(void*, size_t, size_t, int) __attribute__((__weak__)); +extern "C" size_t sallocx(const void*, int) __attribute__((__weak__)); +extern "C" void dallocx(void*, int) __attribute__((__weak__)); +extern "C" void sdallocx(void*, size_t, int) __attribute__((__weak__)); +extern "C" size_t nallocx(size_t, int) __attribute__((__weak__)); +extern "C" int mallctl(const char*, void*, size_t*, void*, size_t) + __attribute__((__weak__)); +extern "C" int mallctlnametomib(const char*, size_t*, size_t*) + __attribute__((__weak__)); +extern "C" int mallctlbymib(const size_t*, size_t, void*, size_t*, void*, + size_t) __attribute__((__weak__)); +extern "C" void malloc_stats_print(void (*)(void*, const char*), void*, + const char*) __attribute__((__weak__)); +extern "C" size_t malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void*) + JEMALLOC_CXX_THROW __attribute__((__weak__)); + +// Check if Jemalloc is linked with the binary. Note the main program might be +// using a different memory allocator even this method return true. +// It is loosely based on folly::usingJEMalloc(), minus the check that actually +// allocate memory and see if it is through jemalloc, to handle the dlopen() +// case: +// https://github.com/facebook/folly/blob/76cf8b5841fb33137cfbf8b224f0226437c855bc/folly/memory/Malloc.h#L147 +static inline bool HasJemalloc() { + return mallocx != nullptr && rallocx != nullptr && xallocx != nullptr && + sallocx != nullptr && dallocx != nullptr && sdallocx != nullptr && + nallocx != nullptr && mallctl != nullptr && + mallctlnametomib != nullptr && mallctlbymib != nullptr && + malloc_stats_print != nullptr && malloc_usable_size != nullptr; +} + +#endif // ROCKSDB_JEMALLOC diff --git a/thirdparty/rocksdb/port/likely.h b/thirdparty/rocksdb/port/likely.h index e5ef786f2e..397d757133 100644 --- a/thirdparty/rocksdb/port/likely.h +++ b/thirdparty/rocksdb/port/likely.h @@ -7,8 +7,7 @@ // 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. -#ifndef PORT_LIKELY_H_ -#define PORT_LIKELY_H_ +#pragma once #if defined(__GNUC__) && __GNUC__ >= 4 #define LIKELY(x) (__builtin_expect((x), 1)) @@ -17,5 +16,3 @@ #define LIKELY(x) (x) #define UNLIKELY(x) (x) #endif - -#endif // PORT_LIKELY_H_ diff --git a/thirdparty/rocksdb/port/dirent.h b/thirdparty/rocksdb/port/port_dirent.h similarity index 89% rename from thirdparty/rocksdb/port/dirent.h rename to thirdparty/rocksdb/port/port_dirent.h index 7bcc356978..cb1adbe129 100644 --- a/thirdparty/rocksdb/port/dirent.h +++ b/thirdparty/rocksdb/port/port_dirent.h @@ -9,8 +9,7 @@ // // See port_example.h for documentation for the following types/functions. -#ifndef STORAGE_LEVELDB_PORT_DIRENT_H_ -#define STORAGE_LEVELDB_PORT_DIRENT_H_ +#pragma once #ifdef ROCKSDB_PLATFORM_POSIX #include @@ -43,5 +42,3 @@ using port::closedir; } // namespace rocksdb #endif // OS_WIN - -#endif // STORAGE_LEVELDB_PORT_DIRENT_H_ diff --git a/thirdparty/rocksdb/port/port_example.h b/thirdparty/rocksdb/port/port_example.h index 05b3240669..a94dc93c26 100644 --- a/thirdparty/rocksdb/port/port_example.h +++ b/thirdparty/rocksdb/port/port_example.h @@ -12,8 +12,7 @@ // specific port_.h file. Use this file as a reference for // how to port this package to a new platform. -#ifndef STORAGE_LEVELDB_PORT_PORT_EXAMPLE_H_ -#define STORAGE_LEVELDB_PORT_PORT_EXAMPLE_H_ +#pragma once namespace rocksdb { namespace port { @@ -100,5 +99,3 @@ extern bool Snappy_Uncompress(const char* input_data, size_t input_length, } // namespace port } // namespace rocksdb - -#endif // STORAGE_LEVELDB_PORT_PORT_EXAMPLE_H_ diff --git a/thirdparty/rocksdb/port/port_posix.cc b/thirdparty/rocksdb/port/port_posix.cc index 129933bb1f..80081e480e 100644 --- a/thirdparty/rocksdb/port/port_posix.cc +++ b/thirdparty/rocksdb/port/port_posix.cc @@ -25,6 +25,21 @@ #include "util/logging.h" namespace rocksdb { + +// We want to give users opportunity to default all the mutexes to adaptive if +// not specified otherwise. This enables a quick way to conduct various +// performance related experiements. +// +// NB! Support for adaptive mutexes is turned on by definining +// ROCKSDB_PTHREAD_ADAPTIVE_MUTEX during the compilation. If you use RocksDB +// build environment then this happens automatically; otherwise it's up to the +// consumer to define the identifier. +#ifdef ROCKSDB_DEFAULT_TO_ADAPTIVE_MUTEX +extern const bool kDefaultToAdaptiveMutex = true; +#else +extern const bool kDefaultToAdaptiveMutex = false; +#endif + namespace port { static int PthreadCall(const char* label, int result) { @@ -36,6 +51,7 @@ static int PthreadCall(const char* label, int result) { } Mutex::Mutex(bool adaptive) { + (void) adaptive; #ifdef ROCKSDB_PTHREAD_ADAPTIVE_MUTEX if (!adaptive) { PthreadCall("init mutex", pthread_mutex_init(&mu_, nullptr)); @@ -187,12 +203,10 @@ int GetMaxOpenFiles() { void *cacheline_aligned_alloc(size_t size) { #if __GNUC__ < 5 && defined(__SANITIZE_ADDRESS__) return malloc(size); -#elif defined(_ISOC11_SOURCE) - return aligned_alloc(CACHE_LINE_SIZE, size); #elif ( _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || defined(__APPLE__)) void *m; errno = posix_memalign(&m, CACHE_LINE_SIZE, size); - return errno ? NULL : m; + return errno ? nullptr : m; #else return malloc(size); #endif diff --git a/thirdparty/rocksdb/port/port_posix.h b/thirdparty/rocksdb/port/port_posix.h index fe0d42644c..63d7239fe6 100644 --- a/thirdparty/rocksdb/port/port_posix.h +++ b/thirdparty/rocksdb/port/port_posix.h @@ -82,12 +82,18 @@ #endif namespace rocksdb { + +extern const bool kDefaultToAdaptiveMutex; + namespace port { // For use at db/file_indexer.h kLevelMaxIndex +const uint32_t kMaxUint32 = std::numeric_limits::max(); const int kMaxInt32 = std::numeric_limits::max(); +const int kMinInt32 = std::numeric_limits::min(); const uint64_t kMaxUint64 = std::numeric_limits::max(); const int64_t kMaxInt64 = std::numeric_limits::max(); +const int64_t kMinInt64 = std::numeric_limits::min(); const size_t kMaxSizet = std::numeric_limits::max(); static const bool kLittleEndian = PLATFORM_IS_LITTLE_ENDIAN; @@ -97,19 +103,7 @@ class CondVar; class Mutex { public: -// We want to give users opportunity to default all the mutexes to adaptive if -// not specified otherwise. This enables a quick way to conduct various -// performance related experiements. -// -// NB! Support for adaptive mutexes is turned on by definining -// ROCKSDB_PTHREAD_ADAPTIVE_MUTEX during the compilation. If you use RocksDB -// build environment then this happens automatically; otherwise it's up to the -// consumer to define the identifier. -#ifdef ROCKSDB_DEFAULT_TO_ADAPTIVE_MUTEX - explicit Mutex(bool adaptive = true); -#else - explicit Mutex(bool adaptive = false); -#endif + explicit Mutex(bool adaptive = kDefaultToAdaptiveMutex); ~Mutex(); void Lock(); diff --git a/thirdparty/rocksdb/port/stack_trace.cc b/thirdparty/rocksdb/port/stack_trace.cc index baaf140142..8f8135a446 100644 --- a/thirdparty/rocksdb/port/stack_trace.cc +++ b/thirdparty/rocksdb/port/stack_trace.cc @@ -13,7 +13,7 @@ namespace rocksdb { namespace port { void InstallStackTraceHandler() {} -void PrintStack(int first_frames_to_skip) {} +void PrintStack(int /*first_frames_to_skip*/) {} } // namespace port } // namespace rocksdb @@ -32,7 +32,7 @@ namespace port { namespace { -#ifdef OS_LINUX +#if defined(OS_LINUX) || defined(OS_FREEBSD) const char* GetExecutableName() { static char name[1024]; diff --git a/thirdparty/rocksdb/port/sys_time.h b/thirdparty/rocksdb/port/sys_time.h index 1e2ad0f5d6..2f83da8b3e 100644 --- a/thirdparty/rocksdb/port/sys_time.h +++ b/thirdparty/rocksdb/port/sys_time.h @@ -10,8 +10,7 @@ // This file is a portable substitute for sys/time.h which does not exist on // Windows -#ifndef STORAGE_LEVELDB_PORT_SYS_TIME_H_ -#define STORAGE_LEVELDB_PORT_SYS_TIME_H_ +#pragma once #if defined(OS_WIN) && defined(_MSC_VER) @@ -44,5 +43,3 @@ using port::localtime_r; #include #include #endif - -#endif // STORAGE_LEVELDB_PORT_SYS_TIME_H_ diff --git a/thirdparty/rocksdb/port/util_logger.h b/thirdparty/rocksdb/port/util_logger.h index a8255ad6d6..ba424705b2 100644 --- a/thirdparty/rocksdb/port/util_logger.h +++ b/thirdparty/rocksdb/port/util_logger.h @@ -7,8 +7,7 @@ // 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. -#ifndef STORAGE_LEVELDB_PORT_UTIL_LOGGER_H_ -#define STORAGE_LEVELDB_PORT_UTIL_LOGGER_H_ +#pragma once // Include the appropriate platform specific file below. If you are // porting to a new platform, see "port_example.h" for documentation @@ -19,5 +18,3 @@ #elif defined(OS_WIN) #include "port/win/win_logger.h" #endif - -#endif // STORAGE_LEVELDB_PORT_UTIL_LOGGER_H_ diff --git a/thirdparty/rocksdb/port/win/env_default.cc b/thirdparty/rocksdb/port/win/env_default.cc index 52a984f74c..d24c21918a 100644 --- a/thirdparty/rocksdb/port/win/env_default.cc +++ b/thirdparty/rocksdb/port/win/env_default.cc @@ -11,16 +11,14 @@ #include #include "port/win/env_win.h" +#include "util/compression_context_cache.h" +#include "util/sync_point.h" +#include "util/thread_local.h" namespace rocksdb { namespace port { -// We choose to create this on the heap and using std::once for the following -// reasons -// 1) Currently available MS compiler does not implement atomic C++11 -// initialization of -// function local statics -// 2) We choose not to destroy the env because joining the threads from the +// We choose not to destroy the env because joining the threads from the // system loader // which destroys the statics (same as from DLLMain) creates a system loader // dead-lock. @@ -29,14 +27,15 @@ namespace { std::once_flag winenv_once_flag; Env* envptr; }; - } Env* Env::Default() { using namespace port; + ThreadLocalPtr::InitSingletons(); + CompressionContextCache::InitSingleton(); + INIT_SYNC_POINT_SINGLETONS(); std::call_once(winenv_once_flag, []() { envptr = new WinEnv(); }); return envptr; } } - diff --git a/thirdparty/rocksdb/port/win/env_win.cc b/thirdparty/rocksdb/port/win/env_win.cc index 462148893b..9abb14d67e 100644 --- a/thirdparty/rocksdb/port/win/env_win.cc +++ b/thirdparty/rocksdb/port/win/env_win.cc @@ -24,7 +24,7 @@ #include "rocksdb/slice.h" #include "port/port.h" -#include "port/dirent.h" +#include "port/port_dirent.h" #include "port/win/win_logger.h" #include "port/win/io_win.h" @@ -35,6 +35,10 @@ #include // for uuid generation #include +#include +#include "strsafe.h" + +#include namespace rocksdb { @@ -44,10 +48,16 @@ ThreadStatusUpdater* CreateThreadStatusUpdater() { namespace { +// Sector size used when physical sector size cannot be obtained from device. +static const size_t kSectorSize = 512; + // RAII helpers for HANDLEs const auto CloseHandleFunc = [](HANDLE h) { ::CloseHandle(h); }; typedef std::unique_ptr UniqueCloseHandlePtr; +const auto FindCloseFunc = [](HANDLE h) { ::FindClose(h); }; +typedef std::unique_ptr UniqueFindClosePtr; + void WinthreadCall(const char* label, std::error_code result) { if (0 != result.value()) { fprintf(stderr, "pthread %s: %s\n", label, strerror(result.value())); @@ -60,10 +70,11 @@ void WinthreadCall(const char* label, std::error_code result) { namespace port { WinEnvIO::WinEnvIO(Env* hosted_env) - : hosted_env_(hosted_env), - page_size_(4 * 1012), + : hosted_env_(hosted_env), + page_size_(4 * 1024), allocation_granularity_(page_size_), perf_counter_frequency_(0), + nano_seconds_per_period_(0), GetSystemTimePreciseAsFileTime_(NULL) { SYSTEM_INFO sinfo; @@ -74,15 +85,21 @@ WinEnvIO::WinEnvIO(Env* hosted_env) { LARGE_INTEGER qpf; - BOOL ret = QueryPerformanceFrequency(&qpf); + BOOL ret __attribute__((__unused__)); + ret = QueryPerformanceFrequency(&qpf); assert(ret == TRUE); perf_counter_frequency_ = qpf.QuadPart; + + if (std::nano::den % perf_counter_frequency_ == 0) { + nano_seconds_per_period_ = std::nano::den / perf_counter_frequency_; + } } HMODULE module = GetModuleHandle("kernel32.dll"); if (module != NULL) { - GetSystemTimePreciseAsFileTime_ = (FnGetSystemTimePreciseAsFileTime)GetProcAddress( - module, "GetSystemTimePreciseAsFileTime"); + GetSystemTimePreciseAsFileTime_ = + (FnGetSystemTimePreciseAsFileTime)GetProcAddress( + module, "GetSystemTimePreciseAsFileTime"); } } @@ -92,13 +109,26 @@ WinEnvIO::~WinEnvIO() { Status WinEnvIO::DeleteFile(const std::string& fname) { Status result; - if (_unlink(fname.c_str())) { - result = IOError("Failed to delete: " + fname, errno); + BOOL ret = RX_DeleteFile(RX_FN(fname).c_str()); + + if(!ret) { + auto lastError = GetLastError(); + result = IOErrorFromWindowsError("Failed to delete: " + fname, + lastError); } return result; } +Status WinEnvIO::Truncate(const std::string& fname, size_t size) { + Status s; + int result = rocksdb::port::Truncate(fname, size); + if (result != 0) { + s = IOError("Failed to truncate: " + fname, errno); + } + return s; +} + Status WinEnvIO::GetCurrentTime(int64_t* unix_time) { time_t time = std::time(nullptr); if (time == (time_t)(-1)) { @@ -110,8 +140,8 @@ Status WinEnvIO::GetCurrentTime(int64_t* unix_time) { } Status WinEnvIO::NewSequentialFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& options) { Status s; result->reset(); @@ -129,17 +159,17 @@ Status WinEnvIO::NewSequentialFile(const std::string& fname, { IOSTATS_TIMER_GUARD(open_nanos); - hFile = CreateFileA( - fname.c_str(), GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, - OPEN_EXISTING, // Original fopen mode is "rb" - fileFlags, NULL); + hFile = RX_CreateFile( + RX_FN(fname).c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, // Original fopen mode is "rb" + fileFlags, NULL); } if (INVALID_HANDLE_VALUE == hFile) { auto lastError = GetLastError(); s = IOErrorFromWindowsError("Failed to open NewSequentialFile" + fname, - lastError); + lastError); } else { result->reset(new WinSequentialFile(fname, hFile, options)); } @@ -147,8 +177,8 @@ Status WinEnvIO::NewSequentialFile(const std::string& fname, } Status WinEnvIO::NewRandomAccessFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& options) { result->reset(); Status s; @@ -167,16 +197,16 @@ Status WinEnvIO::NewRandomAccessFile(const std::string& fname, HANDLE hFile = 0; { IOSTATS_TIMER_GUARD(open_nanos); - hFile = - CreateFileA(fname.c_str(), GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, fileFlags, NULL); + hFile = RX_CreateFile( + RX_FN(fname).c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, fileFlags, NULL); } if (INVALID_HANDLE_VALUE == hFile) { auto lastError = GetLastError(); return IOErrorFromWindowsError( - "NewRandomAccessFile failed to Create/Open: " + fname, lastError); + "NewRandomAccessFile failed to Create/Open: " + fname, lastError); } UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc); @@ -192,54 +222,57 @@ Status WinEnvIO::NewRandomAccessFile(const std::string& fname, // Will not map empty files if (fileSize == 0) { return IOError( - "NewRandomAccessFile failed to map empty file: " + fname, EINVAL); + "NewRandomAccessFile failed to map empty file: " + fname, EINVAL); } - HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, - 0, // Whole file at its present length - 0, - NULL); // Mapping name + HANDLE hMap = RX_CreateFileMapping(hFile, NULL, PAGE_READONLY, + 0, // At its present length + 0, + NULL); // Mapping name if (!hMap) { auto lastError = GetLastError(); return IOErrorFromWindowsError( - "Failed to create file mapping for NewRandomAccessFile: " + fname, - lastError); + "Failed to create file mapping for NewRandomAccessFile: " + fname, + lastError); } UniqueCloseHandlePtr mapGuard(hMap, CloseHandleFunc); const void* mapped_region = MapViewOfFileEx(hMap, FILE_MAP_READ, - 0, // High DWORD of access start - 0, // Low DWORD - fileSize, - NULL); // Let the OS choose the mapping + 0, // High DWORD of access start + 0, // Low DWORD + static_cast(fileSize), + NULL); // Let the OS choose the mapping if (!mapped_region) { auto lastError = GetLastError(); return IOErrorFromWindowsError( - "Failed to MapViewOfFile for NewRandomAccessFile: " + fname, - lastError); + "Failed to MapViewOfFile for NewRandomAccessFile: " + fname, + lastError); } result->reset(new WinMmapReadableFile(fname, hFile, hMap, mapped_region, - fileSize)); + static_cast(fileSize))); mapGuard.release(); fileGuard.release(); } } else { - result->reset(new WinRandomAccessFile(fname, hFile, page_size_, options)); + result->reset(new WinRandomAccessFile(fname, hFile, + std::max(GetSectorSize(fname), + page_size_), + options)); fileGuard.release(); } return s; } Status WinEnvIO::OpenWritableFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options, - bool reopen) { + std::unique_ptr* result, + const EnvOptions& options, + bool reopen) { const size_t c_BufferCapacity = 64 * 1024; @@ -251,7 +284,7 @@ Status WinEnvIO::OpenWritableFile(const std::string& fname, DWORD fileFlags = FILE_ATTRIBUTE_NORMAL; if (local_options.use_direct_writes && !local_options.use_mmap_writes) { - fileFlags = FILE_FLAG_NO_BUFFERING; + fileFlags = FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH; } // Desired access. We are want to write only here but if we want to memory @@ -264,8 +297,7 @@ Status WinEnvIO::OpenWritableFile(const std::string& fname, if (local_options.use_mmap_writes) { desired_access |= GENERIC_READ; - } - else { + } else { // Adding this solely for tests to pass (fault_injection_test, // wal_manager_test). shared_mode |= (FILE_SHARE_WRITE | FILE_SHARE_DELETE); @@ -280,20 +312,21 @@ Status WinEnvIO::OpenWritableFile(const std::string& fname, HANDLE hFile = 0; { IOSTATS_TIMER_GUARD(open_nanos); - hFile = CreateFileA( - fname.c_str(), - desired_access, // Access desired - shared_mode, - NULL, // Security attributes - creation_disposition, // Posix env says (reopen) ? (O_CREATE | O_APPEND) : O_CREAT | O_TRUNC - fileFlags, // Flags - NULL); // Template File + hFile = RX_CreateFile( + RX_FN(fname).c_str(), + desired_access, // Access desired + shared_mode, + NULL, // Security attributes + // Posix env says (reopen) ? (O_CREATE | O_APPEND) : O_CREAT | O_TRUNC + creation_disposition, + fileFlags, // Flags + NULL); // Template File } if (INVALID_HANDLE_VALUE == hFile) { auto lastError = GetLastError(); return IOErrorFromWindowsError( - "Failed to create a NewWriteableFile: " + fname, lastError); + "Failed to create a NewWriteableFile: " + fname, lastError); } // We will start writing at the end, appending @@ -304,7 +337,8 @@ Status WinEnvIO::OpenWritableFile(const std::string& fname, if (!ret) { auto lastError = GetLastError(); return IOErrorFromWindowsError( - "Failed to create a ReopenWritableFile move to the end: " + fname, lastError); + "Failed to create a ReopenWritableFile move to the end: " + fname, + lastError); } } @@ -312,18 +346,21 @@ Status WinEnvIO::OpenWritableFile(const std::string& fname, // We usually do not use mmmapping on SSD and thus we pass memory // page_size result->reset(new WinMmapFile(fname, hFile, page_size_, - allocation_granularity_, local_options)); + allocation_granularity_, local_options)); } else { // Here we want the buffer allocation to be aligned by the SSD page size // and to be a multiple of it - result->reset(new WinWritableFile(fname, hFile, page_size_, - c_BufferCapacity, local_options)); + result->reset(new WinWritableFile(fname, hFile, + std::max(GetSectorSize(fname), + GetPageSize()), + c_BufferCapacity, local_options)); } return s; } Status WinEnvIO::NewRandomRWFile(const std::string & fname, - std::unique_ptr* result, const EnvOptions & options) { + std::unique_ptr* result, + const EnvOptions & options) { Status s; @@ -331,7 +368,7 @@ Status WinEnvIO::NewRandomRWFile(const std::string & fname, // Random access is to disable read-ahead as the system reads too much data DWORD desired_access = GENERIC_READ | GENERIC_WRITE; DWORD shared_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; - DWORD creation_disposition = OPEN_ALWAYS; // Create if necessary or open existing + DWORD creation_disposition = OPEN_EXISTING; // Fail if file does not exist DWORD file_flags = FILE_FLAG_RANDOM_ACCESS; if (options.use_direct_reads && options.use_direct_writes) { @@ -344,13 +381,13 @@ Status WinEnvIO::NewRandomRWFile(const std::string & fname, { IOSTATS_TIMER_GUARD(open_nanos); hFile = - CreateFileA(fname.c_str(), - desired_access, - shared_mode, - NULL, // Security attributes - creation_disposition, - file_flags, - NULL); + RX_CreateFile(RX_FN(fname).c_str(), + desired_access, + shared_mode, + NULL, // Security attributes + creation_disposition, + file_flags, + NULL); } if (INVALID_HANDLE_VALUE == hFile) { @@ -360,78 +397,224 @@ Status WinEnvIO::NewRandomRWFile(const std::string & fname, } UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc); - result->reset(new WinRandomRWFile(fname, hFile, page_size_, options)); + result->reset(new WinRandomRWFile(fname, hFile, + std::max(GetSectorSize(fname), + GetPageSize()), + options)); + fileGuard.release(); + + return s; +} + +Status WinEnvIO::NewMemoryMappedFileBuffer( + const std::string & fname, + std::unique_ptr* result) { + Status s; + result->reset(); + + DWORD fileFlags = FILE_ATTRIBUTE_READONLY; + + HANDLE hFile = INVALID_HANDLE_VALUE; + { + IOSTATS_TIMER_GUARD(open_nanos); + hFile = RX_CreateFile( + RX_FN(fname).c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, // Open only if it exists + fileFlags, + NULL); + } + + if (INVALID_HANDLE_VALUE == hFile) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError( + "Failed to open NewMemoryMappedFileBuffer: " + fname, lastError); + return s; + } + UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc); + + uint64_t fileSize = 0; + s = GetFileSize(fname, &fileSize); + if (!s.ok()) { + return s; + } + // Will not map empty files + if (fileSize == 0) { + return Status::NotSupported( + "NewMemoryMappedFileBuffer can not map zero length files: " + fname); + } + + // size_t is 32-bit with 32-bit builds + if (fileSize > std::numeric_limits::max()) { + return Status::NotSupported( + "The specified file size does not fit into 32-bit memory addressing: " + + fname); + } + + HANDLE hMap = RX_CreateFileMapping(hFile, NULL, PAGE_READWRITE, + 0, // Whole file at its present length + 0, + NULL); // Mapping name + + if (!hMap) { + auto lastError = GetLastError(); + return IOErrorFromWindowsError( + "Failed to create file mapping for: " + fname, lastError); + } + UniqueCloseHandlePtr mapGuard(hMap, CloseHandleFunc); + + void* base = MapViewOfFileEx(hMap, FILE_MAP_WRITE, + 0, // High DWORD of access start + 0, // Low DWORD + static_cast(fileSize), + NULL); // Let the OS choose the mapping + + if (!base) { + auto lastError = GetLastError(); + return IOErrorFromWindowsError( + "Failed to MapViewOfFile for NewMemoryMappedFileBuffer: " + fname, + lastError); + } + + result->reset(new WinMemoryMappedBuffer(hFile, hMap, base, + static_cast(fileSize))); + + mapGuard.release(); fileGuard.release(); return s; } Status WinEnvIO::NewDirectory(const std::string& name, - std::unique_ptr* result) { + std::unique_ptr* result) { Status s; // Must be nullptr on failure result->reset(); - // Must fail if directory does not exist + if (!DirExists(name)) { - s = IOError("Directory does not exist: " + name, EEXIST); - } else { + s = IOErrorFromWindowsError( + "open folder: " + name, ERROR_DIRECTORY); + return s; + } + + HANDLE handle = INVALID_HANDLE_VALUE; + // 0 - for access means read metadata + { IOSTATS_TIMER_GUARD(open_nanos); - result->reset(new WinDirectory); + handle = RX_CreateFile( + RX_FN(name).c_str(), 0, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible + NULL); + } + + if (INVALID_HANDLE_VALUE == handle) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("open folder: " + name, lastError); + return s; } + + result->reset(new WinDirectory(handle)); + return s; } Status WinEnvIO::FileExists(const std::string& fname) { - // F_OK == 0 - const int F_OK_ = 0; - return _access(fname.c_str(), F_OK_) == 0 ? Status::OK() - : Status::NotFound(); + Status s; + // TODO: This does not follow symbolic links at this point + // which is consistent with _access() impl on windows + // but can be added + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (FALSE == RX_GetFileAttributesEx(RX_FN(fname).c_str(), + GetFileExInfoStandard, &attrs)) { + auto lastError = GetLastError(); + switch (lastError) { + case ERROR_ACCESS_DENIED: + case ERROR_NOT_FOUND: + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + s = Status::NotFound(); + break; + default: + s = IOErrorFromWindowsError("Unexpected error for: " + fname, + lastError); + break; + } + } + return s; } Status WinEnvIO::GetChildren(const std::string& dir, - std::vector* result) { + std::vector* result) { + Status status; result->clear(); std::vector output; - Status status; + RX_WIN32_FIND_DATA data; + memset(&data, 0, sizeof(data)); + std::string pattern(dir); + pattern.append("\\").append("*"); - auto CloseDir = [](DIR* p) { closedir(p); }; - std::unique_ptr dirp(opendir(dir.c_str()), - CloseDir); - - if (!dirp) { - switch (errno) { - case EACCES: - case ENOENT: - case ENOTDIR: - return Status::NotFound(); - default: - return IOError(dir, errno); - } - } else { - if (result->capacity() > 0) { - output.reserve(result->capacity()); - } + HANDLE handle = RX_FindFirstFileEx(RX_FN(pattern).c_str(), + // Do not want alternative name + FindExInfoBasic, + &data, + FindExSearchNameMatch, + NULL, // lpSearchFilter + 0); - struct dirent* ent = readdir(dirp.get()); - while (ent) { - output.push_back(ent->d_name); - ent = readdir(dirp.get()); + if (handle == INVALID_HANDLE_VALUE) { + auto lastError = GetLastError(); + switch (lastError) { + case ERROR_NOT_FOUND: + case ERROR_ACCESS_DENIED: + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + status = Status::NotFound(); + break; + default: + status = IOErrorFromWindowsError( + "Failed to GetChhildren for: " + dir, lastError); } + return status; } - output.swap(*result); + UniqueFindClosePtr fc(handle, FindCloseFunc); + if (result->capacity() > 0) { + output.reserve(result->capacity()); + } + + // For safety + data.cFileName[MAX_PATH - 1] = 0; + + while (true) { + auto x = RX_FILESTRING(data.cFileName, RX_FNLEN(data.cFileName)); + output.emplace_back(FN_TO_RX(x)); + BOOL ret =- RX_FindNextFile(handle, &data); + // If the function fails the return value is zero + // and non-zero otherwise. Not TRUE or FALSE. + if (ret == FALSE) { + // Posix does not care why we stopped + break; + } + data.cFileName[MAX_PATH - 1] = 0; + } + output.swap(*result); return status; } Status WinEnvIO::CreateDir(const std::string& name) { Status result; - - if (_mkdir(name.c_str()) != 0) { - auto code = errno; - result = IOError("Failed to create dir: " + name, code); + BOOL ret = RX_CreateDirectory(RX_FN(name).c_str(), NULL); + if (!ret) { + auto lastError = GetLastError(); + result = IOErrorFromWindowsError( + "Failed to create a directory: " + name, lastError); } return result; @@ -444,24 +627,27 @@ Status WinEnvIO::CreateDirIfMissing(const std::string& name) { return result; } - if (_mkdir(name.c_str()) != 0) { - if (errno == EEXIST) { - result = - Status::IOError("`" + name + "' exists but is not a directory"); + BOOL ret = RX_CreateDirectory(RX_FN(name).c_str(), NULL); + if (!ret) { + auto lastError = GetLastError(); + if (lastError != ERROR_ALREADY_EXISTS) { + result = IOErrorFromWindowsError( + "Failed to create a directory: " + name, lastError); } else { - auto code = errno; - result = IOError("Failed to create dir: " + name, code); + result = + Status::IOError(name + ": exists but is not a directory"); } } - return result; } Status WinEnvIO::DeleteDir(const std::string& name) { Status result; - if (_rmdir(name.c_str()) != 0) { - auto code = errno; - result = IOError("Failed to remove dir: " + name, code); + BOOL ret = RX_RemoveDirectory(RX_FN(name).c_str()); + if (!ret) { + auto lastError = GetLastError(); + result = IOErrorFromWindowsError("Failed to remove dir: " + name, + lastError); } return result; } @@ -471,7 +657,8 @@ Status WinEnvIO::GetFileSize(const std::string& fname, Status s; WIN32_FILE_ATTRIBUTE_DATA attrs; - if (GetFileAttributesExA(fname.c_str(), GetFileExInfoStandard, &attrs)) { + if (RX_GetFileAttributesEx(RX_FN(fname).c_str(), GetFileExInfoStandard, + &attrs)) { ULARGE_INTEGER file_size; file_size.HighPart = attrs.nFileSizeHigh; file_size.LowPart = attrs.nFileSizeLow; @@ -506,7 +693,8 @@ Status WinEnvIO::GetFileModificationTime(const std::string& fname, Status s; WIN32_FILE_ATTRIBUTE_DATA attrs; - if (GetFileAttributesExA(fname.c_str(), GetFileExInfoStandard, &attrs)) { + if (RX_GetFileAttributesEx(RX_FN(fname).c_str(), GetFileExInfoStandard, + &attrs)) { *file_mtime = FileTimeToUnixTime(attrs.ftLastWriteTime); } else { auto lastError = GetLastError(); @@ -524,7 +712,8 @@ Status WinEnvIO::RenameFile(const std::string& src, // rename() is not capable of replacing the existing file as on Linux // so use OS API directly - if (!MoveFileExA(src.c_str(), target.c_str(), MOVEFILE_REPLACE_EXISTING)) { + if (!RX_MoveFileEx(RX_FN(src).c_str(), RX_FN(target).c_str(), + MOVEFILE_REPLACE_EXISTING)) { DWORD lastError = GetLastError(); std::string text("Failed to rename: "); @@ -540,8 +729,11 @@ Status WinEnvIO::LinkFile(const std::string& src, const std::string& target) { Status result; - if (!CreateHardLinkA(target.c_str(), src.c_str(), NULL)) { + if (!RX_CreateHardLink(RX_FN(target).c_str(), RX_FN(src).c_str(), NULL)) { DWORD lastError = GetLastError(); + if (lastError == ERROR_NOT_SAME_DEVICE) { + return Status::NotSupported("No cross FS links allowed"); + } std::string text("Failed to link: "); text.append(src).append(" to: ").append(target); @@ -552,8 +744,108 @@ Status WinEnvIO::LinkFile(const std::string& src, return result; } +Status WinEnvIO::NumFileLinks(const std::string& fname, uint64_t* count) { + Status s; + HANDLE handle = RX_CreateFile( + RX_FN(fname).c_str(), 0, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (INVALID_HANDLE_VALUE == handle) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("NumFileLinks: " + fname, lastError); + return s; + } + UniqueCloseHandlePtr handle_guard(handle, CloseHandleFunc); + FILE_STANDARD_INFO standard_info; + if (0 != GetFileInformationByHandleEx(handle, FileStandardInfo, + &standard_info, + sizeof(standard_info))) { + *count = standard_info.NumberOfLinks; + } else { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("GetFileInformationByHandleEx: " + fname, + lastError); + } + return s; +} + +Status WinEnvIO::AreFilesSame(const std::string& first, + const std::string& second, bool* res) { +// For MinGW builds +#if (_WIN32_WINNT == _WIN32_WINNT_VISTA) + Status s = Status::NotSupported(); +#else + assert(res != nullptr); + Status s; + if (res == nullptr) { + s = Status::InvalidArgument("res"); + return s; + } + + // 0 - for access means read metadata + HANDLE file_1 = RX_CreateFile( + RX_FN(first).c_str(), 0, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible + NULL); + + if (INVALID_HANDLE_VALUE == file_1) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("open file: " + first, lastError); + return s; + } + UniqueCloseHandlePtr g_1(file_1, CloseHandleFunc); + + HANDLE file_2 = RX_CreateFile( + RX_FN(second).c_str(), 0, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible + NULL); + + if (INVALID_HANDLE_VALUE == file_2) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("open file: " + second, lastError); + return s; + } + UniqueCloseHandlePtr g_2(file_2, CloseHandleFunc); + + FILE_ID_INFO FileInfo_1; + BOOL result = GetFileInformationByHandleEx(file_1, FileIdInfo, &FileInfo_1, + sizeof(FileInfo_1)); + + if (!result) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("stat file: " + first, lastError); + return s; + } + + FILE_ID_INFO FileInfo_2; + result = GetFileInformationByHandleEx(file_2, FileIdInfo, &FileInfo_2, + sizeof(FileInfo_2)); + + if (!result) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("stat file: " + second, lastError); + return s; + } + + if (FileInfo_1.VolumeSerialNumber == FileInfo_2.VolumeSerialNumber) { + *res = (0 == memcmp(FileInfo_1.FileId.Identifier, + FileInfo_2.FileId.Identifier, + sizeof(FileInfo_1.FileId.Identifier))); + } else { + *res = false; + } +#endif + return s; +} + Status WinEnvIO::LockFile(const std::string& lockFname, - FileLock** lock) { + FileLock** lock) { assert(lock != nullptr); *lock = NULL; @@ -568,15 +860,16 @@ Status WinEnvIO::LockFile(const std::string& lockFname, HANDLE hFile = 0; { IOSTATS_TIMER_GUARD(open_nanos); - hFile = CreateFileA(lockFname.c_str(), (GENERIC_READ | GENERIC_WRITE), - ExclusiveAccessON, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, NULL); + hFile = RX_CreateFile(RX_FN(lockFname).c_str(), + (GENERIC_READ | GENERIC_WRITE), + ExclusiveAccessON, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); } if (INVALID_HANDLE_VALUE == hFile) { auto lastError = GetLastError(); result = IOErrorFromWindowsError( - "Failed to create lock file: " + lockFname, lastError); + "Failed to create lock file: " + lockFname, lastError); } else { *lock = new WinFileLock(hFile); } @@ -595,12 +888,12 @@ Status WinEnvIO::UnlockFile(FileLock* lock) { } Status WinEnvIO::GetTestDirectory(std::string* result) { + std::string output; const char* env = getenv("TEST_TMPDIR"); if (env && env[0] != '\0') { output = env; - CreateDir(output); } else { env = getenv("TMP"); @@ -609,9 +902,8 @@ Status WinEnvIO::GetTestDirectory(std::string* result) { } else { output = "c:\\tmp"; } - - CreateDir(output); } + CreateDir(output); output.append("\\testrocksdb-"); output.append(std::to_string(_getpid())); @@ -624,7 +916,7 @@ Status WinEnvIO::GetTestDirectory(std::string* result) { } Status WinEnvIO::NewLogger(const std::string& fname, - std::shared_ptr* result) { + std::shared_ptr* result) { Status s; result->reset(); @@ -632,15 +924,15 @@ Status WinEnvIO::NewLogger(const std::string& fname, HANDLE hFile = 0; { IOSTATS_TIMER_GUARD(open_nanos); - hFile = CreateFileA( - fname.c_str(), GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_DELETE, // In RocksDb log files are - // renamed and deleted before - // they are closed. This enables - // doing so. - NULL, - CREATE_ALWAYS, // Original fopen mode is "w" - FILE_ATTRIBUTE_NORMAL, NULL); + hFile = RX_CreateFile( + RX_FN(fname).c_str(), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_DELETE, // In RocksDb log files are + // renamed and deleted before + // they are closed. This enables + // doing so. + NULL, + CREATE_ALWAYS, // Original fopen mode is "w" + FILE_ATTRIBUTE_NORMAL, NULL); } if (INVALID_HANDLE_VALUE == hFile) { @@ -687,21 +979,29 @@ uint64_t WinEnvIO::NowMicros() { return li.QuadPart; } using namespace std::chrono; - return duration_cast(system_clock::now().time_since_epoch()).count(); + return duration_cast( + high_resolution_clock::now().time_since_epoch()).count(); } uint64_t WinEnvIO::NowNanos() { - // all std::chrono clocks on windows have the same resolution that is only - // good enough for microseconds but not nanoseconds - // On Windows 8 and Windows 2012 Server - // GetSystemTimePreciseAsFileTime(¤t_time) can be used - LARGE_INTEGER li; - QueryPerformanceCounter(&li); - // Convert to nanoseconds first to avoid loss of precision - // and divide by frequency - li.QuadPart *= std::nano::den; - li.QuadPart /= perf_counter_frequency_; - return li.QuadPart; + if (nano_seconds_per_period_ != 0) { + // all std::chrono clocks on windows have the same resolution that is only + // good enough for microseconds but not nanoseconds + // On Windows 8 and Windows 2012 Server + // GetSystemTimePreciseAsFileTime(¤t_time) can be used + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + // Convert performance counter to nanoseconds by precomputed ratio. + // Directly multiply nano::den with li.QuadPart causes overflow. + // Only do this when nano::den is divisible by perf_counter_frequency_, + // which most likely is the case in reality. If it's not, fall back to + // high_resolution_clock, which may be less precise under old compilers. + li.QuadPart *= nano_seconds_per_period_; + return li.QuadPart; + } + using namespace std::chrono; + return duration_cast( + high_resolution_clock::now().time_since_epoch()).count(); } Status WinEnvIO::GetHostName(char* name, uint64_t len) { @@ -720,29 +1020,32 @@ Status WinEnvIO::GetHostName(char* name, uint64_t len) { } Status WinEnvIO::GetAbsolutePath(const std::string& db_path, - std::string* output_path) { + std::string* output_path) { // Check if we already have an absolute path - // that starts with non dot and has a semicolon in it - if ((!db_path.empty() && (db_path[0] == '/' || db_path[0] == '\\')) || - (db_path.size() > 2 && db_path[0] != '.' && - ((db_path[1] == ':' && db_path[2] == '\\') || - (db_path[1] == ':' && db_path[2] == '/')))) { + // For test compatibility we will consider starting slash as an + // absolute path + if ((!db_path.empty() && (db_path[0] == '\\' || db_path[0] == '/')) || + !RX_PathIsRelative(RX_FN(db_path).c_str())) { *output_path = db_path; return Status::OK(); } - std::string result; - result.resize(_MAX_PATH); + RX_FILESTRING result; + result.resize(MAX_PATH); - char* ret = _getcwd(&result[0], _MAX_PATH); - if (ret == nullptr) { - return Status::IOError("Failed to get current working directory", - strerror(errno)); + // Hopefully no changes the current directory while we do this + // however _getcwd also suffers from the same limitation + DWORD len = RX_GetCurrentDirectory(MAX_PATH, &result[0]); + if (len == 0) { + auto lastError = GetLastError(); + return IOErrorFromWindowsError("Failed to get current working directory", + lastError); } - result.resize(strlen(result.data())); + result.resize(len); + std::string res = FN_TO_RX(result); - result.swap(*output_path); + res.swap(*output_path); return Status::OK(); } @@ -762,8 +1065,8 @@ std::string WinEnvIO::TimeToString(uint64_t secondsSince1970) { char* p = &result[0]; int len = snprintf(p, maxsize, "%04d/%02d/%02d-%02d:%02d:%02d ", - t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, - t.tm_min, t.tm_sec); + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, + t.tm_min, t.tm_sec); assert(len > 0); result.resize(len); @@ -773,42 +1076,109 @@ std::string WinEnvIO::TimeToString(uint64_t secondsSince1970) { } EnvOptions WinEnvIO::OptimizeForLogWrite(const EnvOptions& env_options, - const DBOptions& db_options) const { - EnvOptions optimized = env_options; + const DBOptions& db_options) const { + EnvOptions optimized(env_options); + // These two the same as default optimizations optimized.bytes_per_sync = db_options.wal_bytes_per_sync; + optimized.writable_file_max_buffer_size = + db_options.writable_file_max_buffer_size; + + // This adversely affects %999 on windows optimized.use_mmap_writes = false; - // This is because we flush only whole pages on unbuffered io and - // the last records are not guaranteed to be flushed. + // Direct writes will produce a huge perf impact on + // Windows. Pre-allocate space for WAL. optimized.use_direct_writes = false; - // TODO(icanadi) it's faster if fallocate_with_keep_size is false, but it - // breaks TransactionLogIteratorStallAtLastRecord unit test. Fix the unit - // test and make this false - optimized.fallocate_with_keep_size = true; return optimized; } EnvOptions WinEnvIO::OptimizeForManifestWrite( - const EnvOptions& env_options) const { - EnvOptions optimized = env_options; + const EnvOptions& env_options) const { + EnvOptions optimized(env_options); optimized.use_mmap_writes = false; - optimized.use_direct_writes = false; - optimized.fallocate_with_keep_size = true; + optimized.use_direct_reads = false; + return optimized; +} + +EnvOptions WinEnvIO::OptimizeForManifestRead( + const EnvOptions& env_options) const { + EnvOptions optimized(env_options); + optimized.use_mmap_writes = false; + optimized.use_direct_reads = false; return optimized; } // Returns true iff the named directory exists and is a directory. bool WinEnvIO::DirExists(const std::string& dname) { WIN32_FILE_ATTRIBUTE_DATA attrs; - if (GetFileAttributesExA(dname.c_str(), GetFileExInfoStandard, &attrs)) { + if (RX_GetFileAttributesEx(RX_FN(dname).c_str(), + GetFileExInfoStandard, &attrs)) { return 0 != (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); } return false; } +size_t WinEnvIO::GetSectorSize(const std::string& fname) { + size_t sector_size = kSectorSize; + + if (RX_PathIsRelative(RX_FN(fname).c_str())) { + return sector_size; + } + + // obtain device handle + char devicename[7] = "\\\\.\\"; + int erresult = strncat_s(devicename, sizeof(devicename), fname.c_str(), 2); + + if (erresult) { + assert(false); + return sector_size; + } + + HANDLE hDevice = CreateFile(devicename, 0, 0, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + + if (hDevice == INVALID_HANDLE_VALUE) { + return sector_size; + } + + STORAGE_PROPERTY_QUERY spropertyquery; + spropertyquery.PropertyId = StorageAccessAlignmentProperty; + spropertyquery.QueryType = PropertyStandardQuery; + + BYTE output_buffer[sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)]; + DWORD output_bytes = 0; + + BOOL ret = DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, + &spropertyquery, sizeof(spropertyquery), + output_buffer, + sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR), + &output_bytes, nullptr); + + if (ret) { + sector_size = ((STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR *)output_buffer)->BytesPerLogicalSector; + } else { + // many devices do not support StorageProcessAlignmentProperty. Any failure here and we + // fall back to logical alignment + + DISK_GEOMETRY_EX geometry = { 0 }; + ret = DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, + nullptr, 0, &geometry, sizeof(geometry), &output_bytes, nullptr); + if (ret) { + sector_size = geometry.Geometry.BytesPerSector; + } + } + + if (hDevice != INVALID_HANDLE_VALUE) { + CloseHandle(hDevice); + } + + return sector_size; +} + //////////////////////////////////////////////////////////////////////// // WinEnvThreads -WinEnvThreads::WinEnvThreads(Env* hosted_env) : hosted_env_(hosted_env), thread_pools_(Env::Priority::TOTAL) { +WinEnvThreads::WinEnvThreads(Env* hosted_env) + : hosted_env_(hosted_env), thread_pools_(Env::Priority::TOTAL) { for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) { thread_pools_[pool_id].SetThreadPriority( @@ -827,8 +1197,9 @@ WinEnvThreads::~WinEnvThreads() { } } -void WinEnvThreads::Schedule(void(*function)(void*), void* arg, Env::Priority pri, - void* tag, void(*unschedFunction)(void* arg)) { +void WinEnvThreads::Schedule(void(*function)(void*), void* arg, + Env::Priority pri, void* tag, + void(*unschedFunction)(void* arg)) { assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH); thread_pools_[pri].Schedule(function, arg, tag, unschedFunction); } @@ -923,8 +1294,7 @@ WinEnv::~WinEnv() { delete thread_status_updater_; } -Status WinEnv::GetThreadList( - std::vector* thread_list) { +Status WinEnv::GetThreadList(std::vector* thread_list) { assert(thread_status_updater_); return thread_status_updater_->GetThreadList(thread_list); } @@ -933,19 +1303,23 @@ Status WinEnv::DeleteFile(const std::string& fname) { return winenv_io_.DeleteFile(fname); } +Status WinEnv::Truncate(const std::string& fname, size_t size) { + return winenv_io_.Truncate(fname, size); +} + Status WinEnv::GetCurrentTime(int64_t* unix_time) { return winenv_io_.GetCurrentTime(unix_time); } Status WinEnv::NewSequentialFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& options) { return winenv_io_.NewSequentialFile(fname, result, options); } Status WinEnv::NewRandomAccessFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& options) { return winenv_io_.NewRandomAccessFile(fname, result, options); } @@ -956,17 +1330,25 @@ Status WinEnv::NewWritableFile(const std::string& fname, } Status WinEnv::ReopenWritableFile(const std::string& fname, - std::unique_ptr* result, const EnvOptions& options) { + std::unique_ptr* result, + const EnvOptions& options) { return winenv_io_.OpenWritableFile(fname, result, options, true); } Status WinEnv::NewRandomRWFile(const std::string & fname, - unique_ptr* result, const EnvOptions & options) { + std::unique_ptr* result, + const EnvOptions & options) { return winenv_io_.NewRandomRWFile(fname, result, options); } +Status WinEnv::NewMemoryMappedFileBuffer( + const std::string& fname, + std::unique_ptr* result) { + return winenv_io_.NewMemoryMappedFileBuffer(fname, result); +} + Status WinEnv::NewDirectory(const std::string& name, - std::unique_ptr* result) { + std::unique_ptr* result) { return winenv_io_.NewDirectory(name, result); } @@ -975,7 +1357,7 @@ Status WinEnv::FileExists(const std::string& fname) { } Status WinEnv::GetChildren(const std::string& dir, - std::vector* result) { + std::vector* result) { return winenv_io_.GetChildren(dir, result); } @@ -992,25 +1374,34 @@ Status WinEnv::DeleteDir(const std::string& name) { } Status WinEnv::GetFileSize(const std::string& fname, - uint64_t* size) { + uint64_t* size) { return winenv_io_.GetFileSize(fname, size); } Status WinEnv::GetFileModificationTime(const std::string& fname, - uint64_t* file_mtime) { + uint64_t* file_mtime) { return winenv_io_.GetFileModificationTime(fname, file_mtime); } Status WinEnv::RenameFile(const std::string& src, - const std::string& target) { + const std::string& target) { return winenv_io_.RenameFile(src, target); } Status WinEnv::LinkFile(const std::string& src, - const std::string& target) { + const std::string& target) { return winenv_io_.LinkFile(src, target); } +Status WinEnv::NumFileLinks(const std::string& fname, uint64_t* count) { + return winenv_io_.NumFileLinks(fname, count); +} + +Status WinEnv::AreFilesSame(const std::string& first, + const std::string& second, bool* res) { + return winenv_io_.AreFilesSame(first, second, res); +} + Status WinEnv::LockFile(const std::string& lockFname, FileLock** lock) { return winenv_io_.LockFile(lockFname, lock); @@ -1025,7 +1416,7 @@ Status WinEnv::GetTestDirectory(std::string* result) { } Status WinEnv::NewLogger(const std::string& fname, - std::shared_ptr* result) { + std::shared_ptr* result) { return winenv_io_.NewLogger(fname, result); } @@ -1051,8 +1442,8 @@ std::string WinEnv::TimeToString(uint64_t secondsSince1970) { } void WinEnv::Schedule(void(*function)(void*), void* arg, Env::Priority pri, - void* tag, - void(*unschedFunction)(void* arg)) { + void* tag, + void(*unschedFunction)(void* arg)) { return winenv_threads_.Schedule(function, arg, pri, tag, unschedFunction); } @@ -1093,13 +1484,18 @@ void WinEnv::IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) { return winenv_threads_.IncBackgroundThreadsIfNeeded(num, pri); } +EnvOptions WinEnv::OptimizeForManifestRead( + const EnvOptions& env_options) const { + return winenv_io_.OptimizeForManifestRead(env_options); +} + EnvOptions WinEnv::OptimizeForLogWrite(const EnvOptions& env_options, - const DBOptions& db_options) const { + const DBOptions& db_options) const { return winenv_io_.OptimizeForLogWrite(env_options, db_options); } EnvOptions WinEnv::OptimizeForManifestWrite( - const EnvOptions& env_options) const { + const EnvOptions& env_options) const { return winenv_io_.OptimizeForManifestWrite(env_options); } diff --git a/thirdparty/rocksdb/port/win/env_win.h b/thirdparty/rocksdb/port/win/env_win.h index ce1a61d416..7a4d48de2e 100644 --- a/thirdparty/rocksdb/port/win/env_win.h +++ b/thirdparty/rocksdb/port/win/env_win.h @@ -47,8 +47,7 @@ class WinEnvThreads { WinEnvThreads& operator=(const WinEnvThreads&) = delete; void Schedule(void(*function)(void*), void* arg, Env::Priority pri, - void* tag, - void(*unschedFunction)(void* arg)); + void* tag, void(*unschedFunction)(void* arg)); int UnSchedule(void* arg, Env::Priority pri); @@ -72,8 +71,8 @@ class WinEnvThreads { private: - Env* hosted_env_; - mutable std::mutex mu_; + Env* hosted_env_; + mutable std::mutex mu_; std::vector thread_pools_; std::vector threads_to_join_; @@ -89,34 +88,40 @@ class WinEnvIO { virtual Status DeleteFile(const std::string& fname); + Status Truncate(const std::string& fname, size_t size); + virtual Status GetCurrentTime(int64_t* unix_time); virtual Status NewSequentialFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options); + std::unique_ptr* result, + const EnvOptions& options); // Helper for NewWritable and ReopenWritableFile virtual Status OpenWritableFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options, - bool reopen); + std::unique_ptr* result, + const EnvOptions& options, + bool reopen); virtual Status NewRandomAccessFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options); + std::unique_ptr* result, + const EnvOptions& options); // The returned file will only be accessed by one thread at a time. virtual Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options); + std::unique_ptr* result, + const EnvOptions& options); + + virtual Status NewMemoryMappedFileBuffer( + const std::string& fname, + std::unique_ptr* result); virtual Status NewDirectory(const std::string& name, - std::unique_ptr* result); + std::unique_ptr* result); virtual Status FileExists(const std::string& fname); virtual Status GetChildren(const std::string& dir, - std::vector* result); + std::vector* result); virtual Status CreateDir(const std::string& name); @@ -124,29 +129,31 @@ class WinEnvIO { virtual Status DeleteDir(const std::string& name); - virtual Status GetFileSize(const std::string& fname, - uint64_t* size); + virtual Status GetFileSize(const std::string& fname, uint64_t* size); static uint64_t FileTimeToUnixTime(const FILETIME& ftTime); virtual Status GetFileModificationTime(const std::string& fname, - uint64_t* file_mtime); + uint64_t* file_mtime); + + virtual Status RenameFile(const std::string& src, const std::string& target); + + virtual Status LinkFile(const std::string& src, const std::string& target); - virtual Status RenameFile(const std::string& src, - const std::string& target); + virtual Status NumFileLinks(const std::string& /*fname*/, + uint64_t* /*count*/); - virtual Status LinkFile(const std::string& src, - const std::string& target); + virtual Status AreFilesSame(const std::string& first, + const std::string& second, bool* res); - virtual Status LockFile(const std::string& lockFname, - FileLock** lock); + virtual Status LockFile(const std::string& lockFname, FileLock** lock); virtual Status UnlockFile(FileLock* lock); virtual Status GetTestDirectory(std::string* result); virtual Status NewLogger(const std::string& fname, - std::shared_ptr* result); + std::shared_ptr* result); virtual uint64_t NowMicros(); @@ -155,15 +162,18 @@ class WinEnvIO { virtual Status GetHostName(char* name, uint64_t len); virtual Status GetAbsolutePath(const std::string& db_path, - std::string* output_path); + std::string* output_path); virtual std::string TimeToString(uint64_t secondsSince1970); virtual EnvOptions OptimizeForLogWrite(const EnvOptions& env_options, - const DBOptions& db_options) const; + const DBOptions& db_options) const; virtual EnvOptions OptimizeForManifestWrite( - const EnvOptions& env_options) const; + const EnvOptions& env_options) const; + + virtual EnvOptions OptimizeForManifestRead( + const EnvOptions& env_options) const; size_t GetPageSize() const { return page_size_; } @@ -171,16 +181,19 @@ class WinEnvIO { uint64_t GetPerfCounterFrequency() const { return perf_counter_frequency_; } + static size_t GetSectorSize(const std::string& fname); + private: // Returns true iff the named directory exists and is a directory. virtual bool DirExists(const std::string& dname); typedef VOID(WINAPI * FnGetSystemTimePreciseAsFileTime)(LPFILETIME); - Env* hosted_env_; - size_t page_size_; - size_t allocation_granularity_; - uint64_t perf_counter_frequency_; + Env* hosted_env_; + size_t page_size_; + size_t allocation_granularity_; + uint64_t perf_counter_frequency_; + uint64_t nano_seconds_per_period_; FnGetSystemTimePreciseAsFileTime GetSystemTimePreciseAsFileTime_; }; @@ -192,15 +205,17 @@ class WinEnv : public Env { Status DeleteFile(const std::string& fname) override; + Status Truncate(const std::string& fname, size_t size) override; + Status GetCurrentTime(int64_t* unix_time) override; Status NewSequentialFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) override; + std::unique_ptr* result, + const EnvOptions& options) override; Status NewRandomAccessFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) override; + std::unique_ptr* result, + const EnvOptions& options) override; Status NewWritableFile(const std::string& fname, std::unique_ptr* result, @@ -214,21 +229,25 @@ class WinEnv : public Env { // // The returned file will only be accessed by one thread at a time. Status ReopenWritableFile(const std::string& fname, - std::unique_ptr* result, - const EnvOptions& options) override; + std::unique_ptr* result, + const EnvOptions& options) override; // The returned file will only be accessed by one thread at a time. Status NewRandomRWFile(const std::string& fname, - unique_ptr* result, - const EnvOptions& options) override; + std::unique_ptr* result, + const EnvOptions& options) override; + + Status NewMemoryMappedFileBuffer( + const std::string& fname, + std::unique_ptr* result) override; Status NewDirectory(const std::string& name, - std::unique_ptr* result) override; + std::unique_ptr* result) override; Status FileExists(const std::string& fname) override; Status GetChildren(const std::string& dir, - std::vector* result) override; + std::vector* result) override; Status CreateDir(const std::string& name) override; @@ -237,26 +256,30 @@ class WinEnv : public Env { Status DeleteDir(const std::string& name) override; Status GetFileSize(const std::string& fname, - uint64_t* size) override; + uint64_t* size) override; Status GetFileModificationTime(const std::string& fname, - uint64_t* file_mtime) override; + uint64_t* file_mtime) override; Status RenameFile(const std::string& src, - const std::string& target) override; + const std::string& target) override; Status LinkFile(const std::string& src, - const std::string& target) override; + const std::string& target) override; + + Status NumFileLinks(const std::string& fname, uint64_t* count) override; - Status LockFile(const std::string& lockFname, - FileLock** lock) override; + Status AreFilesSame(const std::string& first, + const std::string& second, bool* res) override; + + Status LockFile(const std::string& lockFname, FileLock** lock) override; Status UnlockFile(FileLock* lock) override; Status GetTestDirectory(std::string* result) override; Status NewLogger(const std::string& fname, - std::shared_ptr* result) override; + std::shared_ptr* result) override; uint64_t NowMicros() override; @@ -265,16 +288,14 @@ class WinEnv : public Env { Status GetHostName(char* name, uint64_t len) override; Status GetAbsolutePath(const std::string& db_path, - std::string* output_path) override; + std::string* output_path) override; std::string TimeToString(uint64_t secondsSince1970) override; - Status GetThreadList( - std::vector* thread_list) override; + Status GetThreadList(std::vector* thread_list) override; void Schedule(void(*function)(void*), void* arg, Env::Priority pri, - void* tag, - void(*unschedFunction)(void* arg)) override; + void* tag, void(*unschedFunction)(void* arg)) override; int UnSchedule(void* arg, Env::Priority pri) override; @@ -294,15 +315,19 @@ class WinEnv : public Env { void IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) override; + EnvOptions OptimizeForManifestRead( + const EnvOptions& env_options) const override; + EnvOptions OptimizeForLogWrite(const EnvOptions& env_options, - const DBOptions& db_options) const override; + const DBOptions& db_options) const override; EnvOptions OptimizeForManifestWrite( - const EnvOptions& env_options) const override; + const EnvOptions& env_options) const override; + private: - WinEnvIO winenv_io_; + WinEnvIO winenv_io_; WinEnvThreads winenv_threads_; }; diff --git a/thirdparty/rocksdb/port/win/io_win.cc b/thirdparty/rocksdb/port/win/io_win.cc index 3d2533a2ef..128cb60b9f 100644 --- a/thirdparty/rocksdb/port/win/io_win.cc +++ b/thirdparty/rocksdb/port/win/io_win.cc @@ -30,7 +30,7 @@ bool IsPowerOfTwo(const size_t alignment) { } inline -bool IsSectorAligned(const size_t off) { +bool IsSectorAligned(const size_t off) { return (off & (kSectorSize - 1)) == 0; } @@ -67,9 +67,20 @@ std::string GetWindowsErrSz(DWORD err) { // Because all the reads/writes happen by the specified offset, the caller in // theory should not // rely on the current file offset. -SSIZE_T pwrite(HANDLE hFile, const char* src, size_t numBytes, - uint64_t offset) { - assert(numBytes <= std::numeric_limits::max()); +Status pwrite(const WinFileData* file_data, const Slice& data, + uint64_t offset, size_t& bytes_written) { + + Status s; + bytes_written = 0; + + size_t num_bytes = data.size(); + if (num_bytes > std::numeric_limits::max()) { + // May happen in 64-bit builds where size_t is 64-bits but + // long is still 32-bit, but that's the API here at the moment + return Status::InvalidArgument("num_bytes is too large for a single write: " + + file_data->GetName()); + } + OVERLAPPED overlapped = { 0 }; ULARGE_INTEGER offsetUnion; offsetUnion.QuadPart = offset; @@ -77,23 +88,32 @@ SSIZE_T pwrite(HANDLE hFile, const char* src, size_t numBytes, overlapped.Offset = offsetUnion.LowPart; overlapped.OffsetHigh = offsetUnion.HighPart; - SSIZE_T result = 0; - - unsigned long bytesWritten = 0; + DWORD bytesWritten = 0; - if (FALSE == WriteFile(hFile, src, static_cast(numBytes), &bytesWritten, - &overlapped)) { - result = -1; + if (FALSE == WriteFile(file_data->GetFileHandle(), data.data(), static_cast(num_bytes), + &bytesWritten, &overlapped)) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("WriteFile failed: " + file_data->GetName(), + lastError); } else { - result = bytesWritten; + bytes_written = bytesWritten; } - return result; + return s; } // See comments for pwrite above -SSIZE_T pread(HANDLE hFile, char* src, size_t numBytes, uint64_t offset) { - assert(numBytes <= std::numeric_limits::max()); +Status pread(const WinFileData* file_data, char* src, size_t num_bytes, + uint64_t offset, size_t& bytes_read) { + + Status s; + bytes_read = 0; + + if (num_bytes > std::numeric_limits::max()) { + return Status::InvalidArgument("num_bytes is too large for a single read: " + + file_data->GetName()); + } + OVERLAPPED overlapped = { 0 }; ULARGE_INTEGER offsetUnion; offsetUnion.QuadPart = offset; @@ -101,18 +121,21 @@ SSIZE_T pread(HANDLE hFile, char* src, size_t numBytes, uint64_t offset) { overlapped.Offset = offsetUnion.LowPart; overlapped.OffsetHigh = offsetUnion.HighPart; - SSIZE_T result = 0; - - unsigned long bytesRead = 0; + DWORD bytesRead = 0; - if (FALSE == ReadFile(hFile, src, static_cast(numBytes), &bytesRead, - &overlapped)) { - return -1; + if (FALSE == ReadFile(file_data->GetFileHandle(), src, static_cast(num_bytes), + &bytesRead, &overlapped)) { + auto lastError = GetLastError(); + // EOF is OK with zero bytes read + if (lastError != ERROR_HANDLE_EOF) { + s = IOErrorFromWindowsError("ReadFile failed: " + file_data->GetName(), + lastError); + } } else { - result = bytesRead; + bytes_read = bytesRead; } - return result; + return s; } // SetFileInformationByHandle() is capable of fast pre-allocates. @@ -157,9 +180,11 @@ size_t GetUniqueIdFromFile(HANDLE hFile, char* id, size_t max_size) { if (max_size < kMaxVarint64Length * 3) { return 0; } - - // This function has to be re-worked for cases when - // ReFS file system introduced on Windows Server 2012 is used +#if (_WIN32_WINNT == _WIN32_WINNT_VISTA) + // MINGGW as defined by CMake file. + // yuslepukhin: I hate the guts of the above macros. + // This impl does not guarantee uniqueness everywhere + // is reasonably good BY_HANDLE_FILE_INFORMATION FileInfo; BOOL result = GetFileInformationByHandle(hFile, &FileInfo); @@ -177,6 +202,33 @@ size_t GetUniqueIdFromFile(HANDLE hFile, char* id, size_t max_size) { assert(rid >= id); return static_cast(rid - id); +#else + FILE_ID_INFO FileInfo; + BOOL result = GetFileInformationByHandleEx(hFile, FileIdInfo, &FileInfo, + sizeof(FileInfo)); + + TEST_SYNC_POINT_CALLBACK("GetUniqueIdFromFile:FS_IOC_GETVERSION", &result); + + if (!result) { + return 0; + } + + static_assert(sizeof(uint64_t) == sizeof(FileInfo.VolumeSerialNumber), + "Wrong sizeof expectations"); + // FileId.Identifier is an array of 16 BYTEs, we encode them as two uint64_t + static_assert(sizeof(uint64_t) * 2 == sizeof(FileInfo.FileId.Identifier), + "Wrong sizeof expectations"); + + char* rid = id; + rid = EncodeVarint64(rid, uint64_t(FileInfo.VolumeSerialNumber)); + uint64_t* file_id = reinterpret_cast(&FileInfo.FileId.Identifier[0]); + rid = EncodeVarint64(rid, *file_id); + ++file_id; + rid = EncodeVarint64(rid, *file_id); + + assert(rid >= id); + return static_cast(rid - id); +#endif } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -192,8 +244,8 @@ WinMmapReadableFile::WinMmapReadableFile(const std::string& fileName, length_(length) {} WinMmapReadableFile::~WinMmapReadableFile() { - BOOL ret = ::UnmapViewOfFile(mapped_region_); - (void)ret; + BOOL ret __attribute__((__unused__)); + ret = ::UnmapViewOfFile(mapped_region_); assert(ret); ret = ::CloseHandle(hMap_); @@ -208,7 +260,7 @@ Status WinMmapReadableFile::Read(uint64_t offset, size_t n, Slice* result, *result = Slice(); return IOError(filename_, EINVAL); } else if (offset + n > length_) { - n = length_ - offset; + n = length_ - static_cast(offset); } *result = Slice(reinterpret_cast(mapped_region_)+offset, n); @@ -265,7 +317,7 @@ Status WinMmapFile::MapNewRegion() { assert(mapped_begin_ == nullptr); - size_t minDiskSize = file_offset_ + view_size_; + size_t minDiskSize = static_cast(file_offset_) + view_size_; if (minDiskSize > reserved_size_) { status = Allocate(file_offset_, view_size_); @@ -279,7 +331,8 @@ Status WinMmapFile::MapNewRegion() { if (hMap_ != NULL) { // Unmap the previous one - BOOL ret = ::CloseHandle(hMap_); + BOOL ret __attribute__((__unused__)); + ret = ::CloseHandle(hMap_); assert(ret); hMap_ = NULL; } @@ -526,7 +579,7 @@ Status WinMmapFile::Allocate(uint64_t offset, uint64_t len) { // Make sure that we reserve an aligned amount of space // since the reservation block size is driven outside so we want // to check if we are ok with reservation here - size_t spaceToReserve = Roundup(offset + len, view_size_); + size_t spaceToReserve = Roundup(static_cast(offset + len), view_size_); // Nothing to do if (spaceToReserve <= reserved_size_) { return status; @@ -556,34 +609,42 @@ WinSequentialFile::~WinSequentialFile() { } Status WinSequentialFile::Read(size_t n, Slice* result, char* scratch) { - assert(result != nullptr && !WinFileData::use_direct_io()); Status s; size_t r = 0; + assert(result != nullptr); + if (WinFileData::use_direct_io()) { + return Status::NotSupported("Read() does not support direct_io"); + } + // Windows ReadFile API accepts a DWORD. - // While it is possible to read in a loop if n is > UINT_MAX - // it is a highly unlikely case. - if (n > UINT_MAX) { - return IOErrorFromWindowsError(filename_, ERROR_INVALID_PARAMETER); + // While it is possible to read in a loop if n is too big + // it is an unlikely case. + if (n > std::numeric_limits::max()) { + return Status::InvalidArgument("n is too big for a single ReadFile: " + + filename_); } DWORD bytesToRead = static_cast(n); //cast is safe due to the check above DWORD bytesRead = 0; BOOL ret = ReadFile(hFile_, scratch, bytesToRead, &bytesRead, NULL); - if (ret == TRUE) { + if (ret != FALSE) { r = bytesRead; } else { - return IOErrorFromWindowsError(filename_, GetLastError()); + auto lastError = GetLastError(); + if (lastError != ERROR_HANDLE_EOF) { + s = IOErrorFromWindowsError("ReadFile failed: " + filename_, + lastError); + } } *result = Slice(scratch, r); - return s; } -SSIZE_T WinSequentialFile::PositionedReadInternal(char* src, size_t numBytes, - uint64_t offset) const { - return pread(GetFileHandle(), src, numBytes, offset); +Status WinSequentialFile::PositionedReadInternal(char* src, size_t numBytes, + uint64_t offset, size_t& bytes_read) const { + return pread(this, src, numBytes, offset, bytes_read); } Status WinSequentialFile::PositionedRead(uint64_t offset, size_t n, Slice* result, @@ -591,27 +652,19 @@ Status WinSequentialFile::PositionedRead(uint64_t offset, size_t n, Slice* resul Status s; - assert(WinFileData::use_direct_io()); - - // Windows ReadFile API accepts a DWORD. - // While it is possible to read in a loop if n is > UINT_MAX - // it is a highly unlikely case. - if (n > UINT_MAX) { - return IOErrorFromWindowsError(GetName(), ERROR_INVALID_PARAMETER); + if (!WinFileData::use_direct_io()) { + return Status::NotSupported("This function is only used for direct_io"); } - auto r = PositionedReadInternal(scratch, n, offset); - - if (r < 0) { - auto lastError = GetLastError(); - // Posix impl wants to treat reads from beyond - // of the file as OK. - if (lastError != ERROR_HANDLE_EOF) { - s = IOErrorFromWindowsError(GetName(), lastError); - } + if (!IsSectorAligned(static_cast(offset)) || + !IsSectorAligned(n)) { + return Status::InvalidArgument( + "WinSequentialFile::PositionedRead: offset is not properly aligned"); } - *result = Slice(scratch, (r < 0) ? 0 : size_t(r)); + size_t bytes_read = 0; // out param + s = PositionedReadInternal(scratch, static_cast(n), offset, bytes_read); + *result = Slice(scratch, bytes_read); return s; } @@ -619,15 +672,18 @@ Status WinSequentialFile::PositionedRead(uint64_t offset, size_t n, Slice* resul Status WinSequentialFile::Skip(uint64_t n) { // Can't handle more than signed max as SetFilePointerEx accepts a signed 64-bit // integer. As such it is a highly unlikley case to have n so large. - if (n > _I64_MAX) { - return IOErrorFromWindowsError(filename_, ERROR_INVALID_PARAMETER); + if (n > static_cast(std::numeric_limits::max())) { + return Status::InvalidArgument("n is too large for a single SetFilePointerEx() call" + + filename_); } LARGE_INTEGER li; - li.QuadPart = static_cast(n); //cast is safe due to the check above + li.QuadPart = static_cast(n); //cast is safe due to the check above BOOL ret = SetFilePointerEx(hFile_, li, NULL, FILE_CURRENT); if (ret == FALSE) { - return IOErrorFromWindowsError(filename_, GetLastError()); + auto lastError = GetLastError(); + return IOErrorFromWindowsError("Skip SetFilePointerEx():" + filename_, + lastError); } return Status::OK(); } @@ -640,10 +696,11 @@ Status WinSequentialFile::InvalidateCache(size_t offset, size_t length) { /// WinRandomAccessBase inline -SSIZE_T WinRandomAccessImpl::PositionedReadInternal(char* src, +Status WinRandomAccessImpl::PositionedReadInternal(char* src, size_t numBytes, - uint64_t offset) const { - return pread(file_base_->GetFileHandle(), src, numBytes, offset); + uint64_t offset, + size_t& bytes_read) const { + return pread(file_base_, src, numBytes, offset, bytes_read); } inline @@ -664,8 +721,10 @@ Status WinRandomAccessImpl::ReadImpl(uint64_t offset, size_t n, Slice* result, // Check buffer alignment if (file_base_->use_direct_io()) { - if (!IsAligned(alignment_, scratch)) { - return Status::InvalidArgument("WinRandomAccessImpl::ReadImpl: scratch is not properly aligned"); + if (!IsSectorAligned(static_cast(offset)) || + !IsAligned(alignment_, scratch)) { + return Status::InvalidArgument( + "WinRandomAccessImpl::ReadImpl: offset or scratch is not properly aligned"); } } @@ -674,23 +733,9 @@ Status WinRandomAccessImpl::ReadImpl(uint64_t offset, size_t n, Slice* result, return s; } - size_t left = n; - char* dest = scratch; - - SSIZE_T r = PositionedReadInternal(scratch, left, offset); - if (r > 0) { - left -= r; - } else if (r < 0) { - auto lastError = GetLastError(); - // Posix impl wants to treat reads from beyond - // of the file as OK. - if(lastError != ERROR_HANDLE_EOF) { - s = IOErrorFromWindowsError(file_base_->GetName(), lastError); - } - } - - *result = Slice(scratch, (r < 0) ? 0 : n - left); - + size_t bytes_read = 0; + s = PositionedReadInternal(scratch, n, offset, bytes_read); + *result = Slice(scratch, bytes_read); return s; } @@ -749,7 +794,7 @@ WinWritableImpl::WinWritableImpl(WinFileData* file_data, size_t alignment) BOOL ret = SetFilePointerEx(file_data_->GetFileHandle(), zero_move, &pos, FILE_CURRENT); // Querying no supped to fail - if (ret) { + if (ret != 0) { next_write_offset_ = pos.QuadPart; } else { assert(false); @@ -761,32 +806,24 @@ Status WinWritableImpl::AppendImpl(const Slice& data) { Status s; - assert(data.size() < std::numeric_limits::max()); + if (data.size() > std::numeric_limits::max()) { + return Status::InvalidArgument("data is too long for a single write" + + file_data_->GetName()); + } - uint64_t written = 0; - (void)written; + size_t bytes_written = 0; // out param if (file_data_->use_direct_io()) { - // With no offset specified we are appending // to the end of the file - assert(IsSectorAligned(next_write_offset_)); - assert(IsSectorAligned(data.size())); - assert(IsAligned(GetAlignement(), data.data())); - - SSIZE_T ret = pwrite(file_data_->GetFileHandle(), data.data(), - data.size(), next_write_offset_); - - if (ret < 0) { - auto lastError = GetLastError(); - s = IOErrorFromWindowsError( - "Failed to pwrite for: " + file_data_->GetName(), lastError); - } - else { - written = ret; + if (!IsSectorAligned(data.size()) || + !IsAligned(static_cast(GetAlignement()), data.data())) { + s = Status::InvalidArgument( + "WriteData must be page aligned, size must be sector aligned"); + } else { + s = pwrite(file_data_, data, next_write_offset_, bytes_written); } - } else { DWORD bytesWritten = 0; @@ -796,15 +833,21 @@ Status WinWritableImpl::AppendImpl(const Slice& data) { s = IOErrorFromWindowsError( "Failed to WriteFile: " + file_data_->GetName(), lastError); - } - else { - written = bytesWritten; + } else { + bytes_written = bytesWritten; } } if(s.ok()) { - assert(written == data.size()); - next_write_offset_ += data.size(); + if (bytes_written == data.size()) { + // This matters for direct_io cases where + // we rely on the fact that next_write_offset_ + // is sector aligned + next_write_offset_ += bytes_written; + } else { + s = Status::IOError("Failed to write all bytes: " + + file_data_->GetName()); + } } return s; @@ -814,39 +857,44 @@ inline Status WinWritableImpl::PositionedAppendImpl(const Slice& data, uint64_t offset) { if(file_data_->use_direct_io()) { - assert(IsSectorAligned(offset)); - assert(IsSectorAligned(data.size())); - assert(IsAligned(GetAlignement(), data.data())); + if (!IsSectorAligned(static_cast(offset)) || + !IsSectorAligned(data.size()) || + !IsAligned(static_cast(GetAlignement()), data.data())) { + return Status::InvalidArgument( + "Data and offset must be page aligned, size must be sector aligned"); + } } - Status s; - - SSIZE_T ret = pwrite(file_data_->GetFileHandle(), data.data(), data.size(), offset); + size_t bytes_written = 0; + Status s = pwrite(file_data_, data, offset, bytes_written); - // Error break - if (ret < 0) { - auto lastError = GetLastError(); - s = IOErrorFromWindowsError( - "Failed to pwrite for: " + file_data_->GetName(), lastError); - } - else { - assert(size_t(ret) == data.size()); - // For sequential write this would be simple - // size extension by data.size() - uint64_t write_end = offset + data.size(); - if (write_end >= next_write_offset_) { - next_write_offset_ = write_end; + if(s.ok()) { + if (bytes_written == data.size()) { + // For sequential write this would be simple + // size extension by data.size() + uint64_t write_end = offset + bytes_written; + if (write_end >= next_write_offset_) { + next_write_offset_ = write_end; + } + } else { + s = Status::IOError("Failed to write all of the requested data: " + + file_data_->GetName()); } } return s; } -// Need to implement this so the file is truncated correctly -// when buffered and unbuffered mode inline Status WinWritableImpl::TruncateImpl(uint64_t size) { + + // It is tempting to check for the size for sector alignment + // but truncation may come at the end and there is not a requirement + // for this to be sector aligned so long as we do not attempt to write + // after that. The interface docs state that the behavior is undefined + // in that case. Status s = ftruncate(file_data_->GetName(), file_data_->GetFileHandle(), size); + if (s.ok()) { next_write_offset_ = size; } @@ -861,14 +909,14 @@ Status WinWritableImpl::CloseImpl() { auto hFile = file_data_->GetFileHandle(); assert(INVALID_HANDLE_VALUE != hFile); - if (fsync(hFile) < 0) { + if (!::FlushFileBuffers(hFile)) { auto lastError = GetLastError(); - s = IOErrorFromWindowsError("fsync failed at Close() for: " + + s = IOErrorFromWindowsError("FlushFileBuffers failed at Close() for: " + file_data_->GetName(), lastError); } - if(!file_data_->CloseFile()) { + if(!file_data_->CloseFile() && s.ok()) { auto lastError = GetLastError(); s = IOErrorFromWindowsError("CloseHandle failed for: " + file_data_->GetName(), lastError); @@ -879,11 +927,10 @@ Status WinWritableImpl::CloseImpl() { inline Status WinWritableImpl::SyncImpl() { Status s; - // Calls flush buffers - if (fsync(file_data_->GetFileHandle()) < 0) { + if (!::FlushFileBuffers (file_data_->GetFileHandle())) { auto lastError = GetLastError(); s = IOErrorFromWindowsError( - "fsync failed at Sync() for: " + file_data_->GetName(), lastError); + "FlushFileBuffers failed at Sync() for: " + file_data_->GetName(), lastError); } return s; } @@ -897,7 +944,7 @@ Status WinWritableImpl::AllocateImpl(uint64_t offset, uint64_t len) { // Make sure that we reserve an aligned amount of space // since the reservation block size is driven outside so we want // to check if we are ok with reservation here - size_t spaceToReserve = Roundup(offset + len, alignment_); + size_t spaceToReserve = Roundup(static_cast(offset + len), static_cast(alignment_)); // Nothing to do if (spaceToReserve <= reservedsize_) { return status; @@ -930,7 +977,7 @@ WinWritableFile::~WinWritableFile() { bool WinWritableFile::use_direct_io() const { return WinFileData::use_direct_io(); } size_t WinWritableFile::GetRequiredBufferAlignment() const { - return GetAlignement(); + return static_cast(GetAlignement()); } Status WinWritableFile::Append(const Slice& data) { @@ -963,6 +1010,8 @@ Status WinWritableFile::Sync() { Status WinWritableFile::Fsync() { return SyncImpl(); } +bool WinWritableFile::IsSyncThreadSafe() const { return true; } + uint64_t WinWritableFile::GetFileSize() { return GetFileNextWriteOffset(); } @@ -988,7 +1037,7 @@ WinRandomRWFile::WinRandomRWFile(const std::string& fname, HANDLE hFile, bool WinRandomRWFile::use_direct_io() const { return WinFileData::use_direct_io(); } size_t WinRandomRWFile::GetRequiredBufferAlignment() const { - return GetAlignement(); + return static_cast(GetAlignement()); } Status WinRandomRWFile::Write(uint64_t offset, const Slice & data) { @@ -1012,16 +1061,41 @@ Status WinRandomRWFile::Close() { return CloseImpl(); } +////////////////////////////////////////////////////////////////////////// +/// WinMemoryMappedBufer +WinMemoryMappedBuffer::~WinMemoryMappedBuffer() { + BOOL ret = FALSE; + if (base_ != nullptr) { + ret = ::UnmapViewOfFile(base_); + assert(ret); + base_ = nullptr; + } + if (map_handle_ != NULL && map_handle_ != INVALID_HANDLE_VALUE) { + ret = ::CloseHandle(map_handle_); + assert(ret); + map_handle_ = NULL; + } + if (file_handle_ != NULL && file_handle_ != INVALID_HANDLE_VALUE) { + ret = ::CloseHandle(file_handle_); + assert(ret); + file_handle_ = NULL; + } +} + ////////////////////////////////////////////////////////////////////////// /// WinDirectory Status WinDirectory::Fsync() { return Status::OK(); } +size_t WinDirectory::GetUniqueId(char* id, size_t max_size) const { + return GetUniqueIdFromFile(handle_, id, max_size); +} ////////////////////////////////////////////////////////////////////////// /// WinFileLock WinFileLock::~WinFileLock() { - BOOL ret = ::CloseHandle(hFile_); + BOOL ret __attribute__((__unused__)); + ret = ::CloseHandle(hFile_); assert(ret); } diff --git a/thirdparty/rocksdb/port/win/io_win.h b/thirdparty/rocksdb/port/win/io_win.h index 2c1d5a1ea9..1c9d803b13 100644 --- a/thirdparty/rocksdb/port/win/io_win.h +++ b/thirdparty/rocksdb/port/win/io_win.h @@ -27,7 +27,9 @@ std::string GetWindowsErrSz(DWORD err); inline Status IOErrorFromWindowsError(const std::string& context, DWORD err) { return ((err == ERROR_HANDLE_DISK_FULL) || (err == ERROR_DISK_FULL)) ? Status::NoSpace(context, GetWindowsErrSz(err)) - : Status::IOError(context, GetWindowsErrSz(err)); + : ((err == ERROR_FILE_NOT_FOUND) || (err == ERROR_PATH_NOT_FOUND)) + ? Status::PathNotFound(context, GetWindowsErrSz(err)) + : Status::IOError(context, GetWindowsErrSz(err)); } inline Status IOErrorFromLastWindowsError(const std::string& context) { @@ -37,25 +39,18 @@ inline Status IOErrorFromLastWindowsError(const std::string& context) { inline Status IOError(const std::string& context, int err_number) { return (err_number == ENOSPC) ? Status::NoSpace(context, strerror(err_number)) - : Status::IOError(context, strerror(err_number)); + : (err_number == ENOENT) + ? Status::PathNotFound(context, strerror(err_number)) + : Status::IOError(context, strerror(err_number)); } -// Note the below two do not set errno because they are used only here in this -// file -// on a Windows handle and, therefore, not necessary. Translating GetLastError() -// to errno -// is a sad business -inline int fsync(HANDLE hFile) { - if (!FlushFileBuffers(hFile)) { - return -1; - } - - return 0; -} +class WinFileData; -SSIZE_T pwrite(HANDLE hFile, const char* src, size_t numBytes, uint64_t offset); +Status pwrite(const WinFileData* file_data, const Slice& data, + uint64_t offset, size_t& bytes_written); -SSIZE_T pread(HANDLE hFile, char* src, size_t numBytes, uint64_t offset); +Status pread(const WinFileData* file_data, char* src, size_t num_bytes, + uint64_t offset, size_t& bytes_read); Status fallocate(const std::string& filename, HANDLE hFile, uint64_t to_size); @@ -67,7 +62,7 @@ class WinFileData { protected: const std::string filename_; HANDLE hFile_; - // If ture, the I/O issued would be direct I/O which the buffer + // If true, the I/O issued would be direct I/O which the buffer // will need to be aligned (not sure there is a guarantee that the buffer // passed in is aligned). const bool use_direct_io_; @@ -104,8 +99,8 @@ class WinFileData { class WinSequentialFile : protected WinFileData, public SequentialFile { // Override for behavior change when creating a custom env - virtual SSIZE_T PositionedReadInternal(char* src, size_t numBytes, - uint64_t offset) const; + virtual Status PositionedReadInternal(char* src, size_t numBytes, + uint64_t offset, size_t& bytes_read) const; public: WinSequentialFile(const std::string& fname, HANDLE f, @@ -240,8 +235,8 @@ class WinRandomAccessImpl { size_t alignment_; // Override for behavior change when creating a custom env - virtual SSIZE_T PositionedReadInternal(char* src, size_t numBytes, - uint64_t offset) const; + virtual Status PositionedReadInternal(char* src, size_t numBytes, + uint64_t offset, size_t& bytes_read) const; WinRandomAccessImpl(WinFileData* file_base, size_t alignment, const EnvOptions& options); @@ -368,6 +363,8 @@ class WinWritableFile : private WinFileData, virtual Status Fsync() override; + virtual bool IsSyncThreadSafe() const override; + // Indicates if the class makes use of direct I/O // Use PositionedAppend virtual bool use_direct_io() const override; @@ -418,11 +415,30 @@ class WinRandomRWFile : private WinFileData, virtual Status Close() override; }; +class WinMemoryMappedBuffer : public MemoryMappedFileBuffer { +private: + HANDLE file_handle_; + HANDLE map_handle_; +public: + WinMemoryMappedBuffer(HANDLE file_handle, HANDLE map_handle, void* base, size_t size) : + MemoryMappedFileBuffer(base, size), + file_handle_(file_handle), + map_handle_(map_handle) {} + ~WinMemoryMappedBuffer() override; +}; + class WinDirectory : public Directory { + HANDLE handle_; public: - WinDirectory() {} - + explicit WinDirectory(HANDLE h) noexcept : handle_(h) { + assert(handle_ != INVALID_HANDLE_VALUE); + } + ~WinDirectory() { + ::CloseHandle(handle_); + } virtual Status Fsync() override; + + size_t GetUniqueId(char* id, size_t max_size) const override; }; class WinFileLock : public FileLock { diff --git a/thirdparty/rocksdb/port/win/port_win.cc b/thirdparty/rocksdb/port/win/port_win.cc index b3fccbd930..03ba6ef428 100644 --- a/thirdparty/rocksdb/port/win/port_win.cc +++ b/thirdparty/rocksdb/port/win/port_win.cc @@ -14,7 +14,7 @@ #include "port/win/port_win.h" #include -#include "port/dirent.h" +#include "port/port_dirent.h" #include "port/sys_time.h" #include @@ -26,11 +26,33 @@ #include #include +#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES +// utf8 <-> utf16 +#include +#include +#include +#endif + #include "util/logging.h" namespace rocksdb { + +extern const bool kDefaultToAdaptiveMutex = false; + namespace port { +#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES +std::string utf16_to_utf8(const std::wstring& utf16) { + std::wstring_convert,wchar_t> convert; + return convert.to_bytes(utf16); +} + +std::wstring utf8_to_utf16(const std::string& utf8) { + std::wstring_convert> converter; + return converter.from_bytes(utf8); +} +#endif + void gettimeofday(struct timeval* tv, struct timezone* /* tz */) { using namespace std::chrono; @@ -108,19 +130,20 @@ void InitOnce(OnceType* once, void (*initializer)()) { // Private structure, exposed only by pointer struct DIR { - intptr_t handle_; - bool firstread_; - struct __finddata64_t data_; + HANDLE handle_; + bool firstread_; + RX_WIN32_FIND_DATA data_; dirent entry_; - DIR() : handle_(-1), firstread_(true) {} + DIR() : handle_(INVALID_HANDLE_VALUE), + firstread_(true) {} DIR(const DIR&) = delete; DIR& operator=(const DIR&) = delete; ~DIR() { - if (-1 != handle_) { - _findclose(handle_); + if (INVALID_HANDLE_VALUE != handle_) { + ::FindClose(handle_); } } }; @@ -136,19 +159,26 @@ DIR* opendir(const char* name) { std::unique_ptr

    dir(new DIR); - dir->handle_ = _findfirst64(pattern.c_str(), &dir->data_); + dir->handle_ = RX_FindFirstFileEx(RX_FN(pattern).c_str(), + FindExInfoBasic, // Do not want alternative name + &dir->data_, + FindExSearchNameMatch, + NULL, // lpSearchFilter + 0); - if (dir->handle_ == -1) { + if (dir->handle_ == INVALID_HANDLE_VALUE) { return nullptr; } - strcpy_s(dir->entry_.d_name, sizeof(dir->entry_.d_name), dir->data_.name); + RX_FILESTRING x(dir->data_.cFileName, RX_FNLEN(dir->data_.cFileName)); + strcpy_s(dir->entry_.d_name, sizeof(dir->entry_.d_name), + FN_TO_RX(x).c_str()); return dir.release(); } struct dirent* readdir(DIR* dirp) { - if (!dirp || dirp->handle_ == -1) { + if (!dirp || dirp->handle_ == INVALID_HANDLE_VALUE) { errno = EBADF; return nullptr; } @@ -158,13 +188,15 @@ struct dirent* readdir(DIR* dirp) { return &dirp->entry_; } - auto ret = _findnext64(dirp->handle_, &dirp->data_); + auto ret = RX_FindNextFile(dirp->handle_, &dirp->data_); - if (ret != 0) { + if (ret == 0) { return nullptr; } - strcpy_s(dirp->entry_.d_name, sizeof(dirp->entry_.d_name), dirp->data_.name); + RX_FILESTRING x(dirp->data_.cFileName, RX_FNLEN(dirp->data_.cFileName)); + strcpy_s(dirp->entry_.d_name, sizeof(dirp->entry_.d_name), + FN_TO_RX(x).c_str()); return &dirp->entry_; } @@ -174,11 +206,15 @@ int closedir(DIR* dirp) { return 0; } -int truncate(const char* path, int64_t len) { +int truncate(const char* path, int64_t length) { if (path == nullptr) { errno = EFAULT; return -1; } + return rocksdb::port::Truncate(path, length); +} + +int Truncate(std::string path, int64_t len) { if (len < 0) { errno = EINVAL; @@ -186,7 +222,7 @@ int truncate(const char* path, int64_t len) { } HANDLE hFile = - CreateFile(path, GENERIC_READ | GENERIC_WRITE, + RX_CreateFile(RX_FN(path).c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, // Security attrs OPEN_EXISTING, // Truncate existing file only diff --git a/thirdparty/rocksdb/port/win/port_win.h b/thirdparty/rocksdb/port/win/port_win.h index f3c8669051..de41cdc7f0 100644 --- a/thirdparty/rocksdb/port/win/port_win.h +++ b/thirdparty/rocksdb/port/win/port_win.h @@ -9,8 +9,7 @@ // // See port_example.h for documentation for the following types/functions. -#ifndef STORAGE_LEVELDB_PORT_PORT_WIN_H_ -#define STORAGE_LEVELDB_PORT_PORT_WIN_H_ +#pragma once // Always want minimum headers #ifndef WIN32_LEAN_AND_MEAN @@ -28,6 +27,7 @@ #include #include #include +#include #include @@ -78,21 +78,12 @@ namespace rocksdb { #define PREFETCH(addr, rw, locality) -namespace port { - -// VS 15 -#if (defined _MSC_VER) && (_MSC_VER >= 1900) - -#define ROCKSDB_NOEXCEPT noexcept - -// For use at db/file_indexer.h kLevelMaxIndex -const int kMaxInt32 = std::numeric_limits::max(); -const uint64_t kMaxUint64 = std::numeric_limits::max(); -const int64_t kMaxInt64 = std::numeric_limits::max(); +extern const bool kDefaultToAdaptiveMutex; -const size_t kMaxSizet = std::numeric_limits::max(); +namespace port { -#else //_MSC_VER +// VS < 2015 +#if defined(_MSC_VER) && (_MSC_VER < 1900) // VS 15 has snprintf #define snprintf _snprintf @@ -102,8 +93,11 @@ const size_t kMaxSizet = std::numeric_limits::max(); // therefore, use the same limits // For use at db/file_indexer.h kLevelMaxIndex +const uint32_t kMaxUint32 = UINT32_MAX; const int kMaxInt32 = INT32_MAX; +const int kMinInt32 = INT32_MIN; const int64_t kMaxInt64 = INT64_MAX; +const int64_t kMinInt64 = INT64_MIN; const uint64_t kMaxUint64 = UINT64_MAX; #ifdef _WIN64 @@ -112,6 +106,20 @@ const size_t kMaxSizet = UINT64_MAX; const size_t kMaxSizet = UINT_MAX; #endif +#else // VS >= 2015 or MinGW + +#define ROCKSDB_NOEXCEPT noexcept + +// For use at db/file_indexer.h kLevelMaxIndex +const uint32_t kMaxUint32 = std::numeric_limits::max(); +const int kMaxInt32 = std::numeric_limits::max(); +const int kMinInt32 = std::numeric_limits::min(); +const uint64_t kMaxUint64 = std::numeric_limits::max(); +const int64_t kMaxInt64 = std::numeric_limits::max(); +const int64_t kMinInt64 = std::numeric_limits::min(); + +const size_t kMaxSizet = std::numeric_limits::max(); + #endif //_MSC_VER const bool kLittleEndian = true; @@ -121,7 +129,7 @@ class CondVar; class Mutex { public: - /* implicit */ Mutex(bool adaptive = false) + /* implicit */ Mutex(bool adaptive = kDefaultToAdaptiveMutex) #ifndef NDEBUG : locked_(false) #endif @@ -241,14 +249,9 @@ extern void InitOnce(OnceType* once, void (*initializer)()); #endif #ifdef ROCKSDB_JEMALLOC -#include "jemalloc/jemalloc.h" // Separate inlines so they can be replaced if needed -inline void* jemalloc_aligned_alloc( size_t size, size_t alignment) { - return je_aligned_alloc(alignment, size); -} -inline void jemalloc_aligned_free(void* p) { - je_free(p); -} +void* jemalloc_aligned_alloc(size_t size, size_t alignment) ROCKSDB_NOEXCEPT; +void jemalloc_aligned_free(void* p) ROCKSDB_NOEXCEPT; #endif inline void *cacheline_aligned_alloc(size_t size) { @@ -330,11 +333,62 @@ inline void* pthread_getspecific(pthread_key_t key) { // using C-runtime to implement. Note, this does not // feel space with zeros in case the file is extended. int truncate(const char* path, int64_t length); +int Truncate(std::string path, int64_t length); void Crash(const std::string& srcfile, int srcline); extern int GetMaxOpenFiles(); +std::string utf16_to_utf8(const std::wstring& utf16); +std::wstring utf8_to_utf16(const std::string& utf8); } // namespace port + +#ifdef ROCKSDB_WINDOWS_UTF8_FILENAMES + +#define RX_FILESTRING std::wstring +#define RX_FN(a) rocksdb::port::utf8_to_utf16(a) +#define FN_TO_RX(a) rocksdb::port::utf16_to_utf8(a) +#define RX_FNLEN(a) ::wcslen(a) + +#define RX_DeleteFile DeleteFileW +#define RX_CreateFile CreateFileW +#define RX_CreateFileMapping CreateFileMappingW +#define RX_GetFileAttributesEx GetFileAttributesExW +#define RX_FindFirstFileEx FindFirstFileExW +#define RX_FindNextFile FindNextFileW +#define RX_WIN32_FIND_DATA WIN32_FIND_DATAW +#define RX_CreateDirectory CreateDirectoryW +#define RX_RemoveDirectory RemoveDirectoryW +#define RX_GetFileAttributesEx GetFileAttributesExW +#define RX_MoveFileEx MoveFileExW +#define RX_CreateHardLink CreateHardLinkW +#define RX_PathIsRelative PathIsRelativeW +#define RX_GetCurrentDirectory GetCurrentDirectoryW + +#else + +#define RX_FILESTRING std::string +#define RX_FN(a) a +#define FN_TO_RX(a) a +#define RX_FNLEN(a) strlen(a) + +#define RX_DeleteFile DeleteFileA +#define RX_CreateFile CreateFileA +#define RX_CreateFileMapping CreateFileMappingA +#define RX_GetFileAttributesEx GetFileAttributesExA +#define RX_FindFirstFileEx FindFirstFileExA +#define RX_CreateDirectory CreateDirectoryA +#define RX_FindNextFile FindNextFileA +#define RX_WIN32_FIND_DATA WIN32_FIND_DATA +#define RX_CreateDirectory CreateDirectoryA +#define RX_RemoveDirectory RemoveDirectoryA +#define RX_GetFileAttributesEx GetFileAttributesExA +#define RX_MoveFileEx MoveFileExA +#define RX_CreateHardLink CreateHardLinkA +#define RX_PathIsRelative PathIsRelativeA +#define RX_GetCurrentDirectory GetCurrentDirectoryA + +#endif + using port::pthread_key_t; using port::pthread_key_create; using port::pthread_key_delete; @@ -343,5 +397,3 @@ using port::pthread_getspecific; using port::truncate; } // namespace rocksdb - -#endif // STORAGE_LEVELDB_PORT_PORT_WIN_H_ diff --git a/thirdparty/rocksdb/port/win/win_jemalloc.cc b/thirdparty/rocksdb/port/win/win_jemalloc.cc index fc46e189c4..3268a56aff 100644 --- a/thirdparty/rocksdb/port/win/win_jemalloc.cc +++ b/thirdparty/rocksdb/port/win/win_jemalloc.cc @@ -13,10 +13,39 @@ #include #include "jemalloc/jemalloc.h" +#include "port/win/port_win.h" + +#if defined(ZSTD) && defined(ZSTD_STATIC_LINKING_ONLY) +#include +#if (ZSTD_VERSION_NUMBER >= 500) +namespace rocksdb { +namespace port { +void* JemallocAllocateForZSTD(void* /* opaque */, size_t size) { + return je_malloc(size); +} +void JemallocDeallocateForZSTD(void* /* opaque */, void* address) { + je_free(address); +} +ZSTD_customMem GetJeZstdAllocationOverrides() { + return {JemallocAllocateForZSTD, JemallocDeallocateForZSTD, nullptr}; +} +} // namespace port +} // namespace rocksdb +#endif // (ZSTD_VERSION_NUMBER >= 500) +#endif // defined(ZSTD) defined(ZSTD_STATIC_LINKING_ONLY) // Global operators to be replaced by a linker when this file is // a part of the build +namespace rocksdb { +namespace port { +void* jemalloc_aligned_alloc(size_t size, size_t alignment) ROCKSDB_NOEXCEPT { + return je_aligned_alloc(alignment, size); +} +void jemalloc_aligned_free(void* p) ROCKSDB_NOEXCEPT { je_free(p); } +} // namespace port +} // namespace rocksdb + void* operator new(size_t size) { void* p = je_malloc(size); if (!p) { @@ -44,4 +73,3 @@ void operator delete[](void* p) { je_free(p); } } - diff --git a/thirdparty/rocksdb/port/win/win_logger.cc b/thirdparty/rocksdb/port/win/win_logger.cc index 0bace9f31f..af722d9054 100644 --- a/thirdparty/rocksdb/port/win/win_logger.cc +++ b/thirdparty/rocksdb/port/win/win_logger.cc @@ -36,9 +36,13 @@ WinLogger::WinLogger(uint64_t (*gettid)(), Env* env, HANDLE file, log_size_(0), last_flush_micros_(0), env_(env), - flush_pending_(false) {} + flush_pending_(false) { + assert(file_ != NULL); + assert(file_ != INVALID_HANDLE_VALUE); +} void WinLogger::DebugWriter(const char* str, int len) { + assert(file_ != INVALID_HANDLE_VALUE); DWORD bytesWritten = 0; BOOL ret = WriteFile(file_, str, len, &bytesWritten, NULL); if (ret == FALSE) { @@ -47,11 +51,38 @@ void WinLogger::DebugWriter(const char* str, int len) { } } -WinLogger::~WinLogger() { close(); } +WinLogger::~WinLogger() { + CloseInternal(); +} + +Status WinLogger::CloseImpl() { + return CloseInternal(); +} -void WinLogger::close() { CloseHandle(file_); } +Status WinLogger::CloseInternal() { + Status s; + if (INVALID_HANDLE_VALUE != file_) { + BOOL ret = FlushFileBuffers(file_); + if (ret == 0) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("Failed to flush LOG on Close() ", + lastError); + } + ret = CloseHandle(file_); + // On error the return value is zero + if (ret == 0 && s.ok()) { + auto lastError = GetLastError(); + s = IOErrorFromWindowsError("Failed to flush LOG on Close() ", + lastError); + } + file_ = INVALID_HANDLE_VALUE; + closed_ = true; + } + return s; +} void WinLogger::Flush() { + assert(file_ != INVALID_HANDLE_VALUE); if (flush_pending_) { flush_pending_ = false; // With Windows API writes go to OS buffers directly so no fflush needed @@ -64,6 +95,7 @@ void WinLogger::Flush() { void WinLogger::Logv(const char* format, va_list ap) { IOSTATS_TIMER_GUARD(logger_nanos); + assert(file_ != INVALID_HANDLE_VALUE); const uint64_t thread_id = (*gettid_)(); diff --git a/thirdparty/rocksdb/port/win/win_logger.h b/thirdparty/rocksdb/port/win/win_logger.h index 2d44f506d1..0982f142f6 100644 --- a/thirdparty/rocksdb/port/win/win_logger.h +++ b/thirdparty/rocksdb/port/win/win_logger.h @@ -36,8 +36,6 @@ class WinLogger : public rocksdb::Logger { WinLogger& operator=(const WinLogger&) = delete; - void close(); - void Flush() override; using rocksdb::Logger::Logv; @@ -47,6 +45,10 @@ class WinLogger : public rocksdb::Logger { void DebugWriter(const char* str, int len); +protected: + + Status CloseImpl() override; + private: HANDLE file_; uint64_t (*gettid_)(); // Return the thread id for the current thread @@ -55,6 +57,8 @@ class WinLogger : public rocksdb::Logger { Env* env_; bool flush_pending_; + Status CloseInternal(); + const static uint64_t flush_every_seconds_ = 5; }; diff --git a/thirdparty/rocksdb/port/win/win_thread.cc b/thirdparty/rocksdb/port/win/win_thread.cc index e55ca7450b..9a976e2c6b 100644 --- a/thirdparty/rocksdb/port/win/win_thread.cc +++ b/thirdparty/rocksdb/port/win/win_thread.cc @@ -39,12 +39,17 @@ struct WindowsThread::Data { void WindowsThread::Init(std::function&& func) { - data_.reset(new Data(std::move(func))); + data_ = std::make_shared(std::move(func)); + // We create another instance of std::shared_ptr to get an additional ref + // since we may detach and destroy this instance before the threadproc + // may start to run. We choose to allocate this additional ref on the heap + // so we do not need to synchronize and allow this thread to proceed + std::unique_ptr> th_data(new std::shared_ptr(data_)); data_->handle_ = _beginthreadex(NULL, 0, // stack size &Data::ThreadProc, - data_.get(), + th_data.get(), 0, // init flag &th_id_); @@ -53,6 +58,7 @@ void WindowsThread::Init(std::function&& func) { std::errc::resource_unavailable_try_again), "Unable to create a thread"); } + th_data.release(); } WindowsThread::WindowsThread() : @@ -129,10 +135,12 @@ void WindowsThread::join() { assert(false); throw std::system_error(static_cast(lastError), std::system_category(), - "WaitForSingleObjectFailed"); + "WaitForSingleObjectFailed: thread join"); } - CloseHandle(reinterpret_cast(data_->handle_)); + BOOL rc; + rc = CloseHandle(reinterpret_cast(data_->handle_)); + assert(rc != 0); data_->handle_ = 0; } @@ -148,7 +156,7 @@ bool WindowsThread::detach() { BOOL ret = CloseHandle(reinterpret_cast(data_->handle_)); data_->handle_ = 0; - return (ret == TRUE); + return (ret != 0); } void WindowsThread::swap(WindowsThread& o) { @@ -157,9 +165,9 @@ void WindowsThread::swap(WindowsThread& o) { } unsigned int __stdcall WindowsThread::Data::ThreadProc(void* arg) { - auto data = reinterpret_cast(arg); - data->func_(); - _endthreadex(0); + auto ptr = reinterpret_cast*>(arg); + std::unique_ptr> data(ptr); + (*data)->func_(); return 0; } } // namespace port diff --git a/thirdparty/rocksdb/port/win/win_thread.h b/thirdparty/rocksdb/port/win/win_thread.h index 993cc02731..1d5b225e6c 100644 --- a/thirdparty/rocksdb/port/win/win_thread.h +++ b/thirdparty/rocksdb/port/win/win_thread.h @@ -28,7 +28,7 @@ class WindowsThread { struct Data; - std::unique_ptr data_; + std::shared_ptr data_; unsigned int th_id_; void Init(std::function&&); diff --git a/thirdparty/rocksdb/src.mk b/thirdparty/rocksdb/src.mk index 5bd5236fa1..55b4e3427c 100644 --- a/thirdparty/rocksdb/src.mk +++ b/thirdparty/rocksdb/src.mk @@ -11,20 +11,23 @@ LIB_SOURCES = \ db/compaction_iterator.cc \ db/compaction_job.cc \ db/compaction_picker.cc \ + db/compaction_picker_fifo.cc \ db/compaction_picker_universal.cc \ db/convenience.cc \ db/db_filesnapshot.cc \ db/db_impl.cc \ - db/db_impl_write.cc \ db/db_impl_compaction_flush.cc \ - db/db_impl_files.cc \ - db/db_impl_open.cc \ db/db_impl_debug.cc \ db/db_impl_experimental.cc \ + db/db_impl_files.cc \ + db/db_impl_open.cc \ db/db_impl_readonly.cc \ + db/db_impl_secondary.cc \ + db/db_impl_write.cc \ db/db_info_dumper.cc \ db/db_iter.cc \ db/dbformat.cc \ + db/error_handler.cc \ db/event_helpers.cc \ db/experimental.cc \ db/external_sst_file_ingestion_job.cc \ @@ -32,16 +35,18 @@ LIB_SOURCES = \ db/flush_job.cc \ db/flush_scheduler.cc \ db/forward_iterator.cc \ + db/in_memory_stats_history.cc \ db/internal_stats.cc \ + db/logs_with_prep_tracker.cc \ db/log_reader.cc \ db/log_writer.cc \ db/malloc_stats.cc \ - db/managed_iterator.cc \ db/memtable.cc \ db/memtable_list.cc \ db/merge_helper.cc \ db/merge_operator.cc \ db/range_del_aggregator.cc \ + db/range_tombstone_fragmenter.cc \ db/repair.cc \ db/snapshot_impl.cc \ db/table_cache.cc \ @@ -63,7 +68,6 @@ LIB_SOURCES = \ env/io_posix.cc \ env/mock_env.cc \ memtable/alloc_tracker.cc \ - memtable/hash_cuckoo_rep.cc \ memtable/hash_linklist_rep.cc \ memtable/hash_skiplist_rep.cc \ memtable/skiplistrep.cc \ @@ -96,11 +100,14 @@ LIB_SOURCES = \ table/block_based_table_factory.cc \ table/block_based_table_reader.cc \ table/block_builder.cc \ + table/block_fetcher.cc \ table/block_prefix_index.cc \ table/bloom_block.cc \ table/cuckoo_table_builder.cc \ table/cuckoo_table_factory.cc \ table/cuckoo_table_reader.cc \ + table/data_block_hash_index.cc \ + table/data_block_footer.cc \ table/flush_block_policy.cc \ table/format.cc \ table/full_filter_block.cc \ @@ -116,6 +123,7 @@ LIB_SOURCES = \ table/plain_table_index.cc \ table/plain_table_key_coding.cc \ table/plain_table_reader.cc \ + table/sst_file_reader.cc \ table/sst_file_writer.cc \ table/table_properties.cc \ table/two_level_iterator.cc \ @@ -127,7 +135,9 @@ LIB_SOURCES = \ util/coding.cc \ util/compaction_job_stats_impl.cc \ util/comparator.cc \ + util/compression_context_cache.cc \ util/concurrent_arena.cc \ + util/concurrent_task_limiter_impl.cc \ util/crc32c.cc \ util/delete_scheduler.cc \ util/dynamic_bloom.cc \ @@ -137,6 +147,7 @@ LIB_SOURCES = \ util/filename.cc \ util/filter_policy.cc \ util/hash.cc \ + util/jemalloc_nodump_allocator.cc \ util/log_buffer.cc \ util/murmurhash.cc \ util/random.cc \ @@ -144,43 +155,40 @@ LIB_SOURCES = \ util/slice.cc \ util/sst_file_manager_impl.cc \ util/status.cc \ - util/status_message.cc \ util/string_util.cc \ util/sync_point.cc \ + util/sync_point_impl.cc \ util/thread_local.cc \ util/threadpool_imp.cc \ + util/trace_replay.cc \ util/transaction_test_util.cc \ util/xxhash.cc \ utilities/backupable/backupable_db.cc \ + utilities/blob_db/blob_compaction_filter.cc \ utilities/blob_db/blob_db.cc \ utilities/blob_db/blob_db_impl.cc \ + utilities/blob_db/blob_db_impl_filesnapshot.cc \ utilities/blob_db/blob_file.cc \ + utilities/blob_db/blob_log_format.cc \ utilities/blob_db/blob_log_reader.cc \ utilities/blob_db/blob_log_writer.cc \ - utilities/blob_db/blob_log_format.cc \ - utilities/blob_db/ttl_extractor.cc \ utilities/cassandra/cassandra_compaction_filter.cc \ utilities/cassandra/format.cc \ utilities/cassandra/merge_operator.cc \ utilities/checkpoint/checkpoint_impl.cc \ utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc \ utilities/convenience/info_log_finder.cc \ - utilities/date_tiered/date_tiered_db_impl.cc \ - utilities/debug.cc \ - utilities/document/document_db.cc \ - utilities/document/json_document.cc \ - utilities/document/json_document_builder.cc \ + utilities/debug.cc \ utilities/env_mirror.cc \ utilities/env_timed.cc \ - utilities/geodb/geodb_impl.cc \ utilities/leveldb_options/leveldb_options.cc \ - utilities/lua/rocks_lua_compaction_filter.cc \ utilities/memory/memory_util.cc \ utilities/merge_operators/max.cc \ utilities/merge_operators/put.cc \ utilities/merge_operators/string_append/stringappend.cc \ utilities/merge_operators/string_append/stringappend2.cc \ utilities/merge_operators/uint64add.cc \ + utilities/merge_operators/bytesxor.cc \ utilities/option_change_migration/option_change_migration.cc \ utilities/options/options_util.cc \ utilities/persistent_cache/block_cache_tier.cc \ @@ -188,28 +196,44 @@ LIB_SOURCES = \ utilities/persistent_cache/block_cache_tier_metadata.cc \ utilities/persistent_cache/persistent_cache_tier.cc \ utilities/persistent_cache/volatile_tier_impl.cc \ - utilities/redis/redis_lists.cc \ utilities/simulator_cache/sim_cache.cc \ - utilities/spatialdb/spatial_db.cc \ utilities/table_properties_collectors/compact_on_deletion_collector.cc \ + utilities/trace/file_trace_reader_writer.cc \ + utilities/transactions/optimistic_transaction.cc \ utilities/transactions/optimistic_transaction_db_impl.cc \ - utilities/transactions/optimistic_transaction.cc \ + utilities/transactions/pessimistic_transaction.cc \ + utilities/transactions/pessimistic_transaction_db.cc \ + utilities/transactions/snapshot_checker.cc \ utilities/transactions/transaction_base.cc \ - utilities/transactions/pessimistic_transaction_db.cc \ utilities/transactions/transaction_db_mutex_impl.cc \ - utilities/transactions/pessimistic_transaction.cc \ utilities/transactions/transaction_lock_mgr.cc \ utilities/transactions/transaction_util.cc \ - utilities/transactions/write_prepared_txn.cc \ + utilities/transactions/write_prepared_txn.cc \ + utilities/transactions/write_prepared_txn_db.cc \ + utilities/transactions/write_unprepared_txn.cc \ + utilities/transactions/write_unprepared_txn_db.cc \ utilities/ttl/db_ttl_impl.cc \ utilities/write_batch_with_index/write_batch_with_index.cc \ utilities/write_batch_with_index/write_batch_with_index_internal.cc \ +ifeq (,$(shell $(CXX) -fsyntax-only -maltivec -xc /dev/null 2>&1)) +LIB_SOURCES_ASM =\ + util/crc32c_ppc_asm.S +LIB_SOURCES_C = \ + util/crc32c_ppc.c +else +LIB_SOURCES_ASM = +LIB_SOURCES_C = +endif + TOOL_LIB_SOURCES = \ - tools/ldb_cmd.cc \ - tools/ldb_tool.cc \ - tools/sst_dump_tool.cc \ - utilities/blob_db/blob_dump_tool.cc \ + tools/ldb_cmd.cc \ + tools/ldb_tool.cc \ + tools/sst_dump_tool.cc \ + utilities/blob_db/blob_dump_tool.cc \ + +ANALYZER_LIB_SOURCES = \ + tools/trace_analyzer_tool.cc \ MOCK_LIB_SOURCES = \ table/mock_table.cc \ @@ -218,21 +242,18 @@ MOCK_LIB_SOURCES = \ BENCH_LIB_SOURCES = \ tools/db_bench_tool.cc \ -EXP_LIB_SOURCES = \ - utilities/col_buf_encoder.cc \ - utilities/col_buf_decoder.cc \ - utilities/column_aware_encoding_util.cc - TEST_LIB_SOURCES = \ - util/testharness.cc \ - util/testutil.cc \ - db/db_test_util.cc \ - utilities/cassandra/test_utils.cc \ + db/db_test_util.cc \ + util/testharness.cc \ + util/testutil.cc \ + utilities/cassandra/test_utils.cc \ -MAIN_SOURCES = \ - cache/cache_bench.cc \ - cache/cache_test.cc \ +MAIN_SOURCES = \ + cache/cache_bench.cc \ + cache/cache_test.cc \ db/column_family_test.cc \ + db/compact_files_test.cc \ + db/compaction_iterator_test.cc \ db/compaction_job_stats_test.cc \ db/compaction_job_test.cc \ db/compaction_picker_test.cc \ @@ -240,47 +261,70 @@ MAIN_SOURCES = \ db/corruption_test.cc \ db/cuckoo_table_db_test.cc \ db/db_basic_test.cc \ + db/db_blob_index_test.cc \ db/db_block_cache_test.cc \ db/db_bloom_filter_test.cc \ db/db_compaction_filter_test.cc \ db/db_compaction_test.cc \ db/db_dynamic_level_test.cc \ db/db_encryption_test.cc \ - db/db_flush_test.cc \ + db/db_flush_test.cc \ db/db_inplace_update_test.cc \ db/db_io_failure_test.cc \ db/db_iter_test.cc \ + db/db_iter_stress_test.cc \ db/db_iterator_test.cc \ db/db_log_iter_test.cc \ db/db_memtable_test.cc \ db/db_merge_operator_test.cc \ db/db_options_test.cc \ + db/db_properties_test.cc \ db/db_range_del_test.cc \ + db/db_secondary_test.cc \ db/db_sst_test.cc \ db/db_statistics_test.cc \ db/db_table_properties_test.cc \ db/db_tailing_iter_test.cc \ db/db_test.cc \ + db/db_test2.cc \ db/db_universal_compaction_test.cc \ db/db_wal_test.cc \ db/db_write_test.cc \ db/dbformat_test.cc \ db/deletefile_test.cc \ + db/env_timed_test.cc \ + db/error_handler_test.cc \ db/external_sst_file_basic_test.cc \ db/external_sst_file_test.cc \ db/fault_injection_test.cc \ db/file_indexer_test.cc \ + db/file_reader_writer_test.cc \ db/filename_test.cc \ db/flush_job_test.cc \ + db/hash_table_test.cc \ + db/hash_test.cc \ + db/heap_test.cc \ db/listener_test.cc \ db/log_test.cc \ + db/lru_cache_test.cc \ db/manual_compaction_test.cc \ + db/memtable_list_test.cc \ + db/merge_helper_test.cc \ db/merge_test.cc \ + db/obsolete_files_test.cc \ + db/options_settable_test.cc \ db/options_file_test.cc \ + db/partitioned_filter_block_test.cc \ db/perf_context_test.cc \ + db/persistent_cache_test.cc \ db/plain_table_db_test.cc \ db/prefix_test.cc \ + db/repair_test.cc \ + db/range_del_aggregator_test.cc \ + db/range_del_aggregator_bench.cc \ + db/range_tombstone_fragmenter_test.cc \ db/table_properties_collector_test.cc \ + db/util_merge_operators_test.cc \ db/version_builder_test.cc \ db/version_edit_test.cc \ db/version_set_test.cc \ @@ -304,8 +348,10 @@ MAIN_SOURCES = \ table/cleanable_test.cc \ table/cuckoo_table_builder_test.cc \ table/cuckoo_table_reader_test.cc \ + table/data_block_hash_index_test.cc \ table/full_filter_block_test.cc \ table/merger_test.cc \ + table/sst_file_reader_test.cc \ table/table_reader_bench.cc \ table/table_test.cc \ third-party/gtest-1.7.0/fused-src/gtest/gtest-all.cc \ @@ -315,6 +361,7 @@ MAIN_SOURCES = \ tools/ldb_cmd_test.cc \ tools/reduce_levels_test.cc \ tools/sst_dump_test.cc \ + tools/trace_analyzer_test.cc \ util/arena_test.cc \ util/auto_roll_logger_test.cc \ util/autovector_test.cc \ @@ -326,6 +373,7 @@ MAIN_SOURCES = \ util/filelock_test.cc \ util/log_write_bench.cc \ util/rate_limiter_test.cc \ + util/repeatable_thread_test.cc \ util/slice_transform_test.cc \ util/timer_queue_test.cc \ util/thread_list_test.cc \ @@ -337,24 +385,17 @@ MAIN_SOURCES = \ utilities/cassandra/cassandra_row_merge_test.cc \ utilities/cassandra/cassandra_serialize_test.cc \ utilities/checkpoint/checkpoint_test.cc \ - utilities/column_aware_encoding_exp.cc \ - utilities/column_aware_encoding_test.cc \ - utilities/date_tiered/date_tiered_test.cc \ - utilities/document/document_db_test.cc \ - utilities/document/json_document_test.cc \ - utilities/geodb/geodb_test.cc \ - utilities/lua/rocks_lua_test.cc \ utilities/memory/memory_test.cc \ utilities/merge_operators/string_append/stringappend_test.cc \ utilities/object_registry_test.cc \ utilities/option_change_migration/option_change_migration_test.cc \ utilities/options/options_util_test.cc \ - utilities/redis/redis_lists_test.cc \ utilities/simulator_cache/sim_cache_test.cc \ - utilities/spatialdb/spatial_db_test.cc \ utilities/table_properties_collectors/compact_on_deletion_collector_test.cc \ utilities/transactions/optimistic_transaction_test.cc \ utilities/transactions/transaction_test.cc \ + utilities/transactions/write_prepared_transaction_test.cc \ + utilities/transactions/write_unprepared_transaction_test.cc \ utilities/ttl/ttl_test.cc \ utilities/write_batch_with_index/write_batch_with_index_test.cc \ @@ -364,7 +405,13 @@ JNI_NATIVE_SOURCES = \ java/rocksjni/checkpoint.cc \ java/rocksjni/clock_cache.cc \ java/rocksjni/columnfamilyhandle.cc \ + java/rocksjni/compact_range_options.cc \ java/rocksjni/compaction_filter.cc \ + java/rocksjni/compaction_filter_factory.cc \ + java/rocksjni/compaction_filter_factory_jnicallback.cc \ + java/rocksjni/compaction_job_info.cc \ + java/rocksjni/compaction_job_stats.cc \ + java/rocksjni/compaction_options.cc \ java/rocksjni/compaction_options_fifo.cc \ java/rocksjni/compaction_options_universal.cc \ java/rocksjni/comparator.cc \ @@ -375,26 +422,50 @@ JNI_NATIVE_SOURCES = \ java/rocksjni/ingest_external_file_options.cc \ java/rocksjni/filter.cc \ java/rocksjni/iterator.cc \ + java/rocksjni/jnicallback.cc \ java/rocksjni/loggerjnicallback.cc \ java/rocksjni/lru_cache.cc \ java/rocksjni/memtablejni.cc \ + java/rocksjni/memory_util.cc \ java/rocksjni/merge_operator.cc \ + java/rocksjni/native_comparator_wrapper_test.cc \ + java/rocksjni/optimistic_transaction_db.cc \ + java/rocksjni/optimistic_transaction_options.cc \ java/rocksjni/options.cc \ + java/rocksjni/options_util.cc \ + java/rocksjni/persistent_cache.cc \ java/rocksjni/ratelimiterjni.cc \ java/rocksjni/remove_emptyvalue_compactionfilterjni.cc \ java/rocksjni/cassandra_compactionfilterjni.cc \ + java/rocksjni/cassandra_value_operator.cc \ java/rocksjni/restorejni.cc \ + java/rocksjni/rocks_callback_object.cc \ java/rocksjni/rocksjni.cc \ java/rocksjni/rocksdb_exception_test.cc \ java/rocksjni/slice.cc \ java/rocksjni/snapshot.cc \ + java/rocksjni/sst_file_manager.cc \ java/rocksjni/sst_file_writerjni.cc \ java/rocksjni/statistics.cc \ java/rocksjni/statisticsjni.cc \ java/rocksjni/table.cc \ + java/rocksjni/table_filter.cc \ + java/rocksjni/table_filter_jnicallback.cc \ + java/rocksjni/thread_status.cc \ + java/rocksjni/trace_writer.cc \ + java/rocksjni/trace_writer_jnicallback.cc \ + java/rocksjni/transaction.cc \ + java/rocksjni/transaction_db.cc \ + java/rocksjni/transaction_options.cc \ + java/rocksjni/transaction_db_options.cc \ java/rocksjni/transaction_log.cc \ + java/rocksjni/transaction_notifier.cc \ + java/rocksjni/transaction_notifier_jnicallback.cc \ java/rocksjni/ttl.cc \ + java/rocksjni/wal_filter.cc \ + java/rocksjni/wal_filter_jnicallback.cc \ java/rocksjni/write_batch.cc \ java/rocksjni/writebatchhandlerjnicallback.cc \ java/rocksjni/write_batch_test.cc \ - java/rocksjni/write_batch_with_index.cc + java/rocksjni/write_batch_with_index.cc \ + java/rocksjni/write_buffer_manager.cc diff --git a/thirdparty/rocksdb/table/adaptive_table_factory.cc b/thirdparty/rocksdb/table/adaptive_table_factory.cc index 47069f8669..bbba3b9193 100644 --- a/thirdparty/rocksdb/table/adaptive_table_factory.cc +++ b/thirdparty/rocksdb/table/adaptive_table_factory.cc @@ -42,9 +42,9 @@ extern const uint64_t kCuckooTableMagicNumber; Status AdaptiveTableFactory::NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table, - bool prefetch_index_and_filter_in_cache) const { + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table, + bool /*prefetch_index_and_filter_in_cache*/) const { Footer footer; auto s = ReadFooterFromFile(file.get(), nullptr /* prefetch_buffer */, file_size, &footer); diff --git a/thirdparty/rocksdb/table/adaptive_table_factory.h b/thirdparty/rocksdb/table/adaptive_table_factory.h index b7b52ba96f..5534c8b372 100644 --- a/thirdparty/rocksdb/table/adaptive_table_factory.h +++ b/thirdparty/rocksdb/table/adaptive_table_factory.h @@ -14,7 +14,6 @@ namespace rocksdb { struct EnvOptions; -using std::unique_ptr; class Status; class RandomAccessFile; class WritableFile; @@ -35,8 +34,8 @@ class AdaptiveTableFactory : public TableFactory { Status NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table, + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table, bool prefetch_index_and_filter_in_cache = true) const override; TableBuilder* NewTableBuilder( @@ -44,8 +43,9 @@ class AdaptiveTableFactory : public TableFactory { uint32_t column_family_id, WritableFileWriter* file) const override; // Sanitizes the specified DB Options. - Status SanitizeOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { + Status SanitizeOptions( + const DBOptions& /*db_opts*/, + const ColumnFamilyOptions& /*cf_opts*/) const override { return Status::OK(); } diff --git a/thirdparty/rocksdb/table/block.cc b/thirdparty/rocksdb/table/block.cc index 372bbd2f0b..7c83ebb640 100644 --- a/thirdparty/rocksdb/table/block.cc +++ b/thirdparty/rocksdb/table/block.cc @@ -20,6 +20,7 @@ #include "port/stack_trace.h" #include "rocksdb/comparator.h" #include "table/block_prefix_index.h" +#include "table/data_block_footer.h" #include "table/format.h" #include "util/coding.h" #include "util/logging.h" @@ -33,35 +34,138 @@ namespace rocksdb { // // If any errors are detected, returns nullptr. Otherwise, returns a // pointer to the key delta (just past the three decoded values). -static inline const char* DecodeEntry(const char* p, const char* limit, - uint32_t* shared, - uint32_t* non_shared, - uint32_t* value_length) { - if (limit - p < 3) return nullptr; - *shared = reinterpret_cast(p)[0]; - *non_shared = reinterpret_cast(p)[1]; - *value_length = reinterpret_cast(p)[2]; - if ((*shared | *non_shared | *value_length) < 128) { - // Fast path: all three values are encoded in one byte each - p += 3; - } else { - if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr; - if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr; - if ((p = GetVarint32Ptr(p, limit, value_length)) == nullptr) return nullptr; +struct DecodeEntry { + inline const char* operator()(const char* p, const char* limit, + uint32_t* shared, uint32_t* non_shared, + uint32_t* value_length) { + // We need 2 bytes for shared and non_shared size. We also need one more + // byte either for value size or the actual value in case of value delta + // encoding. + assert(limit - p >= 3); + *shared = reinterpret_cast(p)[0]; + *non_shared = reinterpret_cast(p)[1]; + *value_length = reinterpret_cast(p)[2]; + if ((*shared | *non_shared | *value_length) < 128) { + // Fast path: all three values are encoded in one byte each + p += 3; + } else { + if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr; + if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr; + if ((p = GetVarint32Ptr(p, limit, value_length)) == nullptr) { + return nullptr; + } + } + + // Using an assert in place of "return null" since we should not pay the + // cost of checking for corruption on every single key decoding + assert(!(static_cast(limit - p) < (*non_shared + *value_length))); + return p; + } +}; + +// Helper routine: similar to DecodeEntry but does not have assertions. +// Instead, returns nullptr so that caller can detect and report failure. +struct CheckAndDecodeEntry { + inline const char* operator()(const char* p, const char* limit, + uint32_t* shared, uint32_t* non_shared, + uint32_t* value_length) { + // We need 2 bytes for shared and non_shared size. We also need one more + // byte either for value size or the actual value in case of value delta + // encoding. + if (limit - p < 3) { + return nullptr; + } + *shared = reinterpret_cast(p)[0]; + *non_shared = reinterpret_cast(p)[1]; + *value_length = reinterpret_cast(p)[2]; + if ((*shared | *non_shared | *value_length) < 128) { + // Fast path: all three values are encoded in one byte each + p += 3; + } else { + if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr; + if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr; + if ((p = GetVarint32Ptr(p, limit, value_length)) == nullptr) { + return nullptr; + } + } + + if (static_cast(limit - p) < (*non_shared + *value_length)) { + return nullptr; + } + return p; } +}; - if (static_cast(limit - p) < (*non_shared + *value_length)) { - return nullptr; +struct DecodeKey { + inline const char* operator()(const char* p, const char* limit, + uint32_t* shared, uint32_t* non_shared) { + uint32_t value_length; + return DecodeEntry()(p, limit, shared, non_shared, &value_length); } - return p; +}; + +// In format_version 4, which is used by index blocks, the value size is not +// encoded before the entry, as the value is known to be the handle with the +// known size. +struct DecodeKeyV4 { + inline const char* operator()(const char* p, const char* limit, + uint32_t* shared, uint32_t* non_shared) { + // We need 2 bytes for shared and non_shared size. We also need one more + // byte either for value size or the actual value in case of value delta + // encoding. + if (limit - p < 3) return nullptr; + *shared = reinterpret_cast(p)[0]; + *non_shared = reinterpret_cast(p)[1]; + if ((*shared | *non_shared) < 128) { + // Fast path: all three values are encoded in one byte each + p += 2; + } else { + if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr; + if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr; + } + return p; + } +}; + +void DataBlockIter::Next() { + assert(Valid()); + ParseNextDataKey(); } -void BlockIter::Next() { +void DataBlockIter::NextOrReport() { assert(Valid()); - ParseNextKey(); + ParseNextDataKey(); } -void BlockIter::Prev() { +void IndexBlockIter::Next() { + assert(Valid()); + ParseNextIndexKey(); +} + +void IndexBlockIter::Prev() { + assert(Valid()); + // Scan backwards to a restart point before current_ + const uint32_t original = current_; + while (GetRestartPoint(restart_index_) >= original) { + if (restart_index_ == 0) { + // No more entries + current_ = restarts_; + restart_index_ = num_restarts_; + return; + } + restart_index_--; + } + SeekToRestartPoint(restart_index_); + do { + if (!ParseNextIndexKey()) { + break; + } + // Loop until end of current entry hits the start of original entry + } while (NextEntryOffset() < original); +} + +// Similar to IndexBlockIter::Prev but also caches the prev entries +void DataBlockIter::Prev() { assert(Valid()); assert(prev_entries_idx_ == -1 || @@ -87,7 +191,7 @@ void BlockIter::Prev() { const Slice current_key(key_ptr, current_prev_entry.key_size); current_ = current_prev_entry.offset; - key_.SetInternalKey(current_key, false /* copy */); + key_.SetKey(current_key, false /* copy */); value_ = current_prev_entry.value; return; @@ -113,7 +217,7 @@ void BlockIter::Prev() { SeekToRestartPoint(restart_index_); do { - if (!ParseNextKey()) { + if (!ParseNextDataKey()) { break; } Slice current_key = key(); @@ -135,7 +239,151 @@ void BlockIter::Prev() { prev_entries_idx_ = static_cast(prev_entries_.size()) - 1; } -void BlockIter::Seek(const Slice& target) { +void DataBlockIter::Seek(const Slice& target) { + Slice seek_key = target; + PERF_TIMER_GUARD(block_seek_nanos); + if (data_ == nullptr) { // Not init yet + return; + } + uint32_t index = 0; + bool ok = BinarySeek(seek_key, 0, num_restarts_ - 1, &index, + comparator_); + + if (!ok) { + return; + } + SeekToRestartPoint(index); + // Linear search (within restart block) for first key >= target + + while (true) { + if (!ParseNextDataKey() || Compare(key_, seek_key) >= 0) { + return; + } + } +} + +// Optimized Seek for point lookup for an internal key `target` +// target = "seek_user_key @ type | seqno". +// +// For any type other than kTypeValue, kTypeDeletion, kTypeSingleDeletion, +// or kTypeBlobIndex, this function behaves identically as Seek(). +// +// For any type in kTypeValue, kTypeDeletion, kTypeSingleDeletion, +// or kTypeBlobIndex: +// +// If the return value is FALSE, iter location is undefined, and it means: +// 1) there is no key in this block falling into the range: +// ["seek_user_key @ type | seqno", "seek_user_key @ kTypeDeletion | 0"], +// inclusive; AND +// 2) the last key of this block has a greater user_key from seek_user_key +// +// If the return value is TRUE, iter location has two possibilies: +// 1) If iter is valid, it is set to a location as if set by BinarySeek. In +// this case, it points to the first key_ with a larger user_key or a +// matching user_key with a seqno no greater than the seeking seqno. +// 2) If the iter is invalid, it means that either all the user_key is less +// than the seek_user_key, or the block ends with a matching user_key but +// with a smaller [ type | seqno ] (i.e. a larger seqno, or the same seqno +// but larger type). +bool DataBlockIter::SeekForGetImpl(const Slice& target) { + Slice user_key = ExtractUserKey(target); + uint32_t map_offset = restarts_ + num_restarts_ * sizeof(uint32_t); + uint8_t entry = data_block_hash_index_->Lookup(data_, map_offset, user_key); + + if (entry == kCollision) { + // HashSeek not effective, falling back + Seek(target); + return true; + } + + if (entry == kNoEntry) { + // Even if we cannot find the user_key in this block, the result may + // exist in the next block. Consider this exmpale: + // + // Block N: [aab@100, ... , app@120] + // bounary key: axy@50 (we make minimal assumption about a boundary key) + // Block N+1: [axy@10, ... ] + // + // If seek_key = axy@60, the search will starts from Block N. + // Even if the user_key is not found in the hash map, the caller still + // have to conntinue searching the next block. + // + // In this case, we pretend the key is the the last restart interval. + // The while-loop below will search the last restart interval for the + // key. It will stop at the first key that is larger than the seek_key, + // or to the end of the block if no one is larger. + entry = static_cast(num_restarts_ - 1); + } + + uint32_t restart_index = entry; + + // check if the key is in the restart_interval + assert(restart_index < num_restarts_); + SeekToRestartPoint(restart_index); + + const char* limit = nullptr; + if (restart_index_ + 1 < num_restarts_) { + limit = data_ + GetRestartPoint(restart_index_ + 1); + } else { + limit = data_ + restarts_; + } + + while (true) { + // Here we only linear seek the target key inside the restart interval. + // If a key does not exist inside a restart interval, we avoid + // further searching the block content accross restart interval boundary. + // + // TODO(fwu): check the left and write boundary of the restart interval + // to avoid linear seek a target key that is out of range. + if (!ParseNextDataKey(limit) || Compare(key_, target) >= 0) { + // we stop at the first potential matching user key. + break; + } + } + + if (current_ == restarts_) { + // Search reaches to the end of the block. There are three possibilites: + // 1) there is only one user_key match in the block (otherwise collsion). + // the matching user_key resides in the last restart interval, and it + // is the last key of the restart interval and of the block as well. + // ParseNextDataKey() skiped it as its [ type | seqno ] is smaller. + // + // 2) The seek_key is not found in the HashIndex Lookup(), i.e. kNoEntry, + // AND all existing user_keys in the restart interval are smaller than + // seek_user_key. + // + // 3) The seek_key is a false positive and happens to be hashed to the + // last restart interval, AND all existing user_keys in the restart + // interval are smaller than seek_user_key. + // + // The result may exist in the next block each case, so we return true. + return true; + } + + if (user_comparator_->Compare(key_.GetUserKey(), user_key) != 0) { + // the key is not in this block and cannot be at the next block either. + return false; + } + + // Here we are conservative and only support a limited set of cases + ValueType value_type = ExtractValueType(key_.GetKey()); + if (value_type != ValueType::kTypeValue && + value_type != ValueType::kTypeDeletion && + value_type != ValueType::kTypeSingleDeletion && + value_type != ValueType::kTypeBlobIndex) { + Seek(target); + return true; + } + + // Result found, and the iter is correctly set. + return true; +} + +void IndexBlockIter::Seek(const Slice& target) { + Slice seek_key = target; + if (!key_includes_seq_) { + seek_key = ExtractUserKey(target); + } PERF_TIMER_GUARD(block_seek_nanos); if (data_ == nullptr) { // Not init yet return; @@ -144,8 +392,12 @@ void BlockIter::Seek(const Slice& target) { bool ok = false; if (prefix_index_) { ok = PrefixSeek(target, &index); + } else if (value_delta_encoded_) { + ok = BinarySeek(seek_key, 0, num_restarts_ - 1, &index, + comparator_); } else { - ok = BinarySeek(target, 0, num_restarts_ - 1, &index); + ok = BinarySeek(seek_key, 0, num_restarts_ - 1, &index, + comparator_); } if (!ok) { @@ -155,57 +407,85 @@ void BlockIter::Seek(const Slice& target) { // Linear search (within restart block) for first key >= target while (true) { - if (!ParseNextKey() || Compare(key_.GetInternalKey(), target) >= 0) { + if (!ParseNextIndexKey() || Compare(key_, seek_key) >= 0) { return; } } } -void BlockIter::SeekForPrev(const Slice& target) { +void DataBlockIter::SeekForPrev(const Slice& target) { PERF_TIMER_GUARD(block_seek_nanos); + Slice seek_key = target; if (data_ == nullptr) { // Not init yet return; } uint32_t index = 0; - bool ok = false; - ok = BinarySeek(target, 0, num_restarts_ - 1, &index); + bool ok = BinarySeek(seek_key, 0, num_restarts_ - 1, &index, + comparator_); if (!ok) { return; } SeekToRestartPoint(index); - // Linear search (within restart block) for first key >= target + // Linear search (within restart block) for first key >= seek_key - while (ParseNextKey() && Compare(key_.GetInternalKey(), target) < 0) { + while (ParseNextDataKey() && Compare(key_, seek_key) < 0) { } if (!Valid()) { SeekToLast(); } else { - while (Valid() && Compare(key_.GetInternalKey(), target) > 0) { + while (Valid() && Compare(key_, seek_key) > 0) { Prev(); } } } -void BlockIter::SeekToFirst() { +void DataBlockIter::SeekToFirst() { + if (data_ == nullptr) { // Not init yet + return; + } + SeekToRestartPoint(0); + ParseNextDataKey(); +} + +void DataBlockIter::SeekToFirstOrReport() { + if (data_ == nullptr) { // Not init yet + return; + } + SeekToRestartPoint(0); + ParseNextDataKey(); +} + +void IndexBlockIter::SeekToFirst() { if (data_ == nullptr) { // Not init yet return; } SeekToRestartPoint(0); - ParseNextKey(); + ParseNextIndexKey(); +} + +void DataBlockIter::SeekToLast() { + if (data_ == nullptr) { // Not init yet + return; + } + SeekToRestartPoint(num_restarts_ - 1); + while (ParseNextDataKey() && NextEntryOffset() < restarts_) { + // Keep skipping + } } -void BlockIter::SeekToLast() { +void IndexBlockIter::SeekToLast() { if (data_ == nullptr) { // Not init yet return; } SeekToRestartPoint(num_restarts_ - 1); - while (ParseNextKey() && NextEntryOffset() < restarts_) { + while (ParseNextIndexKey() && NextEntryOffset() < restarts_) { // Keep skipping } } -void BlockIter::CorruptionError() { +template +void BlockIter::CorruptionError() { current_ = restarts_; restart_index_ = num_restarts_; status_ = Status::Corruption("bad entry in block"); @@ -213,10 +493,14 @@ void BlockIter::CorruptionError() { value_.clear(); } -bool BlockIter::ParseNextKey() { +template +bool DataBlockIter::ParseNextDataKey(const char* limit) { current_ = NextEntryOffset(); const char* p = data_ + current_; - const char* limit = data_ + restarts_; // Restarts come right after data + if (!limit) { + limit = data_ + restarts_; // Restarts come right after data + } + if (p >= limit) { // No more entries to return. Mark as invalid. current_ = restarts_; @@ -226,7 +510,7 @@ bool BlockIter::ParseNextKey() { // Decode next entry uint32_t shared, non_shared, value_length; - p = DecodeEntry(p, limit, &shared, &non_shared, &value_length); + p = DecodeEntryFunc()(p, limit, &shared, &non_shared, &value_length); if (p == nullptr || key_.Size() < shared) { CorruptionError(); return false; @@ -234,7 +518,7 @@ bool BlockIter::ParseNextKey() { if (shared == 0) { // If this key dont share any bytes with prev key then we dont need // to decode it and can use it's address in the block directly. - key_.SetInternalKey(Slice(p, non_shared), false /* copy */); + key_.SetKey(Slice(p, non_shared), false /* copy */); key_pinned_ = true; } else { // This key share `shared` bytes with prev key, we need to decode it @@ -245,13 +529,14 @@ bool BlockIter::ParseNextKey() { if (global_seqno_ != kDisableGlobalSequenceNumber) { // If we are reading a file with a global sequence number we should // expect that all encoded sequence numbers are zeros and any value - // type is kTypeValue, kTypeMerge or kTypeDeletion + // type is kTypeValue, kTypeMerge, kTypeDeletion, or kTypeRangeDeletion. assert(GetInternalKeySeqno(key_.GetInternalKey()) == 0); - ValueType value_type = ExtractValueType(key_.GetInternalKey()); + ValueType value_type = ExtractValueType(key_.GetKey()); assert(value_type == ValueType::kTypeValue || value_type == ValueType::kTypeMerge || - value_type == ValueType::kTypeDeletion); + value_type == ValueType::kTypeDeletion || + value_type == ValueType::kTypeRangeDeletion); if (key_pinned_) { // TODO(tec): Investigate updating the seqno in the loaded block @@ -267,11 +552,97 @@ bool BlockIter::ParseNextKey() { } value_ = Slice(p + non_shared, value_length); + if (shared == 0) { + while (restart_index_ + 1 < num_restarts_ && + GetRestartPoint(restart_index_ + 1) < current_) { + ++restart_index_; + } + } + // else we are in the middle of a restart interval and the restart_index_ + // thus has not changed + return true; + } +} + +bool IndexBlockIter::ParseNextIndexKey() { + current_ = NextEntryOffset(); + const char* p = data_ + current_; + const char* limit = data_ + restarts_; // Restarts come right after data + if (p >= limit) { + // No more entries to return. Mark as invalid. + current_ = restarts_; + restart_index_ = num_restarts_; + return false; + } + + // Decode next entry + uint32_t shared, non_shared, value_length; + if (value_delta_encoded_) { + p = DecodeKeyV4()(p, limit, &shared, &non_shared); + value_length = 0; + } else { + p = DecodeEntry()(p, limit, &shared, &non_shared, &value_length); + } + if (p == nullptr || key_.Size() < shared) { + CorruptionError(); + return false; + } + if (shared == 0) { + // If this key dont share any bytes with prev key then we dont need + // to decode it and can use it's address in the block directly. + key_.SetKey(Slice(p, non_shared), false /* copy */); + key_pinned_ = true; + } else { + // This key share `shared` bytes with prev key, we need to decode it + key_.TrimAppend(shared, p, non_shared); + key_pinned_ = false; + } + value_ = Slice(p + non_shared, value_length); + if (shared == 0) { while (restart_index_ + 1 < num_restarts_ && GetRestartPoint(restart_index_ + 1) < current_) { ++restart_index_; } - return true; + } + // else we are in the middle of a restart interval and the restart_index_ + // thus has not changed + if (value_delta_encoded_) { + assert(value_length == 0); + DecodeCurrentValue(shared); + } + return true; +} + +// The format: +// restart_point 0: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) +// restart_point 1: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) +// ... +// restart_point n-1: k, v (off, sz), k, v (delta-sz), ..., k, v (delta-sz) +// where, k is key, v is value, and its encoding is in parenthesis. +// The format of each key is (shared_size, non_shared_size, shared, non_shared) +// The format of each value, i.e., block hanlde, is (offset, size) whenever the +// shared_size is 0, which included the first entry in each restart point. +// Otherwise the format is delta-size = block handle size - size of last block +// handle. +void IndexBlockIter::DecodeCurrentValue(uint32_t shared) { + assert(value_delta_encoded_); + const char* limit = data_ + restarts_; + if (shared == 0) { + uint64_t o, s; + const char* newp = GetVarint64Ptr(value_.data(), limit, &o); + assert(newp); + newp = GetVarint64Ptr(newp, limit, &s); + assert(newp); + decoded_value_ = BlockHandle(o, s); + value_ = Slice(value_.data(), newp - value_.data()); + } else { + uint64_t next_value_base = + decoded_value_.offset() + decoded_value_.size() + kBlockTrailerSize; + int64_t delta; + const char* newp = GetVarsignedint64Ptr(value_.data(), limit, &delta); + decoded_value_ = + BlockHandle(next_value_base, decoded_value_.size() + delta); + value_ = Slice(value_.data(), newp - value_.data()); } } @@ -279,22 +650,25 @@ bool BlockIter::ParseNextKey() { // is either the last restart point with a key less than target, // which means the key of next restart point is larger than target, or // the first restart point with a key = target -bool BlockIter::BinarySeek(const Slice& target, uint32_t left, uint32_t right, - uint32_t* index) { +template +template +bool BlockIter::BinarySeek(const Slice& target, uint32_t left, + uint32_t right, uint32_t* index, + const Comparator* comp) { assert(left <= right); while (left < right) { uint32_t mid = (left + right + 1) / 2; uint32_t region_offset = GetRestartPoint(mid); - uint32_t shared, non_shared, value_length; - const char* key_ptr = DecodeEntry(data_ + region_offset, data_ + restarts_, - &shared, &non_shared, &value_length); + uint32_t shared, non_shared; + const char* key_ptr = DecodeKeyFunc()( + data_ + region_offset, data_ + restarts_, &shared, &non_shared); if (key_ptr == nullptr || (shared != 0)) { CorruptionError(); return false; } Slice mid_key(key_ptr, non_shared); - int cmp = Compare(mid_key, target); + int cmp = comp->Compare(mid_key, target); if (cmp < 0) { // Key at "mid" is smaller than "target". Therefore all // blocks before "mid" are uninteresting. @@ -314,11 +688,15 @@ bool BlockIter::BinarySeek(const Slice& target, uint32_t left, uint32_t right, // Compare target key and the block key of the block of `block_index`. // Return -1 if error. -int BlockIter::CompareBlockKey(uint32_t block_index, const Slice& target) { +int IndexBlockIter::CompareBlockKey(uint32_t block_index, const Slice& target) { uint32_t region_offset = GetRestartPoint(block_index); - uint32_t shared, non_shared, value_length; - const char* key_ptr = DecodeEntry(data_ + region_offset, data_ + restarts_, - &shared, &non_shared, &value_length); + uint32_t shared, non_shared; + const char* key_ptr = + value_delta_encoded_ + ? DecodeKeyV4()(data_ + region_offset, data_ + restarts_, &shared, + &non_shared) + : DecodeKey()(data_ + region_offset, data_ + restarts_, &shared, + &non_shared); if (key_ptr == nullptr || (shared != 0)) { CorruptionError(); return 1; // Return target is smaller @@ -329,9 +707,9 @@ int BlockIter::CompareBlockKey(uint32_t block_index, const Slice& target) { // Binary search in block_ids to find the first block // with a key >= target -bool BlockIter::BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids, - uint32_t left, uint32_t right, - uint32_t* index) { +bool IndexBlockIter::BinaryBlockIndexSeek(const Slice& target, + uint32_t* block_ids, uint32_t left, + uint32_t right, uint32_t* index) { assert(left <= right); uint32_t left_bound = left; @@ -379,22 +757,62 @@ bool BlockIter::BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids, } } -bool BlockIter::PrefixSeek(const Slice& target, uint32_t* index) { +bool IndexBlockIter::PrefixSeek(const Slice& target, uint32_t* index) { assert(prefix_index_); + Slice seek_key = target; + if (!key_includes_seq_) { + seek_key = ExtractUserKey(target); + } uint32_t* block_ids = nullptr; uint32_t num_blocks = prefix_index_->GetBlocks(target, &block_ids); if (num_blocks == 0) { current_ = restarts_; return false; - } else { - return BinaryBlockIndexSeek(target, block_ids, 0, num_blocks - 1, index); + } else { + return BinaryBlockIndexSeek(seek_key, block_ids, 0, num_blocks - 1, index); } } uint32_t Block::NumRestarts() const { - assert(size_ >= 2*sizeof(uint32_t)); - return DecodeFixed32(data_ + size_ - sizeof(uint32_t)); + assert(size_ >= 2 * sizeof(uint32_t)); + uint32_t block_footer = DecodeFixed32(data_ + size_ - sizeof(uint32_t)); + uint32_t num_restarts = block_footer; + if (size_ > kMaxBlockSizeSupportedByHashIndex) { + // In BlockBuilder, we have ensured a block with HashIndex is less than + // kMaxBlockSizeSupportedByHashIndex (64KiB). + // + // Therefore, if we encounter a block with a size > 64KiB, the block + // cannot have HashIndex. So the footer will directly interpreted as + // num_restarts. + // + // Such check is for backward compatibility. We can ensure legacy block + // with a vary large num_restarts i.e. >= 0x80000000 can be interpreted + // correctly as no HashIndex even if the MSB of num_restarts is set. + return num_restarts; + } + BlockBasedTableOptions::DataBlockIndexType index_type; + UnPackIndexTypeAndNumRestarts(block_footer, &index_type, &num_restarts); + return num_restarts; +} + +BlockBasedTableOptions::DataBlockIndexType Block::IndexType() const { + assert(size_ >= 2 * sizeof(uint32_t)); + if (size_ > kMaxBlockSizeSupportedByHashIndex) { + // The check is for the same reason as that in NumRestarts() + return BlockBasedTableOptions::kDataBlockBinarySearch; + } + uint32_t block_footer = DecodeFixed32(data_ + size_ - sizeof(uint32_t)); + uint32_t num_restarts = block_footer; + BlockBasedTableOptions::DataBlockIndexType index_type; + UnPackIndexTypeAndNumRestarts(block_footer, &index_type, &num_restarts); + return index_type; +} + +Block::~Block() { + // This sync point can be re-enabled if RocksDB can control the + // initialization order of any/all static options created by the user. + // TEST_SYNC_POINT("Block::~Block"); } Block::Block(BlockContents&& contents, SequenceNumber _global_seqno, @@ -402,16 +820,51 @@ Block::Block(BlockContents&& contents, SequenceNumber _global_seqno, : contents_(std::move(contents)), data_(contents_.data.data()), size_(contents_.data.size()), + restart_offset_(0), + num_restarts_(0), global_seqno_(_global_seqno) { + TEST_SYNC_POINT("Block::Block:0"); if (size_ < sizeof(uint32_t)) { size_ = 0; // Error marker } else { - restart_offset_ = - static_cast(size_) - (1 + NumRestarts()) * sizeof(uint32_t); - if (restart_offset_ > size_ - sizeof(uint32_t)) { - // The size is too small for NumRestarts() and therefore - // restart_offset_ wrapped around. - size_ = 0; + // Should only decode restart points for uncompressed blocks + num_restarts_ = NumRestarts(); + switch (IndexType()) { + case BlockBasedTableOptions::kDataBlockBinarySearch: + restart_offset_ = static_cast(size_) - + (1 + num_restarts_) * sizeof(uint32_t); + if (restart_offset_ > size_ - sizeof(uint32_t)) { + // The size is too small for NumRestarts() and therefore + // restart_offset_ wrapped around. + size_ = 0; + } + break; + case BlockBasedTableOptions::kDataBlockBinaryAndHash: + if (size_ < sizeof(uint32_t) /* block footer */ + + sizeof(uint16_t) /* NUM_BUCK */) { + size_ = 0; + break; + } + + uint16_t map_offset; + data_block_hash_index_.Initialize( + contents.data.data(), + static_cast(contents.data.size() - + sizeof(uint32_t)), /*chop off + NUM_RESTARTS*/ + &map_offset); + + restart_offset_ = map_offset - num_restarts_ * sizeof(uint32_t); + + if (restart_offset_ > map_offset) { + // map_offset is too small for NumRestarts() and + // therefore restart_offset_ wrapped around. + size_ = 0; + break; + } + break; + default: + size_ = 0; // Error marker } } if (read_amp_bytes_per_bit != 0 && statistics && size_ != 0) { @@ -420,37 +873,33 @@ Block::Block(BlockContents&& contents, SequenceNumber _global_seqno, } } -InternalIterator* Block::NewIterator(const Comparator* cmp, BlockIter* iter, - bool total_order_seek, Statistics* stats) { - if (size_ < 2*sizeof(uint32_t)) { - if (iter != nullptr) { - iter->SetStatus(Status::Corruption("bad block contents")); - return iter; - } else { - return NewErrorInternalIterator(Status::Corruption("bad block contents")); - } +template <> +DataBlockIter* Block::NewIterator(const Comparator* cmp, const Comparator* ucmp, + DataBlockIter* iter, Statistics* stats, + bool /*total_order_seek*/, + bool /*key_includes_seq*/, + bool /*value_is_full*/, + bool block_contents_pinned, + BlockPrefixIndex* /*prefix_index*/) { + DataBlockIter* ret_iter; + if (iter != nullptr) { + ret_iter = iter; + } else { + ret_iter = new DataBlockIter; } - const uint32_t num_restarts = NumRestarts(); - if (num_restarts == 0) { - if (iter != nullptr) { - iter->SetStatus(Status::OK()); - return iter; - } else { - return NewEmptyInternalIterator(); - } + if (size_ < 2 * sizeof(uint32_t)) { + ret_iter->Invalidate(Status::Corruption("bad block contents")); + return ret_iter; + } + if (num_restarts_ == 0) { + // Empty block. + ret_iter->Invalidate(Status::OK()); + return ret_iter; } else { - BlockPrefixIndex* prefix_index_ptr = - total_order_seek ? nullptr : prefix_index_.get(); - - if (iter != nullptr) { - iter->Initialize(cmp, data_, restart_offset_, num_restarts, - prefix_index_ptr, global_seqno_, read_amp_bitmap_.get()); - } else { - iter = new BlockIter(cmp, data_, restart_offset_, num_restarts, - prefix_index_ptr, global_seqno_, - read_amp_bitmap_.get()); - } - + ret_iter->Initialize( + cmp, ucmp, data_, restart_offset_, num_restarts_, global_seqno_, + read_amp_bitmap_.get(), block_contents_pinned, + data_block_hash_index_.Valid() ? &data_block_hash_index_ : nullptr); if (read_amp_bitmap_) { if (read_amp_bitmap_->GetStatistics() != stats) { // DB changed the Statistics pointer, we need to notify read_amp_bitmap_ @@ -459,17 +908,51 @@ InternalIterator* Block::NewIterator(const Comparator* cmp, BlockIter* iter, } } - return iter; + return ret_iter; } -void Block::SetBlockPrefixIndex(BlockPrefixIndex* prefix_index) { - prefix_index_.reset(prefix_index); +template <> +IndexBlockIter* Block::NewIterator(const Comparator* cmp, + const Comparator* ucmp, IndexBlockIter* iter, + Statistics* /*stats*/, bool total_order_seek, + bool key_includes_seq, bool value_is_full, + bool block_contents_pinned, + BlockPrefixIndex* prefix_index) { + IndexBlockIter* ret_iter; + if (iter != nullptr) { + ret_iter = iter; + } else { + ret_iter = new IndexBlockIter; + } + if (size_ < 2 * sizeof(uint32_t)) { + ret_iter->Invalidate(Status::Corruption("bad block contents")); + return ret_iter; + } + if (num_restarts_ == 0) { + // Empty block. + ret_iter->Invalidate(Status::OK()); + return ret_iter; + } else { + BlockPrefixIndex* prefix_index_ptr = + total_order_seek ? nullptr : prefix_index; + ret_iter->Initialize(cmp, ucmp, data_, restart_offset_, num_restarts_, + prefix_index_ptr, key_includes_seq, value_is_full, + block_contents_pinned, + nullptr /* data_block_hash_index */); + } + + return ret_iter; } size_t Block::ApproximateMemoryUsage() const { size_t usage = usable_size(); - if (prefix_index_) { - usage += prefix_index_->ApproximateMemoryUsage(); +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + usage += malloc_usable_size((void*)this); +#else + usage += sizeof(*this); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + if (read_amp_bitmap_) { + usage += read_amp_bitmap_->ApproximateMemoryUsage(); } return usage; } diff --git a/thirdparty/rocksdb/table/block.h b/thirdparty/rocksdb/table/block.h index 59dc167433..737874abdf 100644 --- a/thirdparty/rocksdb/table/block.h +++ b/thirdparty/rocksdb/table/block.h @@ -22,20 +22,25 @@ #include "db/dbformat.h" #include "db/pinned_iterators_manager.h" +#include "format.h" #include "rocksdb/iterator.h" #include "rocksdb/options.h" #include "rocksdb/statistics.h" +#include "rocksdb/table.h" #include "table/block_prefix_index.h" +#include "table/data_block_hash_index.h" #include "table/internal_iterator.h" #include "util/random.h" #include "util/sync_point.h" -#include "format.h" namespace rocksdb { struct BlockContents; class Comparator; +template class BlockIter; +class DataBlockIter; +class IndexBlockIter; class BlockPrefixIndex; // BlockReadAmpBitmap is a bitmap that map the rocksdb::Block data bytes to @@ -48,8 +53,8 @@ class BlockReadAmpBitmap { : bitmap_(nullptr), bytes_per_bit_pow_(0), statistics_(statistics), - rnd_( - Random::GetTLSInstance()->Uniform(static_cast(bytes_per_bit))) { + rnd_(Random::GetTLSInstance()->Uniform( + static_cast(bytes_per_bit))) { TEST_SYNC_POINT_CALLBACK("BlockReadAmpBitmap:rnd", &rnd_); assert(block_size > 0 && bytes_per_bit > 0); @@ -59,8 +64,7 @@ class BlockReadAmpBitmap { } // num_bits_needed = ceil(block_size / bytes_per_bit) - size_t num_bits_needed = - ((block_size - 1) >> bytes_per_bit_pow_) + 1; + size_t num_bits_needed = ((block_size - 1) >> bytes_per_bit_pow_) + 1; assert(num_bits_needed > 0); // bitmap_size = ceil(num_bits_needed / kBitsPerEntry) @@ -104,6 +108,13 @@ class BlockReadAmpBitmap { uint32_t GetBytesPerBit() { return 1 << bytes_per_bit_pow_; } + size_t ApproximateMemoryUsage() const { +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + return malloc_usable_size((void*)this); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + return sizeof(*this); + } + private: // Get the current value of bit at `bit_idx` and set it to 1 inline bool GetAndSet(uint32_t bit_idx) { @@ -137,42 +148,53 @@ class Block { size_t read_amp_bytes_per_bit = 0, Statistics* statistics = nullptr); - ~Block() = default; + ~Block(); size_t size() const { return size_; } const char* data() const { return data_; } - bool cachable() const { return contents_.cachable; } - size_t usable_size() const { -#ifdef ROCKSDB_MALLOC_USABLE_SIZE - if (contents_.allocation.get() != nullptr) { - return malloc_usable_size(contents_.allocation.get()); - } -#endif // ROCKSDB_MALLOC_USABLE_SIZE - return size_; - } + // The additional memory space taken by the block data. + size_t usable_size() const { return contents_.usable_size(); } uint32_t NumRestarts() const; - CompressionType compression_type() const { - return contents_.compression_type; - } + bool own_bytes() const { return contents_.own_bytes(); } - // If hash index lookup is enabled and `use_hash_index` is true. This block - // will do hash lookup for the key prefix. - // - // NOTE: for the hash based lookup, if a key prefix doesn't match any key, - // the iterator will simply be set as "invalid", rather than returning - // the key that is just pass the target key. + BlockBasedTableOptions::DataBlockIndexType IndexType() const; + + // If comparator is InternalKeyComparator, user_comparator is its user + // comparator; they are equal otherwise. // // If iter is null, return new Iterator // If iter is not null, update this one and return it as Iterator* // - // If total_order_seek is true, hash_index_ and prefix_index_ are ignored. - // This option only applies for index block. For data block, hash_index_ - // and prefix_index_ are null, so this option does not matter. - InternalIterator* NewIterator(const Comparator* comparator, - BlockIter* iter = nullptr, - bool total_order_seek = true, - Statistics* stats = nullptr); - void SetBlockPrefixIndex(BlockPrefixIndex* prefix_index); + // key_includes_seq, default true, means that the keys are in internal key + // format. + // value_is_full, default true, means that no delta encoding is + // applied to values. + // + // NewIterator + // Same as above but also updates read_amp_bitmap_ if it is not nullptr. + // + // NewIterator + // If `prefix_index` is not nullptr this block will do hash lookup for the key + // prefix. If total_order_seek is true, prefix_index_ is ignored. + // + // If `block_contents_pinned` is true, the caller will guarantee that when + // the cleanup functions are transferred from the iterator to other + // classes, e.g. PinnableSlice, the pointer to the bytes will still be + // valid. Either the iterator holds cache handle or ownership of some resource + // and release them in a release function, or caller is sure that the data + // will not go away (for example, it's from mmapped file which will not be + // closed). + // + // NOTE: for the hash based lookup, if a key prefix doesn't match any key, + // the iterator will simply be set as "invalid", rather than returning + // the key that is just pass the target key. + template + TBlockIter* NewIterator( + const Comparator* comparator, const Comparator* user_comparator, + TBlockIter* iter = nullptr, Statistics* stats = nullptr, + bool total_order_seek = true, bool key_includes_seq = true, + bool value_is_full = true, bool block_contents_pinned = false, + BlockPrefixIndex* prefix_index = nullptr); // Report an approximation of how much memory has been used. size_t ApproximateMemoryUsage() const; @@ -181,50 +203,30 @@ class Block { private: BlockContents contents_; - const char* data_; // contents_.data.data() - size_t size_; // contents_.data.size() - uint32_t restart_offset_; // Offset in data_ of restart array - std::unique_ptr prefix_index_; + const char* data_; // contents_.data.data() + size_t size_; // contents_.data.size() + uint32_t restart_offset_; // Offset in data_ of restart array + uint32_t num_restarts_; std::unique_ptr read_amp_bitmap_; // All keys in the block will have seqno = global_seqno_, regardless of // the encoded value (kDisableGlobalSequenceNumber means disabled) const SequenceNumber global_seqno_; + DataBlockHashIndex data_block_hash_index_; + // No copying allowed - Block(const Block&); - void operator=(const Block&); + Block(const Block&) = delete; + void operator=(const Block&) = delete; }; -class BlockIter : public InternalIterator { +template +class BlockIter : public InternalIteratorBase { public: - BlockIter() - : comparator_(nullptr), - data_(nullptr), - restarts_(0), - num_restarts_(0), - current_(0), - restart_index_(0), - status_(Status::OK()), - prefix_index_(nullptr), - key_pinned_(false), - global_seqno_(kDisableGlobalSequenceNumber), - read_amp_bitmap_(nullptr), - last_bitmap_offset_(0) {} - - BlockIter(const Comparator* comparator, const char* data, uint32_t restarts, - uint32_t num_restarts, BlockPrefixIndex* prefix_index, - SequenceNumber global_seqno, BlockReadAmpBitmap* read_amp_bitmap) - : BlockIter() { - Initialize(comparator, data, restarts, num_restarts, prefix_index, - global_seqno, read_amp_bitmap); - } - - void Initialize(const Comparator* comparator, const char* data, - uint32_t restarts, uint32_t num_restarts, - BlockPrefixIndex* prefix_index, SequenceNumber global_seqno, - BlockReadAmpBitmap* read_amp_bitmap) { - assert(data_ == nullptr); // Ensure it is called only once - assert(num_restarts > 0); // Ensure the param is valid + void InitializeBase(const Comparator* comparator, const char* data, + uint32_t restarts, uint32_t num_restarts, + SequenceNumber global_seqno, bool block_contents_pinned) { + assert(data_ == nullptr); // Ensure it is called only once + assert(num_restarts > 0); // Ensure the param is valid comparator_ = comparator; data_ = data; @@ -232,47 +234,34 @@ class BlockIter : public InternalIterator { num_restarts_ = num_restarts; current_ = restarts_; restart_index_ = num_restarts_; - prefix_index_ = prefix_index; global_seqno_ = global_seqno; - read_amp_bitmap_ = read_amp_bitmap; - last_bitmap_offset_ = current_ + 1; + block_contents_pinned_ = block_contents_pinned; } - void SetStatus(Status s) { + // Makes Valid() return false, status() return `s`, and Seek()/Prev()/etc do + // nothing. Calls cleanup functions. + void InvalidateBase(Status s) { + // Assert that the BlockIter is never deleted while Pinning is Enabled. + assert(!pinned_iters_mgr_ || + (pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled())); + + data_ = nullptr; + current_ = restarts_; status_ = s; + + // Call cleanup callbacks. + Cleanable::Reset(); } virtual bool Valid() const override { return current_ < restarts_; } virtual Status status() const override { return status_; } virtual Slice key() const override { assert(Valid()); - return key_.GetInternalKey(); - } - virtual Slice value() const override { - assert(Valid()); - if (read_amp_bitmap_ && current_ < restarts_ && - current_ != last_bitmap_offset_) { - read_amp_bitmap_->Mark(current_ /* current entry offset */, - NextEntryOffset() - 1); - last_bitmap_offset_ = current_; - } - return value_; + return key_.GetKey(); } - virtual void Next() override; - - virtual void Prev() override; - - virtual void Seek(const Slice& target) override; - - virtual void SeekForPrev(const Slice& target) override; - - virtual void SeekToFirst() override; - - virtual void SeekToLast() override; - #ifndef NDEBUG - ~BlockIter() { + virtual ~BlockIter() { // Assert that the BlockIter is never deleted while Pinning is Enabled. assert(!pinned_iters_mgr_ || (pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled())); @@ -284,9 +273,11 @@ class BlockIter : public InternalIterator { PinnedIteratorsManager* pinned_iters_mgr_ = nullptr; #endif - virtual bool IsKeyPinned() const override { return key_pinned_; } + virtual bool IsKeyPinned() const override { + return block_contents_pinned_ && key_pinned_; + } - virtual bool IsValuePinned() const override { return true; } + virtual bool IsValuePinned() const override { return block_contents_pinned_; } size_t TEST_CurrentEntrySize() { return NextEntryOffset() - current_; } @@ -294,27 +285,142 @@ class BlockIter : public InternalIterator { return static_cast(value_.data() - data_); } - private: + protected: + // Note: The type could be changed to InternalKeyComparator but we see a weird + // performance drop by that. const Comparator* comparator_; const char* data_; // underlying block contents - uint32_t restarts_; // Offset of restart array (list of fixed32) uint32_t num_restarts_; // Number of uint32_t entries in restart array + // Index of restart block in which current_ or current_-1 falls + uint32_t restart_index_; + uint32_t restarts_; // Offset of restart array (list of fixed32) // current_ is offset in data_ of current entry. >= restarts_ if !Valid uint32_t current_; - uint32_t restart_index_; // Index of restart block in which current_ falls IterKey key_; Slice value_; Status status_; - BlockPrefixIndex* prefix_index_; bool key_pinned_; + // Whether the block data is guaranteed to outlive this iterator, and + // as long as the cleanup functions are transferred to another class, + // e.g. PinnableSlice, the pointer to the bytes will still be valid. + bool block_contents_pinned_; SequenceNumber global_seqno_; + public: + // Return the offset in data_ just past the end of the current entry. + inline uint32_t NextEntryOffset() const { + // NOTE: We don't support blocks bigger than 2GB + return static_cast((value_.data() + value_.size()) - data_); + } + + uint32_t GetRestartPoint(uint32_t index) { + assert(index < num_restarts_); + return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t)); + } + + void SeekToRestartPoint(uint32_t index) { + key_.Clear(); + restart_index_ = index; + // current_ will be fixed by ParseNextKey(); + + // ParseNextKey() starts at the end of value_, so set value_ accordingly + uint32_t offset = GetRestartPoint(index); + value_ = Slice(data_ + offset, 0); + } + + void CorruptionError(); + + template + inline bool BinarySeek(const Slice& target, uint32_t left, uint32_t right, + uint32_t* index, const Comparator* comp); +}; + +class DataBlockIter final : public BlockIter { + public: + DataBlockIter() + : BlockIter(), read_amp_bitmap_(nullptr), last_bitmap_offset_(0) {} + DataBlockIter(const Comparator* comparator, const Comparator* user_comparator, + const char* data, uint32_t restarts, uint32_t num_restarts, + SequenceNumber global_seqno, + BlockReadAmpBitmap* read_amp_bitmap, bool block_contents_pinned, + DataBlockHashIndex* data_block_hash_index) + : DataBlockIter() { + Initialize(comparator, user_comparator, data, restarts, num_restarts, + global_seqno, read_amp_bitmap, block_contents_pinned, + data_block_hash_index); + } + void Initialize(const Comparator* comparator, + const Comparator* user_comparator, const char* data, + uint32_t restarts, uint32_t num_restarts, + SequenceNumber global_seqno, + BlockReadAmpBitmap* read_amp_bitmap, + bool block_contents_pinned, + DataBlockHashIndex* data_block_hash_index) { + InitializeBase(comparator, data, restarts, num_restarts, global_seqno, + block_contents_pinned); + user_comparator_ = user_comparator; + key_.SetIsUserKey(false); + read_amp_bitmap_ = read_amp_bitmap; + last_bitmap_offset_ = current_ + 1; + data_block_hash_index_ = data_block_hash_index; + } + + virtual Slice value() const override { + assert(Valid()); + if (read_amp_bitmap_ && current_ < restarts_ && + current_ != last_bitmap_offset_) { + read_amp_bitmap_->Mark(current_ /* current entry offset */, + NextEntryOffset() - 1); + last_bitmap_offset_ = current_; + } + return value_; + } + + virtual void Seek(const Slice& target) override; + + inline bool SeekForGet(const Slice& target) { + if (!data_block_hash_index_) { + Seek(target); + return true; + } + + return SeekForGetImpl(target); + } + + virtual void SeekForPrev(const Slice& target) override; + + virtual void Prev() override; + + virtual void Next() override; + + // Try to advance to the next entry in the block. If there is data corruption + // or error, report it to the caller instead of aborting the process. May + // incur higher CPU overhead because we need to perform check on every entry. + void NextOrReport(); + + virtual void SeekToFirst() override; + + // Try to seek to the first entry in the block. If there is data corruption + // or error, report it to caller instead of aborting the process. May incur + // higher CPU overhead because we need to perform check on every entry. + void SeekToFirstOrReport(); + + virtual void SeekToLast() override; + + void Invalidate(Status s) { + InvalidateBase(s); + // Clear prev entries cache. + prev_entries_keys_buff_.clear(); + prev_entries_.clear(); + prev_entries_idx_ = -1; + } + + private: // read-amp bitmap BlockReadAmpBitmap* read_amp_bitmap_; // last `current_` value we report to read-amp bitmp mutable uint32_t last_bitmap_offset_; - struct CachedPrevEntry { explicit CachedPrevEntry(uint32_t _offset, const char* _key_ptr, size_t _key_offset, size_t _key_size, Slice _value) @@ -339,46 +445,124 @@ class BlockIter : public InternalIterator { std::vector prev_entries_; int32_t prev_entries_idx_ = -1; - inline int Compare(const Slice& a, const Slice& b) const { - return comparator_->Compare(a, b); + DataBlockHashIndex* data_block_hash_index_; + const Comparator* user_comparator_; + + template + inline bool ParseNextDataKey(const char* limit = nullptr); + + inline int Compare(const IterKey& ikey, const Slice& b) const { + return comparator_->Compare(ikey.GetInternalKey(), b); } - // Return the offset in data_ just past the end of the current entry. - inline uint32_t NextEntryOffset() const { - // NOTE: We don't support blocks bigger than 2GB - return static_cast((value_.data() + value_.size()) - data_); + bool SeekForGetImpl(const Slice& target); +}; + +class IndexBlockIter final : public BlockIter { + public: + IndexBlockIter() : BlockIter(), prefix_index_(nullptr) {} + + virtual Slice key() const override { + assert(Valid()); + return key_.GetKey(); + } + // key_includes_seq, default true, means that the keys are in internal key + // format. + // value_is_full, default true, means that no delta encoding is + // applied to values. + IndexBlockIter(const Comparator* comparator, + const Comparator* user_comparator, const char* data, + uint32_t restarts, uint32_t num_restarts, + BlockPrefixIndex* prefix_index, bool key_includes_seq, + bool value_is_full, bool block_contents_pinned) + : IndexBlockIter() { + Initialize(comparator, user_comparator, data, restarts, num_restarts, + prefix_index, key_includes_seq, block_contents_pinned, + value_is_full, nullptr /* data_block_hash_index */); } - uint32_t GetRestartPoint(uint32_t index) { - assert(index < num_restarts_); - return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t)); + void Initialize(const Comparator* comparator, + const Comparator* user_comparator, const char* data, + uint32_t restarts, uint32_t num_restarts, + BlockPrefixIndex* prefix_index, bool key_includes_seq, + bool value_is_full, bool block_contents_pinned, + DataBlockHashIndex* /*data_block_hash_index*/) { + InitializeBase(key_includes_seq ? comparator : user_comparator, data, + restarts, num_restarts, kDisableGlobalSequenceNumber, + block_contents_pinned); + key_includes_seq_ = key_includes_seq; + key_.SetIsUserKey(!key_includes_seq_); + prefix_index_ = prefix_index; + value_delta_encoded_ = !value_is_full; } - void SeekToRestartPoint(uint32_t index) { - key_.Clear(); - restart_index_ = index; - // current_ will be fixed by ParseNextKey(); + virtual BlockHandle value() const override { + assert(Valid()); + if (value_delta_encoded_) { + return decoded_value_; + } else { + BlockHandle handle; + Slice v = value_; + Status decode_s __attribute__((__unused__)) = handle.DecodeFrom(&v); + assert(decode_s.ok()); + return handle; + } + } - // ParseNextKey() starts at the end of value_, so set value_ accordingly - uint32_t offset = GetRestartPoint(index); - value_ = Slice(data_ + offset, 0); + virtual void Seek(const Slice& target) override; + + virtual void SeekForPrev(const Slice&) override { + assert(false); + current_ = restarts_; + restart_index_ = num_restarts_; + status_ = Status::InvalidArgument( + "RocksDB internal error: should never call SeekForPrev() on index " + "blocks"); + key_.Clear(); + value_.clear(); } - void CorruptionError(); + virtual void Prev() override; - bool ParseNextKey(); + virtual void Next() override; - bool BinarySeek(const Slice& target, uint32_t left, uint32_t right, - uint32_t* index); + virtual void SeekToFirst() override; - int CompareBlockKey(uint32_t block_index, const Slice& target); + virtual void SeekToLast() override; - bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids, - uint32_t left, uint32_t right, - uint32_t* index); + void Invalidate(Status s) { InvalidateBase(s); } + + private: + // Key is in InternalKey format + bool key_includes_seq_; + bool value_delta_encoded_; + BlockPrefixIndex* prefix_index_; + // Whether the value is delta encoded. In that case the value is assumed to be + // BlockHandle. The first value in each restart interval is the full encoded + // BlockHandle; the restart of encoded size part of the BlockHandle. The + // offset of delta encoded BlockHandles is computed by adding the size of + // previous delta encoded values in the same restart interval to the offset of + // the first value in that restart interval. + BlockHandle decoded_value_; bool PrefixSeek(const Slice& target, uint32_t* index); + bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids, + uint32_t left, uint32_t right, uint32_t* index); + inline int CompareBlockKey(uint32_t block_index, const Slice& target); + + inline int Compare(const Slice& a, const Slice& b) const { + return comparator_->Compare(a, b); + } + + inline int Compare(const IterKey& ikey, const Slice& b) const { + return comparator_->Compare(ikey.GetKey(), b); + } + + inline bool ParseNextIndexKey(); + // When value_delta_encoded_ is enabled it decodes the value which is assumed + // to be BlockHandle and put it to decoded_value_ + inline void DecodeCurrentValue(uint32_t shared); }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_based_filter_block.cc b/thirdparty/rocksdb/table/block_based_filter_block.cc index 697c11a42f..81087b243b 100644 --- a/thirdparty/rocksdb/table/block_based_filter_block.cc +++ b/thirdparty/rocksdb/table/block_based_filter_block.cc @@ -53,7 +53,6 @@ void AppendItem(std::string* props, const TKey& key, const std::string& value) { } } // namespace - // See doc/table_format.txt for an explanation of the filter block format. // Generate new filter every 2KB of data @@ -67,7 +66,8 @@ BlockBasedFilterBlockBuilder::BlockBasedFilterBlockBuilder( prefix_extractor_(prefix_extractor), whole_key_filtering_(table_opt.whole_key_filtering), prev_prefix_start_(0), - prev_prefix_size_(0) { + prev_prefix_size_(0), + num_added_(0) { assert(policy_); } @@ -91,6 +91,7 @@ void BlockBasedFilterBlockBuilder::Add(const Slice& key) { // Add key to filter if needed inline void BlockBasedFilterBlockBuilder::AddKey(const Slice& key) { + num_added_++; start_.push_back(entries_.size()); entries_.append(key.data(), key.size()); } @@ -106,14 +107,13 @@ inline void BlockBasedFilterBlockBuilder::AddPrefix(const Slice& key) { Slice prefix = prefix_extractor_->Transform(key); // insert prefix only when it's different from the previous prefix. if (prev.size() == 0 || prefix != prev) { - start_.push_back(entries_.size()); prev_prefix_start_ = entries_.size(); prev_prefix_size_ = prefix.size(); - entries_.append(prefix.data(), prefix.size()); + AddKey(prefix); } } -Slice BlockBasedFilterBlockBuilder::Finish(const BlockHandle& tmp, +Slice BlockBasedFilterBlockBuilder::Finish(const BlockHandle& /*tmp*/, Status* status) { // In this impl we ignore BlockHandle *status = Status::OK(); @@ -185,8 +185,9 @@ BlockBasedFilterBlockReader::BlockBasedFilterBlockReader( } bool BlockBasedFilterBlockReader::KeyMayMatch( - const Slice& key, uint64_t block_offset, const bool no_io, - const Slice* const const_ikey_ptr) { + const Slice& key, const SliceTransform* /* prefix_extractor */, + uint64_t block_offset, const bool /*no_io*/, + const Slice* const /*const_ikey_ptr*/) { assert(block_offset != kNotValid); if (!whole_key_filtering_) { return true; @@ -195,12 +196,10 @@ bool BlockBasedFilterBlockReader::KeyMayMatch( } bool BlockBasedFilterBlockReader::PrefixMayMatch( - const Slice& prefix, uint64_t block_offset, const bool no_io, - const Slice* const const_ikey_ptr) { + const Slice& prefix, const SliceTransform* /* prefix_extractor */, + uint64_t block_offset, const bool /*no_io*/, + const Slice* const /*const_ikey_ptr*/) { assert(block_offset != kNotValid); - if (!prefix_extractor_) { - return true; - } return MayMatch(prefix, block_offset); } @@ -233,7 +232,7 @@ size_t BlockBasedFilterBlockReader::ApproximateMemoryUsage() const { } std::string BlockBasedFilterBlockReader::ToString() const { - std::string result, filter_meta; + std::string result; result.reserve(1024); std::string s_bo("Block offset"), s_hd("Hex dump"), s_fb("# filter blocks"); diff --git a/thirdparty/rocksdb/table/block_based_filter_block.h b/thirdparty/rocksdb/table/block_based_filter_block.h index 52b79fea50..d1ff585462 100644 --- a/thirdparty/rocksdb/table/block_based_filter_block.h +++ b/thirdparty/rocksdb/table/block_based_filter_block.h @@ -15,8 +15,8 @@ #include #include -#include #include +#include #include #include "rocksdb/options.h" #include "rocksdb/slice.h" @@ -26,7 +26,6 @@ namespace rocksdb { - // A BlockBasedFilterBlockBuilder is used to construct all of the filters for a // particular Table. It generates a single string which is stored as // a special block in the Table. @@ -36,11 +35,12 @@ namespace rocksdb { class BlockBasedFilterBlockBuilder : public FilterBlockBuilder { public: BlockBasedFilterBlockBuilder(const SliceTransform* prefix_extractor, - const BlockBasedTableOptions& table_opt); + const BlockBasedTableOptions& table_opt); virtual bool IsBlockBased() override { return true; } virtual void StartBlock(uint64_t block_offset) override; virtual void Add(const Slice& key) override; + virtual size_t NumAdded() const override { return num_added_; } virtual Slice Finish(const BlockHandle& tmp, Status* status) override; using FilterBlockBuilder::Finish; @@ -65,6 +65,7 @@ class BlockBasedFilterBlockBuilder : public FilterBlockBuilder { std::string result_; // Filter data computed so far std::vector tmp_entries_; // policy_->CreateFilter() argument std::vector filter_offsets_; + size_t num_added_; // Number of keys added // No copying allowed BlockBasedFilterBlockBuilder(const BlockBasedFilterBlockBuilder&); @@ -81,13 +82,14 @@ class BlockBasedFilterBlockReader : public FilterBlockReader { bool whole_key_filtering, BlockContents&& contents, Statistics* statistics); virtual bool IsBlockBased() override { return true; } + virtual bool KeyMayMatch( - const Slice& key, uint64_t block_offset = kNotValid, - const bool no_io = false, + const Slice& key, const SliceTransform* prefix_extractor, + uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) override; virtual bool PrefixMayMatch( - const Slice& prefix, uint64_t block_offset = kNotValid, - const bool no_io = false, + const Slice& prefix, const SliceTransform* prefix_extractor, + uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) override; virtual size_t ApproximateMemoryUsage() const override; diff --git a/thirdparty/rocksdb/table/block_based_filter_block_test.cc b/thirdparty/rocksdb/table/block_based_filter_block_test.cc index f666ba2524..6b352b2f6b 100644 --- a/thirdparty/rocksdb/table/block_based_filter_block_test.cc +++ b/thirdparty/rocksdb/table/block_based_filter_block_test.cc @@ -21,18 +21,16 @@ namespace rocksdb { // For testing: emit an array with one hash value per key class TestHashFilter : public FilterPolicy { public: - virtual const char* Name() const override { return "TestHashFilter"; } + const char* Name() const override { return "TestHashFilter"; } - virtual void CreateFilter(const Slice* keys, int n, - std::string* dst) const override { + void CreateFilter(const Slice* keys, int n, std::string* dst) const override { for (int i = 0; i < n; i++) { uint32_t h = Hash(keys[i].data(), keys[i].size(), 1); PutFixed32(dst, h); } } - virtual bool KeyMayMatch(const Slice& key, - const Slice& filter) const override { + bool KeyMayMatch(const Slice& key, const Slice& filter) const override { uint32_t h = Hash(key.data(), key.size(), 1); for (unsigned int i = 0; i + 4 <= filter.size(); i += 4) { if (h == DecodeFixed32(filter.data() + i)) { @@ -55,16 +53,17 @@ class FilterBlockTest : public testing::Test { TEST_F(FilterBlockTest, EmptyBuilder) { BlockBasedFilterBlockBuilder builder(nullptr, table_options_); - BlockContents block(builder.Finish(), false, kNoCompression); + BlockContents block(builder.Finish()); ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(block.data)); BlockBasedFilterBlockReader reader(nullptr, table_options_, true, std::move(block), nullptr); - ASSERT_TRUE(reader.KeyMayMatch("foo", 0)); - ASSERT_TRUE(reader.KeyMayMatch("foo", 100000)); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr, uint64_t{0})); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr, 100000)); } TEST_F(FilterBlockTest, SingleChunk) { BlockBasedFilterBlockBuilder builder(nullptr, table_options_); + ASSERT_EQ(0, builder.NumAdded()); builder.StartBlock(100); builder.Add("foo"); builder.Add("bar"); @@ -73,16 +72,17 @@ TEST_F(FilterBlockTest, SingleChunk) { builder.Add("box"); builder.StartBlock(300); builder.Add("hello"); - BlockContents block(builder.Finish(), false, kNoCompression); + ASSERT_EQ(5, builder.NumAdded()); + BlockContents block(builder.Finish()); BlockBasedFilterBlockReader reader(nullptr, table_options_, true, std::move(block), nullptr); - ASSERT_TRUE(reader.KeyMayMatch("foo", 100)); - ASSERT_TRUE(reader.KeyMayMatch("bar", 100)); - ASSERT_TRUE(reader.KeyMayMatch("box", 100)); - ASSERT_TRUE(reader.KeyMayMatch("hello", 100)); - ASSERT_TRUE(reader.KeyMayMatch("foo", 100)); - ASSERT_TRUE(!reader.KeyMayMatch("missing", 100)); - ASSERT_TRUE(!reader.KeyMayMatch("other", 100)); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr, 100)); + ASSERT_TRUE(reader.KeyMayMatch("bar", nullptr, 100)); + ASSERT_TRUE(reader.KeyMayMatch("box", nullptr, 100)); + ASSERT_TRUE(reader.KeyMayMatch("hello", nullptr, 100)); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr, 100)); + ASSERT_TRUE(!reader.KeyMayMatch("missing", nullptr, 100)); + ASSERT_TRUE(!reader.KeyMayMatch("other", nullptr, 100)); } TEST_F(FilterBlockTest, MultiChunk) { @@ -105,33 +105,33 @@ TEST_F(FilterBlockTest, MultiChunk) { builder.Add("box"); builder.Add("hello"); - BlockContents block(builder.Finish(), false, kNoCompression); + BlockContents block(builder.Finish()); BlockBasedFilterBlockReader reader(nullptr, table_options_, true, std::move(block), nullptr); // Check first filter - ASSERT_TRUE(reader.KeyMayMatch("foo", 0)); - ASSERT_TRUE(reader.KeyMayMatch("bar", 2000)); - ASSERT_TRUE(!reader.KeyMayMatch("box", 0)); - ASSERT_TRUE(!reader.KeyMayMatch("hello", 0)); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr, uint64_t{0})); + ASSERT_TRUE(reader.KeyMayMatch("bar", nullptr, 2000)); + ASSERT_TRUE(!reader.KeyMayMatch("box", nullptr, uint64_t{0})); + ASSERT_TRUE(!reader.KeyMayMatch("hello", nullptr, uint64_t{0})); // Check second filter - ASSERT_TRUE(reader.KeyMayMatch("box", 3100)); - ASSERT_TRUE(!reader.KeyMayMatch("foo", 3100)); - ASSERT_TRUE(!reader.KeyMayMatch("bar", 3100)); - ASSERT_TRUE(!reader.KeyMayMatch("hello", 3100)); + ASSERT_TRUE(reader.KeyMayMatch("box", nullptr, 3100)); + ASSERT_TRUE(!reader.KeyMayMatch("foo", nullptr, 3100)); + ASSERT_TRUE(!reader.KeyMayMatch("bar", nullptr, 3100)); + ASSERT_TRUE(!reader.KeyMayMatch("hello", nullptr, 3100)); // Check third filter (empty) - ASSERT_TRUE(!reader.KeyMayMatch("foo", 4100)); - ASSERT_TRUE(!reader.KeyMayMatch("bar", 4100)); - ASSERT_TRUE(!reader.KeyMayMatch("box", 4100)); - ASSERT_TRUE(!reader.KeyMayMatch("hello", 4100)); + ASSERT_TRUE(!reader.KeyMayMatch("foo", nullptr, 4100)); + ASSERT_TRUE(!reader.KeyMayMatch("bar", nullptr, 4100)); + ASSERT_TRUE(!reader.KeyMayMatch("box", nullptr, 4100)); + ASSERT_TRUE(!reader.KeyMayMatch("hello", nullptr, 4100)); // Check last filter - ASSERT_TRUE(reader.KeyMayMatch("box", 9000)); - ASSERT_TRUE(reader.KeyMayMatch("hello", 9000)); - ASSERT_TRUE(!reader.KeyMayMatch("foo", 9000)); - ASSERT_TRUE(!reader.KeyMayMatch("bar", 9000)); + ASSERT_TRUE(reader.KeyMayMatch("box", nullptr, 9000)); + ASSERT_TRUE(reader.KeyMayMatch("hello", nullptr, 9000)); + ASSERT_TRUE(!reader.KeyMayMatch("foo", nullptr, 9000)); + ASSERT_TRUE(!reader.KeyMayMatch("bar", nullptr, 9000)); } // Test for block based filter block @@ -144,26 +144,26 @@ class BlockBasedFilterBlockTest : public testing::Test { table_options_.filter_policy.reset(NewBloomFilterPolicy(10)); } - ~BlockBasedFilterBlockTest() {} + ~BlockBasedFilterBlockTest() override {} }; TEST_F(BlockBasedFilterBlockTest, BlockBasedEmptyBuilder) { - FilterBlockBuilder* builder = new BlockBasedFilterBlockBuilder( - nullptr, table_options_); - BlockContents block(builder->Finish(), false, kNoCompression); + FilterBlockBuilder* builder = + new BlockBasedFilterBlockBuilder(nullptr, table_options_); + BlockContents block(builder->Finish()); ASSERT_EQ("\\x00\\x00\\x00\\x00\\x0b", EscapeString(block.data)); FilterBlockReader* reader = new BlockBasedFilterBlockReader( nullptr, table_options_, true, std::move(block), nullptr); - ASSERT_TRUE(reader->KeyMayMatch("foo", 0)); - ASSERT_TRUE(reader->KeyMayMatch("foo", 100000)); + ASSERT_TRUE(reader->KeyMayMatch("foo", nullptr, uint64_t{0})); + ASSERT_TRUE(reader->KeyMayMatch("foo", nullptr, 100000)); delete builder; delete reader; } TEST_F(BlockBasedFilterBlockTest, BlockBasedSingleChunk) { - FilterBlockBuilder* builder = new BlockBasedFilterBlockBuilder( - nullptr, table_options_); + FilterBlockBuilder* builder = + new BlockBasedFilterBlockBuilder(nullptr, table_options_); builder->StartBlock(100); builder->Add("foo"); builder->Add("bar"); @@ -172,24 +172,24 @@ TEST_F(BlockBasedFilterBlockTest, BlockBasedSingleChunk) { builder->Add("box"); builder->StartBlock(300); builder->Add("hello"); - BlockContents block(builder->Finish(), false, kNoCompression); + BlockContents block(builder->Finish()); FilterBlockReader* reader = new BlockBasedFilterBlockReader( nullptr, table_options_, true, std::move(block), nullptr); - ASSERT_TRUE(reader->KeyMayMatch("foo", 100)); - ASSERT_TRUE(reader->KeyMayMatch("bar", 100)); - ASSERT_TRUE(reader->KeyMayMatch("box", 100)); - ASSERT_TRUE(reader->KeyMayMatch("hello", 100)); - ASSERT_TRUE(reader->KeyMayMatch("foo", 100)); - ASSERT_TRUE(!reader->KeyMayMatch("missing", 100)); - ASSERT_TRUE(!reader->KeyMayMatch("other", 100)); + ASSERT_TRUE(reader->KeyMayMatch("foo", nullptr, 100)); + ASSERT_TRUE(reader->KeyMayMatch("bar", nullptr, 100)); + ASSERT_TRUE(reader->KeyMayMatch("box", nullptr, 100)); + ASSERT_TRUE(reader->KeyMayMatch("hello", nullptr, 100)); + ASSERT_TRUE(reader->KeyMayMatch("foo", nullptr, 100)); + ASSERT_TRUE(!reader->KeyMayMatch("missing", nullptr, 100)); + ASSERT_TRUE(!reader->KeyMayMatch("other", nullptr, 100)); delete builder; delete reader; } TEST_F(BlockBasedFilterBlockTest, BlockBasedMultiChunk) { - FilterBlockBuilder* builder = new BlockBasedFilterBlockBuilder( - nullptr, table_options_); + FilterBlockBuilder* builder = + new BlockBasedFilterBlockBuilder(nullptr, table_options_); // First filter builder->StartBlock(0); @@ -208,33 +208,33 @@ TEST_F(BlockBasedFilterBlockTest, BlockBasedMultiChunk) { builder->Add("box"); builder->Add("hello"); - BlockContents block(builder->Finish(), false, kNoCompression); + BlockContents block(builder->Finish()); FilterBlockReader* reader = new BlockBasedFilterBlockReader( nullptr, table_options_, true, std::move(block), nullptr); // Check first filter - ASSERT_TRUE(reader->KeyMayMatch("foo", 0)); - ASSERT_TRUE(reader->KeyMayMatch("bar", 2000)); - ASSERT_TRUE(!reader->KeyMayMatch("box", 0)); - ASSERT_TRUE(!reader->KeyMayMatch("hello", 0)); + ASSERT_TRUE(reader->KeyMayMatch("foo", nullptr, uint64_t{0})); + ASSERT_TRUE(reader->KeyMayMatch("bar", nullptr, 2000)); + ASSERT_TRUE(!reader->KeyMayMatch("box", nullptr, uint64_t{0})); + ASSERT_TRUE(!reader->KeyMayMatch("hello", nullptr, uint64_t{0})); // Check second filter - ASSERT_TRUE(reader->KeyMayMatch("box", 3100)); - ASSERT_TRUE(!reader->KeyMayMatch("foo", 3100)); - ASSERT_TRUE(!reader->KeyMayMatch("bar", 3100)); - ASSERT_TRUE(!reader->KeyMayMatch("hello", 3100)); + ASSERT_TRUE(reader->KeyMayMatch("box", nullptr, 3100)); + ASSERT_TRUE(!reader->KeyMayMatch("foo", nullptr, 3100)); + ASSERT_TRUE(!reader->KeyMayMatch("bar", nullptr, 3100)); + ASSERT_TRUE(!reader->KeyMayMatch("hello", nullptr, 3100)); // Check third filter (empty) - ASSERT_TRUE(!reader->KeyMayMatch("foo", 4100)); - ASSERT_TRUE(!reader->KeyMayMatch("bar", 4100)); - ASSERT_TRUE(!reader->KeyMayMatch("box", 4100)); - ASSERT_TRUE(!reader->KeyMayMatch("hello", 4100)); + ASSERT_TRUE(!reader->KeyMayMatch("foo", nullptr, 4100)); + ASSERT_TRUE(!reader->KeyMayMatch("bar", nullptr, 4100)); + ASSERT_TRUE(!reader->KeyMayMatch("box", nullptr, 4100)); + ASSERT_TRUE(!reader->KeyMayMatch("hello", nullptr, 4100)); // Check last filter - ASSERT_TRUE(reader->KeyMayMatch("box", 9000)); - ASSERT_TRUE(reader->KeyMayMatch("hello", 9000)); - ASSERT_TRUE(!reader->KeyMayMatch("foo", 9000)); - ASSERT_TRUE(!reader->KeyMayMatch("bar", 9000)); + ASSERT_TRUE(reader->KeyMayMatch("box", nullptr, 9000)); + ASSERT_TRUE(reader->KeyMayMatch("hello", nullptr, 9000)); + ASSERT_TRUE(!reader->KeyMayMatch("foo", nullptr, 9000)); + ASSERT_TRUE(!reader->KeyMayMatch("bar", nullptr, 9000)); delete builder; delete reader; diff --git a/thirdparty/rocksdb/table/block_based_table_builder.cc b/thirdparty/rocksdb/table/block_based_table_builder.cc index e82f91aec7..479311f5b0 100644 --- a/thirdparty/rocksdb/table/block_based_table_builder.cc +++ b/thirdparty/rocksdb/table/block_based_table_builder.cc @@ -37,14 +37,14 @@ #include "table/filter_block.h" #include "table/format.h" #include "table/full_filter_block.h" -#include "table/meta_blocks.h" #include "table/table_builder.h" -#include "util/string_util.h" #include "util/coding.h" #include "util/compression.h" #include "util/crc32c.h" +#include "util/memory_allocator.h" #include "util/stop_watch.h" +#include "util/string_util.h" #include "util/xxhash.h" #include "table/index_builder.h" @@ -62,14 +62,17 @@ namespace { // Create a filter block builder based on its type. FilterBlockBuilder* CreateFilterBlockBuilder( - const ImmutableCFOptions& opt, const BlockBasedTableOptions& table_opt, + const ImmutableCFOptions& /*opt*/, const MutableCFOptions& mopt, + const BlockBasedTableOptions& table_opt, + const bool use_delta_encoding_for_index_values, PartitionedIndexBuilder* const p_index_builder) { if (table_opt.filter_policy == nullptr) return nullptr; FilterBitsBuilder* filter_bits_builder = table_opt.filter_policy->GetFilterBitsBuilder(); if (filter_bits_builder == nullptr) { - return new BlockBasedFilterBlockBuilder(opt.prefix_extractor, table_opt); + return new BlockBasedFilterBlockBuilder(mopt.prefix_extractor.get(), + table_opt); } else { if (table_opt.partition_filters) { assert(p_index_builder != nullptr); @@ -77,16 +80,18 @@ FilterBlockBuilder* CreateFilterBlockBuilder( // until index builder actully cuts the partition, we take the lower bound // as partition size. assert(table_opt.block_size_deviation <= 100); - auto partition_size = static_cast( - table_opt.metadata_block_size * - (100 - table_opt.block_size_deviation)); + auto partition_size = + static_cast(((table_opt.metadata_block_size * + (100 - table_opt.block_size_deviation)) + + 99) / + 100); partition_size = std::max(partition_size, static_cast(1)); return new PartitionedFilterBlockBuilder( - opt.prefix_extractor, table_opt.whole_key_filtering, + mopt.prefix_extractor.get(), table_opt.whole_key_filtering, filter_bits_builder, table_opt.index_block_restart_interval, - p_index_builder, partition_size); + use_delta_encoding_for_index_values, p_index_builder, partition_size); } else { - return new FullFilterBlockBuilder(opt.prefix_extractor, + return new FullFilterBlockBuilder(mopt.prefix_extractor.get(), table_opt.whole_key_filtering, filter_bits_builder); } @@ -98,84 +103,105 @@ bool GoodCompressionRatio(size_t compressed_size, size_t raw_size) { return compressed_size < raw_size - (raw_size / 8u); } -} // namespace - -// format_version is the block format as defined in include/rocksdb/table.h -Slice CompressBlock(const Slice& raw, - const CompressionOptions& compression_options, - CompressionType* type, uint32_t format_version, - const Slice& compression_dict, - std::string* compressed_output) { - if (*type == kNoCompression) { - return raw; - } - +bool CompressBlockInternal(const Slice& raw, + const CompressionInfo& compression_info, + uint32_t format_version, + std::string* compressed_output) { // Will return compressed block contents if (1) the compression method is // supported in this platform and (2) the compression rate is "good enough". - switch (*type) { + switch (compression_info.type()) { case kSnappyCompression: - if (Snappy_Compress(compression_options, raw.data(), raw.size(), - compressed_output) && - GoodCompressionRatio(compressed_output->size(), raw.size())) { - return *compressed_output; - } - break; // fall back to no compression. + return Snappy_Compress(compression_info, raw.data(), raw.size(), + compressed_output); case kZlibCompression: - if (Zlib_Compress( - compression_options, - GetCompressFormatForVersion(kZlibCompression, format_version), - raw.data(), raw.size(), compressed_output, compression_dict) && - GoodCompressionRatio(compressed_output->size(), raw.size())) { - return *compressed_output; - } - break; // fall back to no compression. + return Zlib_Compress( + compression_info, + GetCompressFormatForVersion(kZlibCompression, format_version), + raw.data(), raw.size(), compressed_output); case kBZip2Compression: - if (BZip2_Compress( - compression_options, - GetCompressFormatForVersion(kBZip2Compression, format_version), - raw.data(), raw.size(), compressed_output) && - GoodCompressionRatio(compressed_output->size(), raw.size())) { - return *compressed_output; - } - break; // fall back to no compression. + return BZip2_Compress( + compression_info, + GetCompressFormatForVersion(kBZip2Compression, format_version), + raw.data(), raw.size(), compressed_output); case kLZ4Compression: - if (LZ4_Compress( - compression_options, - GetCompressFormatForVersion(kLZ4Compression, format_version), - raw.data(), raw.size(), compressed_output, compression_dict) && - GoodCompressionRatio(compressed_output->size(), raw.size())) { - return *compressed_output; - } - break; // fall back to no compression. + return LZ4_Compress( + compression_info, + GetCompressFormatForVersion(kLZ4Compression, format_version), + raw.data(), raw.size(), compressed_output); case kLZ4HCCompression: - if (LZ4HC_Compress( - compression_options, - GetCompressFormatForVersion(kLZ4HCCompression, format_version), - raw.data(), raw.size(), compressed_output, compression_dict) && - GoodCompressionRatio(compressed_output->size(), raw.size())) { - return *compressed_output; - } - break; // fall back to no compression. + return LZ4HC_Compress( + compression_info, + GetCompressFormatForVersion(kLZ4HCCompression, format_version), + raw.data(), raw.size(), compressed_output); case kXpressCompression: - if (XPRESS_Compress(raw.data(), raw.size(), - compressed_output) && - GoodCompressionRatio(compressed_output->size(), raw.size())) { - return *compressed_output; - } - break; + return XPRESS_Compress(raw.data(), raw.size(), compressed_output); case kZSTD: case kZSTDNotFinalCompression: - if (ZSTD_Compress(compression_options, raw.data(), raw.size(), - compressed_output, compression_dict) && - GoodCompressionRatio(compressed_output->size(), raw.size())) { - return *compressed_output; - } - break; // fall back to no compression. - default: {} // Do not recognize this compression type + return ZSTD_Compress(compression_info, raw.data(), raw.size(), + compressed_output); + default: + // Do not recognize this compression type + return false; + } +} + +} // namespace + +// format_version is the block format as defined in include/rocksdb/table.h +Slice CompressBlock(const Slice& raw, const CompressionInfo& info, + CompressionType* type, uint32_t format_version, + bool do_sample, std::string* compressed_output, + std::string* sampled_output_fast, + std::string* sampled_output_slow) { + *type = info.type(); + + if (info.type() == kNoCompression && !info.SampleForCompression()) { + return raw; } - // Compression method is not supported, or not good compression ratio, so just - // fall back to uncompressed form. + // If requested, we sample one in every N block with a + // fast and slow compression algorithm and report the stats. + // The users can use these stats to decide if it is worthwhile + // enabling compression and they also get a hint about which + // compression algorithm wil be beneficial. + if (do_sample && info.SampleForCompression() && + Random::GetTLSInstance()->OneIn((int)info.SampleForCompression()) && + sampled_output_fast && sampled_output_slow) { + // Sampling with a fast compression algorithm + if (LZ4_Supported() || Snappy_Supported()) { + CompressionType c = + LZ4_Supported() ? kLZ4Compression : kSnappyCompression; + CompressionContext context(c); + CompressionOptions options; + CompressionInfo info_tmp(options, context, + CompressionDict::GetEmptyDict(), c, + info.SampleForCompression()); + + CompressBlockInternal(raw, info_tmp, format_version, sampled_output_fast); + } + + // Sampling with a slow but high-compression algorithm + if (ZSTD_Supported() || Zlib_Supported()) { + CompressionType c = ZSTD_Supported() ? kZSTD : kZlibCompression; + CompressionContext context(c); + CompressionOptions options; + CompressionInfo info_tmp(options, context, + CompressionDict::GetEmptyDict(), c, + info.SampleForCompression()); + CompressBlockInternal(raw, info_tmp, format_version, sampled_output_slow); + } + } + + // Actually compress the data + if (*type != kNoCompression) { + if (CompressBlockInternal(raw, info, format_version, compressed_output) && + GoodCompressionRatio(compressed_output->size(), raw.size())) { + return *compressed_output; + } + } + + // Compression method is not supported, or not good + // compression ratio, so just fall back to uncompressed form. *type = kNoCompression; return raw; } @@ -208,14 +234,22 @@ class BlockBasedTableBuilder::BlockBasedTablePropertiesCollector whole_key_filtering_(whole_key_filtering), prefix_filtering_(prefix_filtering) {} - virtual Status InternalAdd(const Slice& key, const Slice& value, - uint64_t file_size) override { + Status InternalAdd(const Slice& /*key*/, const Slice& /*value*/, + uint64_t /*file_size*/) override { // Intentionally left blank. Have no interest in collecting stats for // individual key/value pairs. return Status::OK(); } - virtual Status Finish(UserCollectedProperties* properties) override { + virtual void BlockAdd(uint64_t /* blockRawBytes */, + uint64_t /* blockCompressedBytesFast */, + uint64_t /* blockCompressedBytesSlow */) override { + // Intentionally left blank. No interest in collecting stats for + // blocks. + return; + } + + Status Finish(UserCollectedProperties* properties) override { std::string val; PutFixed32(&val, static_cast(index_type_)); properties->insert({BlockBasedTablePropertyNames::kIndexType, val}); @@ -227,11 +261,11 @@ class BlockBasedTableBuilder::BlockBasedTablePropertiesCollector } // The name of the properties collector can be used for debugging purpose. - virtual const char* Name() const override { + const char* Name() const override { return "BlockBasedTablePropertiesCollector"; } - virtual UserCollectedProperties GetReadableProperties() const override { + UserCollectedProperties GetReadableProperties() const override { // Intentionally left blank. return UserCollectedProperties(); } @@ -244,12 +278,21 @@ class BlockBasedTableBuilder::BlockBasedTablePropertiesCollector struct BlockBasedTableBuilder::Rep { const ImmutableCFOptions ioptions; + const MutableCFOptions moptions; const BlockBasedTableOptions table_options; const InternalKeyComparator& internal_comparator; WritableFileWriter* file; uint64_t offset = 0; Status status; + size_t alignment; BlockBuilder data_block; + // Buffers uncompressed data blocks and keys to replay later. Needed when + // compression dictionary is enabled so we can finalize the dictionary before + // compressing any data blocks. + // TODO(ajkr): ideally we don't buffer all keys and all uncompressed data + // blocks as it's redundant, but it's easier to implement for now. + std::vector>> + data_block_and_keys_buffers; BlockBuilder range_del_block; InternalKeySliceTransform internal_prefix_transform; @@ -257,13 +300,44 @@ struct BlockBasedTableBuilder::Rep { PartitionedIndexBuilder* p_index_builder_ = nullptr; std::string last_key; - const CompressionType compression_type; - const CompressionOptions compression_opts; - // Data for presetting the compression library's dictionary, or nullptr. - const std::string* compression_dict; + CompressionType compression_type; + uint64_t sample_for_compression; + CompressionOptions compression_opts; + std::unique_ptr compression_dict; + CompressionContext compression_ctx; + std::unique_ptr verify_ctx; + std::unique_ptr verify_dict; + + size_t data_begin_offset = 0; + TableProperties props; - bool closed = false; // Either Finish() or Abandon() has been called. + // States of the builder. + // + // - `kBuffered`: This is the initial state where zero or more data blocks are + // accumulated uncompressed in-memory. From this state, call + // `EnterUnbuffered()` to finalize the compression dictionary if enabled, + // compress/write out any buffered blocks, and proceed to the `kUnbuffered` + // state. + // + // - `kUnbuffered`: This is the state when compression dictionary is finalized + // either because it wasn't enabled in the first place or it's been created + // from sampling previously buffered data. In this state, blocks are simply + // compressed/written out as they fill up. From this state, call `Finish()` + // to complete the file (write meta-blocks, etc.), or `Abandon()` to delete + // the partially created file. + // + // - `kClosed`: This indicates either `Finish()` or `Abandon()` has been + // called, so the table builder is no longer usable. We must be in this + // state by the time the destructor runs. + enum class State { + kBuffered, + kUnbuffered, + kClosed, + }; + State state; + + const bool use_delta_encoding_for_index_values; std::unique_ptr filter_builder; char compressed_cache_key_prefix[BlockBasedTable::kMaxCacheKeyPrefixSize]; size_t compressed_cache_key_prefix_size; @@ -276,53 +350,76 @@ struct BlockBasedTableBuilder::Rep { const std::string& column_family_name; uint64_t creation_time = 0; uint64_t oldest_key_time = 0; + const uint64_t target_file_size; std::vector> table_properties_collectors; - Rep(const ImmutableCFOptions& _ioptions, + Rep(const ImmutableCFOptions& _ioptions, const MutableCFOptions& _moptions, const BlockBasedTableOptions& table_opt, const InternalKeyComparator& icomparator, const std::vector>* int_tbl_prop_collector_factories, uint32_t _column_family_id, WritableFileWriter* f, const CompressionType _compression_type, - const CompressionOptions& _compression_opts, - const std::string* _compression_dict, const bool skip_filters, + const uint64_t _sample_for_compression, + const CompressionOptions& _compression_opts, const bool skip_filters, const std::string& _column_family_name, const uint64_t _creation_time, - const uint64_t _oldest_key_time) + const uint64_t _oldest_key_time, const uint64_t _target_file_size) : ioptions(_ioptions), + moptions(_moptions), table_options(table_opt), internal_comparator(icomparator), file(f), + alignment(table_options.block_align + ? std::min(table_options.block_size, kDefaultPageSize) + : 0), data_block(table_options.block_restart_interval, - table_options.use_delta_encoding), - range_del_block(1), // TODO(andrewkr): restart_interval unnecessary - internal_prefix_transform(_ioptions.prefix_extractor), + table_options.use_delta_encoding, + false /* use_value_delta_encoding */, + icomparator.user_comparator() + ->CanKeysWithDifferentByteContentsBeEqual() + ? BlockBasedTableOptions::kDataBlockBinarySearch + : table_options.data_block_index_type, + table_options.data_block_hash_table_util_ratio), + range_del_block(1 /* block_restart_interval */), + internal_prefix_transform(_moptions.prefix_extractor.get()), compression_type(_compression_type), + sample_for_compression(_sample_for_compression), compression_opts(_compression_opts), - compression_dict(_compression_dict), + compression_dict(), + compression_ctx(_compression_type), + verify_dict(), + state((_compression_opts.max_dict_bytes > 0) ? State::kBuffered + : State::kUnbuffered), + use_delta_encoding_for_index_values(table_opt.format_version >= 4 && + !table_opt.block_align), + compressed_cache_key_prefix_size(0), flush_block_policy( table_options.flush_block_policy_factory->NewFlushBlockPolicy( table_options, data_block)), column_family_id(_column_family_id), column_family_name(_column_family_name), creation_time(_creation_time), - oldest_key_time(_oldest_key_time) { + oldest_key_time(_oldest_key_time), + target_file_size(_target_file_size) { if (table_options.index_type == BlockBasedTableOptions::kTwoLevelIndexSearch) { p_index_builder_ = PartitionedIndexBuilder::CreateIndexBuilder( - &internal_comparator, table_options); + &internal_comparator, use_delta_encoding_for_index_values, + table_options); index_builder.reset(p_index_builder_); } else { index_builder.reset(IndexBuilder::CreateIndexBuilder( table_options.index_type, &internal_comparator, - &this->internal_prefix_transform, table_options)); + &this->internal_prefix_transform, use_delta_encoding_for_index_values, + table_options)); } if (skip_filters) { filter_builder = nullptr; } else { - filter_builder.reset( - CreateFilterBlockBuilder(_ioptions, table_options, p_index_builder_)); + filter_builder.reset(CreateFilterBlockBuilder( + _ioptions, _moptions, table_options, + use_delta_encoding_for_index_values, p_index_builder_)); } for (auto& collector_factories : *int_tbl_prop_collector_factories) { @@ -332,22 +429,31 @@ struct BlockBasedTableBuilder::Rep { table_properties_collectors.emplace_back( new BlockBasedTablePropertiesCollector( table_options.index_type, table_options.whole_key_filtering, - _ioptions.prefix_extractor != nullptr)); + _moptions.prefix_extractor != nullptr)); + if (table_options.verify_compression) { + verify_ctx.reset(new UncompressionContext(UncompressionContext::NoCache(), + compression_type)); + } } + + Rep(const Rep&) = delete; + Rep& operator=(const Rep&) = delete; + + ~Rep() {} }; BlockBasedTableBuilder::BlockBasedTableBuilder( - const ImmutableCFOptions& ioptions, + const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions, const BlockBasedTableOptions& table_options, const InternalKeyComparator& internal_comparator, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, WritableFileWriter* file, const CompressionType compression_type, - const CompressionOptions& compression_opts, - const std::string* compression_dict, const bool skip_filters, + const uint64_t sample_for_compression, + const CompressionOptions& compression_opts, const bool skip_filters, const std::string& column_family_name, const uint64_t creation_time, - const uint64_t oldest_key_time) { + const uint64_t oldest_key_time, const uint64_t target_file_size) { BlockBasedTableOptions sanitized_table_options(table_options); if (sanitized_table_options.format_version == 0 && sanitized_table_options.checksum != kCRC32c) { @@ -360,11 +466,11 @@ BlockBasedTableBuilder::BlockBasedTableBuilder( sanitized_table_options.format_version = 1; } - rep_ = - new Rep(ioptions, sanitized_table_options, internal_comparator, - int_tbl_prop_collector_factories, column_family_id, file, - compression_type, compression_opts, compression_dict, - skip_filters, column_family_name, creation_time, oldest_key_time); + rep_ = new Rep( + ioptions, moptions, sanitized_table_options, internal_comparator, + int_tbl_prop_collector_factories, column_family_id, file, + compression_type, sample_for_compression, compression_opts, skip_filters, + column_family_name, creation_time, oldest_key_time, target_file_size); if (rep_->filter_builder != nullptr) { rep_->filter_builder->StartBlock(0); @@ -378,25 +484,33 @@ BlockBasedTableBuilder::BlockBasedTableBuilder( } BlockBasedTableBuilder::~BlockBasedTableBuilder() { - assert(rep_->closed); // Catch errors where caller forgot to call Finish() + // Catch errors where caller forgot to call Finish() + assert(rep_->state == Rep::State::kClosed); delete rep_; } void BlockBasedTableBuilder::Add(const Slice& key, const Slice& value) { Rep* r = rep_; - assert(!r->closed); + assert(rep_->state != Rep::State::kClosed); if (!ok()) return; ValueType value_type = ExtractValueType(key); if (IsValueType(value_type)) { - if (r->props.num_entries > 0) { +#ifndef NDEBUG + if (r->props.num_entries > r->props.num_range_deletions) { assert(r->internal_comparator.Compare(key, Slice(r->last_key)) > 0); } +#endif // NDEBUG auto should_flush = r->flush_block_policy->Update(key, value); if (should_flush) { assert(!r->data_block.empty()); Flush(); + if (r->state == Rep::State::kBuffered && + r->data_begin_offset > r->target_file_size) { + EnterUnbuffered(); + } + // Add item to index block. // We do not emit the index entry for a block until we have seen the // first key for the next data block. This allows us to use shorter @@ -405,53 +519,61 @@ void BlockBasedTableBuilder::Add(const Slice& key, const Slice& value) { // "the r" as the key for the index block entry since it is >= all // entries in the first block and < all entries in subsequent // blocks. - if (ok()) { + if (ok() && r->state == Rep::State::kUnbuffered) { r->index_builder->AddIndexEntry(&r->last_key, &key, r->pending_handle); } } // Note: PartitionedFilterBlockBuilder requires key being added to filter // builder after being added to index builder. - if (r->filter_builder != nullptr) { + if (r->state == Rep::State::kUnbuffered && r->filter_builder != nullptr) { r->filter_builder->Add(ExtractUserKey(key)); } r->last_key.assign(key.data(), key.size()); r->data_block.Add(key, value); - r->props.num_entries++; - r->props.raw_key_size += key.size(); - r->props.raw_value_size += value.size(); - - r->index_builder->OnKeyAdded(key); + if (r->state == Rep::State::kBuffered) { + // Buffer keys to be replayed during `Finish()` once compression + // dictionary has been finalized. + if (r->data_block_and_keys_buffers.empty() || should_flush) { + r->data_block_and_keys_buffers.emplace_back(); + } + r->data_block_and_keys_buffers.back().second.emplace_back(key.ToString()); + } else { + r->index_builder->OnKeyAdded(key); + } NotifyCollectTableCollectorsOnAdd(key, value, r->offset, r->table_properties_collectors, r->ioptions.info_log); } else if (value_type == kTypeRangeDeletion) { - // TODO(wanning&andrewkr) add num_tomestone to table properties r->range_del_block.Add(key, value); - ++r->props.num_entries; - r->props.raw_key_size += key.size(); - r->props.raw_value_size += value.size(); NotifyCollectTableCollectorsOnAdd(key, value, r->offset, r->table_properties_collectors, r->ioptions.info_log); } else { assert(false); } + + r->props.num_entries++; + r->props.raw_key_size += key.size(); + r->props.raw_value_size += value.size(); + if (value_type == kTypeDeletion || value_type == kTypeSingleDeletion) { + r->props.num_deletions++; + } else if (value_type == kTypeRangeDeletion) { + r->props.num_deletions++; + r->props.num_range_deletions++; + } else if (value_type == kTypeMerge) { + r->props.num_merge_operands++; + } } void BlockBasedTableBuilder::Flush() { Rep* r = rep_; - assert(!r->closed); + assert(rep_->state != Rep::State::kClosed); if (!ok()) return; if (r->data_block.empty()) return; WriteBlock(&r->data_block, &r->pending_handle, true /* is_data_block */); - if (r->filter_builder != nullptr) { - r->filter_builder->StartBlock(r->offset); - } - r->props.data_size = r->offset; - ++r->props.num_data_blocks; } void BlockBasedTableBuilder::WriteBlock(BlockBuilder* block, @@ -472,32 +594,64 @@ void BlockBasedTableBuilder::WriteBlock(const Slice& raw_block_contents, Rep* r = rep_; auto type = r->compression_type; + uint64_t sample_for_compression = r->sample_for_compression; Slice block_contents; bool abort_compression = false; - StopWatchNano timer(r->ioptions.env, - ShouldReportDetailedTime(r->ioptions.env, r->ioptions.statistics)); + StopWatchNano timer( + r->ioptions.env, + ShouldReportDetailedTime(r->ioptions.env, r->ioptions.statistics)); + + if (r->state == Rep::State::kBuffered) { + assert(is_data_block); + assert(!r->data_block_and_keys_buffers.empty()); + r->data_block_and_keys_buffers.back().first = raw_block_contents.ToString(); + r->data_begin_offset += r->data_block_and_keys_buffers.back().first.size(); + return; + } if (raw_block_contents.size() < kCompressionSizeLimit) { - Slice compression_dict; - if (is_data_block && r->compression_dict && r->compression_dict->size()) { - compression_dict = *r->compression_dict; + const CompressionDict* compression_dict; + if (!is_data_block || r->compression_dict == nullptr) { + compression_dict = &CompressionDict::GetEmptyDict(); + } else { + compression_dict = r->compression_dict.get(); } - - block_contents = CompressBlock(raw_block_contents, r->compression_opts, - &type, r->table_options.format_version, - compression_dict, &r->compressed_output); + assert(compression_dict != nullptr); + CompressionInfo compression_info(r->compression_opts, r->compression_ctx, + *compression_dict, type, + sample_for_compression); + + std::string sampled_output_fast; + std::string sampled_output_slow; + block_contents = CompressBlock( + raw_block_contents, compression_info, &type, + r->table_options.format_version, is_data_block /* do_sample */, + &r->compressed_output, &sampled_output_fast, &sampled_output_slow); + + // notify collectors on block add + NotifyCollectTableCollectorsOnBlockAdd( + r->table_properties_collectors, raw_block_contents.size(), + sampled_output_fast.size(), sampled_output_slow.size()); // Some of the compression algorithms are known to be unreliable. If // the verify_compression flag is set then try to de-compress the // compressed data and compare to the input. if (type != kNoCompression && r->table_options.verify_compression) { // Retrieve the uncompressed contents into a new buffer + const UncompressionDict* verify_dict; + if (!is_data_block || r->verify_dict == nullptr) { + verify_dict = &UncompressionDict::GetEmptyDict(); + } else { + verify_dict = r->verify_dict.get(); + } + assert(verify_dict != nullptr); BlockContents contents; + UncompressionInfo uncompression_info(*r->verify_ctx, *verify_dict, + r->compression_type); Status stat = UncompressBlockContentsForCompressionType( - block_contents.data(), block_contents.size(), &contents, - r->table_options.format_version, compression_dict, type, - r->ioptions); + uncompression_info, block_contents.data(), block_contents.size(), + &contents, r->table_options.format_version, r->ioptions); if (stat.ok()) { bool compressed_ok = contents.data.compare(raw_block_contents) == 0; @@ -526,23 +680,33 @@ void BlockBasedTableBuilder::WriteBlock(const Slice& raw_block_contents, RecordTick(r->ioptions.statistics, NUMBER_BLOCK_NOT_COMPRESSED); type = kNoCompression; block_contents = raw_block_contents; - } else if (type != kNoCompression && - ShouldReportDetailedTime(r->ioptions.env, - r->ioptions.statistics)) { - MeasureTime(r->ioptions.statistics, COMPRESSION_TIMES_NANOS, - timer.ElapsedNanos()); - MeasureTime(r->ioptions.statistics, BYTES_COMPRESSED, - raw_block_contents.size()); + } else if (type != kNoCompression) { + if (ShouldReportDetailedTime(r->ioptions.env, r->ioptions.statistics)) { + RecordTimeToHistogram(r->ioptions.statistics, COMPRESSION_TIMES_NANOS, + timer.ElapsedNanos()); + } + RecordInHistogram(r->ioptions.statistics, BYTES_COMPRESSED, + raw_block_contents.size()); RecordTick(r->ioptions.statistics, NUMBER_BLOCK_COMPRESSED); + } else if (type != r->compression_type) { + RecordTick(r->ioptions.statistics, NUMBER_BLOCK_NOT_COMPRESSED); } - WriteRawBlock(block_contents, type, handle); + WriteRawBlock(block_contents, type, handle, is_data_block); r->compressed_output.clear(); + if (is_data_block) { + if (r->filter_builder != nullptr) { + r->filter_builder->StartBlock(r->offset); + } + r->props.data_size = r->offset; + ++r->props.num_data_blocks; + } } void BlockBasedTableBuilder::WriteRawBlock(const Slice& block_contents, CompressionType type, - BlockHandle* handle) { + BlockHandle* handle, + bool is_data_block) { Rep* r = rep_; StopWatch sw(r->ioptions.env, r->ioptions.statistics, WRITE_RAW_BLOCK_MICROS); handle->set_offset(r->offset); @@ -571,26 +735,50 @@ void BlockBasedTableBuilder::WriteRawBlock(const Slice& block_contents, EncodeFixed32(trailer_without_type, XXH32_digest(xxh)); break; } + case kxxHash64: { + XXH64_state_t* const state = XXH64_createState(); + XXH64_reset(state, 0); + XXH64_update(state, block_contents.data(), + static_cast(block_contents.size())); + XXH64_update(state, trailer, 1); // Extend to cover block type + EncodeFixed32( + trailer_without_type, + static_cast(XXH64_digest(state) & // lower 32 bits + uint64_t{0xffffffff})); + XXH64_freeState(state); + break; + } } assert(r->status.ok()); + TEST_SYNC_POINT_CALLBACK( + "BlockBasedTableBuilder::WriteRawBlock:TamperWithChecksum", + static_cast(trailer)); r->status = r->file->Append(Slice(trailer, kBlockTrailerSize)); if (r->status.ok()) { r->status = InsertBlockInCache(block_contents, type, handle); } if (r->status.ok()) { r->offset += block_contents.size() + kBlockTrailerSize; + if (r->table_options.block_align && is_data_block) { + size_t pad_bytes = + (r->alignment - ((block_contents.size() + kBlockTrailerSize) & + (r->alignment - 1))) & + (r->alignment - 1); + r->status = r->file->Pad(pad_bytes); + if (r->status.ok()) { + r->offset += pad_bytes; + } + } } } } -Status BlockBasedTableBuilder::status() const { - return rep_->status; -} +Status BlockBasedTableBuilder::status() const { return rep_->status; } -static void DeleteCachedBlock(const Slice& key, void* value) { - Block* block = reinterpret_cast(value); - delete block; +static void DeleteCachedBlockContents(const Slice& /*key*/, void* value) { + BlockContents* bc = reinterpret_cast(value); + delete bc; } // @@ -603,28 +791,31 @@ Status BlockBasedTableBuilder::InsertBlockInCache(const Slice& block_contents, Cache* block_cache_compressed = r->table_options.block_cache_compressed.get(); if (type != kNoCompression && block_cache_compressed != nullptr) { - size_t size = block_contents.size(); - std::unique_ptr ubuf(new char[size + 1]); + auto ubuf = + AllocateBlock(size + 1, block_cache_compressed->memory_allocator()); memcpy(ubuf.get(), block_contents.data(), size); ubuf[size] = type; - BlockContents results(std::move(ubuf), size, true, type); - - Block* block = new Block(std::move(results), kDisableGlobalSequenceNumber); + BlockContents* block_contents_to_cache = + new BlockContents(std::move(ubuf), size); +#ifndef NDEBUG + block_contents_to_cache->is_raw_block = true; +#endif // NDEBUG // make cache key by appending the file offset to the cache prefix id char* end = EncodeVarint64( - r->compressed_cache_key_prefix + - r->compressed_cache_key_prefix_size, - handle->offset()); - Slice key(r->compressed_cache_key_prefix, static_cast - (end - r->compressed_cache_key_prefix)); + r->compressed_cache_key_prefix + r->compressed_cache_key_prefix_size, + handle->offset()); + Slice key(r->compressed_cache_key_prefix, + static_cast(end - r->compressed_cache_key_prefix)); // Insert into compressed block cache. - block_cache_compressed->Insert(key, block, block->usable_size(), - &DeleteCachedBlock); + block_cache_compressed->Insert( + key, block_contents_to_cache, + block_contents_to_cache->ApproximateMemoryUsage(), + &DeleteCachedBlockContents); // Invalidate OS cache. r->file->InvalidateCache(static_cast(r->offset), size); @@ -632,216 +823,343 @@ Status BlockBasedTableBuilder::InsertBlockInCache(const Slice& block_contents, return Status::OK(); } -Status BlockBasedTableBuilder::Finish() { - Rep* r = rep_; - bool empty_data_block = r->data_block.empty(); - Flush(); - assert(!r->closed); - r->closed = true; - - // To make sure properties block is able to keep the accurate size of index - // block, we will finish writing all index entries here and flush them - // to storage after metaindex block is written. - if (ok() && !empty_data_block) { - r->index_builder->AddIndexEntry( - &r->last_key, nullptr /* no next data block */, r->pending_handle); - } - - BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle, - compression_dict_block_handle, range_del_block_handle; - // Write filter block - if (ok() && r->filter_builder != nullptr) { +void BlockBasedTableBuilder::WriteFilterBlock( + MetaIndexBuilder* meta_index_builder) { + BlockHandle filter_block_handle; + bool empty_filter_block = (rep_->filter_builder == nullptr || + rep_->filter_builder->NumAdded() == 0); + if (ok() && !empty_filter_block) { Status s = Status::Incomplete(); - while (s.IsIncomplete()) { - Slice filter_content = r->filter_builder->Finish(filter_block_handle, &s); + while (ok() && s.IsIncomplete()) { + Slice filter_content = + rep_->filter_builder->Finish(filter_block_handle, &s); assert(s.ok() || s.IsIncomplete()); - r->props.filter_size += filter_content.size(); + rep_->props.filter_size += filter_content.size(); WriteRawBlock(filter_content, kNoCompression, &filter_block_handle); } } + if (ok() && !empty_filter_block) { + // Add mapping from ".Name" to location + // of filter data. + std::string key; + if (rep_->filter_builder->IsBlockBased()) { + key = BlockBasedTable::kFilterBlockPrefix; + } else { + key = rep_->table_options.partition_filters + ? BlockBasedTable::kPartitionedFilterBlockPrefix + : BlockBasedTable::kFullFilterBlockPrefix; + } + key.append(rep_->table_options.filter_policy->Name()); + meta_index_builder->Add(key, filter_block_handle); + } +} +void BlockBasedTableBuilder::WriteIndexBlock( + MetaIndexBuilder* meta_index_builder, BlockHandle* index_block_handle) { IndexBuilder::IndexBlocks index_blocks; - auto index_builder_status = r->index_builder->Finish(&index_blocks); + auto index_builder_status = rep_->index_builder->Finish(&index_blocks); if (index_builder_status.IsIncomplete()) { // We we have more than one index partition then meta_blocks are not // supported for the index. Currently meta_blocks are used only by // HashIndexBuilder which is not multi-partition. assert(index_blocks.meta_blocks.empty()); - } else if (!index_builder_status.ok()) { - return index_builder_status; + } else if (ok() && !index_builder_status.ok()) { + rep_->status = index_builder_status; } - - // Write meta blocks and metaindex block with the following order. - // 1. [meta block: filter] - // 2. [meta block: properties] - // 3. [meta block: compression dictionary] - // 4. [meta block: range deletion tombstone] - // 5. [metaindex block] - // write meta blocks - MetaIndexBuilder meta_index_builder; - for (const auto& item : index_blocks.meta_blocks) { - BlockHandle block_handle; - WriteBlock(item.second, &block_handle, false /* is_data_block */); - meta_index_builder.Add(item.first, block_handle); + if (ok()) { + for (const auto& item : index_blocks.meta_blocks) { + BlockHandle block_handle; + WriteBlock(item.second, &block_handle, false /* is_data_block */); + if (!ok()) { + break; + } + meta_index_builder->Add(item.first, block_handle); + } } + if (ok()) { + if (rep_->table_options.enable_index_compression) { + WriteBlock(index_blocks.index_block_contents, index_block_handle, false); + } else { + WriteRawBlock(index_blocks.index_block_contents, kNoCompression, + index_block_handle); + } + } + // If there are more index partitions, finish them and write them out + Status s = index_builder_status; + while (ok() && s.IsIncomplete()) { + s = rep_->index_builder->Finish(&index_blocks, *index_block_handle); + if (!s.ok() && !s.IsIncomplete()) { + rep_->status = s; + return; + } + if (rep_->table_options.enable_index_compression) { + WriteBlock(index_blocks.index_block_contents, index_block_handle, false); + } else { + WriteRawBlock(index_blocks.index_block_contents, kNoCompression, + index_block_handle); + } + // The last index_block_handle will be for the partition index block + } +} +void BlockBasedTableBuilder::WritePropertiesBlock( + MetaIndexBuilder* meta_index_builder) { + BlockHandle properties_block_handle; if (ok()) { - if (r->filter_builder != nullptr) { - // Add mapping from ".Name" to location - // of filter data. - std::string key; - if (r->filter_builder->IsBlockBased()) { - key = BlockBasedTable::kFilterBlockPrefix; - } else { - key = r->table_options.partition_filters - ? BlockBasedTable::kPartitionedFilterBlockPrefix - : BlockBasedTable::kFullFilterBlockPrefix; + PropertyBlockBuilder property_block_builder; + rep_->props.column_family_id = rep_->column_family_id; + rep_->props.column_family_name = rep_->column_family_name; + rep_->props.filter_policy_name = + rep_->table_options.filter_policy != nullptr + ? rep_->table_options.filter_policy->Name() + : ""; + rep_->props.index_size = + rep_->index_builder->IndexSize() + kBlockTrailerSize; + rep_->props.comparator_name = rep_->ioptions.user_comparator != nullptr + ? rep_->ioptions.user_comparator->Name() + : "nullptr"; + rep_->props.merge_operator_name = + rep_->ioptions.merge_operator != nullptr + ? rep_->ioptions.merge_operator->Name() + : "nullptr"; + rep_->props.compression_name = + CompressionTypeToString(rep_->compression_type); + rep_->props.compression_options = + CompressionOptionsToString(rep_->compression_opts); + rep_->props.prefix_extractor_name = + rep_->moptions.prefix_extractor != nullptr + ? rep_->moptions.prefix_extractor->Name() + : "nullptr"; + + std::string property_collectors_names = "["; + for (size_t i = 0; + i < rep_->ioptions.table_properties_collector_factories.size(); ++i) { + if (i != 0) { + property_collectors_names += ","; } - key.append(r->table_options.filter_policy->Name()); - meta_index_builder.Add(key, filter_block_handle); + property_collectors_names += + rep_->ioptions.table_properties_collector_factories[i]->Name(); } - - // Write properties and compression dictionary blocks. + property_collectors_names += "]"; + rep_->props.property_collectors_names = property_collectors_names; + if (rep_->table_options.index_type == + BlockBasedTableOptions::kTwoLevelIndexSearch) { + assert(rep_->p_index_builder_ != nullptr); + rep_->props.index_partitions = rep_->p_index_builder_->NumPartitions(); + rep_->props.top_level_index_size = + rep_->p_index_builder_->TopLevelIndexSize(rep_->offset); + } + rep_->props.index_key_is_user_key = + !rep_->index_builder->seperator_is_key_plus_seq(); + rep_->props.index_value_is_delta_encoded = + rep_->use_delta_encoding_for_index_values; + rep_->props.creation_time = rep_->creation_time; + rep_->props.oldest_key_time = rep_->oldest_key_time; + + // Add basic properties + property_block_builder.AddTableProperty(rep_->props); + + // Add use collected properties + NotifyCollectTableCollectorsOnFinish(rep_->table_properties_collectors, + rep_->ioptions.info_log, + &property_block_builder); + + WriteRawBlock(property_block_builder.Finish(), kNoCompression, + &properties_block_handle); + } + if (ok()) { +#ifndef NDEBUG { - PropertyBlockBuilder property_block_builder; - r->props.column_family_id = r->column_family_id; - r->props.column_family_name = r->column_family_name; - r->props.filter_policy_name = r->table_options.filter_policy != nullptr ? - r->table_options.filter_policy->Name() : ""; - r->props.index_size = - r->index_builder->EstimatedSize() + kBlockTrailerSize; - r->props.comparator_name = r->ioptions.user_comparator != nullptr - ? r->ioptions.user_comparator->Name() - : "nullptr"; - r->props.merge_operator_name = r->ioptions.merge_operator != nullptr - ? r->ioptions.merge_operator->Name() - : "nullptr"; - r->props.compression_name = CompressionTypeToString(r->compression_type); - r->props.prefix_extractor_name = - r->ioptions.prefix_extractor != nullptr - ? r->ioptions.prefix_extractor->Name() - : "nullptr"; - - std::string property_collectors_names = "["; - property_collectors_names = "["; - for (size_t i = 0; - i < r->ioptions.table_properties_collector_factories.size(); ++i) { - if (i != 0) { - property_collectors_names += ","; - } - property_collectors_names += - r->ioptions.table_properties_collector_factories[i]->Name(); - } - property_collectors_names += "]"; - r->props.property_collectors_names = property_collectors_names; - if (r->table_options.index_type == - BlockBasedTableOptions::kTwoLevelIndexSearch) { - assert(r->p_index_builder_ != nullptr); - r->props.index_partitions = r->p_index_builder_->NumPartitions(); - r->props.top_level_index_size = - r->p_index_builder_->EstimateTopLevelIndexSize(r->offset); - } - r->props.creation_time = r->creation_time; - r->props.oldest_key_time = r->oldest_key_time; - - // Add basic properties - property_block_builder.AddTableProperty(r->props); - - // Add use collected properties - NotifyCollectTableCollectorsOnFinish(r->table_properties_collectors, - r->ioptions.info_log, - &property_block_builder); - - BlockHandle properties_block_handle; - WriteRawBlock( - property_block_builder.Finish(), - kNoCompression, - &properties_block_handle - ); - meta_index_builder.Add(kPropertiesBlock, properties_block_handle); - - // Write compression dictionary block - if (r->compression_dict && r->compression_dict->size()) { - WriteRawBlock(*r->compression_dict, kNoCompression, - &compression_dict_block_handle); - meta_index_builder.Add(kCompressionDictBlock, - compression_dict_block_handle); + uint64_t props_block_offset = properties_block_handle.offset(); + uint64_t props_block_size = properties_block_handle.size(); + TEST_SYNC_POINT_CALLBACK( + "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockOffset", + &props_block_offset); + TEST_SYNC_POINT_CALLBACK( + "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockSize", + &props_block_size); + } +#endif // !NDEBUG + meta_index_builder->Add(kPropertiesBlock, properties_block_handle); + } +} + +void BlockBasedTableBuilder::WriteCompressionDictBlock( + MetaIndexBuilder* meta_index_builder) { + if (rep_->compression_dict != nullptr && + rep_->compression_dict->GetRawDict().size()) { + BlockHandle compression_dict_block_handle; + if (ok()) { + WriteRawBlock(rep_->compression_dict->GetRawDict(), kNoCompression, + &compression_dict_block_handle); +#ifndef NDEBUG + Slice compression_dict = rep_->compression_dict->GetRawDict(); + TEST_SYNC_POINT_CALLBACK( + "BlockBasedTableBuilder::WriteCompressionDictBlock:RawDict", + &compression_dict); +#endif // NDEBUG + } + if (ok()) { + meta_index_builder->Add(kCompressionDictBlock, + compression_dict_block_handle); + } + } +} + +void BlockBasedTableBuilder::WriteRangeDelBlock( + MetaIndexBuilder* meta_index_builder) { + if (ok() && !rep_->range_del_block.empty()) { + BlockHandle range_del_block_handle; + WriteRawBlock(rep_->range_del_block.Finish(), kNoCompression, + &range_del_block_handle); + meta_index_builder->Add(kRangeDelBlock, range_del_block_handle); + } +} + +void BlockBasedTableBuilder::WriteFooter(BlockHandle& metaindex_block_handle, + BlockHandle& index_block_handle) { + Rep* r = rep_; + // No need to write out new footer if we're using default checksum. + // We're writing legacy magic number because we want old versions of RocksDB + // be able to read files generated with new release (just in case if + // somebody wants to roll back after an upgrade) + // TODO(icanadi) at some point in the future, when we're absolutely sure + // nobody will roll back to RocksDB 2.x versions, retire the legacy magic + // number and always write new table files with new magic number + bool legacy = (r->table_options.format_version == 0); + // this is guaranteed by BlockBasedTableBuilder's constructor + assert(r->table_options.checksum == kCRC32c || + r->table_options.format_version != 0); + Footer footer( + legacy ? kLegacyBlockBasedTableMagicNumber : kBlockBasedTableMagicNumber, + r->table_options.format_version); + footer.set_metaindex_handle(metaindex_block_handle); + footer.set_index_handle(index_block_handle); + footer.set_checksum(r->table_options.checksum); + std::string footer_encoding; + footer.EncodeTo(&footer_encoding); + assert(r->status.ok()); + r->status = r->file->Append(footer_encoding); + if (r->status.ok()) { + r->offset += footer_encoding.size(); + } +} + +void BlockBasedTableBuilder::EnterUnbuffered() { + Rep* r = rep_; + assert(r->state == Rep::State::kBuffered); + r->state = Rep::State::kUnbuffered; + const size_t kSampleBytes = r->compression_opts.zstd_max_train_bytes > 0 + ? r->compression_opts.zstd_max_train_bytes + : r->compression_opts.max_dict_bytes; + Random64 generator{r->creation_time}; + std::string compression_dict_samples; + std::vector compression_dict_sample_lens; + if (!r->data_block_and_keys_buffers.empty()) { + while (compression_dict_samples.size() < kSampleBytes) { + size_t rand_idx = + generator.Uniform(r->data_block_and_keys_buffers.size()); + size_t copy_len = + std::min(kSampleBytes - compression_dict_samples.size(), + r->data_block_and_keys_buffers[rand_idx].first.size()); + compression_dict_samples.append( + r->data_block_and_keys_buffers[rand_idx].first, 0, copy_len); + compression_dict_sample_lens.emplace_back(copy_len); + } + } + + // final data block flushed, now we can generate dictionary from the samples. + // OK if compression_dict_samples is empty, we'll just get empty dictionary. + std::string dict; + if (r->compression_opts.zstd_max_train_bytes > 0) { + dict = ZSTD_TrainDictionary(compression_dict_samples, + compression_dict_sample_lens, + r->compression_opts.max_dict_bytes); + } else { + dict = std::move(compression_dict_samples); + } + r->compression_dict.reset(new CompressionDict(dict, r->compression_type, + r->compression_opts.level)); + r->verify_dict.reset(new UncompressionDict( + dict, r->compression_type == kZSTD || + r->compression_type == kZSTDNotFinalCompression)); + + for (size_t i = 0; ok() && i < r->data_block_and_keys_buffers.size(); ++i) { + const auto& data_block = r->data_block_and_keys_buffers[i].first; + auto& keys = r->data_block_and_keys_buffers[i].second; + assert(!data_block.empty()); + assert(!keys.empty()); + + for (const auto& key : keys) { + if (r->filter_builder != nullptr) { + r->filter_builder->Add(ExtractUserKey(key)); } - } // end of properties/compression dictionary block writing + r->index_builder->OnKeyAdded(key); + } + WriteBlock(Slice(data_block), &r->pending_handle, true /* is_data_block */); + if (ok() && i + 1 < r->data_block_and_keys_buffers.size()) { + Slice first_key_in_next_block = + r->data_block_and_keys_buffers[i + 1].second.front(); + Slice* first_key_in_next_block_ptr = &first_key_in_next_block; + r->index_builder->AddIndexEntry(&keys.back(), first_key_in_next_block_ptr, + r->pending_handle); + } + } + r->data_block_and_keys_buffers.clear(); +} - if (ok() && !r->range_del_block.empty()) { - WriteRawBlock(r->range_del_block.Finish(), kNoCompression, - &range_del_block_handle); - meta_index_builder.Add(kRangeDelBlock, range_del_block_handle); - } // range deletion tombstone meta block - } // meta blocks +Status BlockBasedTableBuilder::Finish() { + Rep* r = rep_; + assert(r->state != Rep::State::kClosed); + bool empty_data_block = r->data_block.empty(); + Flush(); + if (r->state == Rep::State::kBuffered) { + EnterUnbuffered(); + } + // To make sure properties block is able to keep the accurate size of index + // block, we will finish writing all index entries first. + if (ok() && !empty_data_block) { + r->index_builder->AddIndexEntry( + &r->last_key, nullptr /* no next data block */, r->pending_handle); + } - // Write index block + // Write meta blocks, metaindex block and footer in the following order. + // 1. [meta block: filter] + // 2. [meta block: index] + // 3. [meta block: compression dictionary] + // 4. [meta block: range deletion tombstone] + // 5. [meta block: properties] + // 6. [metaindex block] + // 7. Footer + BlockHandle metaindex_block_handle, index_block_handle; + MetaIndexBuilder meta_index_builder; + WriteFilterBlock(&meta_index_builder); + WriteIndexBlock(&meta_index_builder, &index_block_handle); + WriteCompressionDictBlock(&meta_index_builder); + WriteRangeDelBlock(&meta_index_builder); + WritePropertiesBlock(&meta_index_builder); if (ok()) { // flush the meta index block WriteRawBlock(meta_index_builder.Finish(), kNoCompression, &metaindex_block_handle); - - const bool is_data_block = true; - WriteBlock(index_blocks.index_block_contents, &index_block_handle, - !is_data_block); - // If there are more index partitions, finish them and write them out - Status& s = index_builder_status; - while (s.IsIncomplete()) { - s = r->index_builder->Finish(&index_blocks, index_block_handle); - if (!s.ok() && !s.IsIncomplete()) { - return s; - } - WriteBlock(index_blocks.index_block_contents, &index_block_handle, - !is_data_block); - // The last index_block_handle will be for the partition index block - } } - - // Write footer if (ok()) { - // No need to write out new footer if we're using default checksum. - // We're writing legacy magic number because we want old versions of RocksDB - // be able to read files generated with new release (just in case if - // somebody wants to roll back after an upgrade) - // TODO(icanadi) at some point in the future, when we're absolutely sure - // nobody will roll back to RocksDB 2.x versions, retire the legacy magic - // number and always write new table files with new magic number - bool legacy = (r->table_options.format_version == 0); - // this is guaranteed by BlockBasedTableBuilder's constructor - assert(r->table_options.checksum == kCRC32c || - r->table_options.format_version != 0); - Footer footer(legacy ? kLegacyBlockBasedTableMagicNumber - : kBlockBasedTableMagicNumber, - r->table_options.format_version); - footer.set_metaindex_handle(metaindex_block_handle); - footer.set_index_handle(index_block_handle); - footer.set_checksum(r->table_options.checksum); - std::string footer_encoding; - footer.EncodeTo(&footer_encoding); - assert(r->status.ok()); - r->status = r->file->Append(footer_encoding); - if (r->status.ok()) { - r->offset += footer_encoding.size(); - } + WriteFooter(metaindex_block_handle, index_block_handle); } - + r->state = Rep::State::kClosed; return r->status; } void BlockBasedTableBuilder::Abandon() { - Rep* r = rep_; - assert(!r->closed); - r->closed = true; + assert(rep_->state != Rep::State::kClosed); + rep_->state = Rep::State::kClosed; } uint64_t BlockBasedTableBuilder::NumEntries() const { return rep_->props.num_entries; } -uint64_t BlockBasedTableBuilder::FileSize() const { - return rep_->offset; -} +uint64_t BlockBasedTableBuilder::FileSize() const { return rep_->offset; } bool BlockBasedTableBuilder::NeedCompact() const { for (const auto& collector : rep_->table_properties_collectors) { diff --git a/thirdparty/rocksdb/table/block_based_table_builder.h b/thirdparty/rocksdb/table/block_based_table_builder.h index 36dfce1f0f..b10494e7b9 100644 --- a/thirdparty/rocksdb/table/block_based_table_builder.h +++ b/thirdparty/rocksdb/table/block_based_table_builder.h @@ -18,7 +18,9 @@ #include "rocksdb/listener.h" #include "rocksdb/options.h" #include "rocksdb/status.h" +#include "table/meta_blocks.h" #include "table/table_builder.h" +#include "util/compression.h" namespace rocksdb { @@ -35,24 +37,26 @@ class BlockBasedTableBuilder : public TableBuilder { // Create a builder that will store the contents of the table it is // building in *file. Does not close the file. It is up to the // caller to close the file after calling Finish(). - // @param compression_dict Data for presetting the compression library's - // dictionary, or nullptr. BlockBasedTableBuilder( - const ImmutableCFOptions& ioptions, + const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions, const BlockBasedTableOptions& table_options, const InternalKeyComparator& internal_comparator, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, WritableFileWriter* file, const CompressionType compression_type, - const CompressionOptions& compression_opts, - const std::string* compression_dict, const bool skip_filters, + const uint64_t sample_for_compression, + const CompressionOptions& compression_opts, const bool skip_filters, const std::string& column_family_name, const uint64_t creation_time = 0, - const uint64_t oldest_key_time = 0); + const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0); // REQUIRES: Either Finish() or Abandon() has been called. ~BlockBasedTableBuilder(); + // No copying allowed + BlockBasedTableBuilder(const BlockBasedTableBuilder&) = delete; + BlockBasedTableBuilder& operator=(const BlockBasedTableBuilder&) = delete; + // Add key,value to the table being constructed. // REQUIRES: key is after any previously added key according to comparator. // REQUIRES: Finish(), Abandon() have not been called @@ -88,6 +92,11 @@ class BlockBasedTableBuilder : public TableBuilder { private: bool ok() const { return status().ok(); } + // Transition state from buffered to unbuffered. See `Rep::State` API comment + // for details of the states. + // REQUIRES: `rep_->state == kBuffered` + void EnterUnbuffered(); + // Call block's Finish() method // and then write the compressed block contents to file. void WriteBlock(BlockBuilder* block, BlockHandle* handle, bool is_data_block); @@ -96,10 +105,21 @@ class BlockBasedTableBuilder : public TableBuilder { void WriteBlock(const Slice& block_contents, BlockHandle* handle, bool is_data_block); // Directly write data to the file. - void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle); + void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle, + bool is_data_block = false); Status InsertBlockInCache(const Slice& block_contents, const CompressionType type, const BlockHandle* handle); + + void WriteFilterBlock(MetaIndexBuilder* meta_index_builder); + void WriteIndexBlock(MetaIndexBuilder* meta_index_builder, + BlockHandle* index_block_handle); + void WritePropertiesBlock(MetaIndexBuilder* meta_index_builder); + void WriteCompressionDictBlock(MetaIndexBuilder* meta_index_builder); + void WriteRangeDelBlock(MetaIndexBuilder* meta_index_builder); + void WriteFooter(BlockHandle& metaindex_block_handle, + BlockHandle& index_block_handle); + struct Rep; class BlockBasedTablePropertiesCollectorFactory; class BlockBasedTablePropertiesCollector; @@ -114,16 +134,12 @@ class BlockBasedTableBuilder : public TableBuilder { // Some compression libraries fail when the raw size is bigger than int. If // uncompressed size is bigger than kCompressionSizeLimit, don't compress it const uint64_t kCompressionSizeLimit = std::numeric_limits::max(); - - // No copying allowed - BlockBasedTableBuilder(const BlockBasedTableBuilder&) = delete; - void operator=(const BlockBasedTableBuilder&) = delete; }; -Slice CompressBlock(const Slice& raw, - const CompressionOptions& compression_options, +Slice CompressBlock(const Slice& raw, const CompressionInfo& info, CompressionType* type, uint32_t format_version, - const Slice& compression_dict, - std::string* compressed_output); + bool do_sample, std::string* compressed_output, + std::string* sampled_output_fast, + std::string* sampled_output_slow); } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_based_table_factory.cc b/thirdparty/rocksdb/table/block_based_table_factory.cc index 0c6bbbcb64..cda8d1e271 100644 --- a/thirdparty/rocksdb/table/block_based_table_factory.cc +++ b/thirdparty/rocksdb/table/block_based_table_factory.cc @@ -9,9 +9,15 @@ #include "table/block_based_table_factory.h" +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include +#include + #include #include -#include #include "options/options_helper.h" #include "port/port.h" @@ -21,10 +27,141 @@ #include "table/block_based_table_builder.h" #include "table/block_based_table_reader.h" #include "table/format.h" +#include "util/mutexlock.h" #include "util/string_util.h" namespace rocksdb { +void TailPrefetchStats::RecordEffectiveSize(size_t len) { + MutexLock l(&mutex_); + if (num_records_ < kNumTracked) { + num_records_++; + } + records_[next_++] = len; + if (next_ == kNumTracked) { + next_ = 0; + } +} + +size_t TailPrefetchStats::GetSuggestedPrefetchSize() { + std::vector sorted; + { + MutexLock l(&mutex_); + + if (num_records_ == 0) { + return 0; + } + sorted.assign(records_, records_ + num_records_); + } + + // Of the historic size, we find the maximum one that satisifis the condtiion + // that if prefetching all, less than 1/8 will be wasted. + std::sort(sorted.begin(), sorted.end()); + + // Assuming we have 5 data points, and after sorting it looks like this: + // + // +---+ + // +---+ | | + // | | | | + // | | | | + // | | | | + // | | | | + // +---+ | | | | + // | | | | | | + // +---+ | | | | | | + // | | | | | | | | + // +---+ | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // +---+ +---+ +---+ +---+ +---+ + // + // and we use every of the value as a candidate, and estimate how much we + // wasted, compared to read. For example, when we use the 3rd record + // as candiate. This area is what we read: + // +---+ + // +---+ | | + // | | | | + // | | | | + // | | | | + // | | | | + // *** *** *** ***+ *** *** *** *** ** + // * | | | | | | + // +---+ | | | | | * + // * | | | | | | | | + // +---+ | | | | | | | * + // * | | | | X | | | | | + // | | | | | | | | | * + // * | | | | | | | | | + // | | | | | | | | | * + // * | | | | | | | | | + // *** *** ***-*** ***--*** ***--*** +**** + // which is (size of the record) X (number of records). + // + // While wasted is this area: + // +---+ + // +---+ | | + // | | | | + // | | | | + // | | | | + // | | | | + // *** *** *** ****---+ | | | | + // * * | | | | | + // * *-*** *** | | | | | + // * * | | | | | | | + // *--** *** | | | | | | | + // | | | | | X | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // +---+ +---+ +---+ +---+ +---+ + // + // Which can be calculated iteratively. + // The difference between wasted using 4st and 3rd record, will + // be following area: + // +---+ + // +--+ +-+ ++ +-+ +-+ +---+ | | + // + xxxxxxxxxxxxxxxxxxxxxxxx | | | | + // xxxxxxxxxxxxxxxxxxxxxxxx | | | | + // + xxxxxxxxxxxxxxxxxxxxxxxx | | | | + // | xxxxxxxxxxxxxxxxxxxxxxxx | | | | + // +-+ +-+ +-+ ++ +---+ +--+ | | | + // | | | | | | | + // +---+ ++ | | | | | | + // | | | | | | X | | | + // +---+ ++ | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // | | | | | | | | | | + // +---+ +---+ +---+ +---+ +---+ + // + // which will be the size difference between 4st and 3rd record, + // times 3, which is number of records before the 4st. + // Here we assume that all data within the prefetch range will be useful. In + // reality, it may not be the case when a partial block is inside the range, + // or there are data in the middle that is not read. We ignore those cases + // for simplicity. + assert(!sorted.empty()); + size_t prev_size = sorted[0]; + size_t max_qualified_size = sorted[0]; + size_t wasted = 0; + for (size_t i = 1; i < sorted.size(); i++) { + size_t read = sorted[i] * sorted.size(); + wasted += (sorted[i] - prev_size) * i; + if (wasted <= read / 8) { + max_qualified_size = sorted[i]; + } + prev_size = sorted[i]; + } + const size_t kMaxPrefetchSize = 512 * 1024; // Never exceed 512KB + return std::min(kMaxPrefetchSize, max_qualified_size); +} + BlockBasedTableFactory::BlockBasedTableFactory( const BlockBasedTableOptions& _table_options) : table_options_(_table_options) { @@ -57,45 +194,49 @@ BlockBasedTableFactory::BlockBasedTableFactory( Status BlockBasedTableFactory::NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table_reader, + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table_reader, bool prefetch_index_and_filter_in_cache) const { return BlockBasedTable::Open( table_reader_options.ioptions, table_reader_options.env_options, table_options_, table_reader_options.internal_comparator, std::move(file), - file_size, table_reader, prefetch_index_and_filter_in_cache, - table_reader_options.skip_filters, table_reader_options.level); + file_size, table_reader, table_reader_options.prefix_extractor, + prefetch_index_and_filter_in_cache, table_reader_options.skip_filters, + table_reader_options.level, table_reader_options.immortal, + table_reader_options.largest_seqno, &tail_prefetch_stats_); } TableBuilder* BlockBasedTableFactory::NewTableBuilder( const TableBuilderOptions& table_builder_options, uint32_t column_family_id, WritableFileWriter* file) const { auto table_builder = new BlockBasedTableBuilder( - table_builder_options.ioptions, table_options_, - table_builder_options.internal_comparator, + table_builder_options.ioptions, table_builder_options.moptions, + table_options_, table_builder_options.internal_comparator, table_builder_options.int_tbl_prop_collector_factories, column_family_id, file, table_builder_options.compression_type, + table_builder_options.sample_for_compression, table_builder_options.compression_opts, - table_builder_options.compression_dict, table_builder_options.skip_filters, table_builder_options.column_family_name, table_builder_options.creation_time, - table_builder_options.oldest_key_time); + table_builder_options.oldest_key_time, + table_builder_options.target_file_size); return table_builder; } Status BlockBasedTableFactory::SanitizeOptions( - const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const { + const DBOptions& /*db_opts*/, const ColumnFamilyOptions& cf_opts) const { if (table_options_.index_type == BlockBasedTableOptions::kHashSearch && cf_opts.prefix_extractor == nullptr) { - return Status::InvalidArgument("Hash index is specified for block-based " + return Status::InvalidArgument( + "Hash index is specified for block-based " "table, but prefix_extractor is not given"); } if (table_options_.cache_index_and_filter_blocks && table_options_.no_block_cache) { - return Status::InvalidArgument("Enable cache_index_and_filter_blocks, " + return Status::InvalidArgument( + "Enable cache_index_and_filter_blocks, " ", but block cache is disabled"); } if (table_options_.pin_l0_filter_and_index_blocks_in_cache && @@ -109,6 +250,23 @@ Status BlockBasedTableFactory::SanitizeOptions( "Unsupported BlockBasedTable format_version. Please check " "include/rocksdb/table.h for more info"); } + if (table_options_.block_align && (cf_opts.compression != kNoCompression)) { + return Status::InvalidArgument( + "Enable block_align, but compression " + "enabled"); + } + if (table_options_.block_align && + (table_options_.block_size & (table_options_.block_size - 1))) { + return Status::InvalidArgument( + "Block alignment requested but block size is not a power of 2"); + } + if (table_options_.data_block_index_type == + BlockBasedTableOptions::kDataBlockBinaryAndHash && + table_options_.data_block_hash_table_util_ratio <= 0) { + return Status::InvalidArgument( + "data_block_hash_table_util_ratio should be greater than 0 when " + "data_block_index_type is set to kDataBlockBinaryAndHash"); + } return Status::OK(); } @@ -133,14 +291,22 @@ std::string BlockBasedTableFactory::GetPrintableTableOptions() const { " pin_l0_filter_and_index_blocks_in_cache: %d\n", table_options_.pin_l0_filter_and_index_blocks_in_cache); ret.append(buffer); + snprintf(buffer, kBufferSize, " pin_top_level_index_and_filter: %d\n", + table_options_.pin_top_level_index_and_filter); + ret.append(buffer); snprintf(buffer, kBufferSize, " index_type: %d\n", table_options_.index_type); ret.append(buffer); + snprintf(buffer, kBufferSize, " data_block_index_type: %d\n", + table_options_.data_block_index_type); + ret.append(buffer); + snprintf(buffer, kBufferSize, " data_block_hash_table_util_ratio: %lf\n", + table_options_.data_block_hash_table_util_ratio); + ret.append(buffer); snprintf(buffer, kBufferSize, " hash_index_allow_collision: %d\n", table_options_.hash_index_allow_collision); ret.append(buffer); - snprintf(buffer, kBufferSize, " checksum: %d\n", - table_options_.checksum); + snprintf(buffer, kBufferSize, " checksum: %d\n", table_options_.checksum); ret.append(buffer); snprintf(buffer, kBufferSize, " no_block_cache: %d\n", table_options_.no_block_cache); @@ -192,16 +358,38 @@ std::string BlockBasedTableFactory::GetPrintableTableOptions() const { snprintf(buffer, kBufferSize, " index_block_restart_interval: %d\n", table_options_.index_block_restart_interval); ret.append(buffer); + snprintf(buffer, kBufferSize, " metadata_block_size: %" PRIu64 "\n", + table_options_.metadata_block_size); + ret.append(buffer); + snprintf(buffer, kBufferSize, " partition_filters: %d\n", + table_options_.partition_filters); + ret.append(buffer); + snprintf(buffer, kBufferSize, " use_delta_encoding: %d\n", + table_options_.use_delta_encoding); + ret.append(buffer); snprintf(buffer, kBufferSize, " filter_policy: %s\n", - table_options_.filter_policy == nullptr ? - "nullptr" : table_options_.filter_policy->Name()); + table_options_.filter_policy == nullptr + ? "nullptr" + : table_options_.filter_policy->Name()); ret.append(buffer); snprintf(buffer, kBufferSize, " whole_key_filtering: %d\n", table_options_.whole_key_filtering); ret.append(buffer); + snprintf(buffer, kBufferSize, " verify_compression: %d\n", + table_options_.verify_compression); + ret.append(buffer); + snprintf(buffer, kBufferSize, " read_amp_bytes_per_bit: %d\n", + table_options_.read_amp_bytes_per_bit); + ret.append(buffer); snprintf(buffer, kBufferSize, " format_version: %d\n", table_options_.format_version); ret.append(buffer); + snprintf(buffer, kBufferSize, " enable_index_compression: %d\n", + table_options_.enable_index_compression); + ret.append(buffer); + snprintf(buffer, kBufferSize, " block_align: %d\n", + table_options_.block_align); + ret.append(buffer); return ret; } @@ -249,7 +437,7 @@ Status BlockBasedTableFactory::GetOptionString( } #else Status BlockBasedTableFactory::GetOptionString( - std::string* opt_string, const std::string& delimiter) const { + std::string* /*opt_string*/, const std::string& /*delimiter*/) const { return Status::OK(); } #endif // !ROCKSDB_LITE @@ -270,11 +458,31 @@ std::string ParseBlockBasedTableOption(const std::string& name, if (!input_strings_escaped) { // if the input string is not escaped, it means this function is // invoked from SetOptions, which takes the old format. - if (name == "block_cache") { - new_options->block_cache = NewLRUCache(ParseSizeT(value)); - return ""; - } else if (name == "block_cache_compressed") { - new_options->block_cache_compressed = NewLRUCache(ParseSizeT(value)); + if (name == "block_cache" || name == "block_cache_compressed") { + // cache options can be specified in the following format + // "block_cache={capacity=1M;num_shard_bits=4; + // strict_capacity_limit=true;high_pri_pool_ratio=0.5;}" + // To support backward compatibility, the following format + // is also supported. + // "block_cache=1M" + std::shared_ptr cache; + // block_cache is specified in format block_cache=. + if (value.find('=') == std::string::npos) { + cache = NewLRUCache(ParseSizeT(value)); + } else { + LRUCacheOptions cache_opts; + if (!ParseOptionHelper(reinterpret_cast(&cache_opts), + OptionType::kLRUCacheOptions, value)) { + return "Invalid cache options"; + } + cache = NewLRUCache(cache_opts); + } + + if (name == "block_cache") { + new_options->block_cache = cache; + } else { + new_options->block_cache_compressed = cache; + } return ""; } else if (name == "filter_policy") { // Expect the following format @@ -347,6 +555,8 @@ Status GetBlockBasedTableOptionsFromMap( (iter->second.verification != OptionVerificationType::kByName && iter->second.verification != OptionVerificationType::kByNameAllowNull && + iter->second.verification != + OptionVerificationType::kByNameAllowFromNull && iter->second.verification != OptionVerificationType::kDeprecated)) { // Restore "new_options" to the default "base_options". *new_table_options = table_options; diff --git a/thirdparty/rocksdb/table/block_based_table_factory.h b/thirdparty/rocksdb/table/block_based_table_factory.h index 39e3eac0b3..100bb0bc41 100644 --- a/thirdparty/rocksdb/table/block_based_table_factory.h +++ b/thirdparty/rocksdb/table/block_based_table_factory.h @@ -23,9 +23,24 @@ namespace rocksdb { struct EnvOptions; -using std::unique_ptr; class BlockBasedTableBuilder; +// A class used to track actual bytes written from the tail in the recent SST +// file opens, and provide a suggestion for following open. +class TailPrefetchStats { + public: + void RecordEffectiveSize(size_t len); + // 0 indicates no information to determine. + size_t GetSuggestedPrefetchSize(); + + private: + const static size_t kNumTracked = 32; + size_t records_[kNumTracked]; + port::Mutex mutex_; + size_t next_ = 0; + size_t num_records_ = 0; +}; + class BlockBasedTableFactory : public TableFactory { public: explicit BlockBasedTableFactory( @@ -37,8 +52,8 @@ class BlockBasedTableFactory : public TableFactory { Status NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table_reader, + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table_reader, bool prefetch_index_and_filter_in_cache = true) const override; TableBuilder* NewTableBuilder( @@ -64,6 +79,7 @@ class BlockBasedTableFactory : public TableFactory { private: BlockBasedTableOptions table_options_; + mutable TailPrefetchStats tail_prefetch_stats_; }; extern const std::string kHashIndexPrefixesBlock; @@ -106,6 +122,14 @@ static std::unordered_map {"hash_index_allow_collision", {offsetof(struct BlockBasedTableOptions, hash_index_allow_collision), OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"data_block_index_type", + {offsetof(struct BlockBasedTableOptions, data_block_index_type), + OptionType::kBlockBasedTableDataBlockIndexType, + OptionVerificationType::kNormal, false, 0}}, + {"data_block_hash_table_util_ratio", + {offsetof(struct BlockBasedTableOptions, + data_block_hash_table_util_ratio), + OptionType::kDouble, OptionVerificationType::kNormal, false, 0}}, {"checksum", {offsetof(struct BlockBasedTableOptions, checksum), OptionType::kChecksumType, OptionVerificationType::kNormal, false, @@ -152,6 +176,16 @@ static std::unordered_map OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, {"read_amp_bytes_per_bit", {offsetof(struct BlockBasedTableOptions, read_amp_bytes_per_bit), - OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}}; + OptionType::kSizeT, OptionVerificationType::kNormal, false, 0}}, + {"enable_index_compression", + {offsetof(struct BlockBasedTableOptions, enable_index_compression), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"block_align", + {offsetof(struct BlockBasedTableOptions, block_align), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}, + {"pin_top_level_index_and_filter", + {offsetof(struct BlockBasedTableOptions, + pin_top_level_index_and_filter), + OptionType::kBoolean, OptionVerificationType::kNormal, false, 0}}}; #endif // !ROCKSDB_LITE } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_based_table_reader.cc b/thirdparty/rocksdb/table/block_based_table_reader.cc index d8c6d807c8..dc2d4263ee 100644 --- a/thirdparty/rocksdb/table/block_based_table_reader.cc +++ b/thirdparty/rocksdb/table/block_based_table_reader.cc @@ -9,6 +9,7 @@ #include "table/block_based_table_reader.h" #include +#include #include #include #include @@ -30,6 +31,7 @@ #include "table/block.h" #include "table/block_based_filter_block.h" #include "table/block_based_table_factory.h" +#include "table/block_fetcher.h" #include "table/block_prefix_index.h" #include "table/filter_block.h" #include "table/format.h" @@ -44,17 +46,18 @@ #include "monitoring/perf_context_imp.h" #include "util/coding.h" +#include "util/crc32c.h" #include "util/file_reader_writer.h" #include "util/stop_watch.h" #include "util/string_util.h" #include "util/sync_point.h" +#include "util/xxhash.h" namespace rocksdb { extern const uint64_t kBlockBasedTableMagicNumber; extern const std::string kHashIndexPrefixesBlock; extern const std::string kHashIndexPrefixesMetadataBlock; -using std::unique_ptr; typedef BlockBasedTable::IndexReader IndexReader; @@ -63,24 +66,29 @@ BlockBasedTable::~BlockBasedTable() { delete rep_; } +std::atomic BlockBasedTable::next_cache_key_id_(0); + namespace { // Read the block identified by "handle" from "file". // The only relevant option is options.verify_checksums for now. // On failure return non-OK. // On success fill *result and return OK - caller owns *result -// @param compression_dict Data for presetting the compression library's +// @param uncompression_dict Data for presetting the compression library's // dictionary. Status ReadBlockFromFile( RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer, const Footer& footer, const ReadOptions& options, const BlockHandle& handle, std::unique_ptr* result, const ImmutableCFOptions& ioptions, - bool do_uncompress, const Slice& compression_dict, + bool do_uncompress, bool maybe_compressed, + const UncompressionDict& uncompression_dict, const PersistentCacheOptions& cache_options, SequenceNumber global_seqno, - size_t read_amp_bytes_per_bit) { + size_t read_amp_bytes_per_bit, MemoryAllocator* memory_allocator) { BlockContents contents; - Status s = ReadBlockContents(file, prefetch_buffer, footer, options, handle, - &contents, ioptions, do_uncompress, - compression_dict, cache_options); + BlockFetcher block_fetcher(file, prefetch_buffer, footer, options, handle, + &contents, ioptions, do_uncompress, + maybe_compressed, uncompression_dict, + cache_options, memory_allocator); + Status s = block_fetcher.ReadBlockContents(); if (s.ok()) { result->reset(new Block(std::move(contents), global_seqno, read_amp_bytes_per_bit, ioptions.statistics)); @@ -89,21 +97,36 @@ Status ReadBlockFromFile( return s; } +inline MemoryAllocator* GetMemoryAllocator( + const BlockBasedTableOptions& table_options) { + return table_options.block_cache.get() + ? table_options.block_cache->memory_allocator() + : nullptr; +} + +inline MemoryAllocator* GetMemoryAllocatorForCompressedBlock( + const BlockBasedTableOptions& table_options) { + return table_options.block_cache_compressed.get() + ? table_options.block_cache_compressed->memory_allocator() + : nullptr; +} + // Delete the resource that is held by the iterator. template -void DeleteHeldResource(void* arg, void* ignored) { +void DeleteHeldResource(void* arg, void* /*ignored*/) { delete reinterpret_cast(arg); } // Delete the entry resided in the cache. template -void DeleteCachedEntry(const Slice& key, void* value) { +void DeleteCachedEntry(const Slice& /*key*/, void* value) { auto entry = reinterpret_cast(value); delete entry; } void DeleteCachedFilterEntry(const Slice& key, void* value); void DeleteCachedIndexEntry(const Slice& key, void* value); +void DeleteCachedUncompressionDictEntry(const Slice& key, void* value); // Release the cached entry and decrement its ref count. void ReleaseCachedEntry(void* arg, void* h) { @@ -112,6 +135,13 @@ void ReleaseCachedEntry(void* arg, void* h) { cache->Release(handle); } +// Release the cached entry and decrement its ref count. +void ForceReleaseCachedEntry(void* arg, void* h) { + Cache* cache = reinterpret_cast(arg); + Cache::Handle* handle = reinterpret_cast(h); + cache->Release(handle, true /* force_erase */); +} + Slice GetCacheKeyFromOffset(const char* cache_key_prefix, size_t cache_key_prefix_size, uint64_t offset, char* cache_key) { @@ -124,29 +154,72 @@ Slice GetCacheKeyFromOffset(const char* cache_key_prefix, } Cache::Handle* GetEntryFromCache(Cache* block_cache, const Slice& key, - Tickers block_cache_miss_ticker, + int level, Tickers block_cache_miss_ticker, Tickers block_cache_hit_ticker, - Statistics* statistics) { + uint64_t* block_cache_miss_stats, + uint64_t* block_cache_hit_stats, + Statistics* statistics, + GetContext* get_context) { auto cache_handle = block_cache->Lookup(key, statistics); if (cache_handle != nullptr) { PERF_COUNTER_ADD(block_cache_hit_count, 1); - // overall cache hit - RecordTick(statistics, BLOCK_CACHE_HIT); - // total bytes read from cache - RecordTick(statistics, BLOCK_CACHE_BYTES_READ, - block_cache->GetUsage(cache_handle)); - // block-type specific cache hit - RecordTick(statistics, block_cache_hit_ticker); + PERF_COUNTER_BY_LEVEL_ADD(block_cache_hit_count, 1, + static_cast(level)); + if (get_context != nullptr) { + // overall cache hit + get_context->get_context_stats_.num_cache_hit++; + // total bytes read from cache + get_context->get_context_stats_.num_cache_bytes_read += + block_cache->GetUsage(cache_handle); + // block-type specific cache hit + (*block_cache_hit_stats)++; + } else { + // overall cache hit + RecordTick(statistics, BLOCK_CACHE_HIT); + // total bytes read from cache + RecordTick(statistics, BLOCK_CACHE_BYTES_READ, + block_cache->GetUsage(cache_handle)); + RecordTick(statistics, block_cache_hit_ticker); + } } else { - // overall cache miss - RecordTick(statistics, BLOCK_CACHE_MISS); - // block-type specific cache miss - RecordTick(statistics, block_cache_miss_ticker); + PERF_COUNTER_BY_LEVEL_ADD(block_cache_miss_count, 1, + static_cast(level)); + if (get_context != nullptr) { + // overall cache miss + get_context->get_context_stats_.num_cache_miss++; + // block-type specific cache miss + (*block_cache_miss_stats)++; + } else { + RecordTick(statistics, BLOCK_CACHE_MISS); + RecordTick(statistics, block_cache_miss_ticker); + } } return cache_handle; } +// For hash based index, return true if prefix_extractor and +// prefix_extractor_block mismatch, false otherwise. This flag will be used +// as total_order_seek via NewIndexIterator +bool PrefixExtractorChanged(const TableProperties* table_properties, + const SliceTransform* prefix_extractor) { + // BlockBasedTableOptions::kHashSearch requires prefix_extractor to be set. + // Turn off hash index in prefix_extractor is not set; if prefix_extractor + // is set but prefix_extractor_block is not set, also disable hash index + if (prefix_extractor == nullptr || table_properties == nullptr || + table_properties->prefix_extractor_name.empty()) { + return true; + } + + // prefix_extractor and prefix_extractor_block are both non-empty + if (table_properties->prefix_extractor_name.compare( + prefix_extractor->Name()) != 0) { + return true; + } else { + return false; + } +} + } // namespace // Index that allows binary search lookup in a two-level index structure. @@ -163,137 +236,164 @@ class PartitionIndexReader : public IndexReader, public Cleanable { const InternalKeyComparator* icomparator, IndexReader** index_reader, const PersistentCacheOptions& cache_options, - const int level) { + const int level, const bool index_key_includes_seq, + const bool index_value_is_full, + MemoryAllocator* memory_allocator) { std::unique_ptr index_block; auto s = ReadBlockFromFile( file, prefetch_buffer, footer, ReadOptions(), index_handle, &index_block, ioptions, true /* decompress */, - Slice() /*compression dict*/, cache_options, - kDisableGlobalSequenceNumber, 0 /* read_amp_bytes_per_bit */); + true /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + cache_options, kDisableGlobalSequenceNumber, + 0 /* read_amp_bytes_per_bit */, memory_allocator); if (s.ok()) { - *index_reader = - new PartitionIndexReader(table, icomparator, std::move(index_block), - ioptions.statistics, level); + *index_reader = new PartitionIndexReader( + table, icomparator, std::move(index_block), ioptions.statistics, + level, index_key_includes_seq, index_value_is_full); } return s; } // return a two-level iterator: first level is on the partition index - virtual InternalIterator* NewIterator(BlockIter* iter = nullptr, - bool dont_care = true) override { + InternalIteratorBase* NewIterator( + IndexBlockIter* /*iter*/ = nullptr, bool /*dont_care*/ = true, + bool fill_cache = true) override { + Statistics* kNullStats = nullptr; // Filters are already checked before seeking the index - const bool skip_filters = true; - const bool is_index = true; - return NewTwoLevelIterator( - new BlockBasedTable::BlockEntryIteratorState( - table_, ReadOptions(), icomparator_, skip_filters, is_index, - partition_map_.size() ? &partition_map_ : nullptr), - index_block_->NewIterator(icomparator_, nullptr, true)); + if (!partition_map_.empty()) { + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. + return NewTwoLevelIterator( + new BlockBasedTable::PartitionedIndexIteratorState( + table_, &partition_map_, index_key_includes_seq_, + index_value_is_full_), + index_block_->NewIterator( + icomparator_, icomparator_->user_comparator(), nullptr, + kNullStats, true, index_key_includes_seq_, index_value_is_full_)); + } else { + auto ro = ReadOptions(); + ro.fill_cache = fill_cache; + bool kIsIndex = true; + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. + return new BlockBasedTableIterator( + table_, ro, *icomparator_, + index_block_->NewIterator( + icomparator_, icomparator_->user_comparator(), nullptr, + kNullStats, true, index_key_includes_seq_, index_value_is_full_), + false, true, /* prefix_extractor */ nullptr, kIsIndex, + index_key_includes_seq_, index_value_is_full_); + } // TODO(myabandeh): Update TwoLevelIterator to be able to make use of // on-stack BlockIter while the state is on heap. Currentlly it assumes // the first level iter is always on heap and will attempt to delete it // in its destructor. } - virtual void CacheDependencies(bool pin) override { + void CacheDependencies(bool pin) override { // Before read partitions, prefetch them to avoid lots of IOs auto rep = table_->rep_; - BlockIter biter; + IndexBlockIter biter; BlockHandle handle; - index_block_->NewIterator(icomparator_, &biter, true); + Statistics* kNullStats = nullptr; + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. + index_block_->NewIterator( + icomparator_, icomparator_->user_comparator(), &biter, kNullStats, true, + index_key_includes_seq_, index_value_is_full_); // Index partitions are assumed to be consecuitive. Prefetch them all. // Read the first block offset biter.SeekToFirst(); - Slice input = biter.value(); - Status s = handle.DecodeFrom(&input); - assert(s.ok()); - if (!s.ok()) { - ROCKS_LOG_WARN(rep->ioptions.info_log, - "Could not read first index partition"); + if (!biter.Valid()) { + // Empty index. return; } + handle = biter.value(); uint64_t prefetch_off = handle.offset(); // Read the last block's offset biter.SeekToLast(); - input = biter.value(); - s = handle.DecodeFrom(&input); - assert(s.ok()); - if (!s.ok()) { - ROCKS_LOG_WARN(rep->ioptions.info_log, - "Could not read last index partition"); + if (!biter.Valid()) { + // Empty index. return; } + handle = biter.value(); uint64_t last_off = handle.offset() + handle.size() + kBlockTrailerSize; uint64_t prefetch_len = last_off - prefetch_off; std::unique_ptr prefetch_buffer; auto& file = table_->rep_->file; prefetch_buffer.reset(new FilePrefetchBuffer()); - s = prefetch_buffer->Prefetch(file.get(), prefetch_off, prefetch_len); + Status s = prefetch_buffer->Prefetch(file.get(), prefetch_off, + static_cast(prefetch_len)); // After prefetch, read the partitions one by one biter.SeekToFirst(); auto ro = ReadOptions(); Cache* block_cache = rep->table_options.block_cache.get(); for (; biter.Valid(); biter.Next()) { - input = biter.value(); - s = handle.DecodeFrom(&input); - assert(s.ok()); - if (!s.ok()) { - ROCKS_LOG_WARN(rep->ioptions.info_log, - "Could not read index partition"); - continue; - } - + handle = biter.value(); BlockBasedTable::CachableEntry block; - Slice compression_dict; - if (rep->compression_dict_block) { - compression_dict = rep->compression_dict_block->data; - } const bool is_index = true; - s = table_->MaybeLoadDataBlockToCache(prefetch_buffer.get(), rep, ro, - handle, compression_dict, &block, - is_index); + // TODO: Support counter batch update for partitioned index and + // filter blocks + s = table_->MaybeReadBlockAndLoadToCache( + prefetch_buffer.get(), rep, ro, handle, + UncompressionDict::GetEmptyDict(), &block, is_index, + nullptr /* get_context */); assert(s.ok() || block.value == nullptr); if (s.ok() && block.value != nullptr) { - assert(block.cache_handle != nullptr); - if (pin) { - partition_map_[handle.offset()] = block; - RegisterCleanup(&ReleaseCachedEntry, block_cache, block.cache_handle); + if (block.cache_handle != nullptr) { + if (pin) { + partition_map_[handle.offset()] = block; + RegisterCleanup(&ReleaseCachedEntry, block_cache, + block.cache_handle); + } else { + block_cache->Release(block.cache_handle); + } } else { - block_cache->Release(block.cache_handle); + delete block.value; } } } } - virtual size_t size() const override { return index_block_->size(); } - virtual size_t usable_size() const override { - return index_block_->usable_size(); - } + size_t size() const override { return index_block_->size(); } + size_t usable_size() const override { return index_block_->usable_size(); } - virtual size_t ApproximateMemoryUsage() const override { + size_t ApproximateMemoryUsage() const override { assert(index_block_); - return index_block_->ApproximateMemoryUsage(); + size_t usage = index_block_->ApproximateMemoryUsage(); +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + usage += malloc_usable_size((void*)this); +#else + usage += sizeof(*this); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + // TODO(myabandeh): more accurate estimate of partition_map_ mem usage + return usage; } private: PartitionIndexReader(BlockBasedTable* table, const InternalKeyComparator* icomparator, std::unique_ptr&& index_block, Statistics* stats, - const int level) + const int /*level*/, const bool index_key_includes_seq, + const bool index_value_is_full) : IndexReader(icomparator, stats), table_(table), - index_block_(std::move(index_block)) { + index_block_(std::move(index_block)), + index_key_includes_seq_(index_key_includes_seq), + index_value_is_full_(index_value_is_full) { assert(index_block_ != nullptr); } BlockBasedTable* table_; std::unique_ptr index_block_; std::unordered_map> partition_map_; + const bool index_key_includes_seq_; + const bool index_value_is_full_; }; // Index that allows binary search lookup for the first key of each block. @@ -311,67 +411,89 @@ class BinarySearchIndexReader : public IndexReader { const ImmutableCFOptions& ioptions, const InternalKeyComparator* icomparator, IndexReader** index_reader, - const PersistentCacheOptions& cache_options) { + const PersistentCacheOptions& cache_options, + const bool index_key_includes_seq, + const bool index_value_is_full, + MemoryAllocator* memory_allocator) { std::unique_ptr index_block; auto s = ReadBlockFromFile( file, prefetch_buffer, footer, ReadOptions(), index_handle, &index_block, ioptions, true /* decompress */, - Slice() /*compression dict*/, cache_options, - kDisableGlobalSequenceNumber, 0 /* read_amp_bytes_per_bit */); + true /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + cache_options, kDisableGlobalSequenceNumber, + 0 /* read_amp_bytes_per_bit */, memory_allocator); if (s.ok()) { *index_reader = new BinarySearchIndexReader( - icomparator, std::move(index_block), ioptions.statistics); + icomparator, std::move(index_block), ioptions.statistics, + index_key_includes_seq, index_value_is_full); } return s; } - virtual InternalIterator* NewIterator(BlockIter* iter = nullptr, - bool dont_care = true) override { - return index_block_->NewIterator(icomparator_, iter, true); + InternalIteratorBase* NewIterator( + IndexBlockIter* iter = nullptr, bool /*dont_care*/ = true, + bool /*dont_care*/ = true) override { + Statistics* kNullStats = nullptr; + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. + return index_block_->NewIterator( + icomparator_, icomparator_->user_comparator(), iter, kNullStats, true, + index_key_includes_seq_, index_value_is_full_); } - virtual size_t size() const override { return index_block_->size(); } - virtual size_t usable_size() const override { - return index_block_->usable_size(); - } + size_t size() const override { return index_block_->size(); } + size_t usable_size() const override { return index_block_->usable_size(); } - virtual size_t ApproximateMemoryUsage() const override { + size_t ApproximateMemoryUsage() const override { assert(index_block_); - return index_block_->ApproximateMemoryUsage(); + size_t usage = index_block_->ApproximateMemoryUsage(); +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + usage += malloc_usable_size((void*)this); +#else + usage += sizeof(*this); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + return usage; } private: BinarySearchIndexReader(const InternalKeyComparator* icomparator, std::unique_ptr&& index_block, - Statistics* stats) - : IndexReader(icomparator, stats), index_block_(std::move(index_block)) { + Statistics* stats, const bool index_key_includes_seq, + const bool index_value_is_full) + : IndexReader(icomparator, stats), + index_block_(std::move(index_block)), + index_key_includes_seq_(index_key_includes_seq), + index_value_is_full_(index_value_is_full) { assert(index_block_ != nullptr); } std::unique_ptr index_block_; + const bool index_key_includes_seq_; + const bool index_value_is_full_; }; // Index that leverages an internal hash table to quicken the lookup for a given // key. class HashIndexReader : public IndexReader { public: - static Status Create(const SliceTransform* hash_key_extractor, - const Footer& footer, RandomAccessFileReader* file, - FilePrefetchBuffer* prefetch_buffer, - const ImmutableCFOptions& ioptions, - const InternalKeyComparator* icomparator, - const BlockHandle& index_handle, - InternalIterator* meta_index_iter, - IndexReader** index_reader, - bool hash_index_allow_collision, - const PersistentCacheOptions& cache_options) { + static Status Create( + const SliceTransform* hash_key_extractor, const Footer& footer, + RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer, + const ImmutableCFOptions& ioptions, + const InternalKeyComparator* icomparator, const BlockHandle& index_handle, + InternalIterator* meta_index_iter, IndexReader** index_reader, + bool /*hash_index_allow_collision*/, + const PersistentCacheOptions& cache_options, + const bool index_key_includes_seq, const bool index_value_is_full, + MemoryAllocator* memory_allocator) { std::unique_ptr index_block; auto s = ReadBlockFromFile( file, prefetch_buffer, footer, ReadOptions(), index_handle, &index_block, ioptions, true /* decompress */, - Slice() /*compression dict*/, cache_options, - kDisableGlobalSequenceNumber, 0 /* read_amp_bytes_per_bit */); + true /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + cache_options, kDisableGlobalSequenceNumber, + 0 /* read_amp_bytes_per_bit */, memory_allocator); if (!s.ok()) { return s; @@ -381,9 +503,9 @@ class HashIndexReader : public IndexReader { // hard error. We can still fall back to the original binary search index. // So, Create will succeed regardless, from this point on. - auto new_index_reader = - new HashIndexReader(icomparator, std::move(index_block), - ioptions.statistics); + auto new_index_reader = new HashIndexReader( + icomparator, std::move(index_block), ioptions.statistics, + index_key_includes_seq, index_value_is_full); *index_reader = new_index_reader; // Get prefixes block @@ -406,18 +528,22 @@ class HashIndexReader : public IndexReader { // Read contents for the blocks BlockContents prefixes_contents; - s = ReadBlockContents(file, prefetch_buffer, footer, ReadOptions(), - prefixes_handle, &prefixes_contents, ioptions, - true /* decompress */, Slice() /*compression dict*/, - cache_options); + BlockFetcher prefixes_block_fetcher( + file, prefetch_buffer, footer, ReadOptions(), prefixes_handle, + &prefixes_contents, ioptions, true /*decompress*/, + true /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + cache_options, memory_allocator); + s = prefixes_block_fetcher.ReadBlockContents(); if (!s.ok()) { return s; } BlockContents prefixes_meta_contents; - s = ReadBlockContents(file, prefetch_buffer, footer, ReadOptions(), - prefixes_meta_handle, &prefixes_meta_contents, - ioptions, true /* decompress */, - Slice() /*compression dict*/, cache_options); + BlockFetcher prefixes_meta_block_fetcher( + file, prefetch_buffer, footer, ReadOptions(), prefixes_meta_handle, + &prefixes_meta_contents, ioptions, true /*decompress*/, + true /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + cache_options, memory_allocator); + s = prefixes_meta_block_fetcher.ReadBlockContents(); if (!s.ok()) { // TODO: log error return Status::OK(); @@ -428,40 +554,61 @@ class HashIndexReader : public IndexReader { prefixes_meta_contents.data, &prefix_index); // TODO: log error if (s.ok()) { - new_index_reader->index_block_->SetBlockPrefixIndex(prefix_index); + new_index_reader->prefix_index_.reset(prefix_index); } return Status::OK(); } - virtual InternalIterator* NewIterator(BlockIter* iter = nullptr, - bool total_order_seek = true) override { - return index_block_->NewIterator(icomparator_, iter, total_order_seek); + InternalIteratorBase* NewIterator( + IndexBlockIter* iter = nullptr, bool total_order_seek = true, + bool /*dont_care*/ = true) override { + Statistics* kNullStats = nullptr; + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. + return index_block_->NewIterator( + icomparator_, icomparator_->user_comparator(), iter, kNullStats, + total_order_seek, index_key_includes_seq_, index_value_is_full_, + false /* block_contents_pinned */, prefix_index_.get()); } - virtual size_t size() const override { return index_block_->size(); } - virtual size_t usable_size() const override { - return index_block_->usable_size(); - } + size_t size() const override { return index_block_->size(); } + size_t usable_size() const override { return index_block_->usable_size(); } - virtual size_t ApproximateMemoryUsage() const override { + size_t ApproximateMemoryUsage() const override { assert(index_block_); - return index_block_->ApproximateMemoryUsage() + - prefixes_contents_.data.size(); + size_t usage = index_block_->ApproximateMemoryUsage(); + usage += prefixes_contents_.usable_size(); +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + usage += malloc_usable_size((void*)this); +#else + if (prefix_index_) { + usage += prefix_index_->ApproximateMemoryUsage(); + } + usage += sizeof(*this); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + return usage; } private: HashIndexReader(const InternalKeyComparator* icomparator, - std::unique_ptr&& index_block, Statistics* stats) - : IndexReader(icomparator, stats), index_block_(std::move(index_block)) { + std::unique_ptr&& index_block, Statistics* stats, + const bool index_key_includes_seq, + const bool index_value_is_full) + : IndexReader(icomparator, stats), + index_block_(std::move(index_block)), + index_key_includes_seq_(index_key_includes_seq), + index_value_is_full_(index_value_is_full) { assert(index_block_ != nullptr); } - ~HashIndexReader() { - } + ~HashIndexReader() override {} std::unique_ptr index_block_; + std::unique_ptr prefix_index_; BlockContents prefixes_contents_; + const bool index_key_includes_seq_; + const bool index_value_is_full_; }; // Helper function to setup the cache key's prefix for the Table. @@ -488,9 +635,8 @@ void BlockBasedTable::SetupCacheKeyPrefix(Rep* rep, uint64_t file_size) { } } -void BlockBasedTable::GenerateCachePrefix(Cache* cc, - RandomAccessFile* file, char* buffer, size_t* size) { - +void BlockBasedTable::GenerateCachePrefix(Cache* cc, RandomAccessFile* file, + char* buffer, size_t* size) { // generate an id from the file *size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize); @@ -502,9 +648,8 @@ void BlockBasedTable::GenerateCachePrefix(Cache* cc, } } -void BlockBasedTable::GenerateCachePrefix(Cache* cc, - WritableFile* file, char* buffer, size_t* size) { - +void BlockBasedTable::GenerateCachePrefix(Cache* cc, WritableFile* file, + char* buffer, size_t* size) { // generate an id from the file *size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize); @@ -535,51 +680,78 @@ bool IsFeatureSupported(const TableProperties& table_properties, return true; } -SequenceNumber GetGlobalSequenceNumber(const TableProperties& table_properties, - Logger* info_log) { - auto& props = table_properties.user_collected_properties; - - auto version_pos = props.find(ExternalSstFilePropertyNames::kVersion); - auto seqno_pos = props.find(ExternalSstFilePropertyNames::kGlobalSeqno); +// Caller has to ensure seqno is not nullptr. +Status GetGlobalSequenceNumber(const TableProperties& table_properties, + SequenceNumber largest_seqno, + SequenceNumber* seqno) { + const auto& props = table_properties.user_collected_properties; + const auto version_pos = props.find(ExternalSstFilePropertyNames::kVersion); + const auto seqno_pos = props.find(ExternalSstFilePropertyNames::kGlobalSeqno); + *seqno = kDisableGlobalSequenceNumber; if (version_pos == props.end()) { if (seqno_pos != props.end()) { + std::array msg_buf; // This is not an external sst file, global_seqno is not supported. - assert(false); - ROCKS_LOG_ERROR( - info_log, + snprintf( + msg_buf.data(), msg_buf.max_size(), "A non-external sst file have global seqno property with value %s", seqno_pos->second.c_str()); + return Status::Corruption(msg_buf.data()); } - return kDisableGlobalSequenceNumber; + return Status::OK(); } uint32_t version = DecodeFixed32(version_pos->second.c_str()); if (version < 2) { if (seqno_pos != props.end() || version != 1) { + std::array msg_buf; // This is a v1 external sst file, global_seqno is not supported. - assert(false); - ROCKS_LOG_ERROR( - info_log, - "An external sst file with version %u have global seqno property " - "with value %s", - version, seqno_pos->second.c_str()); + snprintf(msg_buf.data(), msg_buf.max_size(), + "An external sst file with version %u have global seqno " + "property with value %s", + version, seqno_pos->second.c_str()); + return Status::Corruption(msg_buf.data()); } - return kDisableGlobalSequenceNumber; + return Status::OK(); } - SequenceNumber global_seqno = DecodeFixed64(seqno_pos->second.c_str()); + // Since we have a plan to deprecate global_seqno, we do not return failure + // if seqno_pos == props.end(). We rely on version_pos to detect whether the + // SST is external. + SequenceNumber global_seqno(0); + if (seqno_pos != props.end()) { + global_seqno = DecodeFixed64(seqno_pos->second.c_str()); + } + // SstTableReader open table reader with kMaxSequenceNumber as largest_seqno + // to denote it is unknown. + if (largest_seqno < kMaxSequenceNumber) { + if (global_seqno == 0) { + global_seqno = largest_seqno; + } + if (global_seqno != largest_seqno) { + std::array msg_buf; + snprintf( + msg_buf.data(), msg_buf.max_size(), + "An external sst file with version %u have global seqno property " + "with value %s, while largest seqno in the file is %llu", + version, seqno_pos->second.c_str(), + static_cast(largest_seqno)); + return Status::Corruption(msg_buf.data()); + } + } + *seqno = global_seqno; if (global_seqno > kMaxSequenceNumber) { - assert(false); - ROCKS_LOG_ERROR( - info_log, - "An external sst file with version %u have global seqno property " - "with value %llu, which is greater than kMaxSequenceNumber", - version, global_seqno); + std::array msg_buf; + snprintf(msg_buf.data(), msg_buf.max_size(), + "An external sst file with version %u have global seqno property " + "with value %llu, which is greater than kMaxSequenceNumber", + version, static_cast(global_seqno)); + return Status::Corruption(msg_buf.data()); } - return global_seqno; + return Status::OK(); } } // namespace @@ -599,36 +771,36 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions, const EnvOptions& env_options, const BlockBasedTableOptions& table_options, const InternalKeyComparator& internal_comparator, - unique_ptr&& file, + std::unique_ptr&& file, uint64_t file_size, - unique_ptr* table_reader, + std::unique_ptr* table_reader, + const SliceTransform* prefix_extractor, const bool prefetch_index_and_filter_in_cache, - const bool skip_filters, const int level) { + const bool skip_filters, const int level, + const bool immortal_table, + const SequenceNumber largest_seqno, + TailPrefetchStats* tail_prefetch_stats) { table_reader->reset(); + Status s; Footer footer; - std::unique_ptr prefetch_buffer; - // Before read footer, readahead backwards to prefetch data - const size_t kTailPrefetchSize = 512 * 1024; - size_t prefetch_off; - size_t prefetch_len; - if (file_size < kTailPrefetchSize) { - prefetch_off = 0; - prefetch_len = file_size; - } else { - prefetch_off = file_size - kTailPrefetchSize; - prefetch_len = kTailPrefetchSize; - } - Status s; - // TODO should not have this special logic in the future. - if (!file->use_direct_io()) { - s = file->Prefetch(prefetch_off, prefetch_len); - } else { - prefetch_buffer.reset(new FilePrefetchBuffer()); - s = prefetch_buffer->Prefetch(file.get(), prefetch_off, prefetch_len); - } + // prefetch both index and filters, down to all partitions + const bool prefetch_all = prefetch_index_and_filter_in_cache || level == 0; + const bool preload_all = !table_options.cache_index_and_filter_blocks; + + s = PrefetchTail(file.get(), file_size, tail_prefetch_stats, prefetch_all, + preload_all, &prefetch_buffer); + + // Read in the following order: + // 1. Footer + // 2. [metaindex block] + // 3. [meta block: properties] + // 4. [meta block: range deletion tombstone] + // 5. [meta block: compression dictionary] + // 6. [meta block: index] + // 7. [meta block: filter] s = ReadFooterFromFile(file.get(), prefetch_buffer.get(), file_size, &footer, kBlockBasedTableMagicNumber); if (!s.ok()) { @@ -645,7 +817,8 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions, // raw pointer will be used to create HashIndexReader, whose reset may // access a dangling pointer. Rep* rep = new BlockBasedTable::Rep(ioptions, env_options, table_options, - internal_comparator, skip_filters); + internal_comparator, skip_filters, level, + immortal_table); rep->file = std::move(file); rep->footer = footer; rep->index_type = table_options.index_type; @@ -653,18 +826,18 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions, // We need to wrap data with internal_prefix_transform to make sure it can // handle prefix correctly. rep->internal_prefix_transform.reset( - new InternalKeySliceTransform(rep->ioptions.prefix_extractor)); + new InternalKeySliceTransform(prefix_extractor)); SetupCacheKeyPrefix(rep, file_size); - unique_ptr new_table(new BlockBasedTable(rep)); + std::unique_ptr new_table(new BlockBasedTable(rep)); // page cache options rep->persistent_cache_options = PersistentCacheOptions(rep->table_options.persistent_cache, std::string(rep->persistent_cache_key_prefix, rep->persistent_cache_key_prefix_size), - rep->ioptions.statistics); + rep->ioptions.statistics); - // Read meta index + // Read metaindex std::unique_ptr meta; std::unique_ptr meta_iter; s = ReadMetaBlock(rep, prefetch_buffer.get(), &meta, &meta_iter); @@ -672,38 +845,147 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions, return s; } - // Find filter handle and filter type - if (rep->filter_policy) { - for (auto filter_type : - {Rep::FilterType::kFullFilter, Rep::FilterType::kPartitionedFilter, - Rep::FilterType::kBlockFilter}) { - std::string prefix; - switch (filter_type) { - case Rep::FilterType::kFullFilter: - prefix = kFullFilterBlockPrefix; - break; - case Rep::FilterType::kPartitionedFilter: - prefix = kPartitionedFilterBlockPrefix; - break; - case Rep::FilterType::kBlockFilter: - prefix = kFilterBlockPrefix; - break; - default: - assert(0); - } - std::string filter_block_key = prefix; - filter_block_key.append(rep->filter_policy->Name()); - if (FindMetaBlock(meta_iter.get(), filter_block_key, &rep->filter_handle) - .ok()) { - rep->filter_type = filter_type; - break; - } + s = ReadPropertiesBlock(rep, prefetch_buffer.get(), meta_iter.get(), + largest_seqno); + if (!s.ok()) { + return s; + } + s = ReadRangeDelBlock(rep, prefetch_buffer.get(), meta_iter.get(), + internal_comparator); + if (!s.ok()) { + return s; + } + s = PrefetchIndexAndFilterBlocks(rep, prefetch_buffer.get(), meta_iter.get(), + new_table.get(), prefix_extractor, + prefetch_all, table_options, level, + prefetch_index_and_filter_in_cache); + + if (s.ok()) { + // Update tail prefetch stats + assert(prefetch_buffer.get() != nullptr); + if (tail_prefetch_stats != nullptr) { + assert(prefetch_buffer->min_offset_read() < file_size); + tail_prefetch_stats->RecordEffectiveSize( + static_cast(file_size) - prefetch_buffer->min_offset_read()); } + + *table_reader = std::move(new_table); + } + + return s; +} + +Status BlockBasedTable::PrefetchTail( + RandomAccessFileReader* file, uint64_t file_size, + TailPrefetchStats* tail_prefetch_stats, const bool prefetch_all, + const bool preload_all, + std::unique_ptr* prefetch_buffer) { + size_t tail_prefetch_size = 0; + if (tail_prefetch_stats != nullptr) { + // Multiple threads may get a 0 (no history) when running in parallel, + // but it will get cleared after the first of them finishes. + tail_prefetch_size = tail_prefetch_stats->GetSuggestedPrefetchSize(); + } + if (tail_prefetch_size == 0) { + // Before read footer, readahead backwards to prefetch data. Do more + // readahead if we're going to read index/filter. + // TODO: This may incorrectly select small readahead in case partitioned + // index/filter is enabled and top-level partition pinning is enabled. + // That's because we need to issue readahead before we read the properties, + // at which point we don't yet know the index type. + tail_prefetch_size = prefetch_all || preload_all ? 512 * 1024 : 4 * 1024; + } + size_t prefetch_off; + size_t prefetch_len; + if (file_size < tail_prefetch_size) { + prefetch_off = 0; + prefetch_len = static_cast(file_size); + } else { + prefetch_off = static_cast(file_size - tail_prefetch_size); + prefetch_len = tail_prefetch_size; + } + TEST_SYNC_POINT_CALLBACK("BlockBasedTable::Open::TailPrefetchLen", + &tail_prefetch_size); + Status s; + // TODO should not have this special logic in the future. + if (!file->use_direct_io()) { + prefetch_buffer->reset(new FilePrefetchBuffer(nullptr, 0, 0, false, true)); + s = file->Prefetch(prefetch_off, prefetch_len); + } else { + prefetch_buffer->reset(new FilePrefetchBuffer(nullptr, 0, 0, true, true)); + s = (*prefetch_buffer)->Prefetch(file, prefetch_off, prefetch_len); + } + return s; +} + +Status VerifyChecksum(const ChecksumType type, const char* buf, size_t len, + uint32_t expected) { + Status s; + uint32_t actual = 0; + switch (type) { + case kNoChecksum: + break; + case kCRC32c: + expected = crc32c::Unmask(expected); + actual = crc32c::Value(buf, len); + break; + case kxxHash: + actual = XXH32(buf, static_cast(len), 0); + break; + case kxxHash64: + actual = static_cast(XXH64(buf, static_cast(len), 0) & + uint64_t{0xffffffff}); + break; + default: + s = Status::Corruption("unknown checksum type"); + } + if (s.ok() && actual != expected) { + s = Status::Corruption("properties block checksum mismatched"); + } + return s; +} + +Status BlockBasedTable::TryReadPropertiesWithGlobalSeqno( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, const Slice& handle_value, + TableProperties** table_properties) { + assert(table_properties != nullptr); + // If this is an external SST file ingested with write_global_seqno set to + // true, then we expect the checksum mismatch because checksum was written + // by SstFileWriter, but its global seqno in the properties block may have + // been changed during ingestion. In this case, we read the properties + // block, copy it to a memory buffer, change the global seqno to its + // original value, i.e. 0, and verify the checksum again. + BlockHandle props_block_handle; + CacheAllocationPtr tmp_buf; + Status s = ReadProperties(handle_value, rep->file.get(), prefetch_buffer, + rep->footer, rep->ioptions, table_properties, + false /* verify_checksum */, &props_block_handle, + &tmp_buf, false /* compression_type_missing */, + nullptr /* memory_allocator */); + if (s.ok() && tmp_buf) { + const auto seqno_pos_iter = + (*table_properties) + ->properties_offsets.find( + ExternalSstFilePropertyNames::kGlobalSeqno); + size_t block_size = props_block_handle.size(); + if (seqno_pos_iter != (*table_properties)->properties_offsets.end()) { + uint64_t global_seqno_offset = seqno_pos_iter->second; + EncodeFixed64( + tmp_buf.get() + global_seqno_offset - props_block_handle.offset(), 0); + } + uint32_t value = DecodeFixed32(tmp_buf.get() + block_size + 1); + s = rocksdb::VerifyChecksum(rep->footer.checksum(), tmp_buf.get(), + block_size + 1, value); } + return s; +} - // Read the properties +Status BlockBasedTable::ReadPropertiesBlock( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter, + const SequenceNumber largest_seqno) { bool found_properties_block = true; - s = SeekToPropertiesBlock(meta_iter.get(), &found_properties_block); + Status s; + s = SeekToPropertiesBlock(meta_iter, &found_properties_block); if (!s.ok()) { ROCKS_LOG_WARN(rep->ioptions.info_log, @@ -713,9 +995,20 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions, s = meta_iter->status(); TableProperties* table_properties = nullptr; if (s.ok()) { - s = ReadProperties(meta_iter->value(), rep->file.get(), - prefetch_buffer.get(), rep->footer, rep->ioptions, - &table_properties); + s = ReadProperties( + meta_iter->value(), rep->file.get(), prefetch_buffer, rep->footer, + rep->ioptions, &table_properties, true /* verify_checksum */, + nullptr /* ret_block_handle */, nullptr /* ret_block_contents */, + false /* compression_type_missing */, nullptr /* memory_allocator */); + } + + if (s.IsCorruption()) { + s = TryReadPropertiesWithGlobalSeqno( + rep, prefetch_buffer, meta_iter->value(), &table_properties); + } + std::unique_ptr props_guard; + if (table_properties != nullptr) { + props_guard.reset(table_properties); } if (!s.ok()) { @@ -724,32 +1017,98 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions, "block %s", s.ToString().c_str()); } else { - rep->table_properties.reset(table_properties); + assert(table_properties != nullptr); + rep->table_properties.reset(props_guard.release()); + rep->blocks_maybe_compressed = rep->table_properties->compression_name != + CompressionTypeToString(kNoCompression); + rep->blocks_definitely_zstd_compressed = + (rep->table_properties->compression_name == + CompressionTypeToString(kZSTD) || + rep->table_properties->compression_name == + CompressionTypeToString(kZSTDNotFinalCompression)); } } else { ROCKS_LOG_ERROR(rep->ioptions.info_log, "Cannot find Properties block from file."); } +#ifndef ROCKSDB_LITE + if (rep->table_properties) { + ParseSliceTransform(rep->table_properties->prefix_extractor_name, + &(rep->table_prefix_extractor)); + } +#endif // ROCKSDB_LITE + + // Read the table properties, if provided. + if (rep->table_properties) { + rep->whole_key_filtering &= + IsFeatureSupported(*(rep->table_properties), + BlockBasedTablePropertyNames::kWholeKeyFiltering, + rep->ioptions.info_log); + rep->prefix_filtering &= IsFeatureSupported( + *(rep->table_properties), + BlockBasedTablePropertyNames::kPrefixFiltering, rep->ioptions.info_log); + + s = GetGlobalSequenceNumber(*(rep->table_properties), largest_seqno, + &(rep->global_seqno)); + if (!s.ok()) { + ROCKS_LOG_ERROR(rep->ioptions.info_log, "%s", s.ToString().c_str()); + } + } + return s; +} - // Read the compression dictionary meta block - bool found_compression_dict; - s = SeekToCompressionDictBlock(meta_iter.get(), &found_compression_dict); +Status BlockBasedTable::ReadRangeDelBlock( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter, + const InternalKeyComparator& internal_comparator) { + Status s; + bool found_range_del_block; + BlockHandle range_del_handle; + s = SeekToRangeDelBlock(meta_iter, &found_range_del_block, &range_del_handle); if (!s.ok()) { ROCKS_LOG_WARN( rep->ioptions.info_log, - "Error when seeking to compression dictionary block from file: %s", + "Error when seeking to range delete tombstones block from file: %s", s.ToString().c_str()); - } else if (found_compression_dict) { - // TODO(andrewkr): Add to block cache if cache_index_and_filter_blocks is - // true. - unique_ptr compression_dict_block{new BlockContents()}; - // TODO(andrewkr): ReadMetaBlock repeats SeekToCompressionDictBlock(). - // maybe decode a handle from meta_iter - // and do ReadBlockContents(handle) instead - s = rocksdb::ReadMetaBlock(rep->file.get(), prefetch_buffer.get(), - file_size, kBlockBasedTableMagicNumber, - rep->ioptions, rocksdb::kCompressionDictBlock, - compression_dict_block.get()); + } else if (found_range_del_block && !range_del_handle.IsNull()) { + ReadOptions read_options; + std::unique_ptr iter(NewDataBlockIterator( + rep, read_options, range_del_handle, nullptr /* input_iter */, + false /* is_index */, true /* key_includes_seq */, + true /* index_key_is_full */, nullptr /* get_context */, Status(), + prefetch_buffer)); + assert(iter != nullptr); + s = iter->status(); + if (!s.ok()) { + ROCKS_LOG_WARN( + rep->ioptions.info_log, + "Encountered error while reading data from range del block %s", + s.ToString().c_str()); + } else { + rep->fragmented_range_dels = + std::make_shared(std::move(iter), + internal_comparator); + } + } + return s; +} + +Status BlockBasedTable::ReadCompressionDictBlock( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, + std::unique_ptr* compression_dict_block) { + assert(compression_dict_block != nullptr); + Status s; + if (!rep->compression_dict_handle.IsNull()) { + std::unique_ptr compression_dict_cont{new BlockContents()}; + PersistentCacheOptions cache_options; + ReadOptions read_options; + read_options.verify_checksums = true; + BlockFetcher compression_block_fetcher( + rep->file.get(), prefetch_buffer, rep->footer, read_options, + rep->compression_dict_handle, compression_dict_cont.get(), + rep->ioptions, false /* decompress */, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), cache_options); + s = compression_block_fetcher.ReadBlockContents(); + if (!s.ok()) { ROCKS_LOG_WARN( rep->ioptions.info_log, @@ -757,124 +1116,178 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions, "block %s", s.ToString().c_str()); } else { - rep->compression_dict_block = std::move(compression_dict_block); + *compression_dict_block = std::move(compression_dict_cont); } } + return s; +} - // Read the range del meta block - bool found_range_del_block; - s = SeekToRangeDelBlock(meta_iter.get(), &found_range_del_block, - &rep->range_del_handle); - if (!s.ok()) { - ROCKS_LOG_WARN( - rep->ioptions.info_log, - "Error when seeking to range delete tombstones block from file: %s", - s.ToString().c_str()); - } else { - if (found_range_del_block && !rep->range_del_handle.IsNull()) { - ReadOptions read_options; - s = MaybeLoadDataBlockToCache( - prefetch_buffer.get(), rep, read_options, rep->range_del_handle, - Slice() /* compression_dict */, &rep->range_del_entry); - if (!s.ok()) { - ROCKS_LOG_WARN( - rep->ioptions.info_log, - "Encountered error while reading data from range del block %s", - s.ToString().c_str()); +Status BlockBasedTable::PrefetchIndexAndFilterBlocks( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, InternalIterator* meta_iter, + BlockBasedTable* new_table, const SliceTransform* prefix_extractor, + bool prefetch_all, const BlockBasedTableOptions& table_options, + const int level, const bool prefetch_index_and_filter_in_cache) { + Status s; + + // Find filter handle and filter type + if (rep->filter_policy) { + for (auto filter_type : + {Rep::FilterType::kFullFilter, Rep::FilterType::kPartitionedFilter, + Rep::FilterType::kBlockFilter}) { + std::string prefix; + switch (filter_type) { + case Rep::FilterType::kFullFilter: + prefix = kFullFilterBlockPrefix; + break; + case Rep::FilterType::kPartitionedFilter: + prefix = kPartitionedFilterBlockPrefix; + break; + case Rep::FilterType::kBlockFilter: + prefix = kFilterBlockPrefix; + break; + default: + assert(0); + } + std::string filter_block_key = prefix; + filter_block_key.append(rep->filter_policy->Name()); + if (FindMetaBlock(meta_iter, filter_block_key, &rep->filter_handle) + .ok()) { + rep->filter_type = filter_type; + break; } } } - // Determine whether whole key filtering is supported. - if (rep->table_properties) { - rep->whole_key_filtering &= - IsFeatureSupported(*(rep->table_properties), - BlockBasedTablePropertyNames::kWholeKeyFiltering, - rep->ioptions.info_log); - rep->prefix_filtering &= IsFeatureSupported( - *(rep->table_properties), - BlockBasedTablePropertyNames::kPrefixFiltering, rep->ioptions.info_log); - - rep->global_seqno = GetGlobalSequenceNumber(*(rep->table_properties), - rep->ioptions.info_log); + { + // Find compression dictionary handle + bool found_compression_dict; + s = SeekToCompressionDictBlock(meta_iter, &found_compression_dict, + &rep->compression_dict_handle); } - const bool pin = + bool need_upper_bound_check = + PrefixExtractorChanged(rep->table_properties.get(), prefix_extractor); + + BlockBasedTableOptions::IndexType index_type = new_table->UpdateIndexType(); + // prefetch the first level of index + const bool prefetch_index = + prefetch_all || + (table_options.pin_top_level_index_and_filter && + index_type == BlockBasedTableOptions::kTwoLevelIndexSearch); + // prefetch the first level of filter + const bool prefetch_filter = + prefetch_all || (table_options.pin_top_level_index_and_filter && + rep->filter_type == Rep::FilterType::kPartitionedFilter); + // Partition fitlers cannot be enabled without partition indexes + assert(!prefetch_filter || prefetch_index); + // pin both index and filters, down to all partitions + const bool pin_all = rep->table_options.pin_l0_filter_and_index_blocks_in_cache && level == 0; + // pin the first level of index + const bool pin_index = + pin_all || (table_options.pin_top_level_index_and_filter && + index_type == BlockBasedTableOptions::kTwoLevelIndexSearch); + // pin the first level of filter + const bool pin_filter = + pin_all || (table_options.pin_top_level_index_and_filter && + rep->filter_type == Rep::FilterType::kPartitionedFilter); // pre-fetching of blocks is turned on - // Will use block cache for index/filter blocks access + // Will use block cache for meta-blocks access // Always prefetch index and filter for level 0 + // TODO(ajkr): also prefetch compression dictionary block if (table_options.cache_index_and_filter_blocks) { - if (prefetch_index_and_filter_in_cache || level == 0) { - assert(table_options.block_cache != nullptr); + assert(table_options.block_cache != nullptr); + if (prefetch_index) { // Hack: Call NewIndexIterator() to implicitly add index to the // block_cache - CachableEntry index_entry; - unique_ptr iter( - new_table->NewIndexIterator(ReadOptions(), nullptr, &index_entry)); - index_entry.value->CacheDependencies(pin); - if (pin) { - rep->index_entry = std::move(index_entry); - } else { - index_entry.Release(table_options.block_cache.get()); + // check prefix_extractor match only if hash based index is used + bool disable_prefix_seek = + rep->index_type == BlockBasedTableOptions::kHashSearch && + need_upper_bound_check; + if (s.ok()) { + std::unique_ptr> iter( + new_table->NewIndexIterator(ReadOptions(), disable_prefix_seek, + nullptr, &index_entry)); + s = iter->status(); } - s = iter->status(); - if (s.ok()) { - // Hack: Call GetFilter() to implicitly add filter to the block_cache - auto filter_entry = new_table->GetFilter(); - if (filter_entry.value != nullptr) { - filter_entry.value->CacheDependencies(pin); + // This is the first call to NewIndexIterator() since we're in Open(). + // On success it should give us ownership of the `CachableEntry` by + // populating `index_entry`. + assert(index_entry.value != nullptr); + if (prefetch_all) { + index_entry.value->CacheDependencies(pin_all); } - // if pin_l0_filter_and_index_blocks_in_cache is true, and this is - // a level0 file, then save it in rep_->filter_entry; it will be - // released in the destructor only, hence it will be pinned in the - // cache while this reader is alive - if (pin) { - rep->filter_entry = filter_entry; + if (pin_index) { + rep->index_entry = std::move(index_entry); } else { - filter_entry.Release(table_options.block_cache.get()); + index_entry.Release(table_options.block_cache.get()); } } } + if (s.ok() && prefetch_filter) { + // Hack: Call GetFilter() to implicitly add filter to the block_cache + auto filter_entry = + new_table->GetFilter(rep->table_prefix_extractor.get()); + if (filter_entry.value != nullptr && prefetch_all) { + filter_entry.value->CacheDependencies( + pin_all, rep->table_prefix_extractor.get()); + } + // if pin_filter is true then save it in rep_->filter_entry; it will be + // released in the destructor only, hence it will be pinned in the + // cache while this reader is alive + if (pin_filter) { + rep->filter_entry = filter_entry; + } else { + filter_entry.Release(table_options.block_cache.get()); + } + } } else { - // If we don't use block cache for index/filter blocks access, we'll - // pre-load these blocks, which will kept in member variables in Rep - // and with a same life-time as this table object. + // If we don't use block cache for meta-block access, we'll pre-load these + // blocks, which will kept in member variables in Rep and with a same life- + // time as this table object. IndexReader* index_reader = nullptr; - s = new_table->CreateIndexReader(prefetch_buffer.get(), &index_reader, - meta_iter.get(), level); + if (s.ok()) { + s = new_table->CreateIndexReader(prefetch_buffer, &index_reader, + meta_iter, level); + } + std::unique_ptr compression_dict_block; if (s.ok()) { rep->index_reader.reset(index_reader); // The partitions of partitioned index are always stored in cache. They // are hence follow the configuration for pin and prefetch regardless of // the value of cache_index_and_filter_blocks if (prefetch_index_and_filter_in_cache || level == 0) { - rep->index_reader->CacheDependencies(pin); + rep->index_reader->CacheDependencies(pin_all); } // Set filter block if (rep->filter_policy) { const bool is_a_filter_partition = true; - auto filter = new_table->ReadFilter( - prefetch_buffer.get(), rep->filter_handle, !is_a_filter_partition); + auto filter = new_table->ReadFilter(prefetch_buffer, rep->filter_handle, + !is_a_filter_partition, + rep->table_prefix_extractor.get()); rep->filter.reset(filter); // Refer to the comment above about paritioned indexes always being // cached if (filter && (prefetch_index_and_filter_in_cache || level == 0)) { - filter->CacheDependencies(pin); + filter->CacheDependencies(pin_all, rep->table_prefix_extractor.get()); } } + s = ReadCompressionDictBlock(rep, prefetch_buffer, + &compression_dict_block); } else { delete index_reader; } + if (s.ok() && !rep->compression_dict_handle.IsNull()) { + assert(compression_dict_block != nullptr); + // TODO(ajkr): find a way to avoid the `compression_dict_block` data copy + rep->uncompression_dict.reset(new UncompressionDict( + compression_dict_block->data.ToString(), + rep->blocks_definitely_zstd_compressed, rep->ioptions.statistics)); + } } - - if (s.ok()) { - *table_reader = std::move(new_table); - } - return s; } @@ -909,6 +1322,9 @@ size_t BlockBasedTable::ApproximateMemoryUsage() const { if (rep_->index_reader) { usage += rep_->index_reader->ApproximateMemoryUsage(); } + if (rep_->uncompression_dict) { + usage += rep_->uncompression_dict->ApproximateMemoryUsage(); + } return usage; } @@ -924,9 +1340,10 @@ Status BlockBasedTable::ReadMetaBlock(Rep* rep, Status s = ReadBlockFromFile( rep->file.get(), prefetch_buffer, rep->footer, ReadOptions(), rep->footer.metaindex_handle(), &meta, rep->ioptions, - true /* decompress */, Slice() /*compression dict*/, - rep->persistent_cache_options, kDisableGlobalSequenceNumber, - 0 /* read_amp_bytes_per_bit */); + true /* decompress */, true /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), rep->persistent_cache_options, + kDisableGlobalSequenceNumber, 0 /* read_amp_bytes_per_bit */, + GetMemoryAllocator(rep->table_options)); if (!s.ok()) { ROCKS_LOG_ERROR(rep->ioptions.info_log, @@ -938,28 +1355,38 @@ Status BlockBasedTable::ReadMetaBlock(Rep* rep, *meta_block = std::move(meta); // meta block uses bytewise comparator. - iter->reset(meta_block->get()->NewIterator(BytewiseComparator())); + iter->reset(meta_block->get()->NewIterator( + BytewiseComparator(), BytewiseComparator())); return Status::OK(); } Status BlockBasedTable::GetDataBlockFromCache( const Slice& block_cache_key, const Slice& compressed_block_cache_key, - Cache* block_cache, Cache* block_cache_compressed, - const ImmutableCFOptions& ioptions, const ReadOptions& read_options, - BlockBasedTable::CachableEntry* block, uint32_t format_version, - const Slice& compression_dict, size_t read_amp_bytes_per_bit, - bool is_index) { + Cache* block_cache, Cache* block_cache_compressed, Rep* rep, + const ReadOptions& read_options, + BlockBasedTable::CachableEntry* block, + const UncompressionDict& uncompression_dict, size_t read_amp_bytes_per_bit, + bool is_index, GetContext* get_context) { Status s; - Block* compressed_block = nullptr; + BlockContents* compressed_block = nullptr; Cache::Handle* block_cache_compressed_handle = nullptr; - Statistics* statistics = ioptions.statistics; + Statistics* statistics = rep->ioptions.statistics; // Lookup uncompressed cache first if (block_cache != nullptr) { block->cache_handle = GetEntryFromCache( - block_cache, block_cache_key, + block_cache, block_cache_key, rep->level, is_index ? BLOCK_CACHE_INDEX_MISS : BLOCK_CACHE_DATA_MISS, - is_index ? BLOCK_CACHE_INDEX_HIT : BLOCK_CACHE_DATA_HIT, statistics); + is_index ? BLOCK_CACHE_INDEX_HIT : BLOCK_CACHE_DATA_HIT, + get_context + ? (is_index ? &get_context->get_context_stats_.num_cache_index_miss + : &get_context->get_context_stats_.num_cache_data_miss) + : nullptr, + get_context + ? (is_index ? &get_context->get_context_stats_.num_cache_index_hit + : &get_context->get_context_stats_.num_cache_data_hit) + : nullptr, + statistics, get_context); if (block->cache_handle != nullptr) { block->value = reinterpret_cast(block_cache->Value(block->cache_handle)); @@ -986,44 +1413,62 @@ Status BlockBasedTable::GetDataBlockFromCache( // found compressed block RecordTick(statistics, BLOCK_CACHE_COMPRESSED_HIT); - compressed_block = reinterpret_cast( + compressed_block = reinterpret_cast( block_cache_compressed->Value(block_cache_compressed_handle)); - assert(compressed_block->compression_type() != kNoCompression); + CompressionType compression_type = compressed_block->get_compression_type(); + assert(compression_type != kNoCompression); // Retrieve the uncompressed contents into a new buffer BlockContents contents; - s = UncompressBlockContents(compressed_block->data(), - compressed_block->size(), &contents, - format_version, compression_dict, - ioptions); + UncompressionContext context(compression_type); + UncompressionInfo info(context, uncompression_dict, compression_type); + s = UncompressBlockContents(info, compressed_block->data.data(), + compressed_block->data.size(), &contents, + rep->table_options.format_version, rep->ioptions, + GetMemoryAllocator(rep->table_options)); // Insert uncompressed block into block cache if (s.ok()) { block->value = - new Block(std::move(contents), compressed_block->global_seqno(), + new Block(std::move(contents), rep->get_global_seqno(is_index), read_amp_bytes_per_bit, statistics); // uncompressed block - assert(block->value->compression_type() == kNoCompression); - if (block_cache != nullptr && block->value->cachable() && + if (block_cache != nullptr && block->value->own_bytes() && read_options.fill_cache) { - s = block_cache->Insert( - block_cache_key, block->value, block->value->usable_size(), - &DeleteCachedEntry, &(block->cache_handle)); - block_cache->TEST_mark_as_data_block(block_cache_key, - block->value->usable_size()); + size_t charge = block->value->ApproximateMemoryUsage(); + s = block_cache->Insert(block_cache_key, block->value, charge, + &DeleteCachedEntry, + &(block->cache_handle)); +#ifndef NDEBUG + block_cache->TEST_mark_as_data_block(block_cache_key, charge); +#endif // NDEBUG if (s.ok()) { - RecordTick(statistics, BLOCK_CACHE_ADD); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_add++; + get_context->get_context_stats_.num_cache_bytes_write += charge; + } else { + RecordTick(statistics, BLOCK_CACHE_ADD); + RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, charge); + } if (is_index) { - RecordTick(statistics, BLOCK_CACHE_INDEX_ADD); - RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, - block->value->usable_size()); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_index_add++; + get_context->get_context_stats_.num_cache_index_bytes_insert += + charge; + } else { + RecordTick(statistics, BLOCK_CACHE_INDEX_ADD); + RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, charge); + } } else { - RecordTick(statistics, BLOCK_CACHE_DATA_ADD); - RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT, - block->value->usable_size()); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_data_add++; + get_context->get_context_stats_.num_cache_data_bytes_insert += + charge; + } else { + RecordTick(statistics, BLOCK_CACHE_DATA_ADD); + RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT, charge); + } } - RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, - block->value->usable_size()); } else { RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES); delete block->value; @@ -1040,80 +1485,109 @@ Status BlockBasedTable::GetDataBlockFromCache( Status BlockBasedTable::PutDataBlockToCache( const Slice& block_cache_key, const Slice& compressed_block_cache_key, Cache* block_cache, Cache* block_cache_compressed, - const ReadOptions& read_options, const ImmutableCFOptions& ioptions, - CachableEntry* block, Block* raw_block, uint32_t format_version, - const Slice& compression_dict, size_t read_amp_bytes_per_bit, bool is_index, - Cache::Priority priority) { - assert(raw_block->compression_type() == kNoCompression || + const ReadOptions& /*read_options*/, const ImmutableCFOptions& ioptions, + CachableEntry* cached_block, BlockContents* raw_block_contents, + CompressionType raw_block_comp_type, uint32_t format_version, + const UncompressionDict& uncompression_dict, SequenceNumber seq_no, + size_t read_amp_bytes_per_bit, MemoryAllocator* memory_allocator, + bool is_index, Cache::Priority priority, GetContext* get_context) { + assert(raw_block_comp_type == kNoCompression || block_cache_compressed != nullptr); Status s; // Retrieve the uncompressed contents into a new buffer - BlockContents contents; + BlockContents uncompressed_block_contents; Statistics* statistics = ioptions.statistics; - if (raw_block->compression_type() != kNoCompression) { - s = UncompressBlockContents(raw_block->data(), raw_block->size(), &contents, - format_version, compression_dict, ioptions); + if (raw_block_comp_type != kNoCompression) { + UncompressionContext context(raw_block_comp_type); + UncompressionInfo info(context, uncompression_dict, raw_block_comp_type); + s = UncompressBlockContents(info, raw_block_contents->data.data(), + raw_block_contents->data.size(), + &uncompressed_block_contents, format_version, + ioptions, memory_allocator); } if (!s.ok()) { - delete raw_block; return s; } - if (raw_block->compression_type() != kNoCompression) { - block->value = new Block(std::move(contents), raw_block->global_seqno(), - read_amp_bytes_per_bit, - statistics); // uncompressed block + if (raw_block_comp_type != kNoCompression) { + cached_block->value = new Block(std::move(uncompressed_block_contents), + seq_no, read_amp_bytes_per_bit, + statistics); // uncompressed block } else { - block->value = raw_block; - raw_block = nullptr; + cached_block->value = + new Block(std::move(*raw_block_contents), seq_no, + read_amp_bytes_per_bit, ioptions.statistics); } // Insert compressed block into compressed block cache. // Release the hold on the compressed cache entry immediately. - if (block_cache_compressed != nullptr && raw_block != nullptr && - raw_block->cachable()) { - s = block_cache_compressed->Insert(compressed_block_cache_key, raw_block, - raw_block->usable_size(), - &DeleteCachedEntry); + if (block_cache_compressed != nullptr && + raw_block_comp_type != kNoCompression && raw_block_contents != nullptr && + raw_block_contents->own_bytes()) { +#ifndef NDEBUG + assert(raw_block_contents->is_raw_block); +#endif // NDEBUG + + // We cannot directly put raw_block_contents because this could point to + // an object in the stack. + BlockContents* block_cont_for_comp_cache = + new BlockContents(std::move(*raw_block_contents)); + s = block_cache_compressed->Insert( + compressed_block_cache_key, block_cont_for_comp_cache, + block_cont_for_comp_cache->ApproximateMemoryUsage(), + &DeleteCachedEntry); if (s.ok()) { // Avoid the following code to delete this cached block. - raw_block = nullptr; RecordTick(statistics, BLOCK_CACHE_COMPRESSED_ADD); } else { RecordTick(statistics, BLOCK_CACHE_COMPRESSED_ADD_FAILURES); + delete block_cont_for_comp_cache; } } - delete raw_block; // insert into uncompressed block cache - assert((block->value->compression_type() == kNoCompression)); - if (block_cache != nullptr && block->value->cachable()) { - s = block_cache->Insert( - block_cache_key, block->value, block->value->usable_size(), - &DeleteCachedEntry, &(block->cache_handle), priority); - block_cache->TEST_mark_as_data_block(block_cache_key, - block->value->usable_size()); + if (block_cache != nullptr && cached_block->value->own_bytes()) { + size_t charge = cached_block->value->ApproximateMemoryUsage(); + s = block_cache->Insert(block_cache_key, cached_block->value, charge, + &DeleteCachedEntry, + &(cached_block->cache_handle), priority); +#ifndef NDEBUG + block_cache->TEST_mark_as_data_block(block_cache_key, charge); +#endif // NDEBUG if (s.ok()) { - assert(block->cache_handle != nullptr); - RecordTick(statistics, BLOCK_CACHE_ADD); + assert(cached_block->cache_handle != nullptr); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_add++; + get_context->get_context_stats_.num_cache_bytes_write += charge; + } else { + RecordTick(statistics, BLOCK_CACHE_ADD); + RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, charge); + } if (is_index) { - RecordTick(statistics, BLOCK_CACHE_INDEX_ADD); - RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, - block->value->usable_size()); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_index_add++; + get_context->get_context_stats_.num_cache_index_bytes_insert += + charge; + } else { + RecordTick(statistics, BLOCK_CACHE_INDEX_ADD); + RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, charge); + } } else { - RecordTick(statistics, BLOCK_CACHE_DATA_ADD); - RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT, - block->value->usable_size()); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_data_add++; + get_context->get_context_stats_.num_cache_data_bytes_insert += charge; + } else { + RecordTick(statistics, BLOCK_CACHE_DATA_ADD); + RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT, charge); + } } - RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, - block->value->usable_size()); - assert(reinterpret_cast( - block_cache->Value(block->cache_handle)) == block->value); + assert(reinterpret_cast(block_cache->Value( + cached_block->cache_handle)) == cached_block->value); } else { RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES); - delete block->value; - block->value = nullptr; + delete cached_block->value; + cached_block->value = nullptr; } } @@ -1122,7 +1596,8 @@ Status BlockBasedTable::PutDataBlockToCache( FilterBlockReader* BlockBasedTable::ReadFilter( FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_handle, - const bool is_a_filter_partition) const { + const bool is_a_filter_partition, + const SliceTransform* prefix_extractor) const { auto& rep = rep_; // TODO: We might want to unify with ReadBlockFromFile() if we start // requiring checksum verification in Table::Open. @@ -1130,11 +1605,15 @@ FilterBlockReader* BlockBasedTable::ReadFilter( return nullptr; } BlockContents block; - if (!ReadBlockContents(rep->file.get(), prefetch_buffer, rep->footer, - ReadOptions(), filter_handle, &block, rep->ioptions, - false /* decompress */, Slice() /*compression dict*/, - rep->persistent_cache_options) - .ok()) { + + BlockFetcher block_fetcher( + rep->file.get(), prefetch_buffer, rep->footer, ReadOptions(), + filter_handle, &block, rep->ioptions, false /* decompress */, + false /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + rep->persistent_cache_options, GetMemoryAllocator(rep->table_options)); + Status s = block_fetcher.ReadBlockContents(); + + if (!s.ok()) { // Error reading the block return nullptr; } @@ -1150,14 +1629,18 @@ FilterBlockReader* BlockBasedTable::ReadFilter( switch (filter_type) { case Rep::FilterType::kPartitionedFilter: { return new PartitionedFilterBlockReader( - rep->prefix_filtering ? rep->ioptions.prefix_extractor : nullptr, + rep->prefix_filtering ? prefix_extractor : nullptr, rep->whole_key_filtering, std::move(block), nullptr, - rep->ioptions.statistics, rep->internal_comparator, this); + rep->ioptions.statistics, rep->internal_comparator, this, + rep_->table_properties == nullptr || + rep_->table_properties->index_key_is_user_key == 0, + rep_->table_properties == nullptr || + rep_->table_properties->index_value_is_delta_encoded == 0); } case Rep::FilterType::kBlockFilter: return new BlockBasedFilterBlockReader( - rep->prefix_filtering ? rep->ioptions.prefix_extractor : nullptr, + rep->prefix_filtering ? prefix_extractor : nullptr, rep->table_options, rep->whole_key_filtering, std::move(block), rep->ioptions.statistics); @@ -1166,7 +1649,7 @@ FilterBlockReader* BlockBasedTable::ReadFilter( rep->filter_policy->GetFilterBitsReader(block.data); assert(filter_bits_reader != nullptr); return new FullFilterBlockReader( - rep->prefix_filtering ? rep->ioptions.prefix_extractor : nullptr, + rep->prefix_filtering ? prefix_extractor : nullptr, rep->whole_key_filtering, std::move(block), filter_bits_reader, rep->ioptions.statistics); } @@ -1180,16 +1663,18 @@ FilterBlockReader* BlockBasedTable::ReadFilter( } BlockBasedTable::CachableEntry BlockBasedTable::GetFilter( - FilePrefetchBuffer* prefetch_buffer, bool no_io) const { + const SliceTransform* prefix_extractor, FilePrefetchBuffer* prefetch_buffer, + bool no_io, GetContext* get_context) const { const BlockHandle& filter_blk_handle = rep_->filter_handle; const bool is_a_filter_partition = true; return GetFilter(prefetch_buffer, filter_blk_handle, !is_a_filter_partition, - no_io); + no_io, get_context, prefix_extractor); } BlockBasedTable::CachableEntry BlockBasedTable::GetFilter( FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_blk_handle, - const bool is_a_filter_partition, bool no_io) const { + const bool is_a_filter_partition, bool no_io, GetContext* get_context, + const SliceTransform* prefix_extractor) const { // If cache_index_and_filter_blocks is false, filter should be pre-populated. // We will return rep_->filter anyway. rep_->filter can be nullptr if filter // read fails at Open() time. We don't want to reload again since it will @@ -1217,32 +1702,47 @@ BlockBasedTable::CachableEntry BlockBasedTable::GetFilter( filter_blk_handle, cache_key); Statistics* statistics = rep_->ioptions.statistics; - auto cache_handle = - GetEntryFromCache(block_cache, key, BLOCK_CACHE_FILTER_MISS, - BLOCK_CACHE_FILTER_HIT, statistics); + auto cache_handle = GetEntryFromCache( + block_cache, key, rep_->level, BLOCK_CACHE_FILTER_MISS, + BLOCK_CACHE_FILTER_HIT, + get_context ? &get_context->get_context_stats_.num_cache_filter_miss + : nullptr, + get_context ? &get_context->get_context_stats_.num_cache_filter_hit + : nullptr, + statistics, get_context); FilterBlockReader* filter = nullptr; if (cache_handle != nullptr) { - filter = reinterpret_cast( - block_cache->Value(cache_handle)); + PERF_COUNTER_ADD(block_cache_filter_hit_count, 1); + filter = + reinterpret_cast(block_cache->Value(cache_handle)); } else if (no_io) { // Do not invoke any io. return CachableEntry(); } else { - filter = - ReadFilter(prefetch_buffer, filter_blk_handle, is_a_filter_partition); + filter = ReadFilter(prefetch_buffer, filter_blk_handle, + is_a_filter_partition, prefix_extractor); if (filter != nullptr) { - assert(filter->size() > 0); + size_t usage = filter->ApproximateMemoryUsage(); Status s = block_cache->Insert( - key, filter, filter->size(), &DeleteCachedFilterEntry, &cache_handle, + key, filter, usage, &DeleteCachedFilterEntry, &cache_handle, rep_->table_options.cache_index_and_filter_blocks_with_high_priority ? Cache::Priority::HIGH : Cache::Priority::LOW); if (s.ok()) { - RecordTick(statistics, BLOCK_CACHE_ADD); - RecordTick(statistics, BLOCK_CACHE_FILTER_ADD); - RecordTick(statistics, BLOCK_CACHE_FILTER_BYTES_INSERT, filter->size()); - RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, filter->size()); + PERF_COUNTER_ADD(filter_block_read_count, 1); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_add++; + get_context->get_context_stats_.num_cache_bytes_write += usage; + get_context->get_context_stats_.num_cache_filter_add++; + get_context->get_context_stats_.num_cache_filter_bytes_insert += + usage; + } else { + RecordTick(statistics, BLOCK_CACHE_ADD); + RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, usage); + RecordTick(statistics, BLOCK_CACHE_FILTER_ADD); + RecordTick(statistics, BLOCK_CACHE_FILTER_BYTES_INSERT, usage); + } } else { RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES); delete filter; @@ -1251,21 +1751,108 @@ BlockBasedTable::CachableEntry BlockBasedTable::GetFilter( } } - return { filter, cache_handle }; + return {filter, cache_handle}; +} + +BlockBasedTable::CachableEntry +BlockBasedTable::GetUncompressionDict(Rep* rep, + FilePrefetchBuffer* prefetch_buffer, + bool no_io, GetContext* get_context) { + if (!rep->table_options.cache_index_and_filter_blocks) { + // block cache is either disabled or not used for meta-blocks. In either + // case, BlockBasedTableReader is the owner of the uncompression dictionary. + return {rep->uncompression_dict.get(), nullptr /* cache handle */}; + } + if (rep->compression_dict_handle.IsNull()) { + return {nullptr, nullptr}; + } + char cache_key_buf[kMaxCacheKeyPrefixSize + kMaxVarint64Length]; + auto cache_key = + GetCacheKey(rep->cache_key_prefix, rep->cache_key_prefix_size, + rep->compression_dict_handle, cache_key_buf); + auto cache_handle = GetEntryFromCache( + rep->table_options.block_cache.get(), cache_key, rep->level, + BLOCK_CACHE_COMPRESSION_DICT_MISS, BLOCK_CACHE_COMPRESSION_DICT_HIT, + get_context + ? &get_context->get_context_stats_.num_cache_compression_dict_miss + : nullptr, + get_context + ? &get_context->get_context_stats_.num_cache_compression_dict_hit + : nullptr, + rep->ioptions.statistics, get_context); + UncompressionDict* dict = nullptr; + if (cache_handle != nullptr) { + dict = reinterpret_cast( + rep->table_options.block_cache->Value(cache_handle)); + } else if (no_io) { + // Do not invoke any io. + } else { + std::unique_ptr compression_dict_block; + Status s = + ReadCompressionDictBlock(rep, prefetch_buffer, &compression_dict_block); + size_t usage = 0; + if (s.ok()) { + assert(compression_dict_block != nullptr); + // TODO(ajkr): find a way to avoid the `compression_dict_block` data copy + dict = new UncompressionDict(compression_dict_block->data.ToString(), + rep->blocks_definitely_zstd_compressed, + rep->ioptions.statistics); + usage = dict->ApproximateMemoryUsage(); + s = rep->table_options.block_cache->Insert( + cache_key, dict, usage, &DeleteCachedUncompressionDictEntry, + &cache_handle, + rep->table_options.cache_index_and_filter_blocks_with_high_priority + ? Cache::Priority::HIGH + : Cache::Priority::LOW); + } + if (s.ok()) { + PERF_COUNTER_ADD(compression_dict_block_read_count, 1); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_add++; + get_context->get_context_stats_.num_cache_bytes_write += usage; + get_context->get_context_stats_.num_cache_compression_dict_add++; + get_context->get_context_stats_ + .num_cache_compression_dict_bytes_insert += usage; + } else { + RecordTick(rep->ioptions.statistics, BLOCK_CACHE_ADD); + RecordTick(rep->ioptions.statistics, BLOCK_CACHE_BYTES_WRITE, usage); + RecordTick(rep->ioptions.statistics, BLOCK_CACHE_COMPRESSION_DICT_ADD); + RecordTick(rep->ioptions.statistics, + BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT, usage); + } + } else { + // There should be no way to get here if block cache insertion succeeded. + // Though it is still possible something failed earlier. + RecordTick(rep->ioptions.statistics, BLOCK_CACHE_ADD_FAILURES); + delete dict; + dict = nullptr; + assert(cache_handle == nullptr); + } + } + return {dict, cache_handle}; } -InternalIterator* BlockBasedTable::NewIndexIterator( - const ReadOptions& read_options, BlockIter* input_iter, - CachableEntry* index_entry) { +// disable_prefix_seek should be set to true when prefix_extractor found in SST +// differs from the one in mutable_cf_options and index type is HashBasedIndex +InternalIteratorBase* BlockBasedTable::NewIndexIterator( + const ReadOptions& read_options, bool disable_prefix_seek, + IndexBlockIter* input_iter, CachableEntry* index_entry, + GetContext* get_context) { // index reader has already been pre-populated. if (rep_->index_reader) { + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. return rep_->index_reader->NewIterator( - input_iter, read_options.total_order_seek); + input_iter, read_options.total_order_seek || disable_prefix_seek, + read_options.fill_cache); } // we have a pinned index block if (rep_->index_entry.IsSet()) { - return rep_->index_entry.value->NewIterator(input_iter, - read_options.total_order_seek); + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. + return rep_->index_entry.value->NewIterator( + input_iter, read_options.total_order_seek || disable_prefix_seek, + read_options.fill_cache); } PERF_TIMER_GUARD(read_index_block_nanos); @@ -1277,21 +1864,28 @@ InternalIterator* BlockBasedTable::NewIndexIterator( GetCacheKeyFromOffset(rep_->cache_key_prefix, rep_->cache_key_prefix_size, rep_->dummy_index_reader_offset, cache_key); Statistics* statistics = rep_->ioptions.statistics; - auto cache_handle = - GetEntryFromCache(block_cache, key, BLOCK_CACHE_INDEX_MISS, - BLOCK_CACHE_INDEX_HIT, statistics); + auto cache_handle = GetEntryFromCache( + block_cache, key, rep_->level, BLOCK_CACHE_INDEX_MISS, + BLOCK_CACHE_INDEX_HIT, + get_context ? &get_context->get_context_stats_.num_cache_index_miss + : nullptr, + get_context ? &get_context->get_context_stats_.num_cache_index_hit + : nullptr, + statistics, get_context); if (cache_handle == nullptr && no_io) { if (input_iter != nullptr) { - input_iter->SetStatus(Status::Incomplete("no blocking io")); + input_iter->Invalidate(Status::Incomplete("no blocking io")); return input_iter; } else { - return NewErrorInternalIterator(Status::Incomplete("no blocking io")); + return NewErrorInternalIterator( + Status::Incomplete("no blocking io")); } } IndexReader* index_reader = nullptr; if (cache_handle != nullptr) { + PERF_COUNTER_ADD(block_cache_index_hit_count, 1); index_reader = reinterpret_cast(block_cache->Value(cache_handle)); } else { @@ -1302,22 +1896,28 @@ InternalIterator* BlockBasedTable::NewIndexIterator( TEST_SYNC_POINT("BlockBasedTable::NewIndexIterator::thread1:1"); TEST_SYNC_POINT("BlockBasedTable::NewIndexIterator::thread2:3"); TEST_SYNC_POINT("BlockBasedTable::NewIndexIterator::thread1:4"); + size_t charge = 0; if (s.ok()) { assert(index_reader != nullptr); + charge = index_reader->ApproximateMemoryUsage(); s = block_cache->Insert( - key, index_reader, index_reader->usable_size(), - &DeleteCachedIndexEntry, &cache_handle, + key, index_reader, charge, &DeleteCachedIndexEntry, &cache_handle, rep_->table_options.cache_index_and_filter_blocks_with_high_priority ? Cache::Priority::HIGH : Cache::Priority::LOW); } if (s.ok()) { - size_t usable_size = index_reader->usable_size(); - RecordTick(statistics, BLOCK_CACHE_ADD); + if (get_context != nullptr) { + get_context->get_context_stats_.num_cache_add++; + get_context->get_context_stats_.num_cache_bytes_write += charge; + } else { + RecordTick(statistics, BLOCK_CACHE_ADD); + RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, charge); + } + PERF_COUNTER_ADD(index_block_read_count, 1); RecordTick(statistics, BLOCK_CACHE_INDEX_ADD); - RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, usable_size); - RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, usable_size); + RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, charge); } else { if (index_reader != nullptr) { delete index_reader; @@ -1325,18 +1925,19 @@ InternalIterator* BlockBasedTable::NewIndexIterator( RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES); // make sure if something goes wrong, index_reader shall remain intact. if (input_iter != nullptr) { - input_iter->SetStatus(s); + input_iter->Invalidate(s); return input_iter; } else { - return NewErrorInternalIterator(s); + return NewErrorInternalIterator(s); } } - } assert(cache_handle); + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. auto* iter = index_reader->NewIterator( - input_iter, read_options.total_order_seek); + input_iter, read_options.total_order_seek || disable_prefix_seek); // the caller would like to take ownership of the index block // don't call RegisterCleanup() in this case, the caller will take care of it @@ -1349,102 +1950,153 @@ InternalIterator* BlockBasedTable::NewIndexIterator( return iter; } -InternalIterator* BlockBasedTable::NewDataBlockIterator( - Rep* rep, const ReadOptions& ro, const Slice& index_value, - BlockIter* input_iter, bool is_index) { - BlockHandle handle; - Slice input = index_value; - // We intentionally allow extra stuff in index_value so that we - // can add more features in the future. - Status s = handle.DecodeFrom(&input); - return NewDataBlockIterator(rep, ro, handle, input_iter, is_index, s); -} - // Convert an index iterator value (i.e., an encoded BlockHandle) // into an iterator over the contents of the corresponding block. // If input_iter is null, new a iterator // If input_iter is not null, update this iter and return it -InternalIterator* BlockBasedTable::NewDataBlockIterator( +template +TBlockIter* BlockBasedTable::NewDataBlockIterator( Rep* rep, const ReadOptions& ro, const BlockHandle& handle, - BlockIter* input_iter, bool is_index, Status s) { + TBlockIter* input_iter, bool is_index, bool key_includes_seq, + bool index_key_is_full, GetContext* get_context, Status s, + FilePrefetchBuffer* prefetch_buffer) { PERF_TIMER_GUARD(new_table_block_iter_nanos); - const bool no_io = (ro.read_tier == kBlockCacheTier); Cache* block_cache = rep->table_options.block_cache.get(); CachableEntry block; - Slice compression_dict; - if (s.ok()) { - if (rep->compression_dict_block) { - compression_dict = rep->compression_dict_block->data; + TBlockIter* iter; + { + const bool no_io = (ro.read_tier == kBlockCacheTier); + auto uncompression_dict_storage = + GetUncompressionDict(rep, prefetch_buffer, no_io, get_context); + const UncompressionDict& uncompression_dict = + uncompression_dict_storage.value == nullptr + ? UncompressionDict::GetEmptyDict() + : *uncompression_dict_storage.value; + if (s.ok()) { + s = MaybeReadBlockAndLoadToCache(prefetch_buffer, rep, ro, handle, + uncompression_dict, &block, is_index, + get_context); } - s = MaybeLoadDataBlockToCache(nullptr /*prefetch_buffer*/, rep, ro, handle, - compression_dict, &block, is_index); - } - // Didn't get any data from block caches. - if (s.ok() && block.value == nullptr) { - if (no_io) { - // Could not read from block_cache and can't do IO - if (input_iter != nullptr) { - input_iter->SetStatus(Status::Incomplete("no blocking io")); - return input_iter; - } else { - return NewErrorInternalIterator(Status::Incomplete("no blocking io")); + if (input_iter != nullptr) { + iter = input_iter; + } else { + iter = new TBlockIter; + } + // Didn't get any data from block caches. + if (s.ok() && block.value == nullptr) { + if (no_io) { + // Could not read from block_cache and can't do IO + iter->Invalidate(Status::Incomplete("no blocking io")); + return iter; + } + std::unique_ptr block_value; + { + StopWatch sw(rep->ioptions.env, rep->ioptions.statistics, + READ_BLOCK_GET_MICROS); + s = ReadBlockFromFile( + rep->file.get(), prefetch_buffer, rep->footer, ro, handle, + &block_value, rep->ioptions, + rep->blocks_maybe_compressed /*do_decompress*/, + rep->blocks_maybe_compressed, uncompression_dict, + rep->persistent_cache_options, + is_index ? kDisableGlobalSequenceNumber : rep->global_seqno, + rep->table_options.read_amp_bytes_per_bit, + GetMemoryAllocator(rep->table_options)); + } + if (s.ok()) { + block.value = block_value.release(); } } - std::unique_ptr block_value; - s = ReadBlockFromFile(rep->file.get(), nullptr /* prefetch_buffer */, - rep->footer, ro, handle, &block_value, rep->ioptions, - true /* compress */, compression_dict, - rep->persistent_cache_options, rep->global_seqno, - rep->table_options.read_amp_bytes_per_bit); - if (s.ok()) { - block.value = block_value.release(); - } + // TODO(ajkr): also pin compression dictionary block when + // `pin_l0_filter_and_index_blocks_in_cache == true`. + uncompression_dict_storage.Release(block_cache); } - InternalIterator* iter; if (s.ok()) { assert(block.value != nullptr); - iter = block.value->NewIterator(&rep->internal_comparator, input_iter, true, - rep->ioptions.statistics); + const bool kTotalOrderSeek = true; + // Block contents are pinned and it is still pinned after the iterator + // is destroyed as long as cleanup functions are moved to another object, + // when: + // 1. block cache handle is set to be released in cleanup function, or + // 2. it's pointing to immortal source. If own_bytes is true then we are + // not reading data from the original source, whether immortal or not. + // Otherwise, the block is pinned iff the source is immortal. + bool block_contents_pinned = + (block.cache_handle != nullptr || + (!block.value->own_bytes() && rep->immortal_table)); + iter = block.value->NewIterator( + &rep->internal_comparator, rep->internal_comparator.user_comparator(), + iter, rep->ioptions.statistics, kTotalOrderSeek, key_includes_seq, + index_key_is_full, block_contents_pinned); if (block.cache_handle != nullptr) { iter->RegisterCleanup(&ReleaseCachedEntry, block_cache, block.cache_handle); } else { + if (!ro.fill_cache && rep->cache_key_prefix_size != 0) { + // insert a dummy record to block cache to track the memory usage + Cache::Handle* cache_handle; + // There are two other types of cache keys: 1) SST cache key added in + // `MaybeReadBlockAndLoadToCache` 2) dummy cache key added in + // `write_buffer_manager`. Use longer prefix (41 bytes) to differentiate + // from SST cache key(31 bytes), and use non-zero prefix to + // differentiate from `write_buffer_manager` + const size_t kExtraCacheKeyPrefix = kMaxVarint64Length * 4 + 1; + char cache_key[kExtraCacheKeyPrefix + kMaxVarint64Length]; + // Prefix: use rep->cache_key_prefix padded by 0s + memset(cache_key, 0, kExtraCacheKeyPrefix + kMaxVarint64Length); + assert(rep->cache_key_prefix_size != 0); + assert(rep->cache_key_prefix_size <= kExtraCacheKeyPrefix); + memcpy(cache_key, rep->cache_key_prefix, rep->cache_key_prefix_size); + char* end = EncodeVarint64(cache_key + kExtraCacheKeyPrefix, + next_cache_key_id_++); + assert(end - cache_key <= + static_cast(kExtraCacheKeyPrefix + kMaxVarint64Length)); + Slice unique_key = + Slice(cache_key, static_cast(end - cache_key)); + s = block_cache->Insert(unique_key, nullptr, + block.value->ApproximateMemoryUsage(), nullptr, + &cache_handle); + if (s.ok()) { + if (cache_handle != nullptr) { + iter->RegisterCleanup(&ForceReleaseCachedEntry, block_cache, + cache_handle); + } + } + } iter->RegisterCleanup(&DeleteHeldResource, block.value, nullptr); } } else { assert(block.value == nullptr); - if (input_iter != nullptr) { - input_iter->SetStatus(s); - iter = input_iter; - } else { - iter = NewErrorInternalIterator(s); - } + iter->Invalidate(s); } return iter; } -Status BlockBasedTable::MaybeLoadDataBlockToCache( +Status BlockBasedTable::MaybeReadBlockAndLoadToCache( FilePrefetchBuffer* prefetch_buffer, Rep* rep, const ReadOptions& ro, - const BlockHandle& handle, Slice compression_dict, - CachableEntry* block_entry, bool is_index) { + const BlockHandle& handle, const UncompressionDict& uncompression_dict, + CachableEntry* block_entry, bool is_index, GetContext* get_context) { assert(block_entry != nullptr); const bool no_io = (ro.read_tier == kBlockCacheTier); Cache* block_cache = rep->table_options.block_cache.get(); + + // No point to cache compressed blocks if it never goes away Cache* block_cache_compressed = - rep->table_options.block_cache_compressed.get(); + rep->immortal_table ? nullptr + : rep->table_options.block_cache_compressed.get(); + // First, try to get the block from the cache + // // If either block cache is enabled, we'll try to read from it. Status s; + char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length]; + char compressed_cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length]; + Slice key /* key to the block cache */; + Slice ckey /* key to the compressed block cache */; if (block_cache != nullptr || block_cache_compressed != nullptr) { - Statistics* statistics = rep->ioptions.statistics; - char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length]; - char compressed_cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length]; - Slice key, /* key to the block cache */ - ckey /* key to the compressed block cache */; - // create key for block cache if (block_cache != nullptr) { key = GetCacheKey(rep->cache_key_prefix, rep->cache_key_prefix_size, @@ -1457,33 +2109,47 @@ Status BlockBasedTable::MaybeLoadDataBlockToCache( compressed_cache_key); } - s = GetDataBlockFromCache( - key, ckey, block_cache, block_cache_compressed, rep->ioptions, ro, - block_entry, rep->table_options.format_version, compression_dict, - rep->table_options.read_amp_bytes_per_bit, is_index); + s = GetDataBlockFromCache(key, ckey, block_cache, block_cache_compressed, + rep, ro, block_entry, uncompression_dict, + rep->table_options.read_amp_bytes_per_bit, + is_index, get_context); + // Can't find the block from the cache. If I/O is allowed, read from the + // file. if (block_entry->value == nullptr && !no_io && ro.fill_cache) { - std::unique_ptr raw_block; + Statistics* statistics = rep->ioptions.statistics; + bool do_decompress = + block_cache_compressed == nullptr && rep->blocks_maybe_compressed; + CompressionType raw_block_comp_type; + BlockContents raw_block_contents; { StopWatch sw(rep->ioptions.env, statistics, READ_BLOCK_GET_MICROS); - s = ReadBlockFromFile( + BlockFetcher block_fetcher( rep->file.get(), prefetch_buffer, rep->footer, ro, handle, - &raw_block, rep->ioptions, block_cache_compressed == nullptr, - compression_dict, rep->persistent_cache_options, rep->global_seqno, - rep->table_options.read_amp_bytes_per_bit); + &raw_block_contents, rep->ioptions, + do_decompress /* do uncompress */, rep->blocks_maybe_compressed, + uncompression_dict, rep->persistent_cache_options, + GetMemoryAllocator(rep->table_options), + GetMemoryAllocatorForCompressedBlock(rep->table_options)); + s = block_fetcher.ReadBlockContents(); + raw_block_comp_type = block_fetcher.get_compression_type(); } if (s.ok()) { + SequenceNumber seq_no = rep->get_global_seqno(is_index); + // If filling cache is allowed and a cache is configured, try to put the + // block to the cache. s = PutDataBlockToCache( key, ckey, block_cache, block_cache_compressed, ro, rep->ioptions, - block_entry, raw_block.release(), rep->table_options.format_version, - compression_dict, rep->table_options.read_amp_bytes_per_bit, - is_index, - is_index && - rep->table_options - .cache_index_and_filter_blocks_with_high_priority + block_entry, &raw_block_contents, raw_block_comp_type, + rep->table_options.format_version, uncompression_dict, seq_no, + rep->table_options.read_amp_bytes_per_bit, + GetMemoryAllocator(rep->table_options), is_index, + is_index && rep->table_options + .cache_index_and_filter_blocks_with_high_priority ? Cache::Priority::HIGH - : Cache::Priority::LOW); + : Cache::Priority::LOW, + get_context); } } } @@ -1491,65 +2157,44 @@ Status BlockBasedTable::MaybeLoadDataBlockToCache( return s; } -BlockBasedTable::BlockEntryIteratorState::BlockEntryIteratorState( - BlockBasedTable* table, const ReadOptions& read_options, - const InternalKeyComparator* icomparator, bool skip_filters, bool is_index, - std::unordered_map>* block_map) - : TwoLevelIteratorState(table->rep_->ioptions.prefix_extractor != nullptr), - table_(table), - read_options_(read_options), - icomparator_(icomparator), - skip_filters_(skip_filters), - is_index_(is_index), - block_map_(block_map) {} - -InternalIterator* -BlockBasedTable::BlockEntryIteratorState::NewSecondaryIterator( - const Slice& index_value) { +BlockBasedTable::PartitionedIndexIteratorState::PartitionedIndexIteratorState( + BlockBasedTable* table, + std::unordered_map>* block_map, + bool index_key_includes_seq, bool index_key_is_full) + : table_(table), + block_map_(block_map), + index_key_includes_seq_(index_key_includes_seq), + index_key_is_full_(index_key_is_full) {} + +template +const size_t BlockBasedTableIterator::kMaxReadaheadSize = + 256 * 1024; + +InternalIteratorBase* +BlockBasedTable::PartitionedIndexIteratorState::NewSecondaryIterator( + const BlockHandle& handle) { // Return a block iterator on the index partition - BlockHandle handle; - Slice input = index_value; - Status s = handle.DecodeFrom(&input); - auto rep = table_->rep_; - if (block_map_) { - auto block = block_map_->find(handle.offset()); - // This is a possible scenario since block cache might not have had space - // for the partition - if (block != block_map_->end()) { - PERF_COUNTER_ADD(block_cache_hit_count, 1); - RecordTick(rep->ioptions.statistics, BLOCK_CACHE_INDEX_HIT); - RecordTick(rep->ioptions.statistics, BLOCK_CACHE_HIT); - Cache* block_cache = rep->table_options.block_cache.get(); - assert(block_cache); - RecordTick(rep->ioptions.statistics, BLOCK_CACHE_BYTES_READ, - block_cache->GetUsage(block->second.cache_handle)); - return block->second.value->NewIterator( - &rep->internal_comparator, nullptr, true, rep->ioptions.statistics); - } - } - return NewDataBlockIterator(rep, read_options_, handle, nullptr, is_index_, - s); -} - -bool BlockBasedTable::BlockEntryIteratorState::PrefixMayMatch( - const Slice& internal_key) { - if (read_options_.total_order_seek || skip_filters_) { - return true; - } - return table_->PrefixMayMatch(internal_key); -} - -bool BlockBasedTable::BlockEntryIteratorState::KeyReachedUpperBound( - const Slice& internal_key) { - bool reached_upper_bound = read_options_.iterate_upper_bound != nullptr && - icomparator_ != nullptr && - icomparator_->user_comparator()->Compare( - ExtractUserKey(internal_key), - *read_options_.iterate_upper_bound) >= 0; - TEST_SYNC_POINT_CALLBACK( - "BlockBasedTable::BlockEntryIteratorState::KeyReachedUpperBound", - &reached_upper_bound); - return reached_upper_bound; + auto rep = table_->get_rep(); + auto block = block_map_->find(handle.offset()); + // This is a possible scenario since block cache might not have had space + // for the partition + if (block != block_map_->end()) { + PERF_COUNTER_ADD(block_cache_hit_count, 1); + RecordTick(rep->ioptions.statistics, BLOCK_CACHE_INDEX_HIT); + RecordTick(rep->ioptions.statistics, BLOCK_CACHE_HIT); + Cache* block_cache = rep->table_options.block_cache.get(); + assert(block_cache); + RecordTick(rep->ioptions.statistics, BLOCK_CACHE_BYTES_READ, + block_cache->GetUsage(block->second.cache_handle)); + Statistics* kNullStats = nullptr; + // We don't return pinned datat from index blocks, so no need + // to set `block_contents_pinned`. + return block->second.value->NewIterator( + &rep->internal_comparator, rep->internal_comparator.user_comparator(), + nullptr, kNullStats, true, index_key_includes_seq_, index_key_is_full_); + } + // Create an empty iterator + return new IndexBlockIter(); } // This will be broken if the user specifies an unusual implementation @@ -1564,32 +2209,52 @@ bool BlockBasedTable::BlockEntryIteratorState::KeyReachedUpperBound( // Otherwise, this method guarantees no I/O will be incurred. // // REQUIRES: this method shouldn't be called while the DB lock is held. -bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) { +bool BlockBasedTable::PrefixMayMatch( + const Slice& internal_key, const ReadOptions& read_options, + const SliceTransform* options_prefix_extractor, + const bool need_upper_bound_check) { if (!rep_->filter_policy) { return true; } - assert(rep_->ioptions.prefix_extractor != nullptr); + const SliceTransform* prefix_extractor; + + if (rep_->table_prefix_extractor == nullptr) { + if (need_upper_bound_check) { + return true; + } + prefix_extractor = options_prefix_extractor; + } else { + prefix_extractor = rep_->table_prefix_extractor.get(); + } auto user_key = ExtractUserKey(internal_key); - if (!rep_->ioptions.prefix_extractor->InDomain(user_key) || - rep_->table_properties->prefix_extractor_name.compare( - rep_->ioptions.prefix_extractor->Name()) != 0) { + if (!prefix_extractor->InDomain(user_key)) { return true; } - auto prefix = rep_->ioptions.prefix_extractor->Transform(user_key); bool may_match = true; Status s; // First, try check with full filter - auto filter_entry = GetFilter(); + auto filter_entry = GetFilter(prefix_extractor); FilterBlockReader* filter = filter_entry.value; + bool filter_checked = true; if (filter != nullptr) { if (!filter->IsBlockBased()) { const Slice* const const_ikey_ptr = &internal_key; - may_match = - filter->PrefixMayMatch(prefix, kNotValid, false, const_ikey_ptr); + may_match = filter->RangeMayExist( + read_options.iterate_upper_bound, user_key, prefix_extractor, + rep_->internal_comparator.user_comparator(), const_ikey_ptr, + &filter_checked, need_upper_bound_check); } else { + // if prefix_extractor changed for block based filter, skip filter + if (need_upper_bound_check) { + if (!rep_->filter_entry.IsSet()) { + filter_entry.Release(rep_->table_options.block_cache.get()); + } + return true; + } + auto prefix = prefix_extractor->Transform(user_key); InternalKey internal_key_prefix(prefix, kMaxSequenceNumber, kTypeValue); auto internal_prefix = internal_key_prefix.Encode(); @@ -1600,7 +2265,11 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) { no_io_read_options.read_tier = kBlockCacheTier; // Then, try find it within each block - unique_ptr iiter(NewIndexIterator(no_io_read_options)); + // we already know prefix_extractor and prefix_extractor_name must match + // because `CheckPrefixMayMatch` first checks `check_filter_ == true` + std::unique_ptr> iiter( + NewIndexIterator(no_io_read_options, + /* need_upper_bound_check */ false)); iiter->Seek(internal_prefix); if (!iiter->Valid()) { @@ -1609,7 +2278,10 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) { // and we're not really sure that we're past the end // of the file may_match = iiter->status().IsIncomplete(); - } else if (ExtractUserKey(iiter->key()) + } else if ((rep_->table_properties && + rep_->table_properties->index_key_is_user_key + ? iiter->key() + : ExtractUserKey(iiter->key())) .starts_with(ExtractUserKey(internal_prefix))) { // we need to check for this subtle case because our only // guarantee is that "the key is a string >= last key in that data @@ -1628,19 +2300,19 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) { // after the data block corresponding to iiter->key() cannot // possibly contain the key. Thus, the corresponding data block // is the only on could potentially contain the prefix. - Slice handle_value = iiter->value(); - BlockHandle handle; - s = handle.DecodeFrom(&handle_value); - assert(s.ok()); - may_match = filter->PrefixMayMatch(prefix, handle.offset()); + BlockHandle handle = iiter->value(); + may_match = + filter->PrefixMayMatch(prefix, prefix_extractor, handle.offset()); } } } - Statistics* statistics = rep_->ioptions.statistics; - RecordTick(statistics, BLOOM_FILTER_PREFIX_CHECKED); - if (!may_match) { - RecordTick(statistics, BLOOM_FILTER_PREFIX_USEFUL); + if (filter_checked) { + Statistics* statistics = rep_->ioptions.statistics; + RecordTick(statistics, BLOOM_FILTER_PREFIX_CHECKED); + if (!may_match) { + RecordTick(statistics, BLOOM_FILTER_PREFIX_USEFUL); + } } // if rep_->filter_entry is not set, we should call Release(); otherwise @@ -1649,121 +2321,375 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) { if (!rep_->filter_entry.IsSet()) { filter_entry.Release(rep_->table_options.block_cache.get()); } - return may_match; } -InternalIterator* BlockBasedTable::NewIterator(const ReadOptions& read_options, - Arena* arena, - bool skip_filters) { - return NewTwoLevelIterator( - new BlockEntryIteratorState(this, read_options, - &rep_->internal_comparator, skip_filters), - NewIndexIterator(read_options), arena); +template +void BlockBasedTableIterator::Seek(const Slice& target) { + is_out_of_bound_ = false; + if (!CheckPrefixMayMatch(target)) { + ResetDataIter(); + return; + } + + SavePrevIndexValue(); + + index_iter_->Seek(target); + + if (!index_iter_->Valid()) { + ResetDataIter(); + return; + } + + InitDataBlock(); + + block_iter_.Seek(target); + + FindKeyForward(); + assert( + !block_iter_.Valid() || + (key_includes_seq_ && icomp_.Compare(target, block_iter_.key()) <= 0) || + (!key_includes_seq_ && user_comparator_.Compare(ExtractUserKey(target), + block_iter_.key()) <= 0)); +} + +template +void BlockBasedTableIterator::SeekForPrev( + const Slice& target) { + is_out_of_bound_ = false; + if (!CheckPrefixMayMatch(target)) { + ResetDataIter(); + return; + } + + SavePrevIndexValue(); + + // Call Seek() rather than SeekForPrev() in the index block, because the + // target data block will likely to contain the position for `target`, the + // same as Seek(), rather than than before. + // For example, if we have three data blocks, each containing two keys: + // [2, 4] [6, 8] [10, 12] + // (the keys in the index block would be [4, 8, 12]) + // and the user calls SeekForPrev(7), we need to go to the second block, + // just like if they call Seek(7). + // The only case where the block is difference is when they seek to a position + // in the boundary. For example, if they SeekForPrev(5), we should go to the + // first block, rather than the second. However, we don't have the information + // to distinguish the two unless we read the second block. In this case, we'll + // end up with reading two blocks. + index_iter_->Seek(target); + + if (!index_iter_->Valid()) { + index_iter_->SeekToLast(); + if (!index_iter_->Valid()) { + ResetDataIter(); + block_iter_points_to_real_block_ = false; + return; + } + } + + InitDataBlock(); + + block_iter_.SeekForPrev(target); + + FindKeyBackward(); + assert(!block_iter_.Valid() || + icomp_.Compare(target, block_iter_.key()) >= 0); +} + +template +void BlockBasedTableIterator::SeekToFirst() { + is_out_of_bound_ = false; + SavePrevIndexValue(); + index_iter_->SeekToFirst(); + if (!index_iter_->Valid()) { + ResetDataIter(); + return; + } + InitDataBlock(); + block_iter_.SeekToFirst(); + FindKeyForward(); +} + +template +void BlockBasedTableIterator::SeekToLast() { + is_out_of_bound_ = false; + SavePrevIndexValue(); + index_iter_->SeekToLast(); + if (!index_iter_->Valid()) { + ResetDataIter(); + return; + } + InitDataBlock(); + block_iter_.SeekToLast(); + FindKeyBackward(); +} + +template +void BlockBasedTableIterator::Next() { + assert(block_iter_points_to_real_block_); + block_iter_.Next(); + FindKeyForward(); +} + +template +void BlockBasedTableIterator::Prev() { + assert(block_iter_points_to_real_block_); + block_iter_.Prev(); + FindKeyBackward(); +} + +template +void BlockBasedTableIterator::InitDataBlock() { + BlockHandle data_block_handle = index_iter_->value(); + if (!block_iter_points_to_real_block_ || + data_block_handle.offset() != prev_index_value_.offset() || + // if previous attempt of reading the block missed cache, try again + block_iter_.status().IsIncomplete()) { + if (block_iter_points_to_real_block_) { + ResetDataIter(); + } + auto* rep = table_->get_rep(); + + // Automatically prefetch additional data when a range scan (iterator) does + // more than 2 sequential IOs. This is enabled only for user reads and when + // ReadOptions.readahead_size is 0. + if (!for_compaction_ && read_options_.readahead_size == 0) { + num_file_reads_++; + if (num_file_reads_ > 2) { + if (!rep->file->use_direct_io() && + (data_block_handle.offset() + + static_cast(data_block_handle.size()) + + kBlockTrailerSize > + readahead_limit_)) { + // Buffered I/O + // Discarding the return status of Prefetch calls intentionally, as we + // can fallback to reading from disk if Prefetch fails. + rep->file->Prefetch(data_block_handle.offset(), readahead_size_); + readahead_limit_ = + static_cast(data_block_handle.offset() + readahead_size_); + // Keep exponentially increasing readahead size until + // kMaxReadaheadSize. + readahead_size_ = std::min(kMaxReadaheadSize, readahead_size_ * 2); + } else if (rep->file->use_direct_io() && !prefetch_buffer_) { + // Direct I/O + // Let FilePrefetchBuffer take care of the readahead. + prefetch_buffer_.reset(new FilePrefetchBuffer( + rep->file.get(), kInitReadaheadSize, kMaxReadaheadSize)); + } + } + } + + Status s; + BlockBasedTable::NewDataBlockIterator( + rep, read_options_, data_block_handle, &block_iter_, is_index_, + key_includes_seq_, index_key_is_full_, + /* get_context */ nullptr, s, prefetch_buffer_.get()); + block_iter_points_to_real_block_ = true; + } +} + +template +void BlockBasedTableIterator::FindKeyForward() { + assert(!is_out_of_bound_); + // TODO the while loop inherits from two-level-iterator. We don't know + // whether a block can be empty so it can be replaced by an "if". + while (!block_iter_.Valid()) { + if (!block_iter_.status().ok()) { + return; + } + ResetDataIter(); + // We used to check the current index key for upperbound. + // It will only save a data reading for a small percentage of use cases, + // so for code simplicity, we removed it. We can add it back if there is a + // significnat performance regression. + index_iter_->Next(); + + if (index_iter_->Valid()) { + InitDataBlock(); + block_iter_.SeekToFirst(); + } else { + return; + } + } + + // Check upper bound on the current key + bool reached_upper_bound = + (read_options_.iterate_upper_bound != nullptr && + block_iter_points_to_real_block_ && block_iter_.Valid() && + user_comparator_.Compare(ExtractUserKey(block_iter_.key()), + *read_options_.iterate_upper_bound) >= 0); + TEST_SYNC_POINT_CALLBACK( + "BlockBasedTable::BlockEntryIteratorState::KeyReachedUpperBound", + &reached_upper_bound); + if (reached_upper_bound) { + is_out_of_bound_ = true; + return; + } +} + +template +void BlockBasedTableIterator::FindKeyBackward() { + assert(!is_out_of_bound_); + while (!block_iter_.Valid()) { + if (!block_iter_.status().ok()) { + return; + } + + ResetDataIter(); + index_iter_->Prev(); + + if (index_iter_->Valid()) { + InitDataBlock(); + block_iter_.SeekToLast(); + } else { + return; + } + } + + // We could have check lower bound here too, but we opt not to do it for + // code simplicity. } -InternalIterator* BlockBasedTable::NewRangeTombstoneIterator( +InternalIterator* BlockBasedTable::NewIterator( + const ReadOptions& read_options, const SliceTransform* prefix_extractor, + Arena* arena, bool skip_filters, bool for_compaction) { + bool need_upper_bound_check = + PrefixExtractorChanged(rep_->table_properties.get(), prefix_extractor); + const bool kIsNotIndex = false; + if (arena == nullptr) { + return new BlockBasedTableIterator( + this, read_options, rep_->internal_comparator, + NewIndexIterator( + read_options, + need_upper_bound_check && + rep_->index_type == BlockBasedTableOptions::kHashSearch), + !skip_filters && !read_options.total_order_seek && + prefix_extractor != nullptr, + need_upper_bound_check, prefix_extractor, kIsNotIndex, + true /*key_includes_seq*/, true /*index_key_is_full*/, for_compaction); + } else { + auto* mem = + arena->AllocateAligned(sizeof(BlockBasedTableIterator)); + return new (mem) BlockBasedTableIterator( + this, read_options, rep_->internal_comparator, + NewIndexIterator(read_options, need_upper_bound_check), + !skip_filters && !read_options.total_order_seek && + prefix_extractor != nullptr, + need_upper_bound_check, prefix_extractor, kIsNotIndex, + true /*key_includes_seq*/, true /*index_key_is_full*/, for_compaction); + } +} + +FragmentedRangeTombstoneIterator* BlockBasedTable::NewRangeTombstoneIterator( const ReadOptions& read_options) { - if (rep_->range_del_handle.IsNull()) { - // The block didn't exist, nullptr indicates no range tombstones. + if (rep_->fragmented_range_dels == nullptr) { return nullptr; } - if (rep_->range_del_entry.cache_handle != nullptr) { - // We have a handle to an uncompressed block cache entry that's held for - // this table's lifetime. Increment its refcount before returning an - // iterator based on it since the returned iterator may outlive this table - // reader. - assert(rep_->range_del_entry.value != nullptr); - Cache* block_cache = rep_->table_options.block_cache.get(); - assert(block_cache != nullptr); - if (block_cache->Ref(rep_->range_del_entry.cache_handle)) { - auto iter = rep_->range_del_entry.value->NewIterator( - &rep_->internal_comparator, nullptr /* iter */, - true /* total_order_seek */, rep_->ioptions.statistics); - iter->RegisterCleanup(&ReleaseCachedEntry, block_cache, - rep_->range_del_entry.cache_handle); - return iter; - } + SequenceNumber snapshot = kMaxSequenceNumber; + if (read_options.snapshot != nullptr) { + snapshot = read_options.snapshot->GetSequenceNumber(); } - std::string str; - rep_->range_del_handle.EncodeTo(&str); - // The meta-block exists but isn't in uncompressed block cache (maybe because - // it is disabled), so go through the full lookup process. - return NewDataBlockIterator(rep_, read_options, Slice(str)); + return new FragmentedRangeTombstoneIterator( + rep_->fragmented_range_dels, rep_->internal_comparator, snapshot); } -bool BlockBasedTable::FullFilterKeyMayMatch(const ReadOptions& read_options, - FilterBlockReader* filter, - const Slice& internal_key, - const bool no_io) const { +bool BlockBasedTable::FullFilterKeyMayMatch( + const ReadOptions& read_options, FilterBlockReader* filter, + const Slice& internal_key, const bool no_io, + const SliceTransform* prefix_extractor) const { if (filter == nullptr || filter->IsBlockBased()) { return true; } Slice user_key = ExtractUserKey(internal_key); const Slice* const const_ikey_ptr = &internal_key; + bool may_match = true; if (filter->whole_key_filtering()) { - return filter->KeyMayMatch(user_key, kNotValid, no_io, const_ikey_ptr); - } - if (!read_options.total_order_seek && rep_->ioptions.prefix_extractor && - rep_->table_properties->prefix_extractor_name.compare( - rep_->ioptions.prefix_extractor->Name()) == 0 && - rep_->ioptions.prefix_extractor->InDomain(user_key) && - !filter->PrefixMayMatch( - rep_->ioptions.prefix_extractor->Transform(user_key), kNotValid, - false, const_ikey_ptr)) { - return false; + may_match = filter->KeyMayMatch(user_key, prefix_extractor, kNotValid, + no_io, const_ikey_ptr); + } else if (!read_options.total_order_seek && prefix_extractor && + rep_->table_properties->prefix_extractor_name.compare( + prefix_extractor->Name()) == 0 && + prefix_extractor->InDomain(user_key) && + !filter->PrefixMayMatch(prefix_extractor->Transform(user_key), + prefix_extractor, kNotValid, false, + const_ikey_ptr)) { + may_match = false; + } + if (may_match) { + RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_FULL_POSITIVE); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_positive, 1, rep_->level); } - return true; + return may_match; } Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key, - GetContext* get_context, bool skip_filters) { + GetContext* get_context, + const SliceTransform* prefix_extractor, + bool skip_filters) { + assert(key.size() >= 8); // key must be internal key Status s; const bool no_io = read_options.read_tier == kBlockCacheTier; CachableEntry filter_entry; if (!skip_filters) { - filter_entry = GetFilter(/*prefetch_buffer*/ nullptr, - read_options.read_tier == kBlockCacheTier); + filter_entry = + GetFilter(prefix_extractor, /*prefetch_buffer*/ nullptr, + read_options.read_tier == kBlockCacheTier, get_context); } FilterBlockReader* filter = filter_entry.value; // First check the full filter // If full filter not useful, Then go into each block - if (!FullFilterKeyMayMatch(read_options, filter, key, no_io)) { + if (!FullFilterKeyMayMatch(read_options, filter, key, no_io, + prefix_extractor)) { RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, rep_->level); } else { - BlockIter iiter_on_stack; - auto iiter = NewIndexIterator(read_options, &iiter_on_stack); - std::unique_ptr iiter_unique_ptr; + IndexBlockIter iiter_on_stack; + // if prefix_extractor found in block differs from options, disable + // BlockPrefixIndex. Only do this check when index_type is kHashSearch. + bool need_upper_bound_check = false; + if (rep_->index_type == BlockBasedTableOptions::kHashSearch) { + need_upper_bound_check = PrefixExtractorChanged( + rep_->table_properties.get(), prefix_extractor); + } + auto iiter = + NewIndexIterator(read_options, need_upper_bound_check, &iiter_on_stack, + /* index_entry */ nullptr, get_context); + std::unique_ptr> iiter_unique_ptr; if (iiter != &iiter_on_stack) { iiter_unique_ptr.reset(iiter); } + bool matched = false; // if such user key mathced a key in SST bool done = false; for (iiter->Seek(key); iiter->Valid() && !done; iiter->Next()) { - Slice handle_value = iiter->value(); + BlockHandle handle = iiter->value(); - BlockHandle handle; bool not_exist_in_filter = filter != nullptr && filter->IsBlockBased() == true && - handle.DecodeFrom(&handle_value).ok() && - !filter->KeyMayMatch(ExtractUserKey(key), handle.offset(), no_io); + !filter->KeyMayMatch(ExtractUserKey(key), prefix_extractor, + handle.offset(), no_io); if (not_exist_in_filter) { // Not found // TODO: think about interaction with Merge. If a user key cannot // cross one data block, we should be fine. RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, rep_->level); break; } else { - BlockIter biter; - NewDataBlockIterator(rep_, read_options, iiter->value(), &biter); + DataBlockIter biter; + NewDataBlockIterator( + rep_, read_options, iiter->value(), &biter, false, + true /* key_includes_seq */, true /* index_key_is_full */, + get_context); if (read_options.read_tier == kBlockCacheTier && biter.status().IsIncomplete()) { // couldn't get block from block_cache - // Update Saver.state to Found because we are only looking for whether - // we can guarantee the key is not there when "no_io" is set + // Update Saver.state to Found because we are only looking for + // whether we can guarantee the key is not there when "no_io" is set get_context->MarkKeyMayExist(); break; } @@ -1772,14 +2698,25 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key, break; } + bool may_exist = biter.SeekForGet(key); + if (!may_exist) { + // HashSeek cannot find the key this block and the the iter is not + // the end of the block, i.e. cannot be in the following blocks + // either. In this case, the seek_key cannot be found, so we break + // from the top level for-loop. + break; + } + // Call the *saver function on each entry/block until it returns false - for (biter.Seek(key); biter.Valid(); biter.Next()) { + for (; biter.Valid(); biter.Next()) { ParsedInternalKey parsed_key; if (!ParseInternalKey(biter.key(), &parsed_key)) { s = Status::Corruption(Slice()); } - if (!get_context->SaveValue(parsed_key, biter.value(), &biter)) { + if (!get_context->SaveValue( + parsed_key, biter.value(), &matched, + biter.IsValuePinned() ? &biter : nullptr)) { done = true; break; } @@ -1791,6 +2728,11 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key, break; } } + if (matched && filter != nullptr && !filter->IsBlockBased()) { + RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_FULL_TRUE_POSITIVE); + PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_full_true_positive, 1, + rep_->level); + } if (s.ok()) { s = iiter->status(); } @@ -1808,16 +2750,18 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key, Status BlockBasedTable::Prefetch(const Slice* const begin, const Slice* const end) { auto& comparator = rep_->internal_comparator; + auto user_comparator = comparator.user_comparator(); // pre-condition if (begin && end && comparator.Compare(*begin, *end) > 0) { return Status::InvalidArgument(*begin, *end); } - BlockIter iiter_on_stack; - auto iiter = NewIndexIterator(ReadOptions(), &iiter_on_stack); - std::unique_ptr iiter_unique_ptr; + IndexBlockIter iiter_on_stack; + auto iiter = NewIndexIterator(ReadOptions(), false, &iiter_on_stack); + std::unique_ptr> iiter_unique_ptr; if (iiter != &iiter_on_stack) { - iiter_unique_ptr = std::unique_ptr(iiter); + iiter_unique_ptr = + std::unique_ptr>(iiter); } if (!iiter->status().ok()) { @@ -1830,9 +2774,13 @@ Status BlockBasedTable::Prefetch(const Slice* const begin, for (begin ? iiter->Seek(*begin) : iiter->SeekToFirst(); iiter->Valid(); iiter->Next()) { - Slice block_handle = iiter->value(); - - if (end && comparator.Compare(iiter->key(), *end) >= 0) { + BlockHandle block_handle = iiter->value(); + const bool is_user_key = rep_->table_properties && + rep_->table_properties->index_key_is_user_key > 0; + if (end && + ((!is_user_key && comparator.Compare(iiter->key(), *end) >= 0) || + (is_user_key && + user_comparator->Compare(iiter->key(), ExtractUserKey(*end)) >= 0))) { if (prefetching_boundary_page) { break; } @@ -1843,8 +2791,9 @@ Status BlockBasedTable::Prefetch(const Slice* const begin, } // Load the block specified by the block_handle into the block cache - BlockIter biter; - NewDataBlockIterator(rep_, ReadOptions(), block_handle, &biter); + DataBlockIter biter; + NewDataBlockIterator(rep_, ReadOptions(), block_handle, + &biter); if (!biter.status().ok()) { // there was an unexpected error while pre-fetching @@ -1862,7 +2811,7 @@ Status BlockBasedTable::VerifyChecksum() { std::unique_ptr meta_iter; s = ReadMetaBlock(rep_, nullptr /* prefetch buffer */, &meta, &meta_iter); if (s.ok()) { - s = VerifyChecksumInBlocks(meta_iter.get()); + s = VerifyChecksumInMetaBlocks(meta_iter.get()); if (!s.ok()) { return s; } @@ -1870,11 +2819,13 @@ Status BlockBasedTable::VerifyChecksum() { return s; } // Check Data blocks - BlockIter iiter_on_stack; - InternalIterator* iiter = NewIndexIterator(ReadOptions(), &iiter_on_stack); - std::unique_ptr iiter_unique_ptr; + IndexBlockIter iiter_on_stack; + InternalIteratorBase* iiter = + NewIndexIterator(ReadOptions(), false, &iiter_on_stack); + std::unique_ptr> iiter_unique_ptr; if (iiter != &iiter_on_stack) { - iiter_unique_ptr = std::unique_ptr(iiter); + iiter_unique_ptr = + std::unique_ptr>(iiter); } if (!iiter->status().ok()) { // error opening index iterator @@ -1884,25 +2835,54 @@ Status BlockBasedTable::VerifyChecksum() { return s; } -Status BlockBasedTable::VerifyChecksumInBlocks(InternalIterator* index_iter) { +Status BlockBasedTable::VerifyChecksumInBlocks( + InternalIteratorBase* index_iter) { Status s; for (index_iter->SeekToFirst(); index_iter->Valid(); index_iter->Next()) { s = index_iter->status(); if (!s.ok()) { break; } - BlockHandle handle; - Slice input = index_iter->value(); - s = handle.DecodeFrom(&input); + BlockHandle handle = index_iter->value(); + BlockContents contents; + BlockFetcher block_fetcher( + rep_->file.get(), nullptr /* prefetch buffer */, rep_->footer, + ReadOptions(), handle, &contents, rep_->ioptions, + false /* decompress */, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), rep_->persistent_cache_options); + s = block_fetcher.ReadBlockContents(); + if (!s.ok()) { + break; + } + } + return s; +} + +Status BlockBasedTable::VerifyChecksumInMetaBlocks( + InternalIteratorBase* index_iter) { + Status s; + for (index_iter->SeekToFirst(); index_iter->Valid(); index_iter->Next()) { + s = index_iter->status(); if (!s.ok()) { break; } + BlockHandle handle; + Slice input = index_iter->value(); + s = handle.DecodeFrom(&input); BlockContents contents; - s = ReadBlockContents(rep_->file.get(), nullptr /* prefetch buffer */, - rep_->footer, ReadOptions(), handle, &contents, - rep_->ioptions, false /* decompress */, - Slice() /*compression dict*/, - rep_->persistent_cache_options); + BlockFetcher block_fetcher( + rep_->file.get(), nullptr /* prefetch buffer */, rep_->footer, + ReadOptions(), handle, &contents, rep_->ioptions, + false /* decompress */, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), rep_->persistent_cache_options); + s = block_fetcher.ReadBlockContents(); + if (s.IsCorruption() && index_iter->key() == kPropertiesBlock) { + TableProperties* table_properties; + s = TryReadPropertiesWithGlobalSeqno(rep_, nullptr /* prefetch_buffer */, + index_iter->value(), + &table_properties); + delete table_properties; + } if (!s.ok()) { break; } @@ -1912,30 +2892,41 @@ Status BlockBasedTable::VerifyChecksumInBlocks(InternalIterator* index_iter) { bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options, const Slice& key) { - std::unique_ptr iiter(NewIndexIterator(options)); + std::unique_ptr> iiter( + NewIndexIterator(options)); iiter->Seek(key); assert(iiter->Valid()); CachableEntry block; - BlockHandle handle; - Slice input = iiter->value(); - Status s = handle.DecodeFrom(&input); - assert(s.ok()); + BlockHandle handle = iiter->value(); Cache* block_cache = rep_->table_options.block_cache.get(); assert(block_cache != nullptr); char cache_key_storage[kMaxCacheKeyPrefixSize + kMaxVarint64Length]; Slice cache_key = - GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size, - handle, cache_key_storage); + GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size, handle, + cache_key_storage); Slice ckey; - s = GetDataBlockFromCache( - cache_key, ckey, block_cache, nullptr, rep_->ioptions, options, &block, - rep_->table_options.format_version, - rep_->compression_dict_block ? rep_->compression_dict_block->data - : Slice(), - 0 /* read_amp_bytes_per_bit */); + Status s; + if (!rep_->compression_dict_handle.IsNull()) { + std::unique_ptr compression_dict_block; + s = ReadCompressionDictBlock(rep_, nullptr /* prefetch_buffer */, + &compression_dict_block); + if (s.ok()) { + assert(compression_dict_block != nullptr); + UncompressionDict uncompression_dict( + compression_dict_block->data.ToString(), + rep_->blocks_definitely_zstd_compressed); + s = GetDataBlockFromCache(cache_key, ckey, block_cache, nullptr, rep_, + options, &block, uncompression_dict, + 0 /* read_amp_bytes_per_bit */); + } + } else { + s = GetDataBlockFromCache( + cache_key, ckey, block_cache, nullptr, rep_, options, &block, + UncompressionDict::GetEmptyDict(), 0 /* read_amp_bytes_per_bit */); + } assert(s.ok()); bool in_cache = block.value != nullptr; if (in_cache) { @@ -1944,50 +2935,66 @@ bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options, return in_cache; } -// REQUIRES: The following fields of rep_ should have already been populated: -// 1. file -// 2. index_handle, -// 3. options -// 4. internal_comparator -// 5. index_type -Status BlockBasedTable::CreateIndexReader( - FilePrefetchBuffer* prefetch_buffer, IndexReader** index_reader, - InternalIterator* preloaded_meta_index_iter, int level) { +BlockBasedTableOptions::IndexType BlockBasedTable::UpdateIndexType() { // Some old version of block-based tables don't have index type present in // table properties. If that's the case we can safely use the kBinarySearch. - auto index_type_on_file = BlockBasedTableOptions::kBinarySearch; + BlockBasedTableOptions::IndexType index_type_on_file = + BlockBasedTableOptions::kBinarySearch; if (rep_->table_properties) { auto& props = rep_->table_properties->user_collected_properties; auto pos = props.find(BlockBasedTablePropertyNames::kIndexType); if (pos != props.end()) { index_type_on_file = static_cast( DecodeFixed32(pos->second.c_str())); + // update index_type with the true type + rep_->index_type = index_type_on_file; } } + return index_type_on_file; +} + +// REQUIRES: The following fields of rep_ should have already been populated: +// 1. file +// 2. index_handle, +// 3. options +// 4. internal_comparator +// 5. index_type +Status BlockBasedTable::CreateIndexReader( + FilePrefetchBuffer* prefetch_buffer, IndexReader** index_reader, + InternalIterator* preloaded_meta_index_iter, int level) { + auto index_type_on_file = UpdateIndexType(); auto file = rep_->file.get(); const InternalKeyComparator* icomparator = &rep_->internal_comparator; const Footer& footer = rep_->footer; - if (index_type_on_file == BlockBasedTableOptions::kHashSearch && - rep_->ioptions.prefix_extractor == nullptr) { - ROCKS_LOG_WARN(rep_->ioptions.info_log, - "BlockBasedTableOptions::kHashSearch requires " - "options.prefix_extractor to be set." - " Fall back to binary search index."); - index_type_on_file = BlockBasedTableOptions::kBinarySearch; - } + + // kHashSearch requires non-empty prefix_extractor but bypass checking + // prefix_extractor here since we have no access to MutableCFOptions. + // Add need_upper_bound_check flag in BlockBasedTable::NewIndexIterator. + // If prefix_extractor does not match prefix_extractor_name from table + // properties, turn off Hash Index by setting total_order_seek to true switch (index_type_on_file) { case BlockBasedTableOptions::kTwoLevelIndexSearch: { return PartitionIndexReader::Create( this, file, prefetch_buffer, footer, footer.index_handle(), rep_->ioptions, icomparator, index_reader, - rep_->persistent_cache_options, level); + rep_->persistent_cache_options, level, + rep_->table_properties == nullptr || + rep_->table_properties->index_key_is_user_key == 0, + rep_->table_properties == nullptr || + rep_->table_properties->index_value_is_delta_encoded == 0, + GetMemoryAllocator(rep_->table_options)); } case BlockBasedTableOptions::kBinarySearch: { return BinarySearchIndexReader::Create( file, prefetch_buffer, footer, footer.index_handle(), rep_->ioptions, - icomparator, index_reader, rep_->persistent_cache_options); + icomparator, index_reader, rep_->persistent_cache_options, + rep_->table_properties == nullptr || + rep_->table_properties->index_key_is_user_key == 0, + rep_->table_properties == nullptr || + rep_->table_properties->index_value_is_delta_encoded == 0, + GetMemoryAllocator(rep_->table_options)); } case BlockBasedTableOptions::kHashSearch: { std::unique_ptr meta_guard; @@ -2005,7 +3012,12 @@ Status BlockBasedTable::CreateIndexReader( return BinarySearchIndexReader::Create( file, prefetch_buffer, footer, footer.index_handle(), rep_->ioptions, icomparator, index_reader, - rep_->persistent_cache_options); + rep_->persistent_cache_options, + rep_->table_properties == nullptr || + rep_->table_properties->index_key_is_user_key == 0, + rep_->table_properties == nullptr || + rep_->table_properties->index_value_is_delta_encoded == 0, + GetMemoryAllocator(rep_->table_options)); } meta_index_iter = meta_iter_guard.get(); } @@ -2014,7 +3026,12 @@ Status BlockBasedTable::CreateIndexReader( rep_->internal_prefix_transform.get(), footer, file, prefetch_buffer, rep_->ioptions, icomparator, footer.index_handle(), meta_index_iter, index_reader, rep_->hash_index_allow_collision, - rep_->persistent_cache_options); + rep_->persistent_cache_options, + rep_->table_properties == nullptr || + rep_->table_properties->index_key_is_user_key == 0, + rep_->table_properties == nullptr || + rep_->table_properties->index_value_is_delta_encoded == 0, + GetMemoryAllocator(rep_->table_options)); } default: { std::string error_message = @@ -2025,22 +3042,14 @@ Status BlockBasedTable::CreateIndexReader( } uint64_t BlockBasedTable::ApproximateOffsetOf(const Slice& key) { - unique_ptr index_iter(NewIndexIterator(ReadOptions())); + std::unique_ptr> index_iter( + NewIndexIterator(ReadOptions())); index_iter->Seek(key); uint64_t result; if (index_iter->Valid()) { - BlockHandle handle; - Slice input = index_iter->value(); - Status s = handle.DecodeFrom(&input); - if (s.ok()) { - result = handle.offset(); - } else { - // Strange: we can't decode the block handle in the index block. - // We'll just return the offset of the metaindex block, which is - // close to the whole file size for this case. - result = rep_->footer.metaindex_handle().offset(); - } + BlockHandle handle = index_iter->value(); + result = handle.offset(); } else { // key is past the last key in the file. If table_properties is not // available, approximate the offset by returning the offset of the @@ -2067,7 +3076,7 @@ bool BlockBasedTable::TEST_index_reader_preloaded() const { Status BlockBasedTable::GetKVPairsFromDataBlocks( std::vector* kv_pair_blocks) { - std::unique_ptr blockhandles_iter( + std::unique_ptr> blockhandles_iter( NewIndexIterator(ReadOptions())); Status s = blockhandles_iter->status(); @@ -2085,8 +3094,8 @@ Status BlockBasedTable::GetKVPairsFromDataBlocks( } std::unique_ptr datablock_iter; - datablock_iter.reset( - NewDataBlockIterator(rep_, ReadOptions(), blockhandles_iter->value())); + datablock_iter.reset(NewDataBlockIterator( + rep_, ReadOptions(), blockhandles_iter->value())); s = datablock_iter->status(); if (!s.ok()) { @@ -2115,7 +3124,8 @@ Status BlockBasedTable::GetKVPairsFromDataBlocks( return Status::OK(); } -Status BlockBasedTable::DumpTable(WritableFile* out_file) { +Status BlockBasedTable::DumpTable(WritableFile* out_file, + const SliceTransform* prefix_extractor) { // Output Footer out_file->Append( "Footer Details:\n" @@ -2173,30 +3183,32 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) { " "); out_file->Append(table_properties->ToString("\n ", ": ").c_str()); out_file->Append("\n"); - } - // Output Filter blocks - if (!rep_->filter && !table_properties->filter_policy_name.empty()) { - // Support only BloomFilter as off now - rocksdb::BlockBasedTableOptions table_options; - table_options.filter_policy.reset(rocksdb::NewBloomFilterPolicy(1)); - if (table_properties->filter_policy_name.compare( - table_options.filter_policy->Name()) == 0) { - std::string filter_block_key = kFilterBlockPrefix; - filter_block_key.append(table_properties->filter_policy_name); - BlockHandle handle; - if (FindMetaBlock(meta_iter.get(), filter_block_key, &handle).ok()) { - BlockContents block; - if (ReadBlockContents(rep_->file.get(), nullptr /* prefetch_buffer */, - rep_->footer, ReadOptions(), handle, &block, - rep_->ioptions, false /*decompress*/, - Slice() /*compression dict*/, - rep_->persistent_cache_options) - .ok()) { - rep_->filter.reset(new BlockBasedFilterBlockReader( - rep_->ioptions.prefix_extractor, table_options, - table_options.whole_key_filtering, std::move(block), - rep_->ioptions.statistics)); + // Output Filter blocks + if (!rep_->filter && !table_properties->filter_policy_name.empty()) { + // Support only BloomFilter as off now + rocksdb::BlockBasedTableOptions table_options; + table_options.filter_policy.reset(rocksdb::NewBloomFilterPolicy(1)); + if (table_properties->filter_policy_name.compare( + table_options.filter_policy->Name()) == 0) { + std::string filter_block_key = kFilterBlockPrefix; + filter_block_key.append(table_properties->filter_policy_name); + BlockHandle handle; + if (FindMetaBlock(meta_iter.get(), filter_block_key, &handle).ok()) { + BlockContents block; + BlockFetcher block_fetcher( + rep_->file.get(), nullptr /* prefetch_buffer */, rep_->footer, + ReadOptions(), handle, &block, rep_->ioptions, + false /*decompress*/, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), + rep_->persistent_cache_options); + s = block_fetcher.ReadBlockContents(); + if (!s.ok()) { + rep_->filter.reset(new BlockBasedFilterBlockReader( + prefix_extractor, table_options, + table_options.whole_key_filtering, std::move(block), + rep_->ioptions.statistics)); + } } } } @@ -2217,8 +3229,15 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) { } // Output compression dictionary - if (rep_->compression_dict_block != nullptr) { - auto compression_dict = rep_->compression_dict_block->data; + if (!rep_->compression_dict_handle.IsNull()) { + std::unique_ptr compression_dict_block; + s = ReadCompressionDictBlock(rep_, nullptr /* prefetch_buffer */, + &compression_dict_block); + if (!s.ok()) { + return s; + } + assert(compression_dict_block != nullptr); + auto compression_dict = compression_dict_block->data; out_file->Append( "Compression Dictionary:\n" "--------------------------------------\n"); @@ -2256,22 +3275,36 @@ void BlockBasedTable::Close() { if (rep_->closed) { return; } - rep_->filter_entry.Release(rep_->table_options.block_cache.get()); - rep_->index_entry.Release(rep_->table_options.block_cache.get()); - rep_->range_del_entry.Release(rep_->table_options.block_cache.get()); - // cleanup index and filter blocks to avoid accessing dangling pointer + + Cache* const cache = rep_->table_options.block_cache.get(); + + rep_->filter_entry.Release(cache); + rep_->index_entry.Release(cache); + + // cleanup index, filter, and compression dictionary blocks + // to avoid accessing dangling pointers if (!rep_->table_options.no_block_cache) { char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length]; + // Get the filter block key auto key = GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size, rep_->filter_handle, cache_key); - rep_->table_options.block_cache.get()->Erase(key); + cache->Erase(key); + // Get the index block key key = GetCacheKeyFromOffset(rep_->cache_key_prefix, rep_->cache_key_prefix_size, rep_->dummy_index_reader_offset, cache_key); - rep_->table_options.block_cache.get()->Erase(key); + cache->Erase(key); + + if (!rep_->compression_dict_handle.IsNull()) { + // Get the compression dictionary block key + key = GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size, + rep_->compression_dict_handle, cache_key); + cache->Erase(key); + } } + rep_->closed = true; } @@ -2279,8 +3312,7 @@ Status BlockBasedTable::DumpIndexBlock(WritableFile* out_file) { out_file->Append( "Index Details:\n" "--------------------------------------\n"); - - std::unique_ptr blockhandles_iter( + std::unique_ptr> blockhandles_iter( NewIndexIterator(ReadOptions())); Status s = blockhandles_iter->status(); if (!s.ok()) { @@ -2297,16 +3329,23 @@ Status BlockBasedTable::DumpIndexBlock(WritableFile* out_file) { break; } Slice key = blockhandles_iter->key(); + Slice user_key; InternalKey ikey; - ikey.DecodeFrom(key); + if (rep_->table_properties && + rep_->table_properties->index_key_is_user_key != 0) { + user_key = key; + } else { + ikey.DecodeFrom(key); + user_key = ikey.user_key(); + } out_file->Append(" HEX "); - out_file->Append(ikey.user_key().ToString(true).c_str()); + out_file->Append(user_key.ToString(true).c_str()); out_file->Append(": "); out_file->Append(blockhandles_iter->value().ToString(true).c_str()); out_file->Append("\n"); - std::string str_key = ikey.user_key().ToString(); + std::string str_key = user_key.ToString(); std::string res_key(""); char cspace = ' '; for (size_t i = 0; i < str_key.size(); i++) { @@ -2322,7 +3361,7 @@ Status BlockBasedTable::DumpIndexBlock(WritableFile* out_file) { } Status BlockBasedTable::DumpDataBlocks(WritableFile* out_file) { - std::unique_ptr blockhandles_iter( + std::unique_ptr> blockhandles_iter( NewIndexIterator(ReadOptions())); Status s = blockhandles_iter->status(); if (!s.ok()) { @@ -2342,9 +3381,7 @@ Status BlockBasedTable::DumpDataBlocks(WritableFile* out_file) { break; } - Slice bh_val = blockhandles_iter->value(); - BlockHandle bh; - bh.DecodeFrom(&bh_val); + BlockHandle bh = blockhandles_iter->value(); uint64_t datablock_size = bh.size(); datablock_size_min = std::min(datablock_size_min, datablock_size); datablock_size_max = std::max(datablock_size_max, datablock_size); @@ -2358,8 +3395,8 @@ Status BlockBasedTable::DumpDataBlocks(WritableFile* out_file) { out_file->Append("--------------------------------------\n"); std::unique_ptr datablock_iter; - datablock_iter.reset( - NewDataBlockIterator(rep_, ReadOptions(), blockhandles_iter->value())); + datablock_iter.reset(NewDataBlockIterator( + rep_, ReadOptions(), blockhandles_iter->value())); s = datablock_iter->status(); if (!s.ok()) { @@ -2415,11 +3452,19 @@ void BlockBasedTable::DumpKeyValue(const Slice& key, const Slice& value, std::string res_key(""), res_value(""); char cspace = ' '; for (size_t i = 0; i < str_key.size(); i++) { - res_key.append(&str_key[i], 1); + if (str_key[i] == '\0') { + res_key.append("\\0", 2); + } else { + res_key.append(&str_key[i], 1); + } res_key.append(1, cspace); } for (size_t i = 0; i < str_value.size(); i++) { - res_value.append(&str_value[i], 1); + if (str_value[i] == '\0') { + res_value.append("\\0", 2); + } else { + res_value.append(&str_value[i], 1); + } res_value.append(1, cspace); } @@ -2432,24 +3477,31 @@ void BlockBasedTable::DumpKeyValue(const Slice& key, const Slice& value, namespace { -void DeleteCachedFilterEntry(const Slice& key, void* value) { +void DeleteCachedFilterEntry(const Slice& /*key*/, void* value) { FilterBlockReader* filter = reinterpret_cast(value); if (filter->statistics() != nullptr) { RecordTick(filter->statistics(), BLOCK_CACHE_FILTER_BYTES_EVICT, - filter->size()); + filter->ApproximateMemoryUsage()); } delete filter; } -void DeleteCachedIndexEntry(const Slice& key, void* value) { +void DeleteCachedIndexEntry(const Slice& /*key*/, void* value) { IndexReader* index_reader = reinterpret_cast(value); if (index_reader->statistics() != nullptr) { RecordTick(index_reader->statistics(), BLOCK_CACHE_INDEX_BYTES_EVICT, - index_reader->usable_size()); + index_reader->ApproximateMemoryUsage()); } delete index_reader; } +void DeleteCachedUncompressionDictEntry(const Slice& /*key*/, void* value) { + UncompressionDict* dict = reinterpret_cast(value); + RecordTick(dict->statistics(), BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT, + dict->ApproximateMemoryUsage()); + delete dict; +} + } // anonymous namespace } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_based_table_reader.h b/thirdparty/rocksdb/table/block_based_table_reader.h index a5426cdedf..f0b5cdb1bc 100644 --- a/thirdparty/rocksdb/table/block_based_table_reader.h +++ b/thirdparty/rocksdb/table/block_based_table_reader.h @@ -16,12 +16,15 @@ #include #include +#include "db/range_tombstone_fragmenter.h" #include "options/cf_options.h" #include "rocksdb/options.h" #include "rocksdb/persistent_cache.h" #include "rocksdb/statistics.h" #include "rocksdb/status.h" #include "rocksdb/table.h" +#include "table/block.h" +#include "table/block_based_table_factory.h" #include "table/filter_block.h" #include "table/format.h" #include "table/persistent_cache_helper.h" @@ -30,11 +33,10 @@ #include "table/two_level_iterator.h" #include "util/coding.h" #include "util/file_reader_writer.h" +#include "util/user_comparator_wrapper.h" namespace rocksdb { -class Block; -class BlockIter; class BlockHandle; class Cache; class FilterBlockReader; @@ -51,9 +53,6 @@ struct BlockBasedTableOptions; struct EnvOptions; struct ReadOptions; class GetContext; -class InternalIterator; - -using std::unique_ptr; typedef std::vector> KVPairBlock; @@ -89,27 +88,38 @@ class BlockBasedTable : public TableReader { const EnvOptions& env_options, const BlockBasedTableOptions& table_options, const InternalKeyComparator& internal_key_comparator, - unique_ptr&& file, - uint64_t file_size, unique_ptr* table_reader, + std::unique_ptr&& file, + uint64_t file_size, + std::unique_ptr* table_reader, + const SliceTransform* prefix_extractor = nullptr, bool prefetch_index_and_filter_in_cache = true, - bool skip_filters = false, int level = -1); + bool skip_filters = false, int level = -1, + const bool immortal_table = false, + const SequenceNumber largest_seqno = 0, + TailPrefetchStats* tail_prefetch_stats = nullptr); - bool PrefixMayMatch(const Slice& internal_key); + bool PrefixMayMatch(const Slice& internal_key, + const ReadOptions& read_options, + const SliceTransform* options_prefix_extractor, + const bool need_upper_bound_check); // Returns a new iterator over the table contents. // The result of NewIterator() is initially invalid (caller must // call one of the Seek methods on the iterator before using it). // @param skip_filters Disables loading/accessing the filter block - InternalIterator* NewIterator( - const ReadOptions&, Arena* arena = nullptr, - bool skip_filters = false) override; + InternalIterator* NewIterator(const ReadOptions&, + const SliceTransform* prefix_extractor, + Arena* arena = nullptr, + bool skip_filters = false, + bool for_compaction = false) override; - InternalIterator* NewRangeTombstoneIterator( + FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator( const ReadOptions& read_options) override; // @param skip_filters Disables loading/accessing the filter block Status Get(const ReadOptions& readOptions, const Slice& key, - GetContext* get_context, bool skip_filters = false) override; + GetContext* get_context, const SliceTransform* prefix_extractor, + bool skip_filters = false) override; // Pre-fetch the disk blocks that correspond to the key range specified by // (kbegin, kend). The call will return error status in the event of @@ -137,7 +147,8 @@ class BlockBasedTable : public TableReader { size_t ApproximateMemoryUsage() const override; // convert SST file to a human readable form - Status DumpTable(WritableFile* out_file) override; + Status DumpTable(WritableFile* out_file, + const SliceTransform* prefix_extractor = nullptr) override; Status VerifyChecksum() override; @@ -167,8 +178,9 @@ class BlockBasedTable : public TableReader { // to // a different object then iter and the callee has the ownership of the // returned object. - virtual InternalIterator* NewIterator(BlockIter* iter = nullptr, - bool total_order_seek = true) = 0; + virtual InternalIteratorBase* NewIterator( + IndexBlockIter* iter = nullptr, bool total_order_seek = true, + bool fill_cache = true) = 0; // The size of the index. virtual size_t size() const = 0; @@ -201,29 +213,40 @@ class BlockBasedTable : public TableReader { // The key retrieved are internal keys. Status GetKVPairsFromDataBlocks(std::vector* kv_pair_blocks); - class BlockEntryIteratorState; + template + struct CachableEntry; + struct Rep; + + Rep* get_rep() { return rep_; } + + // input_iter: if it is not null, update this one and return it as Iterator + template + static TBlockIter* NewDataBlockIterator( + Rep* rep, const ReadOptions& ro, const Slice& index_value, + TBlockIter* input_iter = nullptr, bool is_index = false, + bool key_includes_seq = true, bool index_key_is_full = true, + GetContext* get_context = nullptr, + FilePrefetchBuffer* prefetch_buffer = nullptr); + template + static TBlockIter* NewDataBlockIterator( + Rep* rep, const ReadOptions& ro, const BlockHandle& block_hanlde, + TBlockIter* input_iter = nullptr, bool is_index = false, + bool key_includes_seq = true, bool index_key_is_full = true, + GetContext* get_context = nullptr, Status s = Status(), + FilePrefetchBuffer* prefetch_buffer = nullptr); + + class PartitionedIndexIteratorState; friend class PartitionIndexReader; protected: - template - struct CachableEntry; - struct Rep; Rep* rep_; explicit BlockBasedTable(Rep* rep) : rep_(rep) {} private: friend class MockedBlockBasedTable; - // input_iter: if it is not null, update this one and return it as Iterator - static InternalIterator* NewDataBlockIterator(Rep* rep, const ReadOptions& ro, - const Slice& index_value, - BlockIter* input_iter = nullptr, - bool is_index = false); - static InternalIterator* NewDataBlockIterator(Rep* rep, const ReadOptions& ro, - const BlockHandle& block_hanlde, - BlockIter* input_iter = nullptr, - bool is_index = false, - Status s = Status()); + static std::atomic next_cache_key_id_; + // If block cache enabled (compressed or uncompressed), looks for the block // identified by handle in (1) uncompressed cache, (2) compressed cache, and // then (3) file. If found, inserts into the cache(s) that were searched @@ -233,21 +256,27 @@ class BlockBasedTable : public TableReader { // @param block_entry value is set to the uncompressed block if found. If // in uncompressed block cache, also sets cache_handle to reference that // block. - static Status MaybeLoadDataBlockToCache(FilePrefetchBuffer* prefetch_buffer, - Rep* rep, const ReadOptions& ro, - const BlockHandle& handle, - Slice compression_dict, - CachableEntry* block_entry, - bool is_index = false); + static Status MaybeReadBlockAndLoadToCache( + FilePrefetchBuffer* prefetch_buffer, Rep* rep, const ReadOptions& ro, + const BlockHandle& handle, const UncompressionDict& uncompression_dict, + CachableEntry* block_entry, bool is_index = false, + GetContext* get_context = nullptr); // For the following two functions: // if `no_io == true`, we will not try to read filter/index from sst file // were they not present in cache yet. CachableEntry GetFilter( - FilePrefetchBuffer* prefetch_buffer = nullptr, bool no_io = false) const; + const SliceTransform* prefix_extractor = nullptr, + FilePrefetchBuffer* prefetch_buffer = nullptr, bool no_io = false, + GetContext* get_context = nullptr) const; virtual CachableEntry GetFilter( FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_blk_handle, - const bool is_a_filter_partition, bool no_io) const; + const bool is_a_filter_partition, bool no_io, GetContext* get_context, + const SliceTransform* prefix_extractor = nullptr) const; + + static CachableEntry GetUncompressionDict( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, bool no_io, + GetContext* get_context); // Get the iterator from the index reader. // If input_iter is not set, return new Iterator @@ -259,23 +288,26 @@ class BlockBasedTable : public TableReader { // 2. index is not present in block cache. // 3. We disallowed any io to be performed, that is, read_options == // kBlockCacheTier - InternalIterator* NewIndexIterator( - const ReadOptions& read_options, BlockIter* input_iter = nullptr, - CachableEntry* index_entry = nullptr); + InternalIteratorBase* NewIndexIterator( + const ReadOptions& read_options, bool need_upper_bound_check = false, + IndexBlockIter* input_iter = nullptr, + CachableEntry* index_entry = nullptr, + GetContext* get_context = nullptr); // Read block cache from block caches (if set): block_cache and // block_cache_compressed. // On success, Status::OK with be returned and @block will be populated with // pointer to the block as well as its block handle. - // @param compression_dict Data for presetting the compression library's + // @param uncompression_dict Data for presetting the compression library's // dictionary. static Status GetDataBlockFromCache( const Slice& block_cache_key, const Slice& compressed_block_cache_key, - Cache* block_cache, Cache* block_cache_compressed, - const ImmutableCFOptions& ioptions, const ReadOptions& read_options, - BlockBasedTable::CachableEntry* block, uint32_t format_version, - const Slice& compression_dict, size_t read_amp_bytes_per_bit, - bool is_index = false); + Cache* block_cache, Cache* block_cache_compressed, Rep* rep, + const ReadOptions& read_options, + BlockBasedTable::CachableEntry* block, + const UncompressionDict& uncompression_dict, + size_t read_amp_bytes_per_bit, bool is_index = false, + GetContext* get_context = nullptr); // Put a raw block (maybe compressed) to the corresponding block caches. // This method will perform decompression against raw_block if needed and then @@ -283,17 +315,20 @@ class BlockBasedTable : public TableReader { // On success, Status::OK will be returned; also @block will be populated with // uncompressed block and its cache handle. // - // REQUIRES: raw_block is heap-allocated. PutDataBlockToCache() will be - // responsible for releasing its memory if error occurs. - // @param compression_dict Data for presetting the compression library's + // Allocated memory managed by raw_block_contents will be transferred to + // PutDataBlockToCache(). After the call, the object will be invalid. + // @param uncompression_dict Data for presetting the compression library's // dictionary. static Status PutDataBlockToCache( const Slice& block_cache_key, const Slice& compressed_block_cache_key, Cache* block_cache, Cache* block_cache_compressed, const ReadOptions& read_options, const ImmutableCFOptions& ioptions, - CachableEntry* block, Block* raw_block, uint32_t format_version, - const Slice& compression_dict, size_t read_amp_bytes_per_bit, - bool is_index = false, Cache::Priority pri = Cache::Priority::LOW); + CachableEntry* block, BlockContents* raw_block_contents, + CompressionType raw_block_comp_type, uint32_t format_version, + const UncompressionDict& uncompression_dict, SequenceNumber seq_no, + size_t read_amp_bytes_per_bit, MemoryAllocator* memory_allocator, + bool is_index = false, Cache::Priority pri = Cache::Priority::LOW, + GetContext* get_context = nullptr); // Calls (*handle_result)(arg, ...) repeatedly, starting with the entry found // after a call to Seek(key), until handle_result returns false. @@ -303,6 +338,9 @@ class BlockBasedTable : public TableReader { void ReadMeta(const Footer& footer); + // Figure the index type, update it in rep_, and also return it. + BlockBasedTableOptions::IndexType UpdateIndexType(); + // Create a index reader based on the index type stored in the table. // Optionally, user can pass a preloaded meta_index_iter for the index that // need to access extra meta blocks for index construction. This parameter @@ -312,29 +350,56 @@ class BlockBasedTable : public TableReader { InternalIterator* preloaded_meta_index_iter = nullptr, const int level = -1); - bool FullFilterKeyMayMatch(const ReadOptions& read_options, - FilterBlockReader* filter, const Slice& user_key, - const bool no_io) const; + bool FullFilterKeyMayMatch( + const ReadOptions& read_options, FilterBlockReader* filter, + const Slice& user_key, const bool no_io, + const SliceTransform* prefix_extractor = nullptr) const; - // Read the meta block from sst. + static Status PrefetchTail( + RandomAccessFileReader* file, uint64_t file_size, + TailPrefetchStats* tail_prefetch_stats, const bool prefetch_all, + const bool preload_all, + std::unique_ptr* prefetch_buffer); static Status ReadMetaBlock(Rep* rep, FilePrefetchBuffer* prefetch_buffer, std::unique_ptr* meta_block, std::unique_ptr* iter); - - Status VerifyChecksumInBlocks(InternalIterator* index_iter); + static Status TryReadPropertiesWithGlobalSeqno( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, const Slice& handle_value, + TableProperties** table_properties); + static Status ReadPropertiesBlock(Rep* rep, + FilePrefetchBuffer* prefetch_buffer, + InternalIterator* meta_iter, + const SequenceNumber largest_seqno); + static Status ReadRangeDelBlock( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, + InternalIterator* meta_iter, + const InternalKeyComparator& internal_comparator); + static Status ReadCompressionDictBlock( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, + std::unique_ptr* compression_dict_block); + static Status PrefetchIndexAndFilterBlocks( + Rep* rep, FilePrefetchBuffer* prefetch_buffer, + InternalIterator* meta_iter, BlockBasedTable* new_table, + const SliceTransform* prefix_extractor, bool prefetch_all, + const BlockBasedTableOptions& table_options, const int level, + const bool prefetch_index_and_filter_in_cache); + + Status VerifyChecksumInMetaBlocks(InternalIteratorBase* index_iter); + Status VerifyChecksumInBlocks(InternalIteratorBase* index_iter); // Create the filter from the filter block. - FilterBlockReader* ReadFilter(FilePrefetchBuffer* prefetch_buffer, - const BlockHandle& filter_handle, - const bool is_a_filter_partition) const; + virtual FilterBlockReader* ReadFilter( + FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_handle, + const bool is_a_filter_partition, + const SliceTransform* prefix_extractor = nullptr) const; static void SetupCacheKeyPrefix(Rep* rep, uint64_t file_size); // Generate a cache key prefix from the file - static void GenerateCachePrefix(Cache* cc, - RandomAccessFile* file, char* buffer, size_t* size); - static void GenerateCachePrefix(Cache* cc, - WritableFile* file, char* buffer, size_t* size); + static void GenerateCachePrefix(Cache* cc, RandomAccessFile* file, + char* buffer, size_t* size); + static void GenerateCachePrefix(Cache* cc, WritableFile* file, char* buffer, + size_t* size); // Helper functions for DumpTable() Status DumpIndexBlock(WritableFile* out_file); @@ -351,27 +416,22 @@ class BlockBasedTable : public TableReader { }; // Maitaning state of a two-level iteration on a partitioned index structure -class BlockBasedTable::BlockEntryIteratorState : public TwoLevelIteratorState { +class BlockBasedTable::PartitionedIndexIteratorState + : public TwoLevelIteratorState { public: - BlockEntryIteratorState( - BlockBasedTable* table, const ReadOptions& read_options, - const InternalKeyComparator* icomparator, bool skip_filters, - bool is_index = false, - std::unordered_map>* block_map = nullptr); - InternalIterator* NewSecondaryIterator(const Slice& index_value) override; - bool PrefixMayMatch(const Slice& internal_key) override; - bool KeyReachedUpperBound(const Slice& internal_key) override; + PartitionedIndexIteratorState( + BlockBasedTable* table, + std::unordered_map>* block_map, + const bool index_key_includes_seq, const bool index_key_is_full); + InternalIteratorBase* NewSecondaryIterator( + const BlockHandle& index_value) override; private: // Don't own table_ BlockBasedTable* table_; - const ReadOptions read_options_; - const InternalKeyComparator* icomparator_; - bool skip_filters_; - // true if the 2nd level iterator is on indexes instead of on user data. - bool is_index_; std::unordered_map>* block_map_; - port::RWMutex cleaner_mu; + bool index_key_includes_seq_; + bool index_key_is_full_; }; // CachableEntry represents the entries that *may* be fetched from block cache. @@ -400,25 +460,29 @@ struct BlockBasedTable::CachableEntry { struct BlockBasedTable::Rep { Rep(const ImmutableCFOptions& _ioptions, const EnvOptions& _env_options, const BlockBasedTableOptions& _table_opt, - const InternalKeyComparator& _internal_comparator, bool skip_filters) + const InternalKeyComparator& _internal_comparator, bool skip_filters, + int _level, const bool _immortal_table) : ioptions(_ioptions), env_options(_env_options), table_options(_table_opt), filter_policy(skip_filters ? nullptr : _table_opt.filter_policy.get()), internal_comparator(_internal_comparator), filter_type(FilterType::kNoFilter), + index_type(BlockBasedTableOptions::IndexType::kBinarySearch), + hash_index_allow_collision(false), whole_key_filtering(_table_opt.whole_key_filtering), prefix_filtering(true), - range_del_handle(BlockHandle::NullBlockHandle()), - global_seqno(kDisableGlobalSequenceNumber) {} + global_seqno(kDisableGlobalSequenceNumber), + level(_level), + immortal_table(_immortal_table) {} const ImmutableCFOptions& ioptions; const EnvOptions& env_options; - const BlockBasedTableOptions& table_options; + const BlockBasedTableOptions table_options; const FilterPolicy* const filter_policy; const InternalKeyComparator& internal_comparator; Status status; - unique_ptr file; + std::unique_ptr file; char cache_key_prefix[kMaxCacheKeyPrefixSize]; size_t cache_key_prefix_size = 0; char persistent_cache_key_prefix[kMaxCacheKeyPrefixSize]; @@ -431,11 +495,15 @@ struct BlockBasedTable::Rep { // Footer contains the fixed table information Footer footer; - // index_reader and filter will be populated and used only when - // options.block_cache is nullptr; otherwise we will get the index block via - // the block cache. - unique_ptr index_reader; - unique_ptr filter; + // `index_reader`, `filter`, and `uncompression_dict` will be populated (i.e., + // non-nullptr) and used only when options.block_cache is nullptr or when + // `cache_index_and_filter_blocks == false`. Otherwise, we will get the index, + // filter, and compression dictionary blocks via the block cache. In that case + // `dummy_index_reader_offset`, `filter_handle`, and `compression_dict_handle` + // are used to lookup these meta-blocks in block cache. + std::unique_ptr index_reader; + std::unique_ptr filter; + std::unique_ptr uncompression_dict; enum class FilterType { kNoFilter, @@ -445,13 +513,9 @@ struct BlockBasedTable::Rep { }; FilterType filter_type; BlockHandle filter_handle; + BlockHandle compression_dict_handle; std::shared_ptr table_properties; - // Block containing the data for the compression dictionary. We take ownership - // for the entire block struct, even though we only use its Slice member. This - // is easier because the Slice member depends on the continued existence of - // another member ("allocation"). - std::unique_ptr compression_dict_block; BlockBasedTableOptions::IndexType index_type; bool hash_index_allow_collision; bool whole_key_filtering; @@ -460,19 +524,18 @@ struct BlockBasedTable::Rep { // module should not be relying on db module. However to make things easier // and compatible with existing code, we introduce a wrapper that allows // block to extract prefix without knowing if a key is internal or not. - unique_ptr internal_prefix_transform; - - // only used in level 0 files: - // when pin_l0_filter_and_index_blocks_in_cache is true, we do use the - // LRU cache, but we always keep the filter & idndex block's handle checked - // out here (=we don't call Release()), plus the parsed out objects - // the LRU cache will never push flush them out, hence they're pinned + std::unique_ptr internal_prefix_transform; + std::shared_ptr table_prefix_extractor; + + // only used in level 0 files when pin_l0_filter_and_index_blocks_in_cache is + // true or in all levels when pin_top_level_index_and_filter is set in + // combination with partitioned index/filters: then we do use the LRU cache, + // but we always keep the filter & index block's handle checked out here (=we + // don't call Release()), plus the parsed out objects the LRU cache will never + // push flush them out, hence they're pinned CachableEntry filter_entry; CachableEntry index_entry; - // range deletion meta-block is pinned through reader's lifetime when LRU - // cache is enabled. - CachableEntry range_del_entry; - BlockHandle range_del_handle; + std::shared_ptr fragmented_range_dels; // If global_seqno is used, all Keys in this file will have the same // seqno with value `global_seqno`. @@ -480,7 +543,168 @@ struct BlockBasedTable::Rep { // A value of kDisableGlobalSequenceNumber means that this feature is disabled // and every key have it's own seqno. SequenceNumber global_seqno; + + // the level when the table is opened, could potentially change when trivial + // move is involved + int level; + + // If false, blocks in this file are definitely all uncompressed. Knowing this + // before reading individual blocks enables certain optimizations. + bool blocks_maybe_compressed = true; + + // If true, data blocks in this file are definitely ZSTD compressed. If false + // they might not be. When false we skip creating a ZSTD digested + // uncompression dictionary. Even if we get a false negative, things should + // still work, just not as quickly. + bool blocks_definitely_zstd_compressed = false; + bool closed = false; + const bool immortal_table; + + SequenceNumber get_global_seqno(bool is_index) const { + return is_index ? kDisableGlobalSequenceNumber : global_seqno; + } +}; + +template +class BlockBasedTableIterator : public InternalIteratorBase { + public: + BlockBasedTableIterator(BlockBasedTable* table, + const ReadOptions& read_options, + const InternalKeyComparator& icomp, + InternalIteratorBase* index_iter, + bool check_filter, bool need_upper_bound_check, + const SliceTransform* prefix_extractor, bool is_index, + bool key_includes_seq = true, + bool index_key_is_full = true, + bool for_compaction = false) + : table_(table), + read_options_(read_options), + icomp_(icomp), + user_comparator_(icomp.user_comparator()), + index_iter_(index_iter), + pinned_iters_mgr_(nullptr), + block_iter_points_to_real_block_(false), + check_filter_(check_filter), + need_upper_bound_check_(need_upper_bound_check), + prefix_extractor_(prefix_extractor), + is_index_(is_index), + key_includes_seq_(key_includes_seq), + index_key_is_full_(index_key_is_full), + for_compaction_(for_compaction) {} + + ~BlockBasedTableIterator() { delete index_iter_; } + + void Seek(const Slice& target) override; + void SeekForPrev(const Slice& target) override; + void SeekToFirst() override; + void SeekToLast() override; + void Next() override; + void Prev() override; + bool Valid() const override { + return !is_out_of_bound_ && block_iter_points_to_real_block_ && + block_iter_.Valid(); + } + Slice key() const override { + assert(Valid()); + return block_iter_.key(); + } + TValue value() const override { + assert(Valid()); + return block_iter_.value(); + } + Status status() const override { + if (!index_iter_->status().ok()) { + return index_iter_->status(); + } else if (block_iter_points_to_real_block_) { + return block_iter_.status(); + } else { + return Status::OK(); + } + } + + bool IsOutOfBound() override { return is_out_of_bound_; } + + void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override { + pinned_iters_mgr_ = pinned_iters_mgr; + } + bool IsKeyPinned() const override { + return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && + block_iter_points_to_real_block_ && block_iter_.IsKeyPinned(); + } + bool IsValuePinned() const override { + // BlockIter::IsValuePinned() is always true. No need to check + return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && + block_iter_points_to_real_block_; + } + + bool CheckPrefixMayMatch(const Slice& ikey) { + if (check_filter_ && + !table_->PrefixMayMatch(ikey, read_options_, prefix_extractor_, + need_upper_bound_check_)) { + // TODO remember the iterator is invalidated because of prefix + // match. This can avoid the upper level file iterator to falsely + // believe the position is the end of the SST file and move to + // the first key of the next file. + ResetDataIter(); + return false; + } + return true; + } + + void ResetDataIter() { + if (block_iter_points_to_real_block_) { + if (pinned_iters_mgr_ != nullptr && pinned_iters_mgr_->PinningEnabled()) { + block_iter_.DelegateCleanupsTo(pinned_iters_mgr_); + } + block_iter_.Invalidate(Status::OK()); + block_iter_points_to_real_block_ = false; + } + } + + void SavePrevIndexValue() { + if (block_iter_points_to_real_block_) { + // Reseek. If they end up with the same data block, we shouldn't re-fetch + // the same data block. + prev_index_value_ = index_iter_->value(); + } + } + + void InitDataBlock(); + void FindKeyForward(); + void FindKeyBackward(); + + private: + BlockBasedTable* table_; + const ReadOptions read_options_; + const InternalKeyComparator& icomp_; + UserComparatorWrapper user_comparator_; + InternalIteratorBase* index_iter_; + PinnedIteratorsManager* pinned_iters_mgr_; + TBlockIter block_iter_; + bool block_iter_points_to_real_block_; + bool is_out_of_bound_ = false; + bool check_filter_; + // TODO(Zhongyi): pick a better name + bool need_upper_bound_check_; + const SliceTransform* prefix_extractor_; + // If the blocks over which we iterate are index blocks + bool is_index_; + // If the keys in the blocks over which we iterate include 8 byte sequence + bool key_includes_seq_; + bool index_key_is_full_; + // If this iterator is created for compaction + bool for_compaction_; + BlockHandle prev_index_value_; + + static const size_t kInitReadaheadSize = 8 * 1024; + // Found that 256 KB readahead size provides the best performance, based on + // experiments. + static const size_t kMaxReadaheadSize; + size_t readahead_size_ = kInitReadaheadSize; + size_t readahead_limit_ = 0; + int num_file_reads_ = 0; + std::unique_ptr prefetch_buffer_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_builder.cc b/thirdparty/rocksdb/table/block_builder.cc index 39bfffe511..c14b4f6d3e 100644 --- a/thirdparty/rocksdb/table/block_builder.cc +++ b/thirdparty/rocksdb/table/block_builder.cc @@ -33,46 +33,78 @@ #include "table/block_builder.h" -#include #include -#include "rocksdb/comparator.h" +#include #include "db/dbformat.h" +#include "rocksdb/comparator.h" +#include "table/data_block_footer.h" #include "util/coding.h" namespace rocksdb { -BlockBuilder::BlockBuilder(int block_restart_interval, bool use_delta_encoding) +BlockBuilder::BlockBuilder( + int block_restart_interval, bool use_delta_encoding, + bool use_value_delta_encoding, + BlockBasedTableOptions::DataBlockIndexType index_type, + double data_block_hash_table_util_ratio) : block_restart_interval_(block_restart_interval), use_delta_encoding_(use_delta_encoding), + use_value_delta_encoding_(use_value_delta_encoding), restarts_(), counter_(0), finished_(false) { + switch (index_type) { + case BlockBasedTableOptions::kDataBlockBinarySearch: + break; + case BlockBasedTableOptions::kDataBlockBinaryAndHash: + data_block_hash_index_builder_.Initialize( + data_block_hash_table_util_ratio); + break; + default: + assert(0); + } assert(block_restart_interval_ >= 1); - restarts_.push_back(0); // First restart point is at offset 0 + restarts_.push_back(0); // First restart point is at offset 0 estimate_ = sizeof(uint32_t) + sizeof(uint32_t); } void BlockBuilder::Reset() { buffer_.clear(); restarts_.clear(); - restarts_.push_back(0); // First restart point is at offset 0 + restarts_.push_back(0); // First restart point is at offset 0 estimate_ = sizeof(uint32_t) + sizeof(uint32_t); counter_ = 0; finished_ = false; last_key_.clear(); + if (data_block_hash_index_builder_.Valid()) { + data_block_hash_index_builder_.Reset(); + } } -size_t BlockBuilder::EstimateSizeAfterKV(const Slice& key, const Slice& value) - const { +size_t BlockBuilder::EstimateSizeAfterKV(const Slice& key, + const Slice& value) const { size_t estimate = CurrentSizeEstimate(); - estimate += key.size() + value.size(); + // Note: this is an imprecise estimate as it accounts for the whole key size + // instead of non-shared key size. + estimate += key.size(); + // In value delta encoding we estimate the value delta size as half the full + // value size since only the size field of block handle is encoded. + estimate += + !use_value_delta_encoding_ || (counter_ >= block_restart_interval_) + ? value.size() + : value.size() / 2; + if (counter_ >= block_restart_interval_) { - estimate += sizeof(uint32_t); // a new restart entry. + estimate += sizeof(uint32_t); // a new restart entry. } - estimate += sizeof(int32_t); // varint for shared prefix length. - estimate += VarintLength(key.size()); // varint for key length. - estimate += VarintLength(value.size()); // varint for value length. + estimate += sizeof(int32_t); // varint for shared prefix length. + // Note: this is an imprecise estimate as we will have to encoded size, one + // for shared key and one for non-shared key. + estimate += VarintLength(key.size()); // varint for key length. + if (!use_value_delta_encoding_ || (counter_ >= block_restart_interval_)) { + estimate += VarintLength(value.size()); // varint for value length. + } return estimate; } @@ -82,14 +114,29 @@ Slice BlockBuilder::Finish() { for (size_t i = 0; i < restarts_.size(); i++) { PutFixed32(&buffer_, restarts_[i]); } - PutFixed32(&buffer_, static_cast(restarts_.size())); + + uint32_t num_restarts = static_cast(restarts_.size()); + BlockBasedTableOptions::DataBlockIndexType index_type = + BlockBasedTableOptions::kDataBlockBinarySearch; + if (data_block_hash_index_builder_.Valid() && + CurrentSizeEstimate() <= kMaxBlockSizeSupportedByHashIndex) { + data_block_hash_index_builder_.Finish(buffer_); + index_type = BlockBasedTableOptions::kDataBlockBinaryAndHash; + } + + // footer is a packed format of data_block_index_type and num_restarts + uint32_t block_footer = PackIndexTypeAndNumRestarts(index_type, num_restarts); + + PutFixed32(&buffer_, block_footer); finished_ = true; return Slice(buffer_); } -void BlockBuilder::Add(const Slice& key, const Slice& value) { +void BlockBuilder::Add(const Slice& key, const Slice& value, + const Slice* const delta_value) { assert(!finished_); assert(counter_ <= block_restart_interval_); + assert(!use_value_delta_encoding_ || delta_value); size_t shared = 0; // number of bytes shared with prev key if (counter_ >= block_restart_interval_) { // Restart compression @@ -115,14 +162,32 @@ void BlockBuilder::Add(const Slice& key, const Slice& value) { const size_t non_shared = key.size() - shared; const size_t curr_size = buffer_.size(); - // Add "" to buffer_ - PutVarint32Varint32Varint32(&buffer_, static_cast(shared), - static_cast(non_shared), - static_cast(value.size())); + if (use_value_delta_encoding_) { + // Add "" to buffer_ + PutVarint32Varint32(&buffer_, static_cast(shared), + static_cast(non_shared)); + } else { + // Add "" to buffer_ + PutVarint32Varint32Varint32(&buffer_, static_cast(shared), + static_cast(non_shared), + static_cast(value.size())); + } // Add string delta to buffer_ followed by value buffer_.append(key.data() + shared, non_shared); - buffer_.append(value.data(), value.size()); + // Use value delta encoding only when the key has shared bytes. This would + // simplify the decoding, where it can figure which decoding to use simply by + // looking at the shared bytes size. + if (shared != 0 && use_value_delta_encoding_) { + buffer_.append(delta_value->data(), delta_value->size()); + } else { + buffer_.append(value.data(), value.size()); + } + + if (data_block_hash_index_builder_.Valid()) { + data_block_hash_index_builder_.Add(ExtractUserKey(key), + restarts_.size() - 1); + } counter_++; estimate_ += buffer_.size() - curr_size; diff --git a/thirdparty/rocksdb/table/block_builder.h b/thirdparty/rocksdb/table/block_builder.h index 6b5297d041..0576279f50 100644 --- a/thirdparty/rocksdb/table/block_builder.h +++ b/thirdparty/rocksdb/table/block_builder.h @@ -12,6 +12,8 @@ #include #include "rocksdb/slice.h" +#include "rocksdb/table.h" +#include "table/data_block_hash_index.h" namespace rocksdb { @@ -21,14 +23,19 @@ class BlockBuilder { void operator=(const BlockBuilder&) = delete; explicit BlockBuilder(int block_restart_interval, - bool use_delta_encoding = true); + bool use_delta_encoding = true, + bool use_value_delta_encoding = false, + BlockBasedTableOptions::DataBlockIndexType index_type = + BlockBasedTableOptions::kDataBlockBinarySearch, + double data_block_hash_table_util_ratio = 0.75); // Reset the contents as if the BlockBuilder was just constructed. void Reset(); // REQUIRES: Finish() has not been called since the last call to Reset(). // REQUIRES: key is larger than any previously added key - void Add(const Slice& key, const Slice& value); + void Add(const Slice& key, const Slice& value, + const Slice* const delta_value = nullptr); // Finish building the block and return a slice that refers to the // block contents. The returned slice will remain valid for the @@ -37,26 +44,32 @@ class BlockBuilder { // Returns an estimate of the current (uncompressed) size of the block // we are building. - inline size_t CurrentSizeEstimate() const { return estimate_; } + inline size_t CurrentSizeEstimate() const { + return estimate_ + (data_block_hash_index_builder_.Valid() + ? data_block_hash_index_builder_.EstimateSize() + : 0); + } // Returns an estimated block size after appending key and value. size_t EstimateSizeAfterKV(const Slice& key, const Slice& value) const; // Return true iff no entries have been added since the last Reset() - bool empty() const { - return buffer_.empty(); - } + bool empty() const { return buffer_.empty(); } private: - const int block_restart_interval_; - const bool use_delta_encoding_; + const int block_restart_interval_; + // TODO(myabandeh): put it into a separate IndexBlockBuilder + const bool use_delta_encoding_; + // Refer to BlockIter::DecodeCurrentValue for format of delta encoded values + const bool use_value_delta_encoding_; - std::string buffer_; // Destination buffer + std::string buffer_; // Destination buffer std::vector restarts_; // Restart points - size_t estimate_; - int counter_; // Number of entries emitted since restart - bool finished_; // Has Finish() been called? - std::string last_key_; + size_t estimate_; + int counter_; // Number of entries emitted since restart + bool finished_; // Has Finish() been called? + std::string last_key_; + DataBlockHashIndexBuilder data_block_hash_index_builder_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_fetcher.cc b/thirdparty/rocksdb/table/block_fetcher.cc new file mode 100644 index 0000000000..1f209210c1 --- /dev/null +++ b/thirdparty/rocksdb/table/block_fetcher.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#include "table/block_fetcher.h" + +#include +#include + +#include "monitoring/perf_context_imp.h" +#include "monitoring/statistics.h" +#include "rocksdb/env.h" +#include "table/block.h" +#include "table/block_based_table_reader.h" +#include "table/format.h" +#include "table/persistent_cache_helper.h" +#include "util/coding.h" +#include "util/compression.h" +#include "util/crc32c.h" +#include "util/file_reader_writer.h" +#include "util/logging.h" +#include "util/memory_allocator.h" +#include "util/stop_watch.h" +#include "util/string_util.h" +#include "util/xxhash.h" + +namespace rocksdb { + +inline void BlockFetcher::CheckBlockChecksum() { + // Check the crc of the type and the block contents + if (read_options_.verify_checksums) { + const char* data = slice_.data(); // Pointer to where Read put the data + PERF_TIMER_GUARD(block_checksum_time); + uint32_t value = DecodeFixed32(data + block_size_ + 1); + uint32_t actual = 0; + switch (footer_.checksum()) { + case kNoChecksum: + break; + case kCRC32c: + value = crc32c::Unmask(value); + actual = crc32c::Value(data, block_size_ + 1); + break; + case kxxHash: + actual = XXH32(data, static_cast(block_size_) + 1, 0); + break; + case kxxHash64: + actual = static_cast( + XXH64(data, static_cast(block_size_) + 1, 0) & + uint64_t{0xffffffff}); + break; + default: + status_ = Status::Corruption( + "unknown checksum type " + ToString(footer_.checksum()) + " in " + + file_->file_name() + " offset " + ToString(handle_.offset()) + + " size " + ToString(block_size_)); + } + if (status_.ok() && actual != value) { + status_ = Status::Corruption( + "block checksum mismatch: expected " + ToString(actual) + ", got " + + ToString(value) + " in " + file_->file_name() + " offset " + + ToString(handle_.offset()) + " size " + ToString(block_size_)); + } + } +} + +inline bool BlockFetcher::TryGetUncompressBlockFromPersistentCache() { + if (cache_options_.persistent_cache && + !cache_options_.persistent_cache->IsCompressed()) { + Status status = PersistentCacheHelper::LookupUncompressedPage( + cache_options_, handle_, contents_); + if (status.ok()) { + // uncompressed page is found for the block handle + return true; + } else { + // uncompressed page is not found + if (ioptions_.info_log && !status.IsNotFound()) { + assert(!status.ok()); + ROCKS_LOG_INFO(ioptions_.info_log, + "Error reading from persistent cache. %s", + status.ToString().c_str()); + } + } + } + return false; +} + +inline bool BlockFetcher::TryGetFromPrefetchBuffer() { + if (prefetch_buffer_ != nullptr && + prefetch_buffer_->TryReadFromCache( + handle_.offset(), + static_cast(handle_.size()) + kBlockTrailerSize, &slice_)) { + block_size_ = static_cast(handle_.size()); + CheckBlockChecksum(); + if (!status_.ok()) { + return true; + } + got_from_prefetch_buffer_ = true; + used_buf_ = const_cast(slice_.data()); + } + return got_from_prefetch_buffer_; +} + +inline bool BlockFetcher::TryGetCompressedBlockFromPersistentCache() { + if (cache_options_.persistent_cache && + cache_options_.persistent_cache->IsCompressed()) { + // lookup uncompressed cache mode p-cache + std::unique_ptr raw_data; + status_ = PersistentCacheHelper::LookupRawPage( + cache_options_, handle_, &raw_data, block_size_ + kBlockTrailerSize); + if (status_.ok()) { + heap_buf_ = CacheAllocationPtr(raw_data.release()); + used_buf_ = heap_buf_.get(); + slice_ = Slice(heap_buf_.get(), block_size_); + return true; + } else if (!status_.IsNotFound() && ioptions_.info_log) { + assert(!status_.ok()); + ROCKS_LOG_INFO(ioptions_.info_log, + "Error reading from persistent cache. %s", + status_.ToString().c_str()); + } + } + return false; +} + +inline void BlockFetcher::PrepareBufferForBlockFromFile() { + // cache miss read from device + if (do_uncompress_ && + block_size_ + kBlockTrailerSize < kDefaultStackBufferSize) { + // If we've got a small enough hunk of data, read it in to the + // trivially allocated stack buffer instead of needing a full malloc() + used_buf_ = &stack_buf_[0]; + } else if (maybe_compressed_ && !do_uncompress_) { + compressed_buf_ = AllocateBlock(block_size_ + kBlockTrailerSize, + memory_allocator_compressed_); + used_buf_ = compressed_buf_.get(); + } else { + heap_buf_ = + AllocateBlock(block_size_ + kBlockTrailerSize, memory_allocator_); + used_buf_ = heap_buf_.get(); + } +} + +inline void BlockFetcher::InsertCompressedBlockToPersistentCacheIfNeeded() { + if (status_.ok() && read_options_.fill_cache && + cache_options_.persistent_cache && + cache_options_.persistent_cache->IsCompressed()) { + // insert to raw cache + PersistentCacheHelper::InsertRawPage(cache_options_, handle_, used_buf_, + block_size_ + kBlockTrailerSize); + } +} + +inline void BlockFetcher::InsertUncompressedBlockToPersistentCacheIfNeeded() { + if (status_.ok() && !got_from_prefetch_buffer_ && read_options_.fill_cache && + cache_options_.persistent_cache && + !cache_options_.persistent_cache->IsCompressed()) { + // insert to uncompressed cache + PersistentCacheHelper::InsertUncompressedPage(cache_options_, handle_, + *contents_); + } +} + +inline void BlockFetcher::CopyBufferToHeap() { + assert(used_buf_ != heap_buf_.get()); + heap_buf_ = AllocateBlock(block_size_ + kBlockTrailerSize, memory_allocator_); + memcpy(heap_buf_.get(), used_buf_, block_size_ + kBlockTrailerSize); +} + +inline void BlockFetcher::GetBlockContents() { + if (slice_.data() != used_buf_) { + // the slice content is not the buffer provided + *contents_ = BlockContents(Slice(slice_.data(), block_size_)); + } else { + // page can be either uncompressed or compressed, the buffer either stack + // or heap provided. Refer to https://github.com/facebook/rocksdb/pull/4096 + if (got_from_prefetch_buffer_ || used_buf_ == &stack_buf_[0]) { + CopyBufferToHeap(); + } else if (used_buf_ == compressed_buf_.get()) { + if (compression_type_ == kNoCompression && + memory_allocator_ != memory_allocator_compressed_) { + CopyBufferToHeap(); + } else { + heap_buf_ = std::move(compressed_buf_); + } + } + *contents_ = BlockContents(std::move(heap_buf_), block_size_); + } +#ifndef NDEBUG + contents_->is_raw_block = true; +#endif +} + +Status BlockFetcher::ReadBlockContents() { + block_size_ = static_cast(handle_.size()); + + if (TryGetUncompressBlockFromPersistentCache()) { + compression_type_ = kNoCompression; +#ifndef NDEBUG + contents_->is_raw_block = true; +#endif // NDEBUG + return Status::OK(); + } + if (TryGetFromPrefetchBuffer()) { + if (!status_.ok()) { + return status_; + } + } else if (!TryGetCompressedBlockFromPersistentCache()) { + PrepareBufferForBlockFromFile(); + Status s; + + { + PERF_TIMER_GUARD(block_read_time); + // Actual file read + status_ = file_->Read(handle_.offset(), block_size_ + kBlockTrailerSize, + &slice_, used_buf_); + } + PERF_COUNTER_ADD(block_read_count, 1); + PERF_COUNTER_ADD(block_read_byte, block_size_ + kBlockTrailerSize); + if (!status_.ok()) { + return status_; + } + + if (slice_.size() != block_size_ + kBlockTrailerSize) { + return Status::Corruption("truncated block read from " + + file_->file_name() + " offset " + + ToString(handle_.offset()) + ", expected " + + ToString(block_size_ + kBlockTrailerSize) + + " bytes, got " + ToString(slice_.size())); + } + + CheckBlockChecksum(); + if (status_.ok()) { + InsertCompressedBlockToPersistentCacheIfNeeded(); + } else { + return status_; + } + } + + PERF_TIMER_GUARD(block_decompress_time); + + compression_type_ = get_block_compression_type(slice_.data(), block_size_); + + if (do_uncompress_ && compression_type_ != kNoCompression) { + // compressed page, uncompress, update cache + UncompressionContext context(compression_type_); + UncompressionInfo info(context, uncompression_dict_, compression_type_); + status_ = UncompressBlockContents(info, slice_.data(), block_size_, + contents_, footer_.version(), ioptions_, + memory_allocator_); + compression_type_ = kNoCompression; + } else { + GetBlockContents(); + } + + InsertUncompressedBlockToPersistentCacheIfNeeded(); + + return status_; +} + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_fetcher.h b/thirdparty/rocksdb/table/block_fetcher.h new file mode 100644 index 0000000000..b5fee94159 --- /dev/null +++ b/thirdparty/rocksdb/table/block_fetcher.h @@ -0,0 +1,88 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#pragma once +#include "table/block.h" +#include "table/format.h" +#include "util/memory_allocator.h" + +namespace rocksdb { +class BlockFetcher { + public: + // Read the block identified by "handle" from "file". + // The only relevant option is options.verify_checksums for now. + // On failure return non-OK. + // On success fill *result and return OK - caller owns *result + // @param uncompression_dict Data for presetting the compression library's + // dictionary. + BlockFetcher(RandomAccessFileReader* file, + FilePrefetchBuffer* prefetch_buffer, const Footer& footer, + const ReadOptions& read_options, const BlockHandle& handle, + BlockContents* contents, const ImmutableCFOptions& ioptions, + bool do_uncompress, bool maybe_compressed, + const UncompressionDict& uncompression_dict, + const PersistentCacheOptions& cache_options, + MemoryAllocator* memory_allocator = nullptr, + MemoryAllocator* memory_allocator_compressed = nullptr) + : file_(file), + prefetch_buffer_(prefetch_buffer), + footer_(footer), + read_options_(read_options), + handle_(handle), + contents_(contents), + ioptions_(ioptions), + do_uncompress_(do_uncompress), + maybe_compressed_(maybe_compressed), + uncompression_dict_(uncompression_dict), + cache_options_(cache_options), + memory_allocator_(memory_allocator), + memory_allocator_compressed_(memory_allocator_compressed) {} + Status ReadBlockContents(); + CompressionType get_compression_type() const { return compression_type_; } + + private: + static const uint32_t kDefaultStackBufferSize = 5000; + + RandomAccessFileReader* file_; + FilePrefetchBuffer* prefetch_buffer_; + const Footer& footer_; + const ReadOptions read_options_; + const BlockHandle& handle_; + BlockContents* contents_; + const ImmutableCFOptions& ioptions_; + bool do_uncompress_; + bool maybe_compressed_; + const UncompressionDict& uncompression_dict_; + const PersistentCacheOptions& cache_options_; + MemoryAllocator* memory_allocator_; + MemoryAllocator* memory_allocator_compressed_; + Status status_; + Slice slice_; + char* used_buf_ = nullptr; + size_t block_size_; + CacheAllocationPtr heap_buf_; + CacheAllocationPtr compressed_buf_; + char stack_buf_[kDefaultStackBufferSize]; + bool got_from_prefetch_buffer_ = false; + rocksdb::CompressionType compression_type_; + + // return true if found + bool TryGetUncompressBlockFromPersistentCache(); + // return true if found + bool TryGetFromPrefetchBuffer(); + bool TryGetCompressedBlockFromPersistentCache(); + void PrepareBufferForBlockFromFile(); + // Copy content from used_buf_ to new heap buffer. + void CopyBufferToHeap(); + void GetBlockContents(); + void InsertCompressedBlockToPersistentCacheIfNeeded(); + void InsertUncompressedBlockToPersistentCacheIfNeeded(); + void CheckBlockChecksum(); +}; +} // namespace rocksdb diff --git a/thirdparty/rocksdb/table/block_prefix_index.cc b/thirdparty/rocksdb/table/block_prefix_index.cc index df37b5fc2b..67c749d4c3 100644 --- a/thirdparty/rocksdb/table/block_prefix_index.cc +++ b/thirdparty/rocksdb/table/block_prefix_index.cc @@ -41,9 +41,7 @@ inline uint32_t PrefixToBucket(const Slice& prefix, uint32_t num_buckets) { const uint32_t kNoneBlock = 0x7FFFFFFF; const uint32_t kBlockArrayMask = 0x80000000; -inline bool IsNone(uint32_t block_id) { - return block_id == kNoneBlock; -} +inline bool IsNone(uint32_t block_id) { return block_id == kNoneBlock; } inline bool IsBlockId(uint32_t block_id) { return (block_id & kBlockArrayMask) == 0; @@ -74,10 +72,9 @@ class BlockPrefixIndex::Builder { explicit Builder(const SliceTransform* internal_prefix_extractor) : internal_prefix_extractor_(internal_prefix_extractor) {} - void Add(const Slice& key_prefix, uint32_t start_block, - uint32_t num_blocks) { + void Add(const Slice& key_prefix, uint32_t start_block, uint32_t num_blocks) { PrefixRecord* record = reinterpret_cast( - arena_.AllocateAligned(sizeof(PrefixRecord))); + arena_.AllocateAligned(sizeof(PrefixRecord))); record->prefix = key_prefix; record->start_block = start_block; record->end_block = start_block + num_blocks - 1; @@ -169,7 +166,6 @@ class BlockPrefixIndex::Builder { Arena arena_; }; - Status BlockPrefixIndex::Create(const SliceTransform* internal_prefix_extractor, const Slice& prefixes, const Slice& prefix_meta, BlockPrefixIndex** prefix_index) { @@ -191,7 +187,7 @@ Status BlockPrefixIndex::Create(const SliceTransform* internal_prefix_extractor, } if (pos + prefix_size > prefixes.size()) { s = Status::Corruption( - "Corrupted prefix meta block: size inconsistency."); + "Corrupted prefix meta block: size inconsistency."); break; } Slice prefix(prefixes.data() + pos, prefix_size); @@ -211,8 +207,7 @@ Status BlockPrefixIndex::Create(const SliceTransform* internal_prefix_extractor, return s; } -uint32_t BlockPrefixIndex::GetBlocks(const Slice& key, - uint32_t** blocks) { +uint32_t BlockPrefixIndex::GetBlocks(const Slice& key, uint32_t** blocks) { Slice prefix = internal_prefix_extractor_->Transform(key); uint32_t bucket = PrefixToBucket(prefix, num_buckets_); @@ -226,7 +221,7 @@ uint32_t BlockPrefixIndex::GetBlocks(const Slice& key, } else { uint32_t index = DecodeIndex(block_id); assert(index < num_block_array_buffer_entries_); - *blocks = &block_array_buffer_[index+1]; + *blocks = &block_array_buffer_[index + 1]; uint32_t num_blocks = block_array_buffer_[index]; assert(num_blocks > 1); assert(index + num_blocks < num_block_array_buffer_entries_); diff --git a/thirdparty/rocksdb/table/block_prefix_index.h b/thirdparty/rocksdb/table/block_prefix_index.h index dd4282d17b..105606db20 100644 --- a/thirdparty/rocksdb/table/block_prefix_index.h +++ b/thirdparty/rocksdb/table/block_prefix_index.h @@ -19,7 +19,6 @@ class SliceTransform; // that index block. class BlockPrefixIndex { public: - // Maps a key to a list of data blocks that could potentially contain // the key, based on the prefix. // Returns the total number of relevant blocks, 0 means the key does @@ -28,7 +27,7 @@ class BlockPrefixIndex { size_t ApproximateMemoryUsage() const { return sizeof(BlockPrefixIndex) + - (num_block_array_buffer_entries_ + num_buckets_) * sizeof(uint32_t); + (num_block_array_buffer_entries_ + num_buckets_) * sizeof(uint32_t); } // Create hash index by reading from the metadata blocks. @@ -48,8 +47,7 @@ class BlockPrefixIndex { friend Builder; BlockPrefixIndex(const SliceTransform* internal_prefix_extractor, - uint32_t num_buckets, - uint32_t* buckets, + uint32_t num_buckets, uint32_t* buckets, uint32_t num_block_array_buffer_entries, uint32_t* block_array_buffer) : internal_prefix_extractor_(internal_prefix_extractor), diff --git a/thirdparty/rocksdb/table/block_test.cc b/thirdparty/rocksdb/table/block_test.cc index f5c543975f..3e0ff3eab5 100644 --- a/thirdparty/rocksdb/table/block_test.cc +++ b/thirdparty/rocksdb/table/block_test.cc @@ -12,13 +12,13 @@ #include #include "db/dbformat.h" -#include "db/write_batch_internal.h" #include "db/memtable.h" +#include "db/write_batch_internal.h" #include "rocksdb/db.h" #include "rocksdb/env.h" #include "rocksdb/iterator.h" -#include "rocksdb/table.h" #include "rocksdb/slice_transform.h" +#include "rocksdb/table.h" #include "table/block.h" #include "table/block_builder.h" #include "table/format.h" @@ -28,7 +28,7 @@ namespace rocksdb { -static std::string RandomString(Random* rnd, int len) { +static std::string RandomString(Random *rnd, int len) { std::string r; test::RandomString(rnd, len, &r); return r; @@ -68,6 +68,29 @@ void GenerateRandomKVs(std::vector *keys, } } +// Same as GenerateRandomKVs but the values are BlockHandle +void GenerateRandomKBHs(std::vector *keys, + std::vector *values, const int from, + const int len, const int step = 1, + const int padding_size = 0, + const int keys_share_prefix = 1) { + Random rnd(302); + uint64_t offset = 0; + + // generate different prefix + for (int i = from; i < from + len; i += step) { + // generate keys that shares the prefix + for (int j = 0; j < keys_share_prefix; ++j) { + keys->emplace_back(GenerateKey(i, j, padding_size, &rnd)); + + uint64_t size = rnd.Uniform(1024 * 16); + BlockHandle handle(offset, size); + offset += size + kBlockTrailerSize; + values->emplace_back(handle); + } + } +} + class BlockTest : public testing::Test {}; // block test @@ -94,14 +117,13 @@ TEST_F(BlockTest, SimpleTest) { // create block reader BlockContents contents; contents.data = rawblock; - contents.cachable = false; Block reader(std::move(contents), kDisableGlobalSequenceNumber); // read contents of block sequentially int count = 0; - InternalIterator *iter = reader.NewIterator(options.comparator); - for (iter->SeekToFirst();iter->Valid(); count++, iter->Next()) { - + InternalIterator *iter = + reader.NewIterator(options.comparator, options.comparator); + for (iter->SeekToFirst(); iter->Valid(); count++, iter->Next()) { // read kv from block Slice k = iter->key(); Slice v = iter->value(); @@ -113,9 +135,9 @@ TEST_F(BlockTest, SimpleTest) { delete iter; // read block contents randomly - iter = reader.NewIterator(options.comparator); + iter = + reader.NewIterator(options.comparator, options.comparator); for (int i = 0; i < num_records; i++) { - // find a random key in the lookaside array int index = rnd.Uniform(num_records); Slice k(keys[index]); @@ -129,11 +151,88 @@ TEST_F(BlockTest, SimpleTest) { delete iter; } +TEST_F(BlockTest, ValueDeltaEncodingTest) { + Random rnd(301); + Options options = Options(); + std::unique_ptr ic; + ic.reset(new test::PlainInternalKeyComparator(options.comparator)); + + std::vector keys; + std::vector values; + const bool kUseDeltaEncoding = true; + const bool kUseValueDeltaEncoding = true; + BlockBuilder builder(16, kUseDeltaEncoding, kUseValueDeltaEncoding); + int num_records = 100; + + GenerateRandomKBHs(&keys, &values, 0, num_records); + // add a bunch of records to a block + BlockHandle last_encoded_handle; + for (int i = 0; i < num_records; i++) { + auto block_handle = values[i]; + std::string handle_encoding; + block_handle.EncodeTo(&handle_encoding); + std::string handle_delta_encoding; + PutVarsignedint64(&handle_delta_encoding, + block_handle.size() - last_encoded_handle.size()); + last_encoded_handle = block_handle; + const Slice handle_delta_encoding_slice(handle_delta_encoding); + builder.Add(keys[i], handle_encoding, &handle_delta_encoding_slice); + } + + // read serialized contents of the block + Slice rawblock = builder.Finish(); + + // create block reader + BlockContents contents; + contents.data = rawblock; + Block reader(std::move(contents), kDisableGlobalSequenceNumber); + + const bool kTotalOrderSeek = true; + const bool kIncludesSeq = true; + const bool kValueIsFull = !kUseValueDeltaEncoding; + IndexBlockIter *kNullIter = nullptr; + Statistics *kNullStats = nullptr; + // read contents of block sequentially + int count = 0; + InternalIteratorBase *iter = reader.NewIterator( + options.comparator, options.comparator, kNullIter, kNullStats, + kTotalOrderSeek, kIncludesSeq, kValueIsFull); + for (iter->SeekToFirst(); iter->Valid(); count++, iter->Next()) { + // read kv from block + Slice k = iter->key(); + BlockHandle handle = iter->value(); + + // compare with lookaside array + ASSERT_EQ(k.ToString().compare(keys[count]), 0); + + ASSERT_EQ(values[count].offset(), handle.offset()); + ASSERT_EQ(values[count].size(), handle.size()); + } + delete iter; + + // read block contents randomly + iter = reader.NewIterator( + options.comparator, options.comparator, kNullIter, kNullStats, + kTotalOrderSeek, kIncludesSeq, kValueIsFull); + for (int i = 0; i < num_records; i++) { + // find a random key in the lookaside array + int index = rnd.Uniform(num_records); + Slice k(keys[index]); + + // search in block for this key + iter->Seek(k); + ASSERT_TRUE(iter->Valid()); + BlockHandle handle = iter->value(); + ASSERT_EQ(values[index].offset(), handle.offset()); + ASSERT_EQ(values[index].size(), handle.size()); + } + delete iter; +} // return the block contents BlockContents GetBlockContents(std::unique_ptr *builder, const std::vector &keys, const std::vector &values, - const int prefix_group_size = 1) { + const int /*prefix_group_size*/ = 1) { builder->reset(new BlockBuilder(1 /* restart interval */)); // Add only half of the keys @@ -144,7 +243,6 @@ BlockContents GetBlockContents(std::unique_ptr *builder, BlockContents contents; contents.data = rawblock; - contents.cachable = false; return contents; } @@ -154,8 +252,7 @@ void CheckBlockContents(BlockContents contents, const int max_key, const std::vector &values) { const size_t prefix_size = 6; // create block reader - BlockContents contents_ref(contents.data, contents.cachable, - contents.compression_type); + BlockContents contents_ref(contents.data); Block reader1(std::move(contents), kDisableGlobalSequenceNumber); Block reader2(std::move(contents_ref), kDisableGlobalSequenceNumber); @@ -163,7 +260,8 @@ void CheckBlockContents(BlockContents contents, const int max_key, NewFixedPrefixTransform(prefix_size)); std::unique_ptr regular_iter( - reader2.NewIterator(BytewiseComparator())); + reader2.NewIterator(BytewiseComparator(), + BytewiseComparator())); // Seek existent keys for (size_t i = 0; i < keys.size(); i++) { @@ -229,40 +327,67 @@ class BlockReadAmpBitmapSlowAndAccurate { marked_ranges_.emplace(end_offset, start_offset); } + void ResetCheckSequence() { iter_valid_ = false; } + // Return true if any byte in this range was Marked + // This does linear search from the previous position. When calling + // multiple times, `offset` needs to be incremental to get correct results. + // Call ResetCheckSequence() to reset it. bool IsPinMarked(size_t offset) { - auto it = marked_ranges_.lower_bound( + if (iter_valid_) { + // Has existing iterator, try linear search from + // the iterator. + for (int i = 0; i < 64; i++) { + if (offset < iter_->second) { + return false; + } + if (offset <= iter_->first) { + return true; + } + + iter_++; + if (iter_ == marked_ranges_.end()) { + iter_valid_ = false; + return false; + } + } + } + // Initial call or have linear searched too many times. + // Do binary search. + iter_ = marked_ranges_.lower_bound( std::make_pair(offset, static_cast(0))); - if (it == marked_ranges_.end()) { + if (iter_ == marked_ranges_.end()) { + iter_valid_ = false; return false; } - return offset <= it->first && offset >= it->second; + iter_valid_ = true; + return offset <= iter_->first && offset >= iter_->second; } private: std::set> marked_ranges_; + std::set>::iterator iter_; + bool iter_valid_ = false; }; TEST_F(BlockTest, BlockReadAmpBitmap) { uint32_t pin_offset = 0; SyncPoint::GetInstance()->SetCallBack( - "BlockReadAmpBitmap:rnd", [&pin_offset](void* arg) { - pin_offset = *(static_cast(arg)); - }); + "BlockReadAmpBitmap:rnd", [&pin_offset](void *arg) { + pin_offset = *(static_cast(arg)); + }); SyncPoint::GetInstance()->EnableProcessing(); std::vector block_sizes = { - 1, // 1 byte - 32, // 32 bytes - 61, // 61 bytes - 64, // 64 bytes - 512, // 0.5 KB - 1024, // 1 KB - 1024 * 4, // 4 KB - 1024 * 10, // 10 KB - 1024 * 50, // 50 KB - 1024 * 1024, // 1 MB - 1024 * 1024 * 4, // 4 MB - 1024 * 1024 * 50, // 10 MB + 1, // 1 byte + 32, // 32 bytes + 61, // 61 bytes + 64, // 64 bytes + 512, // 0.5 KB + 1024, // 1 KB + 1024 * 4, // 4 KB + 1024 * 10, // 10 KB + 1024 * 50, // 50 KB + 1024 * 1024 * 4, // 5 MB 777, 124653, }; @@ -278,10 +403,6 @@ TEST_F(BlockTest, BlockReadAmpBitmap) { if (block_size % kBytesPerBit != 0) { needed_bits++; } - size_t bitmap_size = needed_bits / 32; - if (needed_bits % 32 != 0) { - bitmap_size++; - } ASSERT_EQ(stats->getTickerCount(READ_AMP_TOTAL_READ_BYTES), block_size); @@ -309,6 +430,7 @@ TEST_F(BlockTest, BlockReadAmpBitmap) { } for (size_t i = 0; i < random_entries.size(); i++) { + read_amp_slow_and_accurate.ResetCheckSequence(); auto ¤t_entry = random_entries[rnd.Next() % random_entries.size()]; read_amp_bitmap.Mark(static_cast(current_entry.first), @@ -319,11 +441,11 @@ TEST_F(BlockTest, BlockReadAmpBitmap) { size_t total_bits = 0; for (size_t bit_idx = 0; bit_idx < needed_bits; bit_idx++) { total_bits += read_amp_slow_and_accurate.IsPinMarked( - bit_idx * kBytesPerBit + pin_offset); + bit_idx * kBytesPerBit + pin_offset); } size_t expected_estimate_useful = total_bits * kBytesPerBit; size_t got_estimate_useful = - stats->getTickerCount(READ_AMP_ESTIMATE_USEFUL_BYTES); + stats->getTickerCount(READ_AMP_ESTIMATE_USEFUL_BYTES); ASSERT_EQ(expected_estimate_useful, got_estimate_useful); } } @@ -358,14 +480,14 @@ TEST_F(BlockTest, BlockWithReadAmpBitmap) { // create block reader BlockContents contents; contents.data = rawblock; - contents.cachable = true; Block reader(std::move(contents), kDisableGlobalSequenceNumber, kBytesPerBit, stats.get()); // read contents of block sequentially size_t read_bytes = 0; - BlockIter *iter = static_cast( - reader.NewIterator(options.comparator, nullptr, true, stats.get())); + DataBlockIter *iter = + static_cast(reader.NewIterator( + options.comparator, options.comparator, nullptr, stats.get())); for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { iter->value(); read_bytes += iter->TEST_CurrentEntrySize(); @@ -392,13 +514,13 @@ TEST_F(BlockTest, BlockWithReadAmpBitmap) { // create block reader BlockContents contents; contents.data = rawblock; - contents.cachable = true; Block reader(std::move(contents), kDisableGlobalSequenceNumber, kBytesPerBit, stats.get()); size_t read_bytes = 0; - BlockIter *iter = static_cast( - reader.NewIterator(options.comparator, nullptr, true, stats.get())); + DataBlockIter *iter = + static_cast(reader.NewIterator( + options.comparator, options.comparator, nullptr, stats.get())); for (int i = 0; i < num_records; i++) { Slice k(keys[i]); @@ -428,13 +550,13 @@ TEST_F(BlockTest, BlockWithReadAmpBitmap) { // create block reader BlockContents contents; contents.data = rawblock; - contents.cachable = true; Block reader(std::move(contents), kDisableGlobalSequenceNumber, kBytesPerBit, stats.get()); size_t read_bytes = 0; - BlockIter *iter = static_cast( - reader.NewIterator(options.comparator, nullptr, true, stats.get())); + DataBlockIter *iter = + static_cast(reader.NewIterator( + options.comparator, options.comparator, nullptr, stats.get())); std::unordered_set read_keys; for (int i = 0; i < num_records; i++) { int index = rnd.Uniform(num_records); diff --git a/thirdparty/rocksdb/table/bloom_block.h b/thirdparty/rocksdb/table/bloom_block.h index 9ff610badd..483fa25d93 100644 --- a/thirdparty/rocksdb/table/bloom_block.h +++ b/thirdparty/rocksdb/table/bloom_block.h @@ -15,8 +15,7 @@ class BloomBlockBuilder { public: static const std::string kBloomBlock; - explicit BloomBlockBuilder(uint32_t num_probes = 6) - : bloom_(num_probes, nullptr) {} + explicit BloomBlockBuilder(uint32_t num_probes = 6) : bloom_(num_probes) {} void SetTotalBits(Allocator* allocator, uint32_t total_bits, uint32_t locality, size_t huge_page_tlb_size, diff --git a/thirdparty/rocksdb/table/cuckoo_table_builder.cc b/thirdparty/rocksdb/table/cuckoo_table_builder.cc index e3ed314b36..f590e6ad40 100644 --- a/thirdparty/rocksdb/table/cuckoo_table_builder.cc +++ b/thirdparty/rocksdb/table/cuckoo_table_builder.cc @@ -164,9 +164,9 @@ bool CuckooTableBuilder::IsDeletedKey(uint64_t idx) const { Slice CuckooTableBuilder::GetKey(uint64_t idx) const { assert(closed_); if (IsDeletedKey(idx)) { - return Slice(&deleted_keys_[(idx - num_values_) * key_size_], key_size_); + return Slice(&deleted_keys_[static_cast((idx - num_values_) * key_size_)], static_cast(key_size_)); } - return Slice(&kvs_[idx * (key_size_ + value_size_)], key_size_); + return Slice(&kvs_[static_cast(idx * (key_size_ + value_size_))], static_cast(key_size_)); } Slice CuckooTableBuilder::GetUserKey(uint64_t idx) const { @@ -177,17 +177,17 @@ Slice CuckooTableBuilder::GetUserKey(uint64_t idx) const { Slice CuckooTableBuilder::GetValue(uint64_t idx) const { assert(closed_); if (IsDeletedKey(idx)) { - static std::string empty_value(value_size_, 'a'); + static std::string empty_value(static_cast(value_size_), 'a'); return Slice(empty_value); } - return Slice(&kvs_[idx * (key_size_ + value_size_) + key_size_], value_size_); + return Slice(&kvs_[static_cast(idx * (key_size_ + value_size_) + key_size_)], static_cast(value_size_)); } Status CuckooTableBuilder::MakeHashTable(std::vector* buckets) { - buckets->resize(hash_table_size_ + cuckoo_block_size_ - 1); + buckets->resize(static_cast(hash_table_size_ + cuckoo_block_size_ - 1)); uint32_t make_space_for_key_call_id = 0; for (uint32_t vector_idx = 0; vector_idx < num_entries_; vector_idx++) { - uint64_t bucket_id; + uint64_t bucket_id = 0; bool bucket_found = false; autovector hash_vals; Slice user_key = GetUserKey(vector_idx); @@ -200,13 +200,13 @@ Status CuckooTableBuilder::MakeHashTable(std::vector* buckets) { // stop searching and proceed for next hash function. for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_; ++block_idx, ++hash_val) { - if ((*buckets)[hash_val].vector_idx == kMaxVectorIdx) { + if ((*buckets)[static_cast(hash_val)].vector_idx == kMaxVectorIdx) { bucket_id = hash_val; bucket_found = true; break; } else { if (ucomp_->Compare(user_key, - GetUserKey((*buckets)[hash_val].vector_idx)) == 0) { + GetUserKey((*buckets)[static_cast(hash_val)].vector_idx)) == 0) { return Status::NotSupported("Same key is being inserted again."); } hash_vals.push_back(hash_val); @@ -226,7 +226,7 @@ Status CuckooTableBuilder::MakeHashTable(std::vector* buckets) { ++num_hash_func_; for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_; ++block_idx, ++hash_val) { - if ((*buckets)[hash_val].vector_idx == kMaxVectorIdx) { + if ((*buckets)[static_cast(hash_val)].vector_idx == kMaxVectorIdx) { bucket_found = true; bucket_id = hash_val; break; @@ -235,7 +235,7 @@ Status CuckooTableBuilder::MakeHashTable(std::vector* buckets) { } } } - (*buckets)[bucket_id].vector_idx = vector_idx; + (*buckets)[static_cast(bucket_id)].vector_idx = vector_idx; } return Status::OK(); } @@ -289,13 +289,14 @@ Status CuckooTableBuilder::Finish() { } } properties_.num_entries = num_entries_; + properties_.num_deletions = num_entries_ - num_values_; properties_.fixed_key_len = key_size_; properties_.user_collected_properties[ CuckooTablePropertyNames::kValueLength].assign( reinterpret_cast(&value_size_), sizeof(value_size_)); uint64_t bucket_size = key_size_ + value_size_; - unused_bucket.resize(bucket_size, 'a'); + unused_bucket.resize(static_cast(bucket_size), 'a'); // Write the table. uint32_t num_added = 0; for (auto& bucket : buckets) { @@ -320,7 +321,7 @@ Status CuckooTableBuilder::Finish() { uint64_t offset = buckets.size() * bucket_size; properties_.data_size = offset; - unused_bucket.resize(properties_.fixed_key_len); + unused_bucket.resize(static_cast(properties_.fixed_key_len)); properties_.user_collected_properties[ CuckooTablePropertyNames::kEmptyKey] = unused_bucket; properties_.user_collected_properties[ @@ -456,7 +457,7 @@ bool CuckooTableBuilder::MakeSpaceForKey( // no. of times this will be called is <= max_num_hash_func_ + num_entries_. for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_; ++hash_cnt) { uint64_t bid = hash_vals[hash_cnt]; - (*buckets)[bid].make_space_for_key_call_id = make_space_for_key_call_id; + (*buckets)[static_cast(bid)].make_space_for_key_call_id = make_space_for_key_call_id; tree.push_back(CuckooNode(bid, 0, 0)); } bool null_found = false; @@ -467,7 +468,7 @@ bool CuckooTableBuilder::MakeSpaceForKey( if (curr_depth >= max_search_depth_) { break; } - CuckooBucket& curr_bucket = (*buckets)[curr_node.bucket_id]; + CuckooBucket& curr_bucket = (*buckets)[static_cast(curr_node.bucket_id)]; for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_ && !null_found; ++hash_cnt) { uint64_t child_bucket_id = CuckooHash(GetUserKey(curr_bucket.vector_idx), @@ -476,15 +477,15 @@ bool CuckooTableBuilder::MakeSpaceForKey( // Iterate inside Cuckoo Block. for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_; ++block_idx, ++child_bucket_id) { - if ((*buckets)[child_bucket_id].make_space_for_key_call_id == + if ((*buckets)[static_cast(child_bucket_id)].make_space_for_key_call_id == make_space_for_key_call_id) { continue; } - (*buckets)[child_bucket_id].make_space_for_key_call_id = + (*buckets)[static_cast(child_bucket_id)].make_space_for_key_call_id = make_space_for_key_call_id; tree.push_back(CuckooNode(child_bucket_id, curr_depth + 1, curr_pos)); - if ((*buckets)[child_bucket_id].vector_idx == kMaxVectorIdx) { + if ((*buckets)[static_cast(child_bucket_id)].vector_idx == kMaxVectorIdx) { null_found = true; break; } @@ -502,8 +503,8 @@ bool CuckooTableBuilder::MakeSpaceForKey( uint32_t bucket_to_replace_pos = static_cast(tree.size()) - 1; while (bucket_to_replace_pos >= num_hash_func_) { CuckooNode& curr_node = tree[bucket_to_replace_pos]; - (*buckets)[curr_node.bucket_id] = - (*buckets)[tree[curr_node.parent_pos].bucket_id]; + (*buckets)[static_cast(curr_node.bucket_id)] = + (*buckets)[static_cast(tree[curr_node.parent_pos].bucket_id)]; bucket_to_replace_pos = curr_node.parent_pos; } *bucket_id = tree[bucket_to_replace_pos].bucket_id; diff --git a/thirdparty/rocksdb/table/cuckoo_table_builder_test.cc b/thirdparty/rocksdb/table/cuckoo_table_builder_test.cc index 93daaca472..c1e350327f 100644 --- a/thirdparty/rocksdb/table/cuckoo_table_builder_test.cc +++ b/thirdparty/rocksdb/table/cuckoo_table_builder_test.cc @@ -23,7 +23,7 @@ namespace { std::unordered_map> hash_map; uint64_t GetSliceHash(const Slice& s, uint32_t index, - uint64_t max_num_buckets) { + uint64_t /*max_num_buckets*/) { return hash_map[s.ToString()][index]; } } // namespace @@ -43,23 +43,31 @@ class CuckooBuilderTest : public testing::Test { std::string expected_unused_bucket, uint64_t expected_table_size, uint32_t expected_num_hash_func, bool expected_is_last_level, uint32_t expected_cuckoo_block_size = 1) { + uint64_t num_deletions = 0; + for (const auto& key : keys) { + ParsedInternalKey parsed; + if (ParseInternalKey(key, &parsed) && parsed.type == kTypeDeletion) { + num_deletions++; + } + } // Read file - unique_ptr read_file; + std::unique_ptr read_file; ASSERT_OK(env_->NewRandomAccessFile(fname, &read_file, env_options_)); uint64_t read_file_size; ASSERT_OK(env_->GetFileSize(fname, &read_file_size)); + // @lint-ignore TXT2 T25377293 Grandfathered in Options options; options.allow_mmap_reads = true; ImmutableCFOptions ioptions(options); // Assert Table Properties. TableProperties* props = nullptr; - unique_ptr file_reader( + std::unique_ptr file_reader( new RandomAccessFileReader(std::move(read_file), fname)); ASSERT_OK(ReadTableProperties(file_reader.get(), read_file_size, kCuckooTableMagicNumber, ioptions, - &props)); + &props, true /* compression_type_missing */)); // Check unused bucket. std::string unused_key = props->user_collected_properties[ CuckooTablePropertyNames::kEmptyKey]; @@ -89,6 +97,7 @@ class CuckooBuilderTest : public testing::Test { ASSERT_EQ(expected_is_last_level, is_last_level_found); ASSERT_EQ(props->num_entries, keys.size()); + ASSERT_EQ(props->num_deletions, num_deletions); ASSERT_EQ(props->fixed_key_len, keys.empty() ? 0 : keys[0].size()); ASSERT_EQ(props->data_size, expected_unused_bucket.size() * (expected_table_size + expected_cuckoo_block_size - 1)); @@ -125,9 +134,10 @@ class CuckooBuilderTest : public testing::Test { } } - std::string GetInternalKey(Slice user_key, bool zero_seqno) { + std::string GetInternalKey(Slice user_key, bool zero_seqno, + ValueType type = kTypeValue) { IterKey ikey; - ikey.SetInternalKey(user_key, zero_seqno ? 0 : 1000, kTypeValue); + ikey.SetInternalKey(user_key, zero_seqno ? 0 : 1000, type); return ikey.GetInternalKey().ToString(); } @@ -151,11 +161,11 @@ class CuckooBuilderTest : public testing::Test { }; TEST_F(CuckooBuilderTest, SuccessWithEmptyFile) { - unique_ptr writable_file; - fname = test::TmpDir() + "/EmptyFile"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("EmptyFile"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, 4, 100, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -168,50 +178,57 @@ TEST_F(CuckooBuilderTest, SuccessWithEmptyFile) { } TEST_F(CuckooBuilderTest, WriteSuccessNoCollisionFullKey) { - uint32_t num_hash_fun = 4; - std::vector user_keys = {"key01", "key02", "key03", "key04"}; - std::vector values = {"v01", "v02", "v03", "v04"}; - // Need to have a temporary variable here as VS compiler does not currently - // support operator= with initializer_list as a parameter - std::unordered_map> hm = { - {user_keys[0], {0, 1, 2, 3}}, - {user_keys[1], {1, 2, 3, 4}}, - {user_keys[2], {2, 3, 4, 5}}, - {user_keys[3], {3, 4, 5, 6}}}; - hash_map = std::move(hm); - - std::vector expected_locations = {0, 1, 2, 3}; - std::vector keys; - for (auto& user_key : user_keys) { - keys.push_back(GetInternalKey(user_key, false)); - } - uint64_t expected_table_size = GetExpectedTableSize(keys.size()); - - unique_ptr writable_file; - fname = test::TmpDir() + "/NoCollisionFullKey"; - ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); - CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, - 100, BytewiseComparator(), 1, false, false, - GetSliceHash, 0 /* column_family_id */, - kDefaultColumnFamilyName); - ASSERT_OK(builder.status()); - for (uint32_t i = 0; i < user_keys.size(); i++) { - builder.Add(Slice(keys[i]), Slice(values[i])); - ASSERT_EQ(builder.NumEntries(), i + 1); + for (auto type : {kTypeValue, kTypeDeletion}) { + uint32_t num_hash_fun = 4; + std::vector user_keys = {"key01", "key02", "key03", "key04"}; + std::vector values; + if (type == kTypeValue) { + values = {"v01", "v02", "v03", "v04"}; + } else { + values = {"", "", "", ""}; + } + // Need to have a temporary variable here as VS compiler does not currently + // support operator= with initializer_list as a parameter + std::unordered_map> hm = { + {user_keys[0], {0, 1, 2, 3}}, + {user_keys[1], {1, 2, 3, 4}}, + {user_keys[2], {2, 3, 4, 5}}, + {user_keys[3], {3, 4, 5, 6}}}; + hash_map = std::move(hm); + + std::vector expected_locations = {0, 1, 2, 3}; + std::vector keys; + for (auto& user_key : user_keys) { + keys.push_back(GetInternalKey(user_key, false, type)); + } + uint64_t expected_table_size = GetExpectedTableSize(keys.size()); + + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("NoCollisionFullKey"); + ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); + CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, + 100, BytewiseComparator(), 1, false, false, + GetSliceHash, 0 /* column_family_id */, + kDefaultColumnFamilyName); ASSERT_OK(builder.status()); + for (uint32_t i = 0; i < user_keys.size(); i++) { + builder.Add(Slice(keys[i]), Slice(values[i])); + ASSERT_EQ(builder.NumEntries(), i + 1); + ASSERT_OK(builder.status()); + } + size_t bucket_size = keys[0].size() + values[0].size(); + ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize()); + ASSERT_OK(builder.Finish()); + ASSERT_OK(file_writer->Close()); + ASSERT_LE(expected_table_size * bucket_size, builder.FileSize()); + + std::string expected_unused_bucket = GetInternalKey("key00", true); + expected_unused_bucket += std::string(values[0].size(), 'a'); + CheckFileContents(keys, values, expected_locations, expected_unused_bucket, + expected_table_size, 2, false); } - size_t bucket_size = keys[0].size() + values[0].size(); - ASSERT_EQ(expected_table_size * bucket_size - 1, builder.FileSize()); - ASSERT_OK(builder.Finish()); - ASSERT_OK(file_writer->Close()); - ASSERT_LE(expected_table_size * bucket_size, builder.FileSize()); - - std::string expected_unused_bucket = GetInternalKey("key00", true); - expected_unused_bucket += std::string(values[0].size(), 'a'); - CheckFileContents(keys, values, expected_locations, - expected_unused_bucket, expected_table_size, 2, false); } TEST_F(CuckooBuilderTest, WriteSuccessWithCollisionFullKey) { @@ -235,11 +252,11 @@ TEST_F(CuckooBuilderTest, WriteSuccessWithCollisionFullKey) { } uint64_t expected_table_size = GetExpectedTableSize(keys.size()); - unique_ptr writable_file; - fname = test::TmpDir() + "/WithCollisionFullKey"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("WithCollisionFullKey"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 100, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -283,12 +300,12 @@ TEST_F(CuckooBuilderTest, WriteSuccessWithCollisionAndCuckooBlock) { } uint64_t expected_table_size = GetExpectedTableSize(keys.size()); - unique_ptr writable_file; + std::unique_ptr writable_file; uint32_t cuckoo_block_size = 2; - fname = test::TmpDir() + "/WithCollisionFullKey2"; + fname = test::PerThreadDBPath("WithCollisionFullKey2"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder( file_writer.get(), kHashTableRatio, num_hash_fun, 100, BytewiseComparator(), cuckoo_block_size, false, false, GetSliceHash, @@ -337,11 +354,11 @@ TEST_F(CuckooBuilderTest, WithCollisionPathFullKey) { } uint64_t expected_table_size = GetExpectedTableSize(keys.size()); - unique_ptr writable_file; - fname = test::TmpDir() + "/WithCollisionPathFullKey"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("WithCollisionPathFullKey"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 100, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -387,11 +404,11 @@ TEST_F(CuckooBuilderTest, WithCollisionPathFullKeyAndCuckooBlock) { } uint64_t expected_table_size = GetExpectedTableSize(keys.size()); - unique_ptr writable_file; - fname = test::TmpDir() + "/WithCollisionPathFullKeyAndCuckooBlock"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("WithCollisionPathFullKeyAndCuckooBlock"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 100, BytewiseComparator(), 2, false, false, GetSliceHash, 0 /* column_family_id */, @@ -430,11 +447,11 @@ TEST_F(CuckooBuilderTest, WriteSuccessNoCollisionUserKey) { std::vector expected_locations = {0, 1, 2, 3}; uint64_t expected_table_size = GetExpectedTableSize(user_keys.size()); - unique_ptr writable_file; - fname = test::TmpDir() + "/NoCollisionUserKey"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("NoCollisionUserKey"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 100, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -474,11 +491,11 @@ TEST_F(CuckooBuilderTest, WriteSuccessWithCollisionUserKey) { std::vector expected_locations = {0, 1, 2, 3}; uint64_t expected_table_size = GetExpectedTableSize(user_keys.size()); - unique_ptr writable_file; - fname = test::TmpDir() + "/WithCollisionUserKey"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("WithCollisionUserKey"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 100, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -520,11 +537,11 @@ TEST_F(CuckooBuilderTest, WithCollisionPathUserKey) { std::vector expected_locations = {0, 1, 3, 4, 2}; uint64_t expected_table_size = GetExpectedTableSize(user_keys.size()); - unique_ptr writable_file; - fname = test::TmpDir() + "/WithCollisionPathUserKey"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("WithCollisionPathUserKey"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 2, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -565,11 +582,11 @@ TEST_F(CuckooBuilderTest, FailWhenCollisionPathTooLong) { }; hash_map = std::move(hm); - unique_ptr writable_file; - fname = test::TmpDir() + "/WithCollisionPathUserKey"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("WithCollisionPathUserKey"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 2, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -593,11 +610,11 @@ TEST_F(CuckooBuilderTest, FailWhenSameKeyInserted) { uint32_t num_hash_fun = 4; std::string user_key = "repeatedkey"; - unique_ptr writable_file; - fname = test::TmpDir() + "/FailWhenSameKeyInserted"; + std::unique_ptr writable_file; + fname = test::PerThreadDBPath("FailWhenSameKeyInserted"); ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), EnvOptions())); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, EnvOptions())); CuckooTableBuilder builder(file_writer.get(), kHashTableRatio, num_hash_fun, 100, BytewiseComparator(), 1, false, false, GetSliceHash, 0 /* column_family_id */, @@ -624,7 +641,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n"); return 0; } diff --git a/thirdparty/rocksdb/table/cuckoo_table_factory.cc b/thirdparty/rocksdb/table/cuckoo_table_factory.cc index 2325bcf77c..74d18d5121 100644 --- a/thirdparty/rocksdb/table/cuckoo_table_factory.cc +++ b/thirdparty/rocksdb/table/cuckoo_table_factory.cc @@ -14,9 +14,9 @@ namespace rocksdb { Status CuckooTableFactory::NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, + std::unique_ptr&& file, uint64_t file_size, std::unique_ptr* table, - bool prefetch_index_and_filter_in_cache) const { + bool /*prefetch_index_and_filter_in_cache*/) const { std::unique_ptr new_reader(new CuckooTableReader( table_reader_options.ioptions, std::move(file), file_size, table_reader_options.internal_comparator.user_comparator(), nullptr)); diff --git a/thirdparty/rocksdb/table/cuckoo_table_factory.h b/thirdparty/rocksdb/table/cuckoo_table_factory.h index db860c3d00..eb3c5e5176 100644 --- a/thirdparty/rocksdb/table/cuckoo_table_factory.h +++ b/thirdparty/rocksdb/table/cuckoo_table_factory.h @@ -24,6 +24,8 @@ static inline uint64_t CuckooHash( if (get_slice_hash != nullptr) { return get_slice_hash(user_key, hash_cnt, table_size_); } +#else + (void)get_slice_hash; #endif uint64_t value = 0; @@ -58,8 +60,8 @@ class CuckooTableFactory : public TableFactory { Status NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table, + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table, bool prefetch_index_and_filter_in_cache = true) const override; TableBuilder* NewTableBuilder( @@ -67,8 +69,9 @@ class CuckooTableFactory : public TableFactory { uint32_t column_family_id, WritableFileWriter* file) const override; // Sanitizes the specified DB Options. - Status SanitizeOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { + Status SanitizeOptions( + const DBOptions& /*db_opts*/, + const ColumnFamilyOptions& /*cf_opts*/) const override { return Status::OK(); } @@ -76,8 +79,8 @@ class CuckooTableFactory : public TableFactory { void* GetOptions() override { return &table_options_; } - Status GetOptionString(std::string* opt_string, - const std::string& delimiter) const override { + Status GetOptionString(std::string* /*opt_string*/, + const std::string& /*delimiter*/) const override { return Status::OK(); } diff --git a/thirdparty/rocksdb/table/cuckoo_table_reader.cc b/thirdparty/rocksdb/table/cuckoo_table_reader.cc index 9cecebaebb..f4df2467fd 100644 --- a/thirdparty/rocksdb/table/cuckoo_table_reader.cc +++ b/thirdparty/rocksdb/table/cuckoo_table_reader.cc @@ -38,6 +38,18 @@ CuckooTableReader::CuckooTableReader( const Comparator* comparator, uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t)) : file_(std::move(file)), + is_last_level_(false), + identity_as_first_hash_(false), + use_module_hash_(false), + num_hash_func_(0), + unused_key_(""), + key_length_(0), + user_key_length_(0), + value_length_(0), + bucket_length_(0), + cuckoo_block_size_(0), + cuckoo_block_bytes_minus_one_(0), + table_size_(0), ucomp_(comparator), get_slice_hash_(get_slice_hash) { if (!ioptions.allow_mmap_reads) { @@ -45,7 +57,7 @@ CuckooTableReader::CuckooTableReader( } TableProperties* props = nullptr; status_ = ReadTableProperties(file_.get(), file_size, kCuckooTableMagicNumber, - ioptions, &props); + ioptions, &props, true /* compression_type_missing */); if (!status_.ok()) { return; } @@ -124,11 +136,13 @@ CuckooTableReader::CuckooTableReader( cuckoo_block_size_ = *reinterpret_cast( cuckoo_block_size->second.data()); cuckoo_block_bytes_minus_one_ = cuckoo_block_size_ * bucket_length_ - 1; - status_ = file_->Read(0, file_size, &file_data_, nullptr); + status_ = file_->Read(0, static_cast(file_size), &file_data_, nullptr); } -Status CuckooTableReader::Get(const ReadOptions& readOptions, const Slice& key, - GetContext* get_context, bool skip_filters) { +Status CuckooTableReader::Get(const ReadOptions& /*readOptions*/, + const Slice& key, GetContext* get_context, + const SliceTransform* /* prefix_extractor */, + bool /*skip_filters*/) { assert(key.size() == key_length_ + (is_last_level_ ? 8 : 0)); Slice user_key = ExtractUserKey(key); for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_; ++hash_cnt) { @@ -157,7 +171,8 @@ Status CuckooTableReader::Get(const ReadOptions& readOptions, const Slice& key, Slice full_key(bucket, key_length_); ParsedInternalKey found_ikey; ParseInternalKey(full_key, &found_ikey); - get_context->SaveValue(found_ikey, value); + bool dont_care __attribute__((__unused__)); + get_context->SaveValue(found_ikey, value, &dont_care); } // We don't support merge operations. So, we return here. return Status::OK(); @@ -182,7 +197,7 @@ void CuckooTableReader::Prepare(const Slice& key) { class CuckooTableIterator : public InternalIterator { public: explicit CuckooTableIterator(CuckooTableReader* reader); - ~CuckooTableIterator() {} + ~CuckooTableIterator() override {} bool Valid() const override; void SeekToFirst() override; void SeekToLast() override; @@ -192,7 +207,7 @@ class CuckooTableIterator : public InternalIterator { void Prev() override; Slice key() const override; Slice value() const override; - Status status() const override { return status_; } + Status status() const override { return Status::OK(); } void InitIfNeeded(); private: @@ -227,7 +242,6 @@ class CuckooTableIterator : public InternalIterator { void PrepareKVAtCurrIdx(); CuckooTableReader* reader_; bool initialized_; - Status status_; // Contains a map of keys to bucket_id sorted in key order. std::vector sorted_bucket_ids_; // We assume that the number of items can be stored in uint32 (4 Billion). @@ -254,7 +268,7 @@ void CuckooTableIterator::InitIfNeeded() { if (initialized_) { return; } - sorted_bucket_ids_.reserve(reader_->GetTableProperties()->num_entries); + sorted_bucket_ids_.reserve(static_cast(reader_->GetTableProperties()->num_entries)); uint64_t num_buckets = reader_->table_size_ + reader_->cuckoo_block_size_ - 1; assert(num_buckets < kInvalidIndex); const char* bucket = reader_->file_data_.data(); @@ -299,7 +313,7 @@ void CuckooTableIterator::Seek(const Slice& target) { PrepareKVAtCurrIdx(); } -void CuckooTableIterator::SeekForPrev(const Slice& target) { +void CuckooTableIterator::SeekForPrev(const Slice& /*target*/) { // Not supported assert(false); } @@ -360,13 +374,12 @@ Slice CuckooTableIterator::value() const { return curr_value_; } -extern InternalIterator* NewErrorInternalIterator(const Status& status, - Arena* arena); - InternalIterator* CuckooTableReader::NewIterator( - const ReadOptions& read_options, Arena* arena, bool skip_filters) { + const ReadOptions& /*read_options*/, + const SliceTransform* /* prefix_extractor */, Arena* arena, + bool /*skip_filters*/, bool /*for_compaction*/) { if (!status().ok()) { - return NewErrorInternalIterator( + return NewErrorInternalIterator( Status::Corruption("CuckooTableReader status is not okay."), arena); } CuckooTableIterator* iter; diff --git a/thirdparty/rocksdb/table/cuckoo_table_reader.h b/thirdparty/rocksdb/table/cuckoo_table_reader.h index 4beac8f9d0..b37d46373e 100644 --- a/thirdparty/rocksdb/table/cuckoo_table_reader.h +++ b/thirdparty/rocksdb/table/cuckoo_table_reader.h @@ -25,7 +25,6 @@ namespace rocksdb { class Arena; class TableReader; -class InternalIterator; class CuckooTableReader: public TableReader { public: @@ -42,19 +41,22 @@ class CuckooTableReader: public TableReader { Status status() const { return status_; } - Status Get(const ReadOptions& read_options, const Slice& key, - GetContext* get_context, bool skip_filters = false) override; + Status Get(const ReadOptions& readOptions, const Slice& key, + GetContext* get_context, const SliceTransform* prefix_extractor, + bool skip_filters = false) override; - InternalIterator* NewIterator( - const ReadOptions&, Arena* arena = nullptr, - bool skip_filters = false) override; + InternalIterator* NewIterator(const ReadOptions&, + const SliceTransform* prefix_extractor, + Arena* arena = nullptr, + bool skip_filters = false, + bool for_compaction = false) override; void Prepare(const Slice& target) override; // Report an approximation of how much memory has been used. size_t ApproximateMemoryUsage() const override; // Following methods are not implemented for Cuckoo Table Reader - uint64_t ApproximateOffsetOf(const Slice& key) override { return 0; } + uint64_t ApproximateOffsetOf(const Slice& /*key*/) override { return 0; } void SetupForCompaction() override {} // End of methods not implemented. diff --git a/thirdparty/rocksdb/table/cuckoo_table_reader_test.cc b/thirdparty/rocksdb/table/cuckoo_table_reader_test.cc index 7e131e56e3..74fb52e6c7 100644 --- a/thirdparty/rocksdb/table/cuckoo_table_reader_test.cc +++ b/thirdparty/rocksdb/table/cuckoo_table_reader_test.cc @@ -18,24 +18,24 @@ int main() { #endif #include -#include #include #include #include -#include "table/meta_blocks.h" #include "table/cuckoo_table_builder.h" -#include "table/cuckoo_table_reader.h" #include "table/cuckoo_table_factory.h" +#include "table/cuckoo_table_reader.h" #include "table/get_context.h" +#include "table/meta_blocks.h" #include "util/arena.h" +#include "util/gflags_compat.h" #include "util/random.h" #include "util/string_util.h" #include "util/testharness.h" #include "util/testutil.h" -using GFLAGS::ParseCommandLineFlags; -using GFLAGS::SetUsageMessage; +using GFLAGS_NAMESPACE::ParseCommandLineFlags; +using GFLAGS_NAMESPACE::SetUsageMessage; DEFINE_string(file_dir, "", "Directory where the files will be created" " for benchmark. Added for using tmpfs."); @@ -61,7 +61,7 @@ void AddHashLookups(const std::string& s, uint64_t bucket_id, } uint64_t GetSliceHash(const Slice& s, uint32_t index, - uint64_t max_num_buckets) { + uint64_t /*max_num_buckets*/) { return hash_map[s.ToString()][index]; } } // namespace @@ -95,8 +95,8 @@ class CuckooReaderTest : public testing::Test { const Comparator* ucomp = BytewiseComparator()) { std::unique_ptr writable_file; ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), env_options)); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, env_options)); CuckooTableBuilder builder( file_writer.get(), 0.9, kNumHashFunc, 100, ucomp, 2, false, false, @@ -115,7 +115,7 @@ class CuckooReaderTest : public testing::Test { // Check reader now. std::unique_ptr read_file; ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options)); - unique_ptr file_reader( + std::unique_ptr file_reader( new RandomAccessFileReader(std::move(read_file), fname)); const ImmutableCFOptions ioptions(options); CuckooTableReader reader(ioptions, std::move(file_reader), file_size, ucomp, @@ -127,7 +127,8 @@ class CuckooReaderTest : public testing::Test { GetContext get_context(ucomp, nullptr, nullptr, nullptr, GetContext::kNotFound, Slice(user_keys[i]), &value, nullptr, nullptr, nullptr, nullptr); - ASSERT_OK(reader.Get(ReadOptions(), Slice(keys[i]), &get_context)); + ASSERT_OK( + reader.Get(ReadOptions(), Slice(keys[i]), &get_context, nullptr)); ASSERT_STREQ(values[i].c_str(), value.data()); } } @@ -143,13 +144,14 @@ class CuckooReaderTest : public testing::Test { void CheckIterator(const Comparator* ucomp = BytewiseComparator()) { std::unique_ptr read_file; ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options)); - unique_ptr file_reader( + std::unique_ptr file_reader( new RandomAccessFileReader(std::move(read_file), fname)); const ImmutableCFOptions ioptions(options); CuckooTableReader reader(ioptions, std::move(file_reader), file_size, ucomp, GetSliceHash); ASSERT_OK(reader.status()); - InternalIterator* it = reader.NewIterator(ReadOptions(), nullptr); + InternalIterator* it = + reader.NewIterator(ReadOptions(), nullptr, nullptr, false); ASSERT_OK(it->status()); ASSERT_TRUE(!it->Valid()); it->SeekToFirst(); @@ -188,7 +190,7 @@ class CuckooReaderTest : public testing::Test { delete it; Arena arena; - it = reader.NewIterator(ReadOptions(), &arena); + it = reader.NewIterator(ReadOptions(), nullptr, &arena); ASSERT_OK(it->status()); ASSERT_TRUE(!it->Valid()); it->Seek(keys[num_items/2]); @@ -213,7 +215,7 @@ class CuckooReaderTest : public testing::Test { TEST_F(CuckooReaderTest, WhenKeyExists) { SetUp(kNumHashFunc); - fname = test::TmpDir() + "/CuckooReader_WhenKeyExists"; + fname = test::PerThreadDBPath("CuckooReader_WhenKeyExists"); for (uint64_t i = 0; i < num_items; i++) { user_keys[i] = "key" + NumToStr(i); ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue); @@ -240,7 +242,7 @@ TEST_F(CuckooReaderTest, WhenKeyExists) { TEST_F(CuckooReaderTest, WhenKeyExistsWithUint64Comparator) { SetUp(kNumHashFunc); - fname = test::TmpDir() + "/CuckooReaderUint64_WhenKeyExists"; + fname = test::PerThreadDBPath("CuckooReaderUint64_WhenKeyExists"); for (uint64_t i = 0; i < num_items; i++) { user_keys[i].resize(8); memcpy(&user_keys[i][0], static_cast(&i), 8); @@ -268,7 +270,7 @@ TEST_F(CuckooReaderTest, WhenKeyExistsWithUint64Comparator) { TEST_F(CuckooReaderTest, CheckIterator) { SetUp(2*kNumHashFunc); - fname = test::TmpDir() + "/CuckooReader_CheckIterator"; + fname = test::PerThreadDBPath("CuckooReader_CheckIterator"); for (uint64_t i = 0; i < num_items; i++) { user_keys[i] = "key" + NumToStr(i); ParsedInternalKey ikey(user_keys[i], 1000, kTypeValue); @@ -287,7 +289,7 @@ TEST_F(CuckooReaderTest, CheckIterator) { TEST_F(CuckooReaderTest, CheckIteratorUint64) { SetUp(2*kNumHashFunc); - fname = test::TmpDir() + "/CuckooReader_CheckIterator"; + fname = test::PerThreadDBPath("CuckooReader_CheckIterator"); for (uint64_t i = 0; i < num_items; i++) { user_keys[i].resize(8); memcpy(&user_keys[i][0], static_cast(&i), 8); @@ -308,7 +310,7 @@ TEST_F(CuckooReaderTest, CheckIteratorUint64) { TEST_F(CuckooReaderTest, WhenKeyNotFound) { // Add keys with colliding hash values. SetUp(kNumHashFunc); - fname = test::TmpDir() + "/CuckooReader_WhenKeyNotFound"; + fname = test::PerThreadDBPath("CuckooReader_WhenKeyNotFound"); for (uint64_t i = 0; i < num_items; i++) { user_keys[i] = "key" + NumToStr(i); ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue); @@ -321,7 +323,7 @@ TEST_F(CuckooReaderTest, WhenKeyNotFound) { CreateCuckooFileAndCheckReader(); std::unique_ptr read_file; ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options)); - unique_ptr file_reader( + std::unique_ptr file_reader( new RandomAccessFileReader(std::move(read_file), fname)); const ImmutableCFOptions ioptions(options); CuckooTableReader reader(ioptions, std::move(file_reader), file_size, ucmp, @@ -337,7 +339,8 @@ TEST_F(CuckooReaderTest, WhenKeyNotFound) { GetContext get_context(ucmp, nullptr, nullptr, nullptr, GetContext::kNotFound, Slice(not_found_key), &value, nullptr, nullptr, nullptr, nullptr); - ASSERT_OK(reader.Get(ReadOptions(), Slice(not_found_key), &get_context)); + ASSERT_OK( + reader.Get(ReadOptions(), Slice(not_found_key), &get_context, nullptr)); ASSERT_TRUE(value.empty()); ASSERT_OK(reader.status()); // Search for a key with an independent hash value. @@ -350,7 +353,8 @@ TEST_F(CuckooReaderTest, WhenKeyNotFound) { GetContext get_context2(ucmp, nullptr, nullptr, nullptr, GetContext::kNotFound, Slice(not_found_key2), &value, nullptr, nullptr, nullptr, nullptr); - ASSERT_OK(reader.Get(ReadOptions(), Slice(not_found_key2), &get_context2)); + ASSERT_OK( + reader.Get(ReadOptions(), Slice(not_found_key2), &get_context2, nullptr)); ASSERT_TRUE(value.empty()); ASSERT_OK(reader.status()); @@ -365,7 +369,8 @@ TEST_F(CuckooReaderTest, WhenKeyNotFound) { GetContext get_context3(ucmp, nullptr, nullptr, nullptr, GetContext::kNotFound, Slice(unused_key), &value, nullptr, nullptr, nullptr, nullptr); - ASSERT_OK(reader.Get(ReadOptions(), Slice(unused_key), &get_context3)); + ASSERT_OK( + reader.Get(ReadOptions(), Slice(unused_key), &get_context3, nullptr)); ASSERT_TRUE(value.empty()); ASSERT_OK(reader.status()); } @@ -390,8 +395,8 @@ std::string GetFileName(uint64_t num) { if (FLAGS_file_dir.empty()) { FLAGS_file_dir = test::TmpDir(); } - return FLAGS_file_dir + "/cuckoo_read_benchmark" + - ToString(num/1000000) + "Mkeys"; + return test::PerThreadDBPath(FLAGS_file_dir, "cuckoo_read_benchmark") + + ToString(num / 1000000) + "Mkeys"; } // Create last level file as we are interested in measuring performance of @@ -406,8 +411,8 @@ void WriteFile(const std::vector& keys, std::unique_ptr writable_file; ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options)); - unique_ptr file_writer( - new WritableFileWriter(std::move(writable_file), env_options)); + std::unique_ptr file_writer( + new WritableFileWriter(std::move(writable_file), fname, env_options)); CuckooTableBuilder builder( file_writer.get(), hash_ratio, 64, 1000, test::Uint64Comparator(), 5, false, FLAGS_identity_as_first_hash, nullptr, 0 /* column_family_id */, @@ -427,7 +432,7 @@ void WriteFile(const std::vector& keys, env->GetFileSize(fname, &file_size); std::unique_ptr read_file; ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options)); - unique_ptr file_reader( + std::unique_ptr file_reader( new RandomAccessFileReader(std::move(read_file), fname)); const ImmutableCFOptions ioptions(options); @@ -443,7 +448,7 @@ void WriteFile(const std::vector& keys, for (uint64_t i = 0; i < num; ++i) { value.Reset(); value.clear(); - ASSERT_OK(reader.Get(r_options, Slice(keys[i]), &get_context)); + ASSERT_OK(reader.Get(r_options, Slice(keys[i]), &get_context, nullptr)); ASSERT_TRUE(Slice(keys[i]) == Slice(&keys[i][0], 4)); } } @@ -459,7 +464,7 @@ void ReadKeys(uint64_t num, uint32_t batch_size) { env->GetFileSize(fname, &file_size); std::unique_ptr read_file; ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options)); - unique_ptr file_reader( + std::unique_ptr file_reader( new RandomAccessFileReader(std::move(read_file), fname)); const ImmutableCFOptions ioptions(options); @@ -496,13 +501,13 @@ void ReadKeys(uint64_t num, uint32_t batch_size) { } for (uint64_t j = i; j < i+batch_size && j < num; ++j) { reader.Get(r_options, Slice(reinterpret_cast(&keys[j]), 16), - &get_context); + &get_context, nullptr); } } } else { for (uint64_t i = 0; i < num; i++) { reader.Get(r_options, Slice(reinterpret_cast(&keys[i]), 16), - &get_context); + &get_context, nullptr); } } float time_per_op = (env->NowMicros() - start_time) * 1.0f / num; @@ -560,7 +565,7 @@ int main(int argc, char** argv) { #else #include -int main(int argc, char** argv) { +int main(int /*argc*/, char** /*argv*/) { fprintf(stderr, "SKIPPED as Cuckoo table is not supported in ROCKSDB_LITE\n"); return 0; } diff --git a/thirdparty/rocksdb/table/data_block_footer.cc b/thirdparty/rocksdb/table/data_block_footer.cc new file mode 100644 index 0000000000..cb9e143815 --- /dev/null +++ b/thirdparty/rocksdb/table/data_block_footer.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#include "data_block_footer.h" + +#include "rocksdb/table.h" + +namespace rocksdb { + +const int kDataBlockIndexTypeBitShift = 31; + +// 0x7FFFFFFF +const uint32_t kMaxNumRestarts = (1u << kDataBlockIndexTypeBitShift) - 1u; + +// 0x7FFFFFFF +const uint32_t kNumRestartsMask = (1u << kDataBlockIndexTypeBitShift) - 1u; + +uint32_t PackIndexTypeAndNumRestarts( + BlockBasedTableOptions::DataBlockIndexType index_type, + uint32_t num_restarts) { + if (num_restarts > kMaxNumRestarts) { + assert(0); // mute travis "unused" warning + } + + uint32_t block_footer = num_restarts; + if (index_type == BlockBasedTableOptions::kDataBlockBinaryAndHash) { + block_footer |= 1u << kDataBlockIndexTypeBitShift; + } else if (index_type != BlockBasedTableOptions::kDataBlockBinarySearch) { + assert(0); + } + + return block_footer; +} + +void UnPackIndexTypeAndNumRestarts( + uint32_t block_footer, + BlockBasedTableOptions::DataBlockIndexType* index_type, + uint32_t* num_restarts) { + if (index_type) { + if (block_footer & 1u << kDataBlockIndexTypeBitShift) { + *index_type = BlockBasedTableOptions::kDataBlockBinaryAndHash; + } else { + *index_type = BlockBasedTableOptions::kDataBlockBinarySearch; + } + } + + if (num_restarts) { + *num_restarts = block_footer & kNumRestartsMask; + assert(*num_restarts <= kMaxNumRestarts); + } +} + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/table/data_block_footer.h b/thirdparty/rocksdb/table/data_block_footer.h new file mode 100644 index 0000000000..e6ff20bccb --- /dev/null +++ b/thirdparty/rocksdb/table/data_block_footer.h @@ -0,0 +1,25 @@ +// Copyright (c) 2011-present, Facebook, 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). +// +// 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. + +#pragma once + +#include "rocksdb/table.h" + +namespace rocksdb { + +uint32_t PackIndexTypeAndNumRestarts( + BlockBasedTableOptions::DataBlockIndexType index_type, + uint32_t num_restarts); + +void UnPackIndexTypeAndNumRestarts( + uint32_t block_footer, + BlockBasedTableOptions::DataBlockIndexType* index_type, + uint32_t* num_restarts); + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/table/data_block_hash_index.cc b/thirdparty/rocksdb/table/data_block_hash_index.cc new file mode 100644 index 0000000000..adb1d7b8c2 --- /dev/null +++ b/thirdparty/rocksdb/table/data_block_hash_index.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2011-present, Facebook, 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). +#include +#include + +#include "rocksdb/slice.h" +#include "table/data_block_hash_index.h" +#include "util/coding.h" +#include "util/hash.h" + +namespace rocksdb { + +void DataBlockHashIndexBuilder::Add(const Slice& key, + const size_t restart_index) { + assert(Valid()); + if (restart_index > kMaxRestartSupportedByHashIndex) { + valid_ = false; + return; + } + + uint32_t hash_value = GetSliceHash(key); + hash_and_restart_pairs_.emplace_back(hash_value, + static_cast(restart_index)); + estimated_num_buckets_ += bucket_per_key_; +} + +void DataBlockHashIndexBuilder::Finish(std::string& buffer) { + assert(Valid()); + uint16_t num_buckets = static_cast(estimated_num_buckets_); + + if (num_buckets == 0) { + num_buckets = 1; // sanity check + } + + // The build-in hash cannot well distribute strings when into different + // buckets when num_buckets is power of two, resulting in high hash + // collision. + // We made the num_buckets to be odd to avoid this issue. + num_buckets |= 1; + + std::vector buckets(num_buckets, kNoEntry); + // write the restart_index array + for (auto& entry : hash_and_restart_pairs_) { + uint32_t hash_value = entry.first; + uint8_t restart_index = entry.second; + uint16_t buck_idx = static_cast(hash_value % num_buckets); + if (buckets[buck_idx] == kNoEntry) { + buckets[buck_idx] = restart_index; + } else if (buckets[buck_idx] != restart_index) { + // same bucket cannot store two different restart_index, mark collision + buckets[buck_idx] = kCollision; + } + } + + for (uint8_t restart_index : buckets) { + buffer.append( + const_cast(reinterpret_cast(&restart_index)), + sizeof(restart_index)); + } + + // write NUM_BUCK + PutFixed16(&buffer, num_buckets); + + assert(buffer.size() <= kMaxBlockSizeSupportedByHashIndex); +} + +void DataBlockHashIndexBuilder::Reset() { + estimated_num_buckets_ = 0; + valid_ = true; + hash_and_restart_pairs_.clear(); +} + +void DataBlockHashIndex::Initialize(const char* data, uint16_t size, + uint16_t* map_offset) { + assert(size >= sizeof(uint16_t)); // NUM_BUCKETS + num_buckets_ = DecodeFixed16(data + size - sizeof(uint16_t)); + assert(num_buckets_ > 0); + assert(size > num_buckets_ * sizeof(uint8_t)); + *map_offset = static_cast(size - sizeof(uint16_t) - + num_buckets_ * sizeof(uint8_t)); +} + +uint8_t DataBlockHashIndex::Lookup(const char* data, uint32_t map_offset, + const Slice& key) const { + uint32_t hash_value = GetSliceHash(key); + uint16_t idx = static_cast(hash_value % num_buckets_); + const char* bucket_table = data + map_offset; + return static_cast(*(bucket_table + idx * sizeof(uint8_t))); +} + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/table/data_block_hash_index.h b/thirdparty/rocksdb/table/data_block_hash_index.h new file mode 100644 index 0000000000..0af8b257c2 --- /dev/null +++ b/thirdparty/rocksdb/table/data_block_hash_index.h @@ -0,0 +1,136 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#pragma once + +#include +#include + +#include "rocksdb/slice.h" + +namespace rocksdb { +// This is an experimental feature aiming to reduce the CPU utilization of +// point-lookup within a data-block. It is only used in data blocks, and not +// in meta-data blocks or per-table index blocks. +// +// It only used to support BlockBasedTable::Get(). +// +// A serialized hash index is appended to the data-block. The new block data +// format is as follows: +// +// DATA_BLOCK: [RI RI RI ... RI RI_IDX HASH_IDX FOOTER] +// +// RI: Restart Interval (the same as the default data-block format) +// RI_IDX: Restart Interval index (the same as the default data-block format) +// HASH_IDX: The new data-block hash index feature. +// FOOTER: A 32bit block footer, which is the NUM_RESTARTS with the MSB as +// the flag indicating if this hash index is in use. Note that +// given a data block < 32KB, the MSB is never used. So we can +// borrow the MSB as the hash index flag. Therefore, this format is +// compatible with the legacy data-blocks with num_restarts < 32768, +// as the MSB is 0. +// +// The format of the data-block hash index is as follows: +// +// HASH_IDX: [B B B ... B NUM_BUCK] +// +// B: bucket, an array of restart index. Each buckets is uint8_t. +// NUM_BUCK: Number of buckets, which is the length of the bucket array. +// +// We reserve two special flag: +// kNoEntry=255, +// kCollision=254. +// +// Therefore, the max number of restarts this hash index can supoport is 253. +// +// Buckets are initialized to be kNoEntry. +// +// When storing a key in the hash index, the key is first hashed to a bucket. +// If there the bucket is empty (kNoEntry), the restart index is stored in +// the bucket. If there is already a restart index there, we will update the +// existing restart index to a collision marker (kCollision). If the +// the bucket is already marked as collision, we do not store the restart +// index either. +// +// During query process, a key is first hashed to a bucket. Then we examine if +// the buckets store nothing (kNoEntry) or the bucket had a collision +// (kCollision). If either of those happens, we get the restart index of +// the key and will directly go to the restart interval to search the key. +// +// Note that we only support blocks with #restart_interval < 254. If a block +// has more restart interval than that, hash index will not be create for it. + +const uint8_t kNoEntry = 255; +const uint8_t kCollision = 254; +const uint8_t kMaxRestartSupportedByHashIndex = 253; + +// Because we use uint16_t address, we only support block no more than 64KB +const size_t kMaxBlockSizeSupportedByHashIndex = 1u << 16; +const double kDefaultUtilRatio = 0.75; + +class DataBlockHashIndexBuilder { + public: + DataBlockHashIndexBuilder() + : bucket_per_key_(-1 /*uninitialized marker*/), + estimated_num_buckets_(0), + valid_(false) {} + + void Initialize(double util_ratio) { + if (util_ratio <= 0) { + util_ratio = kDefaultUtilRatio; // sanity check + } + bucket_per_key_ = 1 / util_ratio; + valid_ = true; + } + + inline bool Valid() const { return valid_ && bucket_per_key_ > 0; } + void Add(const Slice& key, const size_t restart_index); + void Finish(std::string& buffer); + void Reset(); + inline size_t EstimateSize() const { + uint16_t estimated_num_buckets = + static_cast(estimated_num_buckets_); + + // Maching the num_buckets number in DataBlockHashIndexBuilder::Finish. + estimated_num_buckets |= 1; + + return sizeof(uint16_t) + + static_cast(estimated_num_buckets * sizeof(uint8_t)); + } + + private: + double bucket_per_key_; // is the multiplicative inverse of util_ratio_ + double estimated_num_buckets_; + + // Now the only usage for `valid_` is to mark false when the inserted + // restart_index is larger than supported. In this case HashIndex is not + // appended to the block content. + bool valid_; + + std::vector> hash_and_restart_pairs_; + friend class DataBlockHashIndex_DataBlockHashTestSmall_Test; +}; + +class DataBlockHashIndex { + public: + DataBlockHashIndex() : num_buckets_(0) {} + + void Initialize(const char* data, uint16_t size, uint16_t* map_offset); + + uint8_t Lookup(const char* data, uint32_t map_offset, const Slice& key) const; + + inline bool Valid() { return num_buckets_ != 0; } + + private: + // To make the serialized hash index compact and to save the space overhead, + // here all the data fields persisted in the block are in uint16 format. + // We find that a uint16 is large enough to index every offset of a 64KiB + // block. + // So in other words, DataBlockHashIndex does not support block size equal + // or greater then 64KiB. + uint16_t num_buckets_; +}; + +} // namespace rocksdb diff --git a/thirdparty/rocksdb/table/data_block_hash_index_test.cc b/thirdparty/rocksdb/table/data_block_hash_index_test.cc new file mode 100644 index 0000000000..11226648ef --- /dev/null +++ b/thirdparty/rocksdb/table/data_block_hash_index_test.cc @@ -0,0 +1,724 @@ +// Copyright (c) 2011-present, Facebook, 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). + +#include +#include +#include + +#include "db/table_properties_collector.h" +#include "rocksdb/slice.h" +#include "table/block.h" +#include "table/block_based_table_reader.h" +#include "table/block_builder.h" +#include "table/data_block_hash_index.h" +#include "table/get_context.h" +#include "table/table_builder.h" +#include "util/testharness.h" +#include "util/testutil.h" + +namespace rocksdb { + +bool SearchForOffset(DataBlockHashIndex& index, const char* data, + uint16_t map_offset, const Slice& key, + uint8_t& restart_point) { + uint8_t entry = index.Lookup(data, map_offset, key); + if (entry == kCollision) { + return true; + } + + if (entry == kNoEntry) { + return false; + } + + return entry == restart_point; +} + +// Random KV generator similer to block_test +static std::string RandomString(Random* rnd, int len) { + std::string r; + test::RandomString(rnd, len, &r); + return r; +} +std::string GenerateKey(int primary_key, int secondary_key, int padding_size, + Random* rnd) { + char buf[50]; + char* p = &buf[0]; + snprintf(buf, sizeof(buf), "%6d%4d", primary_key, secondary_key); + std::string k(p); + if (padding_size) { + k += RandomString(rnd, padding_size); + } + + return k; +} + +// Generate random key value pairs. +// The generated key will be sorted. You can tune the parameters to generated +// different kinds of test key/value pairs for different scenario. +void GenerateRandomKVs(std::vector* keys, + std::vector* values, const int from, + const int len, const int step = 1, + const int padding_size = 0, + const int keys_share_prefix = 1) { + Random rnd(302); + + // generate different prefix + for (int i = from; i < from + len; i += step) { + // generating keys that shares the prefix + for (int j = 0; j < keys_share_prefix; ++j) { + keys->emplace_back(GenerateKey(i, j, padding_size, &rnd)); + + // 100 bytes values + values->emplace_back(RandomString(&rnd, 100)); + } + } +} + +TEST(DataBlockHashIndex, DataBlockHashTestSmall) { + DataBlockHashIndexBuilder builder; + builder.Initialize(0.75 /*util_ratio*/); + for (int j = 0; j < 5; j++) { + for (uint8_t i = 0; i < 2 + j; i++) { + std::string key("key" + std::to_string(i)); + uint8_t restart_point = i; + builder.Add(key, restart_point); + } + + size_t estimated_size = builder.EstimateSize(); + + std::string buffer("fake"), buffer2; + size_t original_size = buffer.size(); + estimated_size += original_size; + builder.Finish(buffer); + + ASSERT_EQ(buffer.size(), estimated_size); + + buffer2 = buffer; // test for the correctness of relative offset + + Slice s(buffer2); + DataBlockHashIndex index; + uint16_t map_offset; + index.Initialize(s.data(), static_cast(s.size()), &map_offset); + + // the additional hash map should start at the end of the buffer + ASSERT_EQ(original_size, map_offset); + for (uint8_t i = 0; i < 2; i++) { + std::string key("key" + std::to_string(i)); + uint8_t restart_point = i; + ASSERT_TRUE( + SearchForOffset(index, s.data(), map_offset, key, restart_point)); + } + builder.Reset(); + } +} + +TEST(DataBlockHashIndex, DataBlockHashTest) { + // bucket_num = 200, #keys = 100. 50% utilization + DataBlockHashIndexBuilder builder; + builder.Initialize(0.75 /*util_ratio*/); + + for (uint8_t i = 0; i < 100; i++) { + std::string key("key" + std::to_string(i)); + uint8_t restart_point = i; + builder.Add(key, restart_point); + } + + size_t estimated_size = builder.EstimateSize(); + + std::string buffer("fake content"), buffer2; + size_t original_size = buffer.size(); + estimated_size += original_size; + builder.Finish(buffer); + + ASSERT_EQ(buffer.size(), estimated_size); + + buffer2 = buffer; // test for the correctness of relative offset + + Slice s(buffer2); + DataBlockHashIndex index; + uint16_t map_offset; + index.Initialize(s.data(), static_cast(s.size()), &map_offset); + + // the additional hash map should start at the end of the buffer + ASSERT_EQ(original_size, map_offset); + for (uint8_t i = 0; i < 100; i++) { + std::string key("key" + std::to_string(i)); + uint8_t restart_point = i; + ASSERT_TRUE( + SearchForOffset(index, s.data(), map_offset, key, restart_point)); + } +} + +TEST(DataBlockHashIndex, DataBlockHashTestCollision) { + // bucket_num = 2. There will be intense hash collisions + DataBlockHashIndexBuilder builder; + builder.Initialize(0.75 /*util_ratio*/); + + for (uint8_t i = 0; i < 100; i++) { + std::string key("key" + std::to_string(i)); + uint8_t restart_point = i; + builder.Add(key, restart_point); + } + + size_t estimated_size = builder.EstimateSize(); + + std::string buffer("some other fake content to take up space"), buffer2; + size_t original_size = buffer.size(); + estimated_size += original_size; + builder.Finish(buffer); + + ASSERT_EQ(buffer.size(), estimated_size); + + buffer2 = buffer; // test for the correctness of relative offset + + Slice s(buffer2); + DataBlockHashIndex index; + uint16_t map_offset; + index.Initialize(s.data(), static_cast(s.size()), &map_offset); + + // the additional hash map should start at the end of the buffer + ASSERT_EQ(original_size, map_offset); + for (uint8_t i = 0; i < 100; i++) { + std::string key("key" + std::to_string(i)); + uint8_t restart_point = i; + ASSERT_TRUE( + SearchForOffset(index, s.data(), map_offset, key, restart_point)); + } +} + +TEST(DataBlockHashIndex, DataBlockHashTestLarge) { + DataBlockHashIndexBuilder builder; + builder.Initialize(0.75 /*util_ratio*/); + std::unordered_map m; + + for (uint8_t i = 0; i < 100; i++) { + if (i % 2) { + continue; // leave half of the keys out + } + std::string key = "key" + std::to_string(i); + uint8_t restart_point = i; + builder.Add(key, restart_point); + m[key] = restart_point; + } + + size_t estimated_size = builder.EstimateSize(); + + std::string buffer("filling stuff"), buffer2; + size_t original_size = buffer.size(); + estimated_size += original_size; + builder.Finish(buffer); + + ASSERT_EQ(buffer.size(), estimated_size); + + buffer2 = buffer; // test for the correctness of relative offset + + Slice s(buffer2); + DataBlockHashIndex index; + uint16_t map_offset; + index.Initialize(s.data(), static_cast(s.size()), &map_offset); + + // the additional hash map should start at the end of the buffer + ASSERT_EQ(original_size, map_offset); + for (uint8_t i = 0; i < 100; i++) { + std::string key = "key" + std::to_string(i); + uint8_t restart_point = i; + if (m.count(key)) { + ASSERT_TRUE(m[key] == restart_point); + ASSERT_TRUE( + SearchForOffset(index, s.data(), map_offset, key, restart_point)); + } else { + // we allow false positve, so don't test the nonexisting keys. + // when false positive happens, the search will continue to the + // restart intervals to see if the key really exist. + } + } +} + +TEST(DataBlockHashIndex, RestartIndexExceedMax) { + DataBlockHashIndexBuilder builder; + builder.Initialize(0.75 /*util_ratio*/); + std::unordered_map m; + + for (uint8_t i = 0; i <= 253; i++) { + std::string key = "key" + std::to_string(i); + uint8_t restart_point = i; + builder.Add(key, restart_point); + } + ASSERT_TRUE(builder.Valid()); + + builder.Reset(); + + for (uint8_t i = 0; i <= 254; i++) { + std::string key = "key" + std::to_string(i); + uint8_t restart_point = i; + builder.Add(key, restart_point); + } + + ASSERT_FALSE(builder.Valid()); + + builder.Reset(); + ASSERT_TRUE(builder.Valid()); +} + +TEST(DataBlockHashIndex, BlockRestartIndexExceedMax) { + Options options = Options(); + + BlockBuilder builder(1 /* block_restart_interval */, + true /* use_delta_encoding */, + false /* use_value_delta_encoding */, + BlockBasedTableOptions::kDataBlockBinaryAndHash); + + // #restarts <= 253. HashIndex is valid + for (int i = 0; i <= 253; i++) { + std::string ukey = "key" + std::to_string(i); + InternalKey ikey(ukey, 0, kTypeValue); + builder.Add(ikey.Encode().ToString(), "value"); + } + + { + // read serialized contents of the block + Slice rawblock = builder.Finish(); + + // create block reader + BlockContents contents; + contents.data = rawblock; + Block reader(std::move(contents), kDisableGlobalSequenceNumber); + + ASSERT_EQ(reader.IndexType(), + BlockBasedTableOptions::kDataBlockBinaryAndHash); + } + + builder.Reset(); + + // #restarts > 253. HashIndex is not used + for (int i = 0; i <= 254; i++) { + std::string ukey = "key" + std::to_string(i); + InternalKey ikey(ukey, 0, kTypeValue); + builder.Add(ikey.Encode().ToString(), "value"); + } + + { + // read serialized contents of the block + Slice rawblock = builder.Finish(); + + // create block reader + BlockContents contents; + contents.data = rawblock; + Block reader(std::move(contents), kDisableGlobalSequenceNumber); + + ASSERT_EQ(reader.IndexType(), + BlockBasedTableOptions::kDataBlockBinarySearch); + } +} + +TEST(DataBlockHashIndex, BlockSizeExceedMax) { + Options options = Options(); + std::string ukey(10, 'k'); + InternalKey ikey(ukey, 0, kTypeValue); + + BlockBuilder builder(1 /* block_restart_interval */, + false /* use_delta_encoding */, + false /* use_value_delta_encoding */, + BlockBasedTableOptions::kDataBlockBinaryAndHash); + + { + // insert a large value. The block size plus HashIndex is 65536. + std::string value(65502, 'v'); + + builder.Add(ikey.Encode().ToString(), value); + + // read serialized contents of the block + Slice rawblock = builder.Finish(); + ASSERT_LE(rawblock.size(), kMaxBlockSizeSupportedByHashIndex); + std::cerr << "block size: " << rawblock.size() << std::endl; + + // create block reader + BlockContents contents; + contents.data = rawblock; + Block reader(std::move(contents), kDisableGlobalSequenceNumber); + + ASSERT_EQ(reader.IndexType(), + BlockBasedTableOptions::kDataBlockBinaryAndHash); + } + + builder.Reset(); + + { + // insert a large value. The block size plus HashIndex would be 65537. + // This excceed the max block size supported by HashIndex (65536). + // So when build finishes HashIndex will not be created for the block. + std::string value(65503, 'v'); + + builder.Add(ikey.Encode().ToString(), value); + + // read serialized contents of the block + Slice rawblock = builder.Finish(); + ASSERT_LE(rawblock.size(), kMaxBlockSizeSupportedByHashIndex); + std::cerr << "block size: " << rawblock.size() << std::endl; + + // create block reader + BlockContents contents; + contents.data = rawblock; + Block reader(std::move(contents), kDisableGlobalSequenceNumber); + + // the index type have fallen back to binary when build finish. + ASSERT_EQ(reader.IndexType(), + BlockBasedTableOptions::kDataBlockBinarySearch); + } +} + +TEST(DataBlockHashIndex, BlockTestSingleKey) { + Options options = Options(); + + BlockBuilder builder(16 /* block_restart_interval */, + true /* use_delta_encoding */, + false /* use_value_delta_encoding */, + BlockBasedTableOptions::kDataBlockBinaryAndHash); + + std::string ukey("gopher"); + std::string value("gold"); + InternalKey ikey(ukey, 10, kTypeValue); + builder.Add(ikey.Encode().ToString(), value /*value*/); + + // read serialized contents of the block + Slice rawblock = builder.Finish(); + + // create block reader + BlockContents contents; + contents.data = rawblock; + Block reader(std::move(contents), kDisableGlobalSequenceNumber); + + const InternalKeyComparator icmp(BytewiseComparator()); + auto iter = reader.NewIterator(&icmp, icmp.user_comparator()); + bool may_exist; + // search in block for the key just inserted + { + InternalKey seek_ikey(ukey, 10, kValueTypeForSeek); + may_exist = iter->SeekForGet(seek_ikey.Encode().ToString()); + ASSERT_TRUE(may_exist); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ( + options.comparator->Compare(iter->key(), ikey.Encode().ToString()), 0); + ASSERT_EQ(iter->value(), value); + } + + // search in block for the existing ukey, but with higher seqno + { + InternalKey seek_ikey(ukey, 20, kValueTypeForSeek); + + // HashIndex should be able to set the iter correctly + may_exist = iter->SeekForGet(seek_ikey.Encode().ToString()); + ASSERT_TRUE(may_exist); + ASSERT_TRUE(iter->Valid()); + + // user key should match + ASSERT_EQ(options.comparator->Compare(ExtractUserKey(iter->key()), ukey), + 0); + + // seek_key seqno number should be greater than that of iter result + ASSERT_GT(GetInternalKeySeqno(seek_ikey.Encode()), + GetInternalKeySeqno(iter->key())); + + ASSERT_EQ(iter->value(), value); + } + + // Search in block for the existing ukey, but with lower seqno + // in this case, hash can find the only occurrence of the user_key, but + // ParseNextDataKey() will skip it as it does not have a older seqno. + // In this case, GetForSeek() is effective to locate the user_key, and + // iter->Valid() == false indicates that we've reached to the end of + // the block and the caller should continue searching the next block. + { + InternalKey seek_ikey(ukey, 5, kValueTypeForSeek); + may_exist = iter->SeekForGet(seek_ikey.Encode().ToString()); + ASSERT_TRUE(may_exist); + ASSERT_FALSE(iter->Valid()); // should have reached to the end of block + } + + delete iter; +} + +TEST(DataBlockHashIndex, BlockTestLarge) { + Random rnd(1019); + Options options = Options(); + std::vector keys; + std::vector values; + + BlockBuilder builder(16 /* block_restart_interval */, + true /* use_delta_encoding */, + false /* use_value_delta_encoding */, + BlockBasedTableOptions::kDataBlockBinaryAndHash); + int num_records = 500; + + GenerateRandomKVs(&keys, &values, 0, num_records); + + // Generate keys. Adding a trailing "1" to indicate existent keys. + // Later will Seeking for keys with a trailing "0" to test seeking + // non-existent keys. + for (int i = 0; i < num_records; i++) { + std::string ukey(keys[i] + "1" /* existing key marker */); + InternalKey ikey(ukey, 0, kTypeValue); + builder.Add(ikey.Encode().ToString(), values[i]); + } + + // read serialized contents of the block + Slice rawblock = builder.Finish(); + + // create block reader + BlockContents contents; + contents.data = rawblock; + Block reader(std::move(contents), kDisableGlobalSequenceNumber); + const InternalKeyComparator icmp(BytewiseComparator()); + + // random seek existent keys + for (int i = 0; i < num_records; i++) { + auto iter = + reader.NewIterator(&icmp, icmp.user_comparator()); + // find a random key in the lookaside array + int index = rnd.Uniform(num_records); + std::string ukey(keys[index] + "1" /* existing key marker */); + InternalKey ikey(ukey, 0, kTypeValue); + + // search in block for this key + bool may_exist = iter->SeekForGet(ikey.Encode().ToString()); + ASSERT_TRUE(may_exist); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(values[index], iter->value()); + + delete iter; + } + + // random seek non-existent user keys + // In this case A), the user_key cannot be found in HashIndex. The key may + // exist in the next block. So the iter is set invalidated to tell the + // caller to search the next block. This test case belongs to this case A). + // + // Note that for non-existent keys, there is possibility of false positive, + // i.e. the key is still hashed into some restart interval. + // Two additional possible outcome: + // B) linear seek the restart interval and not found, the iter stops at the + // starting of the next restart interval. The key does not exist + // anywhere. + // C) linear seek the restart interval and not found, the iter stops at the + // the end of the block, i.e. restarts_. The key may exist in the next + // block. + // So these combinations are possible when searching non-existent user_key: + // + // case# may_exist iter->Valid() + // A true false + // B false true + // C true false + + for (int i = 0; i < num_records; i++) { + auto iter = + reader.NewIterator(&icmp, icmp.user_comparator()); + // find a random key in the lookaside array + int index = rnd.Uniform(num_records); + std::string ukey(keys[index] + "0" /* non-existing key marker */); + InternalKey ikey(ukey, 0, kTypeValue); + + // search in block for this key + bool may_exist = iter->SeekForGet(ikey.Encode().ToString()); + if (!may_exist) { + ASSERT_TRUE(iter->Valid()); + } + if (!iter->Valid()) { + ASSERT_TRUE(may_exist); + } + + delete iter; + } +} + +// helper routine for DataBlockHashIndex.BlockBoundary +void TestBoundary(InternalKey& ik1, std::string& v1, InternalKey& ik2, + std::string& v2, InternalKey& seek_ikey, + GetContext& get_context, Options& options) { + std::unique_ptr file_writer; + std::unique_ptr file_reader; + std::unique_ptr table_reader; + int level_ = -1; + + std::vector keys; + const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); + const InternalKeyComparator internal_comparator(options.comparator); + + EnvOptions soptions; + + soptions.use_mmap_reads = ioptions.allow_mmap_reads; + file_writer.reset( + test::GetWritableFileWriter(new test::StringSink(), "" /* don't care */)); + std::unique_ptr builder; + std::vector> + int_tbl_prop_collector_factories; + std::string column_family_name; + builder.reset(ioptions.table_factory->NewTableBuilder( + TableBuilderOptions(ioptions, moptions, internal_comparator, + &int_tbl_prop_collector_factories, + options.compression, options.sample_for_compression, + CompressionOptions(), false /* skip_filters */, + column_family_name, level_), + TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, + file_writer.get())); + + builder->Add(ik1.Encode().ToString(), v1); + builder->Add(ik2.Encode().ToString(), v2); + EXPECT_TRUE(builder->status().ok()); + + Status s = builder->Finish(); + file_writer->Flush(); + EXPECT_TRUE(s.ok()) << s.ToString(); + + EXPECT_EQ(static_cast(file_writer->writable_file()) + ->contents() + .size(), + builder->FileSize()); + + // Open the table + file_reader.reset(test::GetRandomAccessFileReader(new test::StringSource( + static_cast(file_writer->writable_file())->contents(), + 0 /*uniq_id*/, ioptions.allow_mmap_reads))); + const bool kSkipFilters = true; + const bool kImmortal = true; + ioptions.table_factory->NewTableReader( + TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions, + internal_comparator, !kSkipFilters, !kImmortal, + level_), + std::move(file_reader), + static_cast(file_writer->writable_file()) + ->contents() + .size(), + &table_reader); + // Search using Get() + ReadOptions ro; + + ASSERT_OK(table_reader->Get(ro, seek_ikey.Encode().ToString(), &get_context, + moptions.prefix_extractor.get())); +} + +TEST(DataBlockHashIndex, BlockBoundary) { + BlockBasedTableOptions table_options; + table_options.data_block_index_type = + BlockBasedTableOptions::kDataBlockBinaryAndHash; + table_options.block_restart_interval = 1; + table_options.block_size = 4096; + + Options options; + options.comparator = BytewiseComparator(); + + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + + // insert two large k/v pair. Given that the block_size is 4096, one k/v + // pair will take up one block. + // [ k1/v1 ][ k2/v2 ] + // [ Block N ][ Block N+1 ] + + { + // [ "aab"@100 ][ "axy"@10 ] + // | Block N ][ Block N+1 ] + // seek for "axy"@60 + std::string uk1("aab"); + InternalKey ik1(uk1, 100, kTypeValue); + std::string v1(4100, '1'); // large value + + std::string uk2("axy"); + InternalKey ik2(uk2, 10, kTypeValue); + std::string v2(4100, '2'); // large value + + PinnableSlice value; + std::string seek_ukey("axy"); + InternalKey seek_ikey(seek_ukey, 60, kTypeValue); + GetContext get_context(options.comparator, nullptr, nullptr, nullptr, + GetContext::kNotFound, seek_ukey, &value, nullptr, + nullptr, nullptr, nullptr); + + TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options); + ASSERT_EQ(get_context.State(), GetContext::kFound); + ASSERT_EQ(value, v2); + value.Reset(); + } + + { + // [ "axy"@100 ][ "axy"@10 ] + // | Block N ][ Block N+1 ] + // seek for "axy"@60 + std::string uk1("axy"); + InternalKey ik1(uk1, 100, kTypeValue); + std::string v1(4100, '1'); // large value + + std::string uk2("axy"); + InternalKey ik2(uk2, 10, kTypeValue); + std::string v2(4100, '2'); // large value + + PinnableSlice value; + std::string seek_ukey("axy"); + InternalKey seek_ikey(seek_ukey, 60, kTypeValue); + GetContext get_context(options.comparator, nullptr, nullptr, nullptr, + GetContext::kNotFound, seek_ukey, &value, nullptr, + nullptr, nullptr, nullptr); + + TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options); + ASSERT_EQ(get_context.State(), GetContext::kFound); + ASSERT_EQ(value, v2); + value.Reset(); + } + + { + // [ "axy"@100 ][ "axy"@10 ] + // | Block N ][ Block N+1 ] + // seek for "axy"@120 + std::string uk1("axy"); + InternalKey ik1(uk1, 100, kTypeValue); + std::string v1(4100, '1'); // large value + + std::string uk2("axy"); + InternalKey ik2(uk2, 10, kTypeValue); + std::string v2(4100, '2'); // large value + + PinnableSlice value; + std::string seek_ukey("axy"); + InternalKey seek_ikey(seek_ukey, 120, kTypeValue); + GetContext get_context(options.comparator, nullptr, nullptr, nullptr, + GetContext::kNotFound, seek_ukey, &value, nullptr, + nullptr, nullptr, nullptr); + + TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options); + ASSERT_EQ(get_context.State(), GetContext::kFound); + ASSERT_EQ(value, v1); + value.Reset(); + } + + { + // [ "axy"@100 ][ "axy"@10 ] + // | Block N ][ Block N+1 ] + // seek for "axy"@5 + std::string uk1("axy"); + InternalKey ik1(uk1, 100, kTypeValue); + std::string v1(4100, '1'); // large value + + std::string uk2("axy"); + InternalKey ik2(uk2, 10, kTypeValue); + std::string v2(4100, '2'); // large value + + PinnableSlice value; + std::string seek_ukey("axy"); + InternalKey seek_ikey(seek_ukey, 5, kTypeValue); + GetContext get_context(options.comparator, nullptr, nullptr, nullptr, + GetContext::kNotFound, seek_ukey, &value, nullptr, + nullptr, nullptr, nullptr); + + TestBoundary(ik1, v1, ik2, v2, seek_ikey, get_context, options); + ASSERT_EQ(get_context.State(), GetContext::kNotFound); + value.Reset(); + } +} + +} // namespace rocksdb + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/thirdparty/rocksdb/table/filter_block.h b/thirdparty/rocksdb/table/filter_block.h index 7bf3b31324..a930495479 100644 --- a/thirdparty/rocksdb/table/filter_block.h +++ b/thirdparty/rocksdb/table/filter_block.h @@ -51,6 +51,7 @@ class FilterBlockBuilder { virtual bool IsBlockBased() = 0; // If is blockbased filter virtual void StartBlock(uint64_t block_offset) = 0; // Start new block filter virtual void Add(const Slice& key) = 0; // Add a key to current filter + virtual size_t NumAdded() const = 0; // Number of keys added Slice Finish() { // Generate Filter const BlockHandle empty_handle; Status dont_care_status; @@ -92,16 +93,21 @@ class FilterBlockReader { * built upon InternalKey and must be provided via const_ikey_ptr when running * queries. */ - virtual bool KeyMayMatch(const Slice& key, uint64_t block_offset = kNotValid, + virtual bool KeyMayMatch(const Slice& key, + const SliceTransform* prefix_extractor, + uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) = 0; + /** * no_io and const_ikey_ptr here means the same as in KeyMayMatch */ virtual bool PrefixMayMatch(const Slice& prefix, + const SliceTransform* prefix_extractor, uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) = 0; + virtual size_t ApproximateMemoryUsage() const = 0; virtual size_t size() const { return size_; } virtual Statistics* statistics() const { return statistics_; } @@ -114,7 +120,19 @@ class FilterBlockReader { return error_msg; } - virtual void CacheDependencies(bool pin) {} + virtual void CacheDependencies(bool /*pin*/, + const SliceTransform* /*prefix_extractor*/) {} + + virtual bool RangeMayExist( + const Slice* /*iterate_upper_bound*/, const Slice& user_key, + const SliceTransform* prefix_extractor, + const Comparator* /*comparator*/, const Slice* const const_ikey_ptr, + bool* filter_checked, bool /*need_upper_bound_check*/) { + *filter_checked = true; + Slice prefix = prefix_extractor->Transform(user_key); + return PrefixMayMatch(prefix, prefix_extractor, kNotValid, false, + const_ikey_ptr); + } protected: bool whole_key_filtering_; diff --git a/thirdparty/rocksdb/table/flush_block_policy.cc b/thirdparty/rocksdb/table/flush_block_policy.cc index 9a8dea4cb0..1b1675828d 100644 --- a/thirdparty/rocksdb/table/flush_block_policy.cc +++ b/thirdparty/rocksdb/table/flush_block_policy.cc @@ -3,10 +3,11 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -#include "rocksdb/options.h" #include "rocksdb/flush_block_policy.h" +#include "rocksdb/options.h" #include "rocksdb/slice.h" #include "table/block_builder.h" +#include "table/format.h" #include @@ -21,14 +22,15 @@ class FlushBlockBySizePolicy : public FlushBlockPolicy { // reaches the configured FlushBlockBySizePolicy(const uint64_t block_size, const uint64_t block_size_deviation, + const bool align, const BlockBuilder& data_block_builder) : block_size_(block_size), block_size_deviation_limit_( ((block_size * (100 - block_size_deviation)) + 99) / 100), + align_(align), data_block_builder_(data_block_builder) {} - virtual bool Update(const Slice& key, - const Slice& value) override { + bool Update(const Slice& key, const Slice& value) override { // it makes no sense to flush when the data block is empty if (data_block_builder_.empty()) { return false; @@ -51,8 +53,13 @@ class FlushBlockBySizePolicy : public FlushBlockPolicy { } const auto curr_size = data_block_builder_.CurrentSizeEstimate(); - const auto estimated_size_after = - data_block_builder_.EstimateSizeAfterKV(key, value); + auto estimated_size_after = + data_block_builder_.EstimateSizeAfterKV(key, value); + + if (align_) { + estimated_size_after += kBlockTrailerSize; + return estimated_size_after > block_size_; + } return estimated_size_after > block_size_ && curr_size > block_size_deviation_limit_; @@ -60,6 +67,7 @@ class FlushBlockBySizePolicy : public FlushBlockPolicy { const uint64_t block_size_; const uint64_t block_size_deviation_limit_; + const bool align_; const BlockBuilder& data_block_builder_; }; @@ -68,13 +76,13 @@ FlushBlockPolicy* FlushBlockBySizePolicyFactory::NewFlushBlockPolicy( const BlockBuilder& data_block_builder) const { return new FlushBlockBySizePolicy( table_options.block_size, table_options.block_size_deviation, - data_block_builder); + table_options.block_align, data_block_builder); } FlushBlockPolicy* FlushBlockBySizePolicyFactory::NewFlushBlockPolicy( const uint64_t size, const int deviation, const BlockBuilder& data_block_builder) { - return new FlushBlockBySizePolicy(size, deviation, data_block_builder); + return new FlushBlockBySizePolicy(size, deviation, false, data_block_builder); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/format.cc b/thirdparty/rocksdb/table/format.cc index 364766e9a8..476db85f73 100644 --- a/thirdparty/rocksdb/table/format.cc +++ b/thirdparty/rocksdb/table/format.cc @@ -9,20 +9,22 @@ #include "table/format.h" -#include #include +#include #include "monitoring/perf_context_imp.h" #include "monitoring/statistics.h" #include "rocksdb/env.h" #include "table/block.h" #include "table/block_based_table_reader.h" +#include "table/block_fetcher.h" #include "table/persistent_cache_helper.h" #include "util/coding.h" #include "util/compression.h" #include "util/crc32c.h" #include "util/file_reader_writer.h" #include "util/logging.h" +#include "util/memory_allocator.h" #include "util/stop_watch.h" #include "util/string_util.h" #include "util/xxhash.h" @@ -40,11 +42,10 @@ extern const uint64_t kPlainTableMagicNumber; const uint64_t kLegacyPlainTableMagicNumber = 0; const uint64_t kPlainTableMagicNumber = 0; #endif -const uint32_t DefaultStackBufferSize = 5000; bool ShouldReportDetailedTime(Env* env, Statistics* stats) { return env != nullptr && stats != nullptr && - stats->stats_level_ > kExceptDetailedTimers; + stats->get_stats_level() > kExceptDetailedTimers; } void BlockHandle::EncodeTo(std::string* dst) const { @@ -55,8 +56,19 @@ void BlockHandle::EncodeTo(std::string* dst) const { } Status BlockHandle::DecodeFrom(Slice* input) { - if (GetVarint64(input, &offset_) && - GetVarint64(input, &size_)) { + if (GetVarint64(input, &offset_) && GetVarint64(input, &size_)) { + return Status::OK(); + } else { + // reset in case failure after partially decoding + offset_ = 0; + size_ = 0; + return Status::Corruption("bad block handle"); + } +} + +Status BlockHandle::DecodeSizeFrom(uint64_t _offset, Slice* input) { + if (GetVarint64(input, &size_)) { + offset_ = _offset; return Status::OK(); } else { // reset in case failure after partially decoding @@ -146,7 +158,7 @@ Status Footer::DecodeFrom(Slice* input) { assert(input != nullptr); assert(input->size() >= kMinEncodedLength); - const char *magic_ptr = + const char* magic_ptr = input->data() + input->size() - kMagicNumberLengthByte; const uint32_t magic_lo = DecodeFixed32(magic_ptr); const uint32_t magic_hi = DecodeFixed32(magic_ptr + 4); @@ -196,7 +208,7 @@ Status Footer::DecodeFrom(Slice* input) { } std::string Footer::ToString() const { - std::string result, handle_; + std::string result; result.reserve(1024); bool legacy = IsLegacyFooterFormat(table_magic_number_); @@ -221,9 +233,10 @@ Status ReadFooterFromFile(RandomAccessFileReader* file, uint64_t file_size, Footer* footer, uint64_t enforce_table_magic_number) { if (file_size < Footer::kMinEncodedLength) { - return Status::Corruption( - "file is too short (" + ToString(file_size) + " bytes) to be an " - "sstable: " + file->file_name()); + return Status::Corruption("file is too short (" + ToString(file_size) + + " bytes) to be an " + "sstable: " + + file->file_name()); } char footer_space[Footer::kMaxEncodedLength]; @@ -244,9 +257,10 @@ Status ReadFooterFromFile(RandomAccessFileReader* file, // Check that we actually read the whole footer from the file. It may be // that size isn't correct. if (footer_input.size() < Footer::kMinEncodedLength) { - return Status::Corruption( - "file is too short (" + ToString(file_size) + " bytes) to be an " - "sstable" + file->file_name()); + return Status::Corruption("file is too short (" + ToString(file_size) + + " bytes) to be an " + "sstable" + + file->file_name()); } s = footer->DecodeFrom(&footer_input); @@ -256,321 +270,122 @@ Status ReadFooterFromFile(RandomAccessFileReader* file, if (enforce_table_magic_number != 0 && enforce_table_magic_number != footer->table_magic_number()) { return Status::Corruption( - "Bad table magic number: expected " - + ToString(enforce_table_magic_number) + ", found " - + ToString(footer->table_magic_number()) - + " in " + file->file_name()); + "Bad table magic number: expected " + + ToString(enforce_table_magic_number) + ", found " + + ToString(footer->table_magic_number()) + " in " + file->file_name()); } return Status::OK(); } -// Without anonymous namespace here, we fail the warning -Wmissing-prototypes -namespace { -Status CheckBlockChecksum(const ReadOptions& options, const Footer& footer, - const Slice& contents, size_t block_size, - RandomAccessFileReader* file, - const BlockHandle& handle) { - Status s; - // Check the crc of the type and the block contents - if (options.verify_checksums) { - const char* data = contents.data(); // Pointer to where Read put the data - PERF_TIMER_GUARD(block_checksum_time); - uint32_t value = DecodeFixed32(data + block_size + 1); - uint32_t actual = 0; - switch (footer.checksum()) { - case kNoChecksum: - break; - case kCRC32c: - value = crc32c::Unmask(value); - actual = crc32c::Value(data, block_size + 1); - break; - case kxxHash: - actual = XXH32(data, static_cast(block_size) + 1, 0); - break; - default: - s = Status::Corruption( - "unknown checksum type " + ToString(footer.checksum()) + " in " + - file->file_name() + " offset " + ToString(handle.offset()) + - " size " + ToString(block_size)); - } - if (s.ok() && actual != value) { - s = Status::Corruption( - "block checksum mismatch: expected " + ToString(actual) + ", got " + - ToString(value) + " in " + file->file_name() + " offset " + - ToString(handle.offset()) + " size " + ToString(block_size)); - } - if (!s.ok()) { - return s; - } - } - return s; -} - -// Read a block and check its CRC -// contents is the result of reading. -// According to the implementation of file->Read, contents may not point to buf -Status ReadBlock(RandomAccessFileReader* file, const Footer& footer, - const ReadOptions& options, const BlockHandle& handle, - Slice* contents, /* result of reading */ char* buf) { - size_t n = static_cast(handle.size()); - Status s; - - { - PERF_TIMER_GUARD(block_read_time); - s = file->Read(handle.offset(), n + kBlockTrailerSize, contents, buf); - } - - PERF_COUNTER_ADD(block_read_count, 1); - PERF_COUNTER_ADD(block_read_byte, n + kBlockTrailerSize); - - if (!s.ok()) { - return s; - } - if (contents->size() != n + kBlockTrailerSize) { - return Status::Corruption("truncated block read from " + file->file_name() + - " offset " + ToString(handle.offset()) + - ", expected " + ToString(n + kBlockTrailerSize) + - " bytes, got " + ToString(contents->size())); - } - return CheckBlockChecksum(options, footer, *contents, n, file, handle); -} - -} // namespace - -Status ReadBlockContents(RandomAccessFileReader* file, - FilePrefetchBuffer* prefetch_buffer, - const Footer& footer, const ReadOptions& read_options, - const BlockHandle& handle, BlockContents* contents, - const ImmutableCFOptions& ioptions, - bool decompression_requested, - const Slice& compression_dict, - const PersistentCacheOptions& cache_options) { - Status status; - Slice slice; - size_t n = static_cast(handle.size()); - std::unique_ptr heap_buf; - char stack_buf[DefaultStackBufferSize]; - char* used_buf = nullptr; - rocksdb::CompressionType compression_type; - - if (cache_options.persistent_cache && - !cache_options.persistent_cache->IsCompressed()) { - status = PersistentCacheHelper::LookupUncompressedPage(cache_options, - handle, contents); - if (status.ok()) { - // uncompressed page is found for the block handle - return status; - } else { - // uncompressed page is not found - if (ioptions.info_log && !status.IsNotFound()) { - assert(!status.ok()); - ROCKS_LOG_INFO(ioptions.info_log, - "Error reading from persistent cache. %s", - status.ToString().c_str()); - } - } - } - - bool got_from_prefetch_buffer = false; - if (prefetch_buffer != nullptr && - prefetch_buffer->TryReadFromCache( - handle.offset(), - static_cast(handle.size()) + kBlockTrailerSize, &slice)) { - status = - CheckBlockChecksum(read_options, footer, slice, - static_cast(handle.size()), file, handle); - if (!status.ok()) { - return status; - } - got_from_prefetch_buffer = true; - used_buf = const_cast(slice.data()); - } else if (cache_options.persistent_cache && - cache_options.persistent_cache->IsCompressed()) { - // lookup uncompressed cache mode p-cache - status = PersistentCacheHelper::LookupRawPage( - cache_options, handle, &heap_buf, n + kBlockTrailerSize); - } else { - status = Status::NotFound(); - } - - if (!got_from_prefetch_buffer) { - if (status.ok()) { - // cache hit - used_buf = heap_buf.get(); - slice = Slice(heap_buf.get(), n); - } else { - if (ioptions.info_log && !status.IsNotFound()) { - assert(!status.ok()); - ROCKS_LOG_INFO(ioptions.info_log, - "Error reading from persistent cache. %s", - status.ToString().c_str()); - } - // cache miss read from device - if (decompression_requested && - n + kBlockTrailerSize < DefaultStackBufferSize) { - // If we've got a small enough hunk of data, read it in to the - // trivially allocated stack buffer instead of needing a full malloc() - used_buf = &stack_buf[0]; - } else { - heap_buf = std::unique_ptr(new char[n + kBlockTrailerSize]); - used_buf = heap_buf.get(); - } - - status = ReadBlock(file, footer, read_options, handle, &slice, used_buf); - if (status.ok() && read_options.fill_cache && - cache_options.persistent_cache && - cache_options.persistent_cache->IsCompressed()) { - // insert to raw cache - PersistentCacheHelper::InsertRawPage(cache_options, handle, used_buf, - n + kBlockTrailerSize); - } - } - - if (!status.ok()) { - return status; - } - } - - PERF_TIMER_GUARD(block_decompress_time); - - compression_type = static_cast(slice.data()[n]); - - if (decompression_requested && compression_type != kNoCompression) { - // compressed page, uncompress, update cache - status = UncompressBlockContents(slice.data(), n, contents, - footer.version(), compression_dict, - ioptions); - } else if (slice.data() != used_buf) { - // the slice content is not the buffer provided - *contents = BlockContents(Slice(slice.data(), n), false, compression_type); - } else { - // page is uncompressed, the buffer either stack or heap provided - if (got_from_prefetch_buffer || used_buf == &stack_buf[0]) { - heap_buf = std::unique_ptr(new char[n]); - memcpy(heap_buf.get(), used_buf, n); - } - *contents = BlockContents(std::move(heap_buf), n, true, compression_type); - } - - if (status.ok() && !got_from_prefetch_buffer && read_options.fill_cache && - cache_options.persistent_cache && - !cache_options.persistent_cache->IsCompressed()) { - // insert to uncompressed cache - PersistentCacheHelper::InsertUncompressedPage(cache_options, handle, - *contents); - } - - return status; -} - Status UncompressBlockContentsForCompressionType( - const char* data, size_t n, BlockContents* contents, - uint32_t format_version, const Slice& compression_dict, - CompressionType compression_type, const ImmutableCFOptions &ioptions) { - std::unique_ptr ubuf; + const UncompressionInfo& uncompression_info, const char* data, size_t n, + BlockContents* contents, uint32_t format_version, + const ImmutableCFOptions& ioptions, MemoryAllocator* allocator) { + CacheAllocationPtr ubuf; - assert(compression_type != kNoCompression && "Invalid compression type"); + assert(uncompression_info.type() != kNoCompression && + "Invalid compression type"); - StopWatchNano timer(ioptions.env, - ShouldReportDetailedTime(ioptions.env, ioptions.statistics)); + StopWatchNano timer(ioptions.env, ShouldReportDetailedTime( + ioptions.env, ioptions.statistics)); int decompress_size = 0; - switch (compression_type) { + switch (uncompression_info.type()) { case kSnappyCompression: { size_t ulength = 0; static char snappy_corrupt_msg[] = - "Snappy not supported or corrupted Snappy compressed block contents"; + "Snappy not supported or corrupted Snappy compressed block contents"; if (!Snappy_GetUncompressedLength(data, n, &ulength)) { return Status::Corruption(snappy_corrupt_msg); } - ubuf.reset(new char[ulength]); + ubuf = AllocateBlock(ulength, allocator); if (!Snappy_Uncompress(data, n, ubuf.get())) { return Status::Corruption(snappy_corrupt_msg); } - *contents = BlockContents(std::move(ubuf), ulength, true, kNoCompression); + *contents = BlockContents(std::move(ubuf), ulength); break; } case kZlibCompression: - ubuf.reset(Zlib_Uncompress( - data, n, &decompress_size, + ubuf = Zlib_Uncompress( + uncompression_info, data, n, &decompress_size, GetCompressFormatForVersion(kZlibCompression, format_version), - compression_dict)); + allocator); if (!ubuf) { static char zlib_corrupt_msg[] = - "Zlib not supported or corrupted Zlib compressed block contents"; + "Zlib not supported or corrupted Zlib compressed block contents"; return Status::Corruption(zlib_corrupt_msg); } - *contents = - BlockContents(std::move(ubuf), decompress_size, true, kNoCompression); + *contents = BlockContents(std::move(ubuf), decompress_size); break; case kBZip2Compression: - ubuf.reset(BZip2_Uncompress( + ubuf = BZip2_Uncompress( data, n, &decompress_size, - GetCompressFormatForVersion(kBZip2Compression, format_version))); + GetCompressFormatForVersion(kBZip2Compression, format_version), + allocator); if (!ubuf) { static char bzip2_corrupt_msg[] = - "Bzip2 not supported or corrupted Bzip2 compressed block contents"; + "Bzip2 not supported or corrupted Bzip2 compressed block contents"; return Status::Corruption(bzip2_corrupt_msg); } - *contents = - BlockContents(std::move(ubuf), decompress_size, true, kNoCompression); + *contents = BlockContents(std::move(ubuf), decompress_size); break; case kLZ4Compression: - ubuf.reset(LZ4_Uncompress( - data, n, &decompress_size, + ubuf = LZ4_Uncompress( + uncompression_info, data, n, &decompress_size, GetCompressFormatForVersion(kLZ4Compression, format_version), - compression_dict)); + allocator); if (!ubuf) { static char lz4_corrupt_msg[] = - "LZ4 not supported or corrupted LZ4 compressed block contents"; + "LZ4 not supported or corrupted LZ4 compressed block contents"; return Status::Corruption(lz4_corrupt_msg); } - *contents = - BlockContents(std::move(ubuf), decompress_size, true, kNoCompression); + *contents = BlockContents(std::move(ubuf), decompress_size); break; case kLZ4HCCompression: - ubuf.reset(LZ4_Uncompress( - data, n, &decompress_size, + ubuf = LZ4_Uncompress( + uncompression_info, data, n, &decompress_size, GetCompressFormatForVersion(kLZ4HCCompression, format_version), - compression_dict)); + allocator); if (!ubuf) { static char lz4hc_corrupt_msg[] = - "LZ4HC not supported or corrupted LZ4HC compressed block contents"; + "LZ4HC not supported or corrupted LZ4HC compressed block contents"; return Status::Corruption(lz4hc_corrupt_msg); } - *contents = - BlockContents(std::move(ubuf), decompress_size, true, kNoCompression); + *contents = BlockContents(std::move(ubuf), decompress_size); break; case kXpressCompression: + // XPRESS allocates memory internally, thus no support for custom + // allocator. ubuf.reset(XPRESS_Uncompress(data, n, &decompress_size)); if (!ubuf) { static char xpress_corrupt_msg[] = - "XPRESS not supported or corrupted XPRESS compressed block contents"; + "XPRESS not supported or corrupted XPRESS compressed block " + "contents"; return Status::Corruption(xpress_corrupt_msg); } - *contents = - BlockContents(std::move(ubuf), decompress_size, true, kNoCompression); + *contents = BlockContents(std::move(ubuf), decompress_size); break; case kZSTD: case kZSTDNotFinalCompression: - ubuf.reset(ZSTD_Uncompress(data, n, &decompress_size, compression_dict)); + ubuf = ZSTD_Uncompress(uncompression_info, data, n, &decompress_size, + allocator); if (!ubuf) { static char zstd_corrupt_msg[] = "ZSTD not supported or corrupted ZSTD compressed block contents"; return Status::Corruption(zstd_corrupt_msg); } - *contents = - BlockContents(std::move(ubuf), decompress_size, true, kNoCompression); + *contents = BlockContents(std::move(ubuf), decompress_size); break; default: return Status::Corruption("bad block type"); } - if(ShouldReportDetailedTime(ioptions.env, ioptions.statistics)){ - MeasureTime(ioptions.statistics, DECOMPRESSION_TIMES_NANOS, - timer.ElapsedNanos()); - MeasureTime(ioptions.statistics, BYTES_DECOMPRESSED, contents->data.size()); - RecordTick(ioptions.statistics, NUMBER_BLOCK_DECOMPRESSED); + if (ShouldReportDetailedTime(ioptions.env, ioptions.statistics)) { + RecordTimeToHistogram(ioptions.statistics, DECOMPRESSION_TIMES_NANOS, + timer.ElapsedNanos()); } + RecordTimeToHistogram(ioptions.statistics, BYTES_DECOMPRESSED, + contents->data.size()); + RecordTick(ioptions.statistics, NUMBER_BLOCK_DECOMPRESSED); return Status::OK(); } @@ -582,14 +397,16 @@ Status UncompressBlockContentsForCompressionType( // buffer is returned via 'result' and it is upto the caller to // free this buffer. // format_version is the block format as defined in include/rocksdb/table.h -Status UncompressBlockContents(const char* data, size_t n, +Status UncompressBlockContents(const UncompressionInfo& uncompression_info, + const char* data, size_t n, BlockContents* contents, uint32_t format_version, - const Slice& compression_dict, - const ImmutableCFOptions &ioptions) { + const ImmutableCFOptions& ioptions, + MemoryAllocator* allocator) { assert(data[n] != kNoCompression); - return UncompressBlockContentsForCompressionType( - data, n, contents, format_version, compression_dict, - (CompressionType)data[n], ioptions); + assert(data[n] == uncompression_info.type()); + return UncompressBlockContentsForCompressionType(uncompression_info, data, n, + contents, format_version, + ioptions, allocator); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/format.h b/thirdparty/rocksdb/table/format.h index 512b4a32bf..f585885055 100644 --- a/thirdparty/rocksdb/table/format.h +++ b/thirdparty/rocksdb/table/format.h @@ -8,21 +8,28 @@ // found in the LICENSE file. See the AUTHORS file for names of contributors. #pragma once -#include #include +#include +#ifdef ROCKSDB_MALLOC_USABLE_SIZE +#ifdef OS_FREEBSD +#include +#else +#include +#endif +#endif +#include "rocksdb/options.h" #include "rocksdb/slice.h" #include "rocksdb/status.h" -#include "rocksdb/options.h" #include "rocksdb/table.h" #include "options/cf_options.h" #include "port/port.h" // noexcept #include "table/persistent_cache_options.h" #include "util/file_reader_writer.h" +#include "util/memory_allocator.h" namespace rocksdb { -class Block; class RandomAccessFile; struct ReadOptions; @@ -48,19 +55,16 @@ class BlockHandle { void EncodeTo(std::string* dst) const; Status DecodeFrom(Slice* input); + Status DecodeSizeFrom(uint64_t offset, Slice* input); // Return a string that contains the copy of handle. std::string ToString(bool hex = true) const; // if the block handle's offset and size are both "0", we will view it // as a null block handle that points to no where. - bool IsNull() const { - return offset_ == 0 && size_ == 0; - } + bool IsNull() const { return offset_ == 0 && size_ == 0; } - static const BlockHandle& NullBlockHandle() { - return kNullBlockHandle; - } + static const BlockHandle& NullBlockHandle() { return kNullBlockHandle; } // Maximum encoding length of a BlockHandle enum { kMaxEncodedLength = 10 + 10 }; @@ -74,6 +78,9 @@ class BlockHandle { inline uint32_t GetCompressFormatForVersion(CompressionType compression_type, uint32_t version) { +#ifdef NDEBUG + (void)compression_type; +#endif // snappy is not versioned assert(compression_type != kSnappyCompression && compression_type != kXpressCompression && @@ -85,7 +92,7 @@ inline uint32_t GetCompressFormatForVersion(CompressionType compression_type, } inline bool BlockBasedTableSupportedVersion(uint32_t version) { - return version <= 2; + return version <= 4; } // Footer encapsulates the fixed information stored at the tail @@ -182,32 +189,74 @@ Status ReadFooterFromFile(RandomAccessFileReader* file, // 1-byte type + 32-bit crc static const size_t kBlockTrailerSize = 5; +inline CompressionType get_block_compression_type(const char* block_data, + size_t block_size) { + return static_cast(block_data[block_size]); +} + struct BlockContents { - Slice data; // Actual contents of data - bool cachable; // True iff data can be cached - CompressionType compression_type; - std::unique_ptr allocation; + Slice data; // Actual contents of data + CacheAllocationPtr allocation; + +#ifndef NDEBUG + // Whether the block is a raw block, which contains compression type + // byte. It is only used for assertion. + bool is_raw_block = false; +#endif // NDEBUG + + BlockContents() {} - BlockContents() : cachable(false), compression_type(kNoCompression) {} + BlockContents(const Slice& _data) : data(_data) {} - BlockContents(const Slice& _data, bool _cachable, - CompressionType _compression_type) - : data(_data), cachable(_cachable), compression_type(_compression_type) {} + BlockContents(CacheAllocationPtr&& _data, size_t _size) + : data(_data.get(), _size), allocation(std::move(_data)) {} - BlockContents(std::unique_ptr&& _data, size_t _size, bool _cachable, - CompressionType _compression_type) - : data(_data.get(), _size), - cachable(_cachable), - compression_type(_compression_type), - allocation(std::move(_data)) {} + BlockContents(std::unique_ptr&& _data, size_t _size) + : data(_data.get(), _size) { + allocation.reset(_data.release()); + } + + bool own_bytes() const { return allocation.get() != nullptr; } + + // It's the caller's responsibility to make sure that this is + // for raw block contents, which contains the compression + // byte in the end. + CompressionType get_compression_type() const { + assert(is_raw_block); + return get_block_compression_type(data.data(), data.size()); + } + + // The additional memory space taken by the block data. + size_t usable_size() const { + if (allocation.get() != nullptr) { + auto allocator = allocation.get_deleter().allocator; + if (allocator) { + return allocator->UsableSize(allocation.get(), data.size()); + } +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + return malloc_usable_size(allocation.get()); +#else + return data.size(); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + } else { + return 0; // no extra memory is occupied by the data + } + } - BlockContents(BlockContents&& other) ROCKSDB_NOEXCEPT { *this = std::move(other); } + size_t ApproximateMemoryUsage() const { + return usable_size() + sizeof(*this); + } + + BlockContents(BlockContents&& other) ROCKSDB_NOEXCEPT { + *this = std::move(other); + } BlockContents& operator=(BlockContents&& other) { data = std::move(other.data); - cachable = other.cachable; - compression_type = other.compression_type; allocation = std::move(other.allocation); +#ifndef NDEBUG + is_raw_block = other.is_raw_block; +#endif // NDEBUG return *this; } }; @@ -228,19 +277,20 @@ extern Status ReadBlockContents( // free this buffer. // For description of compress_format_version and possible values, see // util/compression.h -extern Status UncompressBlockContents(const char* data, size_t n, +extern Status UncompressBlockContents(const UncompressionInfo& info, + const char* data, size_t n, BlockContents* contents, uint32_t compress_format_version, - const Slice& compression_dict, - const ImmutableCFOptions &ioptions); + const ImmutableCFOptions& ioptions, + MemoryAllocator* allocator = nullptr); // This is an extension to UncompressBlockContents that accepts // a specific compression type. This is used by un-wrapped blocks // with no compression header. extern Status UncompressBlockContentsForCompressionType( - const char* data, size_t n, BlockContents* contents, - uint32_t compress_format_version, const Slice& compression_dict, - CompressionType compression_type, const ImmutableCFOptions &ioptions); + const UncompressionInfo& info, const char* data, size_t n, + BlockContents* contents, uint32_t compress_format_version, + const ImmutableCFOptions& ioptions, MemoryAllocator* allocator = nullptr); // Implementation details follow. Clients should ignore, @@ -248,9 +298,7 @@ extern Status UncompressBlockContentsForCompressionType( // BlockHandle. Currently we use zeros for null and use negation-of-zeros for // uninitialized. inline BlockHandle::BlockHandle() - : BlockHandle(~static_cast(0), - ~static_cast(0)) { -} + : BlockHandle(~static_cast(0), ~static_cast(0)) {} inline BlockHandle::BlockHandle(uint64_t _offset, uint64_t _size) : offset_(_offset), size_(_size) {} diff --git a/thirdparty/rocksdb/table/full_filter_bits_builder.h b/thirdparty/rocksdb/table/full_filter_bits_builder.h index b3be7e897f..851ed1e2ab 100644 --- a/thirdparty/rocksdb/table/full_filter_bits_builder.h +++ b/thirdparty/rocksdb/table/full_filter_bits_builder.h @@ -51,6 +51,7 @@ class FullFilterBitsBuilder : public FilterBitsBuilder { uint32_t* num_lines); private: + friend class FullFilterBlockTest_DuplicateEntries_Test; size_t bits_per_key_; size_t num_probes_; std::vector hash_entries_; diff --git a/thirdparty/rocksdb/table/full_filter_block.cc b/thirdparty/rocksdb/table/full_filter_block.cc index 5739494e8d..a7491a7161 100644 --- a/thirdparty/rocksdb/table/full_filter_block.cc +++ b/thirdparty/rocksdb/table/full_filter_block.cc @@ -5,6 +5,14 @@ #include "table/full_filter_block.h" +#ifdef ROCKSDB_MALLOC_USABLE_SIZE +#ifdef OS_FREEBSD +#include +#else +#include +#endif +#endif + #include "monitoring/perf_context_imp.h" #include "port/port.h" #include "rocksdb/filter_policy.h" @@ -17,16 +25,32 @@ FullFilterBlockBuilder::FullFilterBlockBuilder( FilterBitsBuilder* filter_bits_builder) : prefix_extractor_(prefix_extractor), whole_key_filtering_(whole_key_filtering), + last_whole_key_recorded_(false), + last_prefix_recorded_(false), num_added_(0) { assert(filter_bits_builder != nullptr); filter_bits_builder_.reset(filter_bits_builder); } void FullFilterBlockBuilder::Add(const Slice& key) { + const bool add_prefix = prefix_extractor_ && prefix_extractor_->InDomain(key); if (whole_key_filtering_) { - AddKey(key); + if (!add_prefix) { + AddKey(key); + } else { + // if both whole_key and prefix are added to bloom then we will have whole + // key and prefix addition being interleaved and thus cannot rely on the + // bits builder to properly detect the duplicates by comparing with the + // last item. + Slice last_whole_key = Slice(last_whole_key_str_); + if (!last_whole_key_recorded_ || last_whole_key.compare(key) != 0) { + AddKey(key); + last_whole_key_recorded_ = true; + last_whole_key_str_.assign(key.data(), key.size()); + } + } } - if (prefix_extractor_ && prefix_extractor_->InDomain(key)) { + if (add_prefix) { AddPrefix(key); } } @@ -40,10 +64,30 @@ inline void FullFilterBlockBuilder::AddKey(const Slice& key) { // Add prefix to filter if needed inline void FullFilterBlockBuilder::AddPrefix(const Slice& key) { Slice prefix = prefix_extractor_->Transform(key); - AddKey(prefix); + if (whole_key_filtering_) { + // if both whole_key and prefix are added to bloom then we will have whole + // key and prefix addition being interleaved and thus cannot rely on the + // bits builder to properly detect the duplicates by comparing with the last + // item. + Slice last_prefix = Slice(last_prefix_str_); + if (!last_prefix_recorded_ || last_prefix.compare(prefix) != 0) { + AddKey(prefix); + last_prefix_recorded_ = true; + last_prefix_str_.assign(prefix.data(), prefix.size()); + } + } else { + AddKey(prefix); + } } -Slice FullFilterBlockBuilder::Finish(const BlockHandle& tmp, Status* status) { +void FullFilterBlockBuilder::Reset() { + last_whole_key_recorded_ = false; + last_prefix_recorded_ = false; +} + +Slice FullFilterBlockBuilder::Finish(const BlockHandle& /*tmp*/, + Status* status) { + Reset(); // In this impl we ignore BlockHandle *status = Status::OK(); if (num_added_ != 0) { @@ -62,6 +106,10 @@ FullFilterBlockReader::FullFilterBlockReader( contents_(contents) { assert(filter_bits_reader != nullptr); filter_bits_reader_.reset(filter_bits_reader); + if (prefix_extractor_ != nullptr) { + full_length_enabled_ = + prefix_extractor_->FullLengthEnabled(&prefix_extractor_full_length_); + } } FullFilterBlockReader::FullFilterBlockReader( @@ -73,9 +121,13 @@ FullFilterBlockReader::FullFilterBlockReader( block_contents_ = std::move(contents); } -bool FullFilterBlockReader::KeyMayMatch(const Slice& key, uint64_t block_offset, - const bool no_io, - const Slice* const const_ikey_ptr) { +bool FullFilterBlockReader::KeyMayMatch( + const Slice& key, const SliceTransform* /*prefix_extractor*/, + uint64_t block_offset, const bool /*no_io*/, + const Slice* const /*const_ikey_ptr*/) { +#ifdef NDEBUG + (void)block_offset; +#endif assert(block_offset == kNotValid); if (!whole_key_filtering_) { return true; @@ -83,14 +135,14 @@ bool FullFilterBlockReader::KeyMayMatch(const Slice& key, uint64_t block_offset, return MayMatch(key); } -bool FullFilterBlockReader::PrefixMayMatch(const Slice& prefix, - uint64_t block_offset, - const bool no_io, - const Slice* const const_ikey_ptr) { +bool FullFilterBlockReader::PrefixMayMatch( + const Slice& prefix, const SliceTransform* /* prefix_extractor */, + uint64_t block_offset, const bool /*no_io*/, + const Slice* const /*const_ikey_ptr*/) { +#ifdef NDEBUG + (void)block_offset; +#endif assert(block_offset == kNotValid); - if (!prefix_extractor_) { - return true; - } return MayMatch(prefix); } @@ -108,6 +160,67 @@ bool FullFilterBlockReader::MayMatch(const Slice& entry) { } size_t FullFilterBlockReader::ApproximateMemoryUsage() const { - return contents_.size(); + size_t usage = block_contents_.usable_size(); +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + usage += malloc_usable_size((void*)this); + usage += malloc_usable_size(filter_bits_reader_.get()); +#else + usage += sizeof(*this); + usage += sizeof(*filter_bits_reader_.get()); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + return usage; } + +bool FullFilterBlockReader::RangeMayExist(const Slice* iterate_upper_bound, + const Slice& user_key, const SliceTransform* prefix_extractor, + const Comparator* comparator, const Slice* const const_ikey_ptr, + bool* filter_checked, bool need_upper_bound_check) { + if (!prefix_extractor || !prefix_extractor->InDomain(user_key)) { + *filter_checked = false; + return true; + } + Slice prefix = prefix_extractor->Transform(user_key); + if (need_upper_bound_check && + !IsFilterCompatible(iterate_upper_bound, prefix, comparator)) { + *filter_checked = false; + return true; + } else { + *filter_checked = true; + return PrefixMayMatch(prefix, prefix_extractor, kNotValid, false, + const_ikey_ptr); + } +} + +bool FullFilterBlockReader::IsFilterCompatible( + const Slice* iterate_upper_bound, const Slice& prefix, + const Comparator* comparator) { + // Try to reuse the bloom filter in the SST table if prefix_extractor in + // mutable_cf_options has changed. If range [user_key, upper_bound) all + // share the same prefix then we may still be able to use the bloom filter. + if (iterate_upper_bound != nullptr && prefix_extractor_) { + if (!prefix_extractor_->InDomain(*iterate_upper_bound)) { + return false; + } + Slice upper_bound_xform = + prefix_extractor_->Transform(*iterate_upper_bound); + // first check if user_key and upper_bound all share the same prefix + if (!comparator->Equal(prefix, upper_bound_xform)) { + // second check if user_key's prefix is the immediate predecessor of + // upper_bound and have the same length. If so, we know for sure all + // keys in the range [user_key, upper_bound) share the same prefix. + // Also need to make sure upper_bound are full length to ensure + // correctness + if (!full_length_enabled_ || + iterate_upper_bound->size() != prefix_extractor_full_length_ || + !comparator->IsSameLengthImmediateSuccessor(prefix, + *iterate_upper_bound)) { + return false; + } + } + return true; + } else { + return false; + } +} + } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/full_filter_block.h b/thirdparty/rocksdb/table/full_filter_block.h index be27c58b61..e4384c91a4 100644 --- a/thirdparty/rocksdb/table/full_filter_block.h +++ b/thirdparty/rocksdb/table/full_filter_block.h @@ -43,14 +43,16 @@ class FullFilterBlockBuilder : public FilterBlockBuilder { ~FullFilterBlockBuilder() {} virtual bool IsBlockBased() override { return false; } - virtual void StartBlock(uint64_t block_offset) override {} + virtual void StartBlock(uint64_t /*block_offset*/) override {} virtual void Add(const Slice& key) override; + virtual size_t NumAdded() const override { return num_added_; } virtual Slice Finish(const BlockHandle& tmp, Status* status) override; using FilterBlockBuilder::Finish; protected: virtual void AddKey(const Slice& key); std::unique_ptr filter_bits_builder_; + virtual void Reset(); private: // important: all of these might point to invalid addresses @@ -58,6 +60,10 @@ class FullFilterBlockBuilder : public FilterBlockBuilder { // should NOT dereference them. const SliceTransform* prefix_extractor_; bool whole_key_filtering_; + bool last_whole_key_recorded_; + std::string last_whole_key_str_; + bool last_prefix_recorded_; + std::string last_prefix_str_; uint32_t num_added_; std::unique_ptr filter_data_; @@ -91,27 +97,37 @@ class FullFilterBlockReader : public FilterBlockReader { ~FullFilterBlockReader() {} virtual bool IsBlockBased() override { return false; } + virtual bool KeyMayMatch( - const Slice& key, uint64_t block_offset = kNotValid, - const bool no_io = false, + const Slice& key, const SliceTransform* prefix_extractor, + uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) override; + virtual bool PrefixMayMatch( - const Slice& prefix, uint64_t block_offset = kNotValid, - const bool no_io = false, + const Slice& prefix, const SliceTransform* prefix_extractor, + uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) override; virtual size_t ApproximateMemoryUsage() const override; - + virtual bool RangeMayExist(const Slice* iterate_upper_bound, const Slice& user_key, + const SliceTransform* prefix_extractor, + const Comparator* comparator, + const Slice* const const_ikey_ptr, bool* filter_checked, + bool need_upper_bound_check) override; private: const SliceTransform* prefix_extractor_; Slice contents_; std::unique_ptr filter_bits_reader_; BlockContents block_contents_; - std::unique_ptr filter_data_; + bool full_length_enabled_; + size_t prefix_extractor_full_length_; // No copying allowed FullFilterBlockReader(const FullFilterBlockReader&); bool MayMatch(const Slice& entry); void operator=(const FullFilterBlockReader&); + bool IsFilterCompatible(const Slice* iterate_upper_bound, + const Slice& prefix, const Comparator* comparator); + }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/full_filter_block_test.cc b/thirdparty/rocksdb/table/full_filter_block_test.cc index 5fbda4c6f0..f01ae52bf7 100644 --- a/thirdparty/rocksdb/table/full_filter_block_test.cc +++ b/thirdparty/rocksdb/table/full_filter_block_test.cc @@ -6,6 +6,7 @@ #include "table/full_filter_block.h" #include "rocksdb/filter_policy.h" +#include "table/full_filter_bits_builder.h" #include "util/coding.h" #include "util/hash.h" #include "util/string_util.h" @@ -19,12 +20,12 @@ class TestFilterBitsBuilder : public FilterBitsBuilder { explicit TestFilterBitsBuilder() {} // Add Key to filter - virtual void AddKey(const Slice& key) override { + void AddKey(const Slice& key) override { hash_entries_.push_back(Hash(key.data(), key.size(), 1)); } // Generate the filter using the keys that are added - virtual Slice Finish(std::unique_ptr* buf) override { + Slice Finish(std::unique_ptr* buf) override { uint32_t len = static_cast(hash_entries_.size()) * 4; char* data = new char[len]; for (size_t i = 0; i < hash_entries_.size(); i++) { @@ -44,7 +45,7 @@ class TestFilterBitsReader : public FilterBitsReader { explicit TestFilterBitsReader(const Slice& contents) : data_(contents.data()), len_(static_cast(contents.size())) {} - virtual bool MayMatch(const Slice& entry) override { + bool MayMatch(const Slice& entry) override { uint32_t h = Hash(entry.data(), entry.size(), 1); for (size_t i = 0; i + 4 <= len_; i += 4) { if (h == DecodeFixed32(data_ + i)) { @@ -62,18 +63,16 @@ class TestFilterBitsReader : public FilterBitsReader { class TestHashFilter : public FilterPolicy { public: - virtual const char* Name() const override { return "TestHashFilter"; } + const char* Name() const override { return "TestHashFilter"; } - virtual void CreateFilter(const Slice* keys, int n, - std::string* dst) const override { + void CreateFilter(const Slice* keys, int n, std::string* dst) const override { for (int i = 0; i < n; i++) { uint32_t h = Hash(keys[i].data(), keys[i].size(), 1); PutFixed32(dst, h); } } - virtual bool KeyMayMatch(const Slice& key, - const Slice& filter) const override { + bool KeyMayMatch(const Slice& key, const Slice& filter) const override { uint32_t h = Hash(key.data(), key.size(), 1); for (unsigned int i = 0; i + 4 <= filter.size(); i += 4) { if (h == DecodeFixed32(filter.data() + i)) { @@ -83,12 +82,11 @@ class TestHashFilter : public FilterPolicy { return false; } - virtual FilterBitsBuilder* GetFilterBitsBuilder() const override { + FilterBitsBuilder* GetFilterBitsBuilder() const override { return new TestFilterBitsBuilder(); } - virtual FilterBitsReader* GetFilterBitsReader(const Slice& contents) - const override { + FilterBitsReader* GetFilterBitsReader(const Slice& contents) const override { return new TestFilterBitsReader(contents); } }; @@ -112,7 +110,7 @@ TEST_F(PluginFullFilterBlockTest, PluginEmptyBuilder) { nullptr, true, block, table_options_.filter_policy->GetFilterBitsReader(block), nullptr); // Remain same symantic with blockbased filter - ASSERT_TRUE(reader.KeyMayMatch("foo")); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr)); } TEST_F(PluginFullFilterBlockTest, PluginSingleChunk) { @@ -127,13 +125,13 @@ TEST_F(PluginFullFilterBlockTest, PluginSingleChunk) { FullFilterBlockReader reader( nullptr, true, block, table_options_.filter_policy->GetFilterBitsReader(block), nullptr); - ASSERT_TRUE(reader.KeyMayMatch("foo")); - ASSERT_TRUE(reader.KeyMayMatch("bar")); - ASSERT_TRUE(reader.KeyMayMatch("box")); - ASSERT_TRUE(reader.KeyMayMatch("hello")); - ASSERT_TRUE(reader.KeyMayMatch("foo")); - ASSERT_TRUE(!reader.KeyMayMatch("missing")); - ASSERT_TRUE(!reader.KeyMayMatch("other")); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("bar", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("box", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("hello", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr)); + ASSERT_TRUE(!reader.KeyMayMatch("missing", nullptr)); + ASSERT_TRUE(!reader.KeyMayMatch("other", nullptr)); } class FullFilterBlockTest : public testing::Test { @@ -144,7 +142,7 @@ class FullFilterBlockTest : public testing::Test { table_options_.filter_policy.reset(NewBloomFilterPolicy(10, false)); } - ~FullFilterBlockTest() {} + ~FullFilterBlockTest() override {} }; TEST_F(FullFilterBlockTest, EmptyBuilder) { @@ -157,28 +155,63 @@ TEST_F(FullFilterBlockTest, EmptyBuilder) { nullptr, true, block, table_options_.filter_policy->GetFilterBitsReader(block), nullptr); // Remain same symantic with blockbased filter - ASSERT_TRUE(reader.KeyMayMatch("foo")); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr)); +} + +TEST_F(FullFilterBlockTest, DuplicateEntries) { + { // empty prefixes + std::unique_ptr prefix_extractor( + NewFixedPrefixTransform(0)); + auto bits_builder = dynamic_cast( + table_options_.filter_policy->GetFilterBitsBuilder()); + const bool WHOLE_KEY = true; + FullFilterBlockBuilder builder(prefix_extractor.get(), WHOLE_KEY, + bits_builder); + ASSERT_EQ(0, builder.NumAdded()); + builder.Add("key"); // test with empty prefix + ASSERT_EQ(2, bits_builder->hash_entries_.size()); + } + + // mix of empty and non-empty + std::unique_ptr prefix_extractor( + NewFixedPrefixTransform(7)); + auto bits_builder = dynamic_cast( + table_options_.filter_policy->GetFilterBitsBuilder()); + const bool WHOLE_KEY = true; + FullFilterBlockBuilder builder(prefix_extractor.get(), WHOLE_KEY, + bits_builder); + ASSERT_EQ(0, builder.NumAdded()); + builder.Add(""); // test with empty key too + builder.Add("prefix1key1"); + builder.Add("prefix1key1"); + builder.Add("prefix1key2"); + builder.Add("prefix1key3"); + builder.Add("prefix2key4"); + // two prefix adn 4 keys + ASSERT_EQ(1 + 2 + 4, bits_builder->hash_entries_.size()); } TEST_F(FullFilterBlockTest, SingleChunk) { FullFilterBlockBuilder builder( nullptr, true, table_options_.filter_policy->GetFilterBitsBuilder()); + ASSERT_EQ(0, builder.NumAdded()); builder.Add("foo"); builder.Add("bar"); builder.Add("box"); builder.Add("box"); builder.Add("hello"); + ASSERT_EQ(5, builder.NumAdded()); Slice block = builder.Finish(); FullFilterBlockReader reader( nullptr, true, block, table_options_.filter_policy->GetFilterBitsReader(block), nullptr); - ASSERT_TRUE(reader.KeyMayMatch("foo")); - ASSERT_TRUE(reader.KeyMayMatch("bar")); - ASSERT_TRUE(reader.KeyMayMatch("box")); - ASSERT_TRUE(reader.KeyMayMatch("hello")); - ASSERT_TRUE(reader.KeyMayMatch("foo")); - ASSERT_TRUE(!reader.KeyMayMatch("missing")); - ASSERT_TRUE(!reader.KeyMayMatch("other")); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("bar", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("box", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("hello", nullptr)); + ASSERT_TRUE(reader.KeyMayMatch("foo", nullptr)); + ASSERT_TRUE(!reader.KeyMayMatch("missing", nullptr)); + ASSERT_TRUE(!reader.KeyMayMatch("other", nullptr)); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/get_context.cc b/thirdparty/rocksdb/table/get_context.cc index 258891ec4c..24c9ba7d5b 100644 --- a/thirdparty/rocksdb/table/get_context.cc +++ b/thirdparty/rocksdb/table/get_context.cc @@ -6,6 +6,7 @@ #include "table/get_context.h" #include "db/merge_helper.h" #include "db/pinned_iterators_manager.h" +#include "db/read_callback.h" #include "monitoring/file_read_sample.h" #include "monitoring/perf_context_imp.h" #include "monitoring/statistics.h" @@ -28,17 +29,24 @@ void appendToReplayLog(std::string* replay_log, ValueType type, Slice value) { replay_log->push_back(type); PutLengthPrefixedSlice(replay_log, value); } +#else + (void)replay_log; + (void)type; + (void)value; #endif // ROCKSDB_LITE } } // namespace -GetContext::GetContext( - const Comparator* ucmp, const MergeOperator* merge_operator, Logger* logger, - Statistics* statistics, GetState init_state, const Slice& user_key, - PinnableSlice* pinnable_val, bool* value_found, MergeContext* merge_context, - RangeDelAggregator* _range_del_agg, Env* env, SequenceNumber* seq, - PinnedIteratorsManager* _pinned_iters_mgr, bool* is_blob_index) +GetContext::GetContext(const Comparator* ucmp, + const MergeOperator* merge_operator, Logger* logger, + Statistics* statistics, GetState init_state, + const Slice& user_key, PinnableSlice* pinnable_val, + bool* value_found, MergeContext* merge_context, + SequenceNumber* _max_covering_tombstone_seq, Env* env, + SequenceNumber* seq, + PinnedIteratorsManager* _pinned_iters_mgr, + ReadCallback* callback, bool* is_blob_index) : ucmp_(ucmp), merge_operator_(merge_operator), logger_(logger), @@ -48,11 +56,12 @@ GetContext::GetContext( pinnable_val_(pinnable_val), value_found_(value_found), merge_context_(merge_context), - range_del_agg_(_range_del_agg), + max_covering_tombstone_seq_(_max_covering_tombstone_seq), env_(env), seq_(seq), replay_log_(nullptr), pinned_iters_mgr_(_pinned_iters_mgr), + callback_(callback), is_blob_index_(is_blob_index) { if (seq_) { *seq_ = kMaxSequenceNumber; @@ -72,7 +81,7 @@ void GetContext::MarkKeyMayExist() { } } -void GetContext::SaveValue(const Slice& value, SequenceNumber seq) { +void GetContext::SaveValue(const Slice& value, SequenceNumber /*seq*/) { assert(state_ == kNotFound); appendToReplayLog(replay_log_, kTypeValue, value); @@ -82,11 +91,104 @@ void GetContext::SaveValue(const Slice& value, SequenceNumber seq) { } } +void GetContext::ReportCounters() { + if (get_context_stats_.num_cache_hit > 0) { + RecordTick(statistics_, BLOCK_CACHE_HIT, get_context_stats_.num_cache_hit); + } + if (get_context_stats_.num_cache_index_hit > 0) { + RecordTick(statistics_, BLOCK_CACHE_INDEX_HIT, + get_context_stats_.num_cache_index_hit); + } + if (get_context_stats_.num_cache_data_hit > 0) { + RecordTick(statistics_, BLOCK_CACHE_DATA_HIT, + get_context_stats_.num_cache_data_hit); + } + if (get_context_stats_.num_cache_filter_hit > 0) { + RecordTick(statistics_, BLOCK_CACHE_FILTER_HIT, + get_context_stats_.num_cache_filter_hit); + } + if (get_context_stats_.num_cache_compression_dict_hit > 0) { + RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_HIT, + get_context_stats_.num_cache_compression_dict_hit); + } + if (get_context_stats_.num_cache_index_miss > 0) { + RecordTick(statistics_, BLOCK_CACHE_INDEX_MISS, + get_context_stats_.num_cache_index_miss); + } + if (get_context_stats_.num_cache_filter_miss > 0) { + RecordTick(statistics_, BLOCK_CACHE_FILTER_MISS, + get_context_stats_.num_cache_filter_miss); + } + if (get_context_stats_.num_cache_data_miss > 0) { + RecordTick(statistics_, BLOCK_CACHE_DATA_MISS, + get_context_stats_.num_cache_data_miss); + } + if (get_context_stats_.num_cache_compression_dict_miss > 0) { + RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_MISS, + get_context_stats_.num_cache_compression_dict_miss); + } + if (get_context_stats_.num_cache_bytes_read > 0) { + RecordTick(statistics_, BLOCK_CACHE_BYTES_READ, + get_context_stats_.num_cache_bytes_read); + } + if (get_context_stats_.num_cache_miss > 0) { + RecordTick(statistics_, BLOCK_CACHE_MISS, + get_context_stats_.num_cache_miss); + } + if (get_context_stats_.num_cache_add > 0) { + RecordTick(statistics_, BLOCK_CACHE_ADD, get_context_stats_.num_cache_add); + } + if (get_context_stats_.num_cache_bytes_write > 0) { + RecordTick(statistics_, BLOCK_CACHE_BYTES_WRITE, + get_context_stats_.num_cache_bytes_write); + } + if (get_context_stats_.num_cache_index_add > 0) { + RecordTick(statistics_, BLOCK_CACHE_INDEX_ADD, + get_context_stats_.num_cache_index_add); + } + if (get_context_stats_.num_cache_index_bytes_insert > 0) { + RecordTick(statistics_, BLOCK_CACHE_INDEX_BYTES_INSERT, + get_context_stats_.num_cache_index_bytes_insert); + } + if (get_context_stats_.num_cache_data_add > 0) { + RecordTick(statistics_, BLOCK_CACHE_DATA_ADD, + get_context_stats_.num_cache_data_add); + } + if (get_context_stats_.num_cache_data_bytes_insert > 0) { + RecordTick(statistics_, BLOCK_CACHE_DATA_BYTES_INSERT, + get_context_stats_.num_cache_data_bytes_insert); + } + if (get_context_stats_.num_cache_filter_add > 0) { + RecordTick(statistics_, BLOCK_CACHE_FILTER_ADD, + get_context_stats_.num_cache_filter_add); + } + if (get_context_stats_.num_cache_filter_bytes_insert > 0) { + RecordTick(statistics_, BLOCK_CACHE_FILTER_BYTES_INSERT, + get_context_stats_.num_cache_filter_bytes_insert); + } + if (get_context_stats_.num_cache_compression_dict_add > 0) { + RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_ADD, + get_context_stats_.num_cache_compression_dict_add); + } + if (get_context_stats_.num_cache_compression_dict_bytes_insert > 0) { + RecordTick(statistics_, BLOCK_CACHE_COMPRESSION_DICT_BYTES_INSERT, + get_context_stats_.num_cache_compression_dict_bytes_insert); + } +} + bool GetContext::SaveValue(const ParsedInternalKey& parsed_key, - const Slice& value, Cleanable* value_pinner) { + const Slice& value, bool* matched, + Cleanable* value_pinner) { + assert(matched); assert((state_ != kMerge && parsed_key.type != kTypeMerge) || merge_context_ != nullptr); if (ucmp_->Equal(parsed_key.user_key, user_key_)) { + *matched = true; + // If the value is not in the snapshot, skip it + if (!CheckCallback(parsed_key.sequence)) { + return true; // to continue to the next seq + } + appendToReplayLog(replay_log_, parsed_key.type, value); if (seq_ != nullptr) { @@ -99,7 +201,8 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key, auto type = parsed_key.type; // Key matches. Process it if ((type == kTypeValue || type == kTypeMerge || type == kTypeBlobIndex) && - range_del_agg_ != nullptr && range_del_agg_->ShouldDelete(parsed_key)) { + max_covering_tombstone_seq_ != nullptr && + *max_covering_tombstone_seq_ > parsed_key.sequence) { type = kTypeRangeDeletion; } switch (type) { @@ -118,6 +221,8 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key, // If the backing resources for the value are provided, pin them pinnable_val_->PinSlice(value, value_pinner); } else { + TEST_SYNC_POINT_CALLBACK("GetContext::SaveValue::PinSelf", this); + // Otherwise copy the value pinnable_val_->PinSelf(value); } @@ -175,6 +280,21 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key, } else { merge_context_->PushOperand(value, false); } + if (merge_operator_ != nullptr && + merge_operator_->ShouldMerge(merge_context_->GetOperandsDirectionBackward())) { + state_ = kFound; + if (LIKELY(pinnable_val_ != nullptr)) { + Status merge_status = MergeHelper::TimedFullMerge( + merge_operator_, user_key_, nullptr, + merge_context_->GetOperands(), pinnable_val_->GetSelf(), + logger_, statistics_, env_); + pinnable_val_->PinSelf(); + if (!merge_status.ok()) { + state_ = kCorrupt; + } + } + return false; + } return true; default: @@ -199,13 +319,18 @@ void replayGetContextLog(const Slice& replay_log, const Slice& user_key, assert(ret); (void)ret; + bool dont_care __attribute__((__unused__)); // Since SequenceNumber is not stored and unknown, we will use // kMaxSequenceNumber. get_context->SaveValue( ParsedInternalKey(user_key, kMaxSequenceNumber, type), value, - value_pinner); + &dont_care, value_pinner); } #else // ROCKSDB_LITE + (void)replay_log; + (void)user_key; + (void)get_context; + (void)value_pinner; assert(false); #endif // ROCKSDB_LITE } diff --git a/thirdparty/rocksdb/table/get_context.h b/thirdparty/rocksdb/table/get_context.h index a708f6be74..d7d0e9808b 100644 --- a/thirdparty/rocksdb/table/get_context.h +++ b/thirdparty/rocksdb/table/get_context.h @@ -6,8 +6,9 @@ #pragma once #include #include "db/merge_context.h" -#include "db/range_del_aggregator.h" +#include "db/read_callback.h" #include "rocksdb/env.h" +#include "rocksdb/statistics.h" #include "rocksdb/types.h" #include "table/block.h" @@ -15,6 +16,30 @@ namespace rocksdb { class MergeContext; class PinnedIteratorsManager; +struct GetContextStats { + uint64_t num_cache_hit = 0; + uint64_t num_cache_index_hit = 0; + uint64_t num_cache_data_hit = 0; + uint64_t num_cache_filter_hit = 0; + uint64_t num_cache_compression_dict_hit = 0; + uint64_t num_cache_index_miss = 0; + uint64_t num_cache_filter_miss = 0; + uint64_t num_cache_data_miss = 0; + uint64_t num_cache_compression_dict_miss = 0; + uint64_t num_cache_bytes_read = 0; + uint64_t num_cache_miss = 0; + uint64_t num_cache_add = 0; + uint64_t num_cache_bytes_write = 0; + uint64_t num_cache_index_add = 0; + uint64_t num_cache_index_bytes_insert = 0; + uint64_t num_cache_data_add = 0; + uint64_t num_cache_data_bytes_insert = 0; + uint64_t num_cache_filter_add = 0; + uint64_t num_cache_filter_bytes_insert = 0; + uint64_t num_cache_compression_dict_add = 0; + uint64_t num_cache_compression_dict_bytes_insert = 0; +}; + class GetContext { public: enum GetState { @@ -25,24 +50,29 @@ class GetContext { kMerge, // saver contains the current merge result (the operands) kBlobIndex, }; + GetContextStats get_context_stats_; GetContext(const Comparator* ucmp, const MergeOperator* merge_operator, Logger* logger, Statistics* statistics, GetState init_state, const Slice& user_key, PinnableSlice* value, bool* value_found, - MergeContext* merge_context, RangeDelAggregator* range_del_agg, - Env* env, SequenceNumber* seq = nullptr, + MergeContext* merge_context, + SequenceNumber* max_covering_tombstone_seq, Env* env, + SequenceNumber* seq = nullptr, PinnedIteratorsManager* _pinned_iters_mgr = nullptr, - bool* is_blob_index = nullptr); + ReadCallback* callback = nullptr, bool* is_blob_index = nullptr); void MarkKeyMayExist(); // Records this key, value, and any meta-data (such as sequence number and // state) into this GetContext. // + // If the parsed_key matches the user key that we are looking for, sets + // mathced to true. + // // Returns True if more keys need to be read (due to merges) or // False if the complete value has been found. bool SaveValue(const ParsedInternalKey& parsed_key, const Slice& value, - Cleanable* value_pinner = nullptr); + bool* matched, Cleanable* value_pinner = nullptr); // Simplified version of the previous function. Should only be used when we // know that the operation is a Put. @@ -50,7 +80,9 @@ class GetContext { GetState State() const { return state_; } - RangeDelAggregator* range_del_agg() { return range_del_agg_; } + SequenceNumber* max_covering_tombstone_seq() { + return max_covering_tombstone_seq_; + } PinnedIteratorsManager* pinned_iters_mgr() { return pinned_iters_mgr_; } @@ -64,6 +96,15 @@ class GetContext { bool sample() const { return sample_; } + bool CheckCallback(SequenceNumber seq) { + if (callback_) { + return callback_->IsVisible(seq); + } + return true; + } + + void ReportCounters(); + private: const Comparator* ucmp_; const MergeOperator* merge_operator_; @@ -76,7 +117,7 @@ class GetContext { PinnableSlice* pinnable_val_; bool* value_found_; // Is value set correctly? Used by KeyMayExist MergeContext* merge_context_; - RangeDelAggregator* range_del_agg_; + SequenceNumber* max_covering_tombstone_seq_; Env* env_; // If a key is found, seq_ will be set to the SequenceNumber of most recent // write to the key or kMaxSequenceNumber if unknown @@ -84,6 +125,7 @@ class GetContext { std::string* replay_log_; // Used to temporarily pin blocks when state_ == GetContext::kMerge PinnedIteratorsManager* pinned_iters_mgr_; + ReadCallback* callback_; bool sample_; bool* is_blob_index_; }; diff --git a/thirdparty/rocksdb/table/index_builder.cc b/thirdparty/rocksdb/table/index_builder.cc index cdf20aee92..cd28c42a8b 100644 --- a/thirdparty/rocksdb/table/index_builder.cc +++ b/thirdparty/rocksdb/table/index_builder.cc @@ -27,42 +27,65 @@ IndexBuilder* IndexBuilder::CreateIndexBuilder( BlockBasedTableOptions::IndexType index_type, const InternalKeyComparator* comparator, const InternalKeySliceTransform* int_key_slice_transform, + const bool use_value_delta_encoding, const BlockBasedTableOptions& table_opt) { + IndexBuilder* result = nullptr; switch (index_type) { case BlockBasedTableOptions::kBinarySearch: { - return new ShortenedIndexBuilder(comparator, - table_opt.index_block_restart_interval); + result = new ShortenedIndexBuilder( + comparator, table_opt.index_block_restart_interval, + table_opt.format_version, use_value_delta_encoding); } + break; case BlockBasedTableOptions::kHashSearch: { - return new HashIndexBuilder(comparator, int_key_slice_transform, - table_opt.index_block_restart_interval); + result = new HashIndexBuilder(comparator, int_key_slice_transform, + table_opt.index_block_restart_interval, + table_opt.format_version, + use_value_delta_encoding); } + break; case BlockBasedTableOptions::kTwoLevelIndexSearch: { - return PartitionedIndexBuilder::CreateIndexBuilder(comparator, table_opt); + result = PartitionedIndexBuilder::CreateIndexBuilder( + comparator, use_value_delta_encoding, table_opt); } + break; default: { assert(!"Do not recognize the index type "); - return nullptr; } + break; } - // impossible. - assert(false); - return nullptr; + return result; } PartitionedIndexBuilder* PartitionedIndexBuilder::CreateIndexBuilder( const InternalKeyComparator* comparator, + const bool use_value_delta_encoding, const BlockBasedTableOptions& table_opt) { - return new PartitionedIndexBuilder(comparator, table_opt); + return new PartitionedIndexBuilder(comparator, table_opt, + use_value_delta_encoding); } PartitionedIndexBuilder::PartitionedIndexBuilder( const InternalKeyComparator* comparator, - const BlockBasedTableOptions& table_opt) + const BlockBasedTableOptions& table_opt, + const bool use_value_delta_encoding) : IndexBuilder(comparator), - index_block_builder_(table_opt.index_block_restart_interval), + index_block_builder_(table_opt.index_block_restart_interval, + true /*use_delta_encoding*/, + use_value_delta_encoding), + index_block_builder_without_seq_(table_opt.index_block_restart_interval, + true /*use_delta_encoding*/, + use_value_delta_encoding), sub_index_builder_(nullptr), - table_opt_(table_opt) {} + table_opt_(table_opt), + // We start by false. After each partition we revise the value based on + // what the sub_index_builder has decided. If the feature is disabled + // entirely, this will be set to true after switching the first + // sub_index_builder. Otherwise, it could be set to true even one of the + // sub_index_builders could not safely exclude seq from the keys, then it + // wil be enforced on all sub_index_builders on ::Finish. + seperator_is_key_plus_seq_(false), + use_value_delta_encoding_(use_value_delta_encoding) {} PartitionedIndexBuilder::~PartitionedIndexBuilder() { delete sub_index_builder_; @@ -71,10 +94,15 @@ PartitionedIndexBuilder::~PartitionedIndexBuilder() { void PartitionedIndexBuilder::MakeNewSubIndexBuilder() { assert(sub_index_builder_ == nullptr); sub_index_builder_ = new ShortenedIndexBuilder( - comparator_, table_opt_.index_block_restart_interval); + comparator_, table_opt_.index_block_restart_interval, + table_opt_.format_version, use_value_delta_encoding_); flush_policy_.reset(FlushBlockBySizePolicyFactory::NewFlushBlockPolicy( table_opt_.metadata_block_size, table_opt_.block_size_deviation, - sub_index_builder_->index_block_builder_)); + // Note: this is sub-optimal since sub_index_builder_ could later reset + // seperator_is_key_plus_seq_ but the probability of that is low. + sub_index_builder_->seperator_is_key_plus_seq_ + ? sub_index_builder_->index_block_builder_ + : sub_index_builder_->index_block_builder_without_seq_)); partition_cut_requested_ = false; } @@ -93,6 +121,10 @@ void PartitionedIndexBuilder::AddIndexEntry( } sub_index_builder_->AddIndexEntry(last_key_in_current_block, first_key_in_next_block, block_handle); + if (sub_index_builder_->seperator_is_key_plus_seq_) { + // then we need to apply it to all sub-index builders + seperator_is_key_plus_seq_ = true; + } sub_index_last_key_ = std::string(*last_key_in_current_block); entries_.push_back( {sub_index_last_key_, @@ -121,67 +153,62 @@ void PartitionedIndexBuilder::AddIndexEntry( sub_index_builder_->AddIndexEntry(last_key_in_current_block, first_key_in_next_block, block_handle); sub_index_last_key_ = std::string(*last_key_in_current_block); + if (sub_index_builder_->seperator_is_key_plus_seq_) { + // then we need to apply it to all sub-index builders + seperator_is_key_plus_seq_ = true; + } } } Status PartitionedIndexBuilder::Finish( IndexBlocks* index_blocks, const BlockHandle& last_partition_block_handle) { - assert(!entries_.empty()); + if (partition_cnt_ == 0) { + partition_cnt_ = entries_.size(); + } // It must be set to null after last key is added assert(sub_index_builder_ == nullptr); if (finishing_indexes == true) { Entry& last_entry = entries_.front(); std::string handle_encoding; last_partition_block_handle.EncodeTo(&handle_encoding); - index_block_builder_.Add(last_entry.key, handle_encoding); + std::string handle_delta_encoding; + PutVarsignedint64( + &handle_delta_encoding, + last_partition_block_handle.size() - last_encoded_handle_.size()); + last_encoded_handle_ = last_partition_block_handle; + const Slice handle_delta_encoding_slice(handle_delta_encoding); + index_block_builder_.Add(last_entry.key, handle_encoding, + &handle_delta_encoding_slice); + if (!seperator_is_key_plus_seq_) { + index_block_builder_without_seq_.Add(ExtractUserKey(last_entry.key), + handle_encoding, + &handle_delta_encoding_slice); + } entries_.pop_front(); } // If there is no sub_index left, then return the 2nd level index. if (UNLIKELY(entries_.empty())) { - index_blocks->index_block_contents = index_block_builder_.Finish(); + if (seperator_is_key_plus_seq_) { + index_blocks->index_block_contents = index_block_builder_.Finish(); + } else { + index_blocks->index_block_contents = + index_block_builder_without_seq_.Finish(); + } + top_level_index_size_ = index_blocks->index_block_contents.size(); + index_size_ += top_level_index_size_; return Status::OK(); } else { // Finish the next partition index in line and Incomplete() to indicate we // expect more calls to Finish Entry& entry = entries_.front(); + // Apply the policy to all sub-indexes + entry.value->seperator_is_key_plus_seq_ = seperator_is_key_plus_seq_; auto s = entry.value->Finish(index_blocks); + index_size_ += index_blocks->index_block_contents.size(); finishing_indexes = true; return s.ok() ? Status::Incomplete() : s; } } -// Estimate size excluding the top-level index -// It is assumed that this method is called before writing index partition -// starts -size_t PartitionedIndexBuilder::EstimatedSize() const { - size_t total = 0; - for (auto it = entries_.begin(); it != entries_.end(); ++it) { - total += it->value->EstimatedSize(); - } - total += - sub_index_builder_ == nullptr ? 0 : sub_index_builder_->EstimatedSize(); - return total; -} - -// Since when this method is called we do not know the index block offsets yet, -// the top-level index does not exist. Hence we estimate the block offsets and -// create a temporary top-level index. -size_t PartitionedIndexBuilder::EstimateTopLevelIndexSize( - uint64_t offset) const { - BlockBuilder tmp_builder( - table_opt_.index_block_restart_interval); // tmp top-level index builder - for (auto it = entries_.begin(); it != entries_.end(); ++it) { - std::string tmp_handle_encoding; - uint64_t size = it->value->EstimatedSize(); - BlockHandle tmp_block_handle(offset, size); - tmp_block_handle.EncodeTo(&tmp_handle_encoding); - tmp_builder.Add(it->key, tmp_handle_encoding); - offset += size; - } - return tmp_builder.CurrentSizeEstimate(); -} - -size_t PartitionedIndexBuilder::NumPartitions() const { - return entries_.size(); -} +size_t PartitionedIndexBuilder::NumPartitions() const { return partition_cnt_; } } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/index_builder.h b/thirdparty/rocksdb/table/index_builder.h index d591e0e533..87d7b7a71b 100644 --- a/thirdparty/rocksdb/table/index_builder.h +++ b/thirdparty/rocksdb/table/index_builder.h @@ -38,6 +38,7 @@ class IndexBuilder { BlockBasedTableOptions::IndexType index_type, const rocksdb::InternalKeyComparator* comparator, const InternalKeySliceTransform* int_key_slice_transform, + const bool use_value_delta_encoding, const BlockBasedTableOptions& table_opt); // Index builder will construct a set of blocks which contain: @@ -69,7 +70,7 @@ class IndexBuilder { // This method will be called whenever a key is added. The subclasses may // override OnKeyAdded() if they need to collect additional information. - virtual void OnKeyAdded(const Slice& key) {} + virtual void OnKeyAdded(const Slice& /*key*/) {} // Inform the index builder that all entries has been written. Block builder // may therefore perform any operation required for block finalization. @@ -96,11 +97,15 @@ class IndexBuilder { virtual Status Finish(IndexBlocks* index_blocks, const BlockHandle& last_partition_block_handle) = 0; - // Get the estimated size for index block. - virtual size_t EstimatedSize() const = 0; + // Get the size for index block. Must be called after ::Finish. + virtual size_t IndexSize() const = 0; + + virtual bool seperator_is_key_plus_seq() { return true; } protected: const InternalKeyComparator* comparator_; + // Set after ::Finish is called + size_t index_size_ = 0; }; // This index builder builds space-efficient index block. @@ -115,9 +120,19 @@ class IndexBuilder { class ShortenedIndexBuilder : public IndexBuilder { public: explicit ShortenedIndexBuilder(const InternalKeyComparator* comparator, - int index_block_restart_interval) + const int index_block_restart_interval, + const uint32_t format_version, + const bool use_value_delta_encoding) : IndexBuilder(comparator), - index_block_builder_(index_block_restart_interval) {} + index_block_builder_(index_block_restart_interval, + true /*use_delta_encoding*/, + use_value_delta_encoding), + index_block_builder_without_seq_(index_block_restart_interval, + true /*use_delta_encoding*/, + use_value_delta_encoding) { + // Making the default true will disable the feature for old versions + seperator_is_key_plus_seq_ = (format_version <= 2); + } virtual void AddIndexEntry(std::string* last_key_in_current_block, const Slice* first_key_in_next_block, @@ -125,31 +140,60 @@ class ShortenedIndexBuilder : public IndexBuilder { if (first_key_in_next_block != nullptr) { comparator_->FindShortestSeparator(last_key_in_current_block, *first_key_in_next_block); + if (!seperator_is_key_plus_seq_ && + comparator_->user_comparator()->Compare( + ExtractUserKey(*last_key_in_current_block), + ExtractUserKey(*first_key_in_next_block)) == 0) { + seperator_is_key_plus_seq_ = true; + } } else { comparator_->FindShortSuccessor(last_key_in_current_block); } + auto sep = Slice(*last_key_in_current_block); std::string handle_encoding; block_handle.EncodeTo(&handle_encoding); - index_block_builder_.Add(*last_key_in_current_block, handle_encoding); + std::string handle_delta_encoding; + PutVarsignedint64(&handle_delta_encoding, + block_handle.size() - last_encoded_handle_.size()); + assert(handle_delta_encoding.size() != 0); + last_encoded_handle_ = block_handle; + const Slice handle_delta_encoding_slice(handle_delta_encoding); + index_block_builder_.Add(sep, handle_encoding, + &handle_delta_encoding_slice); + if (!seperator_is_key_plus_seq_) { + index_block_builder_without_seq_.Add(ExtractUserKey(sep), handle_encoding, + &handle_delta_encoding_slice); + } } using IndexBuilder::Finish; virtual Status Finish( IndexBlocks* index_blocks, - const BlockHandle& last_partition_block_handle) override { - index_blocks->index_block_contents = index_block_builder_.Finish(); + const BlockHandle& /*last_partition_block_handle*/) override { + if (seperator_is_key_plus_seq_) { + index_blocks->index_block_contents = index_block_builder_.Finish(); + } else { + index_blocks->index_block_contents = + index_block_builder_without_seq_.Finish(); + } + index_size_ = index_blocks->index_block_contents.size(); return Status::OK(); } - virtual size_t EstimatedSize() const override { - return index_block_builder_.CurrentSizeEstimate(); + virtual size_t IndexSize() const override { return index_size_; } + + virtual bool seperator_is_key_plus_seq() override { + return seperator_is_key_plus_seq_; } friend class PartitionedIndexBuilder; private: BlockBuilder index_block_builder_; + BlockBuilder index_block_builder_without_seq_; + bool seperator_is_key_plus_seq_; + BlockHandle last_encoded_handle_; }; // HashIndexBuilder contains a binary-searchable primary index and the @@ -183,9 +227,11 @@ class HashIndexBuilder : public IndexBuilder { public: explicit HashIndexBuilder(const InternalKeyComparator* comparator, const SliceTransform* hash_key_extractor, - int index_block_restart_interval) + int index_block_restart_interval, + int format_version, bool use_value_delta_encoding) : IndexBuilder(comparator), - primary_index_builder_(comparator, index_block_restart_interval), + primary_index_builder_(comparator, index_block_restart_interval, + format_version, use_value_delta_encoding), hash_key_extractor_(hash_key_extractor) {} virtual void AddIndexEntry(std::string* last_key_in_current_block, @@ -226,7 +272,9 @@ class HashIndexBuilder : public IndexBuilder { virtual Status Finish( IndexBlocks* index_blocks, const BlockHandle& last_partition_block_handle) override { - FlushPendingPrefix(); + if (pending_block_num_ != 0) { + FlushPendingPrefix(); + } primary_index_builder_.Finish(index_blocks, last_partition_block_handle); index_blocks->meta_blocks.insert( {kHashIndexPrefixesBlock.c_str(), prefix_block_}); @@ -235,11 +283,15 @@ class HashIndexBuilder : public IndexBuilder { return Status::OK(); } - virtual size_t EstimatedSize() const override { - return primary_index_builder_.EstimatedSize() + prefix_block_.size() + + virtual size_t IndexSize() const override { + return primary_index_builder_.IndexSize() + prefix_block_.size() + prefix_meta_block_.size(); } + virtual bool seperator_is_key_plus_seq() override { + return primary_index_builder_.seperator_is_key_plus_seq(); + } + private: void FlushPendingPrefix() { prefix_block_.append(pending_entry_prefix_.data(), @@ -282,10 +334,12 @@ class PartitionedIndexBuilder : public IndexBuilder { public: static PartitionedIndexBuilder* CreateIndexBuilder( const rocksdb::InternalKeyComparator* comparator, + const bool use_value_delta_encoding, const BlockBasedTableOptions& table_opt); explicit PartitionedIndexBuilder(const InternalKeyComparator* comparator, - const BlockBasedTableOptions& table_opt); + const BlockBasedTableOptions& table_opt, + const bool use_value_delta_encoding); virtual ~PartitionedIndexBuilder(); @@ -297,8 +351,8 @@ class PartitionedIndexBuilder : public IndexBuilder { IndexBlocks* index_blocks, const BlockHandle& last_partition_block_handle) override; - virtual size_t EstimatedSize() const override; - size_t EstimateTopLevelIndexSize(uint64_t) const; + virtual size_t IndexSize() const override { return index_size_; } + size_t TopLevelIndexSize(uint64_t) const { return top_level_index_size_; } size_t NumPartitions() const; inline bool ShouldCutFilterBlock() { @@ -316,7 +370,18 @@ class PartitionedIndexBuilder : public IndexBuilder { // cutting the next partition void RequestPartitionCut(); + virtual bool seperator_is_key_plus_seq() override { + return seperator_is_key_plus_seq_; + } + + bool get_use_value_delta_encoding() { return use_value_delta_encoding_; } + private: + // Set after ::Finish is called + size_t top_level_index_size_ = 0; + // Set after ::Finish is called + size_t partition_cnt_ = 0; + void MakeNewSubIndexBuilder(); struct Entry { @@ -325,6 +390,7 @@ class PartitionedIndexBuilder : public IndexBuilder { }; std::list entries_; // list of partitioned indexes and their keys BlockBuilder index_block_builder_; // top-level index builder + BlockBuilder index_block_builder_without_seq_; // same for user keys // the active partition index builder ShortenedIndexBuilder* sub_index_builder_; // the last key in the active partition index builder @@ -333,10 +399,13 @@ class PartitionedIndexBuilder : public IndexBuilder { // true if Finish is called once but not complete yet. bool finishing_indexes = false; const BlockBasedTableOptions& table_opt_; + bool seperator_is_key_plus_seq_; + bool use_value_delta_encoding_; // true if an external entity (such as filter partition builder) request // cutting the next partition bool partition_cut_requested_ = true; // true if it should cut the next filter partition block bool cut_filter_block = false; + BlockHandle last_encoded_handle_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/internal_iterator.h b/thirdparty/rocksdb/table/internal_iterator.h index 2bfdb7d952..a173d60690 100644 --- a/thirdparty/rocksdb/table/internal_iterator.h +++ b/thirdparty/rocksdb/table/internal_iterator.h @@ -10,18 +10,21 @@ #include "rocksdb/comparator.h" #include "rocksdb/iterator.h" #include "rocksdb/status.h" +#include "table/format.h" namespace rocksdb { class PinnedIteratorsManager; -class InternalIterator : public Cleanable { +template +class InternalIteratorBase : public Cleanable { public: - InternalIterator() {} - virtual ~InternalIterator() {} + InternalIteratorBase() {} + virtual ~InternalIteratorBase() {} // An iterator is either positioned at a key/value pair, or // not valid. This method returns true iff the iterator is valid. + // Always returns false if !status().ok(). virtual bool Valid() const = 0; // Position at the first key in the source. The iterator is Valid() @@ -35,6 +38,9 @@ class InternalIterator : public Cleanable { // Position at the first key in the source that at or past target // The iterator is Valid() after this call iff the source contains // an entry that comes at or past target. + // All Seek*() methods clear any error status() that the iterator had prior to + // the call; after the seek, status() indicates only the error (if any) that + // happened during the seek, not any past errors. virtual void Seek(const Slice& target) = 0; // Position at the first key in the source that at or before target @@ -61,20 +67,25 @@ class InternalIterator : public Cleanable { // Return the value for the current entry. The underlying storage for // the returned slice is valid only until the next modification of // the iterator. - // REQUIRES: !AtEnd() && !AtStart() - virtual Slice value() const = 0; + // REQUIRES: Valid() + virtual TValue value() const = 0; // If an error has occurred, return it. Else return an ok status. // If non-blocking IO is requested and this operation cannot be // satisfied without doing some IO, then this returns Status::Incomplete(). virtual Status status() const = 0; + // True if the iterator is invalidated because it is out of the iterator + // upper bound + virtual bool IsOutOfBound() { return false; } + // Pass the PinnedIteratorsManager to the Iterator, most Iterators dont // communicate with PinnedIteratorsManager so default implementation is no-op // but for Iterators that need to communicate with PinnedIteratorsManager // they will implement this function and use the passed pointer to communicate // with PinnedIteratorsManager. - virtual void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) {} + virtual void SetPinnedItersMgr(PinnedIteratorsManager* /*pinned_iters_mgr*/) { + } // If true, this means that the Slice returned by key() is valid as long as // PinnedIteratorsManager::ReleasePinnedData is not called and the @@ -91,7 +102,7 @@ class InternalIterator : public Cleanable { // Iterator is not deleted. virtual bool IsValuePinned() const { return false; } - virtual Status GetProperty(std::string prop_name, std::string* prop) { + virtual Status GetProperty(std::string /*prop_name*/, std::string* /*prop*/) { return Status::NotSupported(""); } @@ -108,14 +119,24 @@ class InternalIterator : public Cleanable { private: // No copying allowed - InternalIterator(const InternalIterator&) = delete; - InternalIterator& operator=(const InternalIterator&) = delete; + InternalIteratorBase(const InternalIteratorBase&) = delete; + InternalIteratorBase& operator=(const InternalIteratorBase&) = delete; }; +using InternalIterator = InternalIteratorBase; + // Return an empty iterator (yields nothing). -extern InternalIterator* NewEmptyInternalIterator(); +template +extern InternalIteratorBase* NewEmptyInternalIterator(); // Return an empty iterator with the specified status. -extern InternalIterator* NewErrorInternalIterator(const Status& status); +template +extern InternalIteratorBase* NewErrorInternalIterator( + const Status& status); + +// Return an empty iterator with the specified status, allocated arena. +template +extern InternalIteratorBase* NewErrorInternalIterator( + const Status& status, Arena* arena); } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/iter_heap.h b/thirdparty/rocksdb/table/iter_heap.h index 74c06caeaf..f30c122722 100644 --- a/thirdparty/rocksdb/table/iter_heap.h +++ b/thirdparty/rocksdb/table/iter_heap.h @@ -6,7 +6,7 @@ #pragma once -#include "rocksdb/comparator.h" +#include "db/dbformat.h" #include "table/iterator_wrapper.h" namespace rocksdb { @@ -15,28 +15,28 @@ namespace rocksdb { // iterator with the max/largest key on top. class MaxIteratorComparator { public: - MaxIteratorComparator(const Comparator* comparator) : - comparator_(comparator) {} + MaxIteratorComparator(const InternalKeyComparator* comparator) + : comparator_(comparator) {} bool operator()(IteratorWrapper* a, IteratorWrapper* b) const { return comparator_->Compare(a->key(), b->key()) < 0; } private: - const Comparator* comparator_; + const InternalKeyComparator* comparator_; }; // When used with std::priority_queue, this comparison functor puts the // iterator with the min/smallest key on top. class MinIteratorComparator { public: - MinIteratorComparator(const Comparator* comparator) : - comparator_(comparator) {} + MinIteratorComparator(const InternalKeyComparator* comparator) + : comparator_(comparator) {} bool operator()(IteratorWrapper* a, IteratorWrapper* b) const { return comparator_->Compare(a->key(), b->key()) > 0; } private: - const Comparator* comparator_; + const InternalKeyComparator* comparator_; }; } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/iterator.cc b/thirdparty/rocksdb/table/iterator.cc index ed6a2cdea4..0475b9d134 100644 --- a/thirdparty/rocksdb/table/iterator.cc +++ b/thirdparty/rocksdb/table/iterator.cc @@ -103,20 +103,20 @@ Status Iterator::GetProperty(std::string prop_name, std::string* prop) { *prop = "0"; return Status::OK(); } - return Status::InvalidArgument("Undentified property."); + return Status::InvalidArgument("Unidentified property."); } namespace { class EmptyIterator : public Iterator { public: explicit EmptyIterator(const Status& s) : status_(s) { } - virtual bool Valid() const override { return false; } - virtual void Seek(const Slice& target) override {} - virtual void SeekForPrev(const Slice& target) override {} - virtual void SeekToFirst() override {} - virtual void SeekToLast() override {} - virtual void Next() override { assert(false); } - virtual void Prev() override { assert(false); } + bool Valid() const override { return false; } + void Seek(const Slice& /*target*/) override {} + void SeekForPrev(const Slice& /*target*/) override {} + void SeekToFirst() override {} + void SeekToLast() override {} + void Next() override { assert(false); } + void Prev() override { assert(false); } Slice key() const override { assert(false); return Slice(); @@ -125,69 +125,86 @@ class EmptyIterator : public Iterator { assert(false); return Slice(); } - virtual Status status() const override { return status_; } + Status status() const override { return status_; } private: Status status_; }; -class EmptyInternalIterator : public InternalIterator { +template +class EmptyInternalIterator : public InternalIteratorBase { public: explicit EmptyInternalIterator(const Status& s) : status_(s) {} - virtual bool Valid() const override { return false; } - virtual void Seek(const Slice& target) override {} - virtual void SeekForPrev(const Slice& target) override {} - virtual void SeekToFirst() override {} - virtual void SeekToLast() override {} - virtual void Next() override { assert(false); } - virtual void Prev() override { assert(false); } + bool Valid() const override { return false; } + void Seek(const Slice& /*target*/) override {} + void SeekForPrev(const Slice& /*target*/) override {} + void SeekToFirst() override {} + void SeekToLast() override {} + void Next() override { assert(false); } + void Prev() override { assert(false); } Slice key() const override { assert(false); return Slice(); } - Slice value() const override { + TValue value() const override { assert(false); - return Slice(); + return TValue(); } - virtual Status status() const override { return status_; } + Status status() const override { return status_; } private: Status status_; }; } // namespace -Iterator* NewEmptyIterator() { - return new EmptyIterator(Status::OK()); -} +Iterator* NewEmptyIterator() { return new EmptyIterator(Status::OK()); } Iterator* NewErrorIterator(const Status& status) { return new EmptyIterator(status); } -InternalIterator* NewEmptyInternalIterator() { - return new EmptyInternalIterator(Status::OK()); +template +InternalIteratorBase* NewErrorInternalIterator(const Status& status) { + return new EmptyInternalIterator(status); } - -InternalIterator* NewEmptyInternalIterator(Arena* arena) { +template InternalIteratorBase* NewErrorInternalIterator( + const Status& status); +template InternalIteratorBase* NewErrorInternalIterator( + const Status& status); + +template +InternalIteratorBase* NewErrorInternalIterator(const Status& status, + Arena* arena) { if (arena == nullptr) { - return NewEmptyInternalIterator(); + return NewErrorInternalIterator(status); } else { - auto mem = arena->AllocateAligned(sizeof(EmptyIterator)); - return new (mem) EmptyInternalIterator(Status::OK()); + auto mem = arena->AllocateAligned(sizeof(EmptyInternalIterator)); + return new (mem) EmptyInternalIterator(status); } } - -InternalIterator* NewErrorInternalIterator(const Status& status) { - return new EmptyInternalIterator(status); +template InternalIteratorBase* NewErrorInternalIterator( + const Status& status, Arena* arena); +template InternalIteratorBase* NewErrorInternalIterator( + const Status& status, Arena* arena); + +template +InternalIteratorBase* NewEmptyInternalIterator() { + return new EmptyInternalIterator(Status::OK()); } +template InternalIteratorBase* NewEmptyInternalIterator(); +template InternalIteratorBase* NewEmptyInternalIterator(); -InternalIterator* NewErrorInternalIterator(const Status& status, Arena* arena) { +template +InternalIteratorBase* NewEmptyInternalIterator(Arena* arena) { if (arena == nullptr) { - return NewErrorInternalIterator(status); + return NewEmptyInternalIterator(); } else { - auto mem = arena->AllocateAligned(sizeof(EmptyIterator)); - return new (mem) EmptyInternalIterator(status); + auto mem = arena->AllocateAligned(sizeof(EmptyInternalIterator)); + return new (mem) EmptyInternalIterator(Status::OK()); } } +template InternalIteratorBase* NewEmptyInternalIterator( + Arena* arena); +template InternalIteratorBase* NewEmptyInternalIterator(Arena* arena); } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/iterator_wrapper.h b/thirdparty/rocksdb/table/iterator_wrapper.h index f14acdb9bf..5941b846a1 100644 --- a/thirdparty/rocksdb/table/iterator_wrapper.h +++ b/thirdparty/rocksdb/table/iterator_wrapper.h @@ -19,19 +19,21 @@ namespace rocksdb { // the valid() and key() results for an underlying iterator. // This can help avoid virtual function calls and also gives better // cache locality. -class IteratorWrapper { +template +class IteratorWrapperBase { public: - IteratorWrapper() : iter_(nullptr), valid_(false) {} - explicit IteratorWrapper(InternalIterator* _iter) : iter_(nullptr) { + IteratorWrapperBase() : iter_(nullptr), valid_(false) {} + explicit IteratorWrapperBase(InternalIteratorBase* _iter) + : iter_(nullptr) { Set(_iter); } - ~IteratorWrapper() {} - InternalIterator* iter() const { return iter_; } + ~IteratorWrapperBase() {} + InternalIteratorBase* iter() const { return iter_; } // Set the underlying Iterator to _iter and return // previous underlying Iterator. - InternalIterator* Set(InternalIterator* _iter) { - InternalIterator* old_iter = iter_; + InternalIteratorBase* Set(InternalIteratorBase* _iter) { + InternalIteratorBase* old_iter = iter_; iter_ = _iter; if (iter_ == nullptr) { @@ -47,7 +49,7 @@ class IteratorWrapper { if (!is_arena_mode) { delete iter_; } else { - iter_->~InternalIterator(); + iter_->~InternalIteratorBase(); } } } @@ -55,7 +57,10 @@ class IteratorWrapper { // Iterator interface methods bool Valid() const { return valid_; } Slice key() const { assert(Valid()); return key_; } - Slice value() const { assert(Valid()); return iter_->value(); } + TValue value() const { + assert(Valid()); + return iter_->value(); + } // Methods below require iter() != nullptr Status status() const { assert(iter_); return iter_->status(); } void Next() { assert(iter_); iter_->Next(); Update(); } @@ -87,20 +92,20 @@ class IteratorWrapper { valid_ = iter_->Valid(); if (valid_) { key_ = iter_->key(); + assert(iter_->status().ok()); } } - InternalIterator* iter_; + InternalIteratorBase* iter_; bool valid_; Slice key_; }; +using IteratorWrapper = IteratorWrapperBase; + class Arena; // Return an empty iterator (yields nothing) allocated from arena. -extern InternalIterator* NewEmptyInternalIterator(Arena* arena); - -// Return an empty iterator with the specified status, allocated arena. -extern InternalIterator* NewErrorInternalIterator(const Status& status, - Arena* arena); +template +extern InternalIteratorBase* NewEmptyInternalIterator(Arena* arena); } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/merger_test.cc b/thirdparty/rocksdb/table/merger_test.cc index 379a6f412d..1b04d06572 100644 --- a/thirdparty/rocksdb/table/merger_test.cc +++ b/thirdparty/rocksdb/table/merger_test.cc @@ -15,12 +15,18 @@ namespace rocksdb { class MergerTest : public testing::Test { public: MergerTest() - : rnd_(3), merging_iterator_(nullptr), single_iterator_(nullptr) {} - ~MergerTest() = default; + : icomp_(BytewiseComparator()), + rnd_(3), + merging_iterator_(nullptr), + single_iterator_(nullptr) {} + ~MergerTest() override = default; std::vector GenerateStrings(size_t len, int string_len) { std::vector ret; + for (size_t i = 0; i < len; ++i) { - ret.push_back(test::RandomHumanReadableString(&rnd_, string_len)); + InternalKey ik(test::RandomHumanReadableString(&rnd_, string_len), 0, + ValueType::kTypeValue); + ret.push_back(ik.Encode().ToString(false)); } return ret; } @@ -37,7 +43,11 @@ class MergerTest : public testing::Test { } } - void SeekToRandom() { Seek(test::RandomHumanReadableString(&rnd_, 5)); } + void SeekToRandom() { + InternalKey ik(test::RandomHumanReadableString(&rnd_, 5), 0, + ValueType::kTypeValue); + Seek(ik.Encode().ToString(false)); + } void Seek(std::string target) { merging_iterator_->Seek(target); @@ -96,11 +106,12 @@ class MergerTest : public testing::Test { } merging_iterator_.reset( - NewMergingIterator(BytewiseComparator(), &small_iterators[0], + NewMergingIterator(&icomp_, &small_iterators[0], static_cast(small_iterators.size()))); single_iterator_.reset(new test::VectorIterator(all_keys_)); } + InternalKeyComparator icomp_; Random rnd_; std::unique_ptr merging_iterator_; std::unique_ptr single_iterator_; diff --git a/thirdparty/rocksdb/table/merging_iterator.cc b/thirdparty/rocksdb/table/merging_iterator.cc index da30e1e635..bd4a186b3c 100644 --- a/thirdparty/rocksdb/table/merging_iterator.cc +++ b/thirdparty/rocksdb/table/merging_iterator.cc @@ -10,6 +10,7 @@ #include "table/merging_iterator.h" #include #include +#include "db/dbformat.h" #include "db/pinned_iterators_manager.h" #include "monitoring/perf_context_imp.h" #include "rocksdb/comparator.h" @@ -35,8 +36,9 @@ const size_t kNumIterReserve = 4; class MergingIterator : public InternalIterator { public: - MergingIterator(const Comparator* comparator, InternalIterator** children, - int n, bool is_arena_mode, bool prefix_seek_mode) + MergingIterator(const InternalKeyComparator* comparator, + InternalIterator** children, int n, bool is_arena_mode, + bool prefix_seek_mode) : is_arena_mode_(is_arena_mode), comparator_(comparator), current_(nullptr), @@ -50,12 +52,21 @@ class MergingIterator : public InternalIterator { } for (auto& child : children_) { if (child.Valid()) { + assert(child.status().ok()); minHeap_.push(&child); + } else { + considerStatus(child.status()); } } current_ = CurrentForward(); } + void considerStatus(Status s) { + if (!s.ok() && status_.ok()) { + status_ = s; + } + } + virtual void AddIterator(InternalIterator* iter) { assert(direction_ == kForward); children_.emplace_back(iter); @@ -64,46 +75,60 @@ class MergingIterator : public InternalIterator { } auto new_wrapper = children_.back(); if (new_wrapper.Valid()) { + assert(new_wrapper.status().ok()); minHeap_.push(&new_wrapper); current_ = CurrentForward(); + } else { + considerStatus(new_wrapper.status()); } } - virtual ~MergingIterator() { + ~MergingIterator() override { for (auto& child : children_) { child.DeleteIter(is_arena_mode_); } } - virtual bool Valid() const override { return (current_ != nullptr); } + bool Valid() const override { return current_ != nullptr && status_.ok(); } - virtual void SeekToFirst() override { + Status status() const override { return status_; } + + void SeekToFirst() override { ClearHeaps(); + status_ = Status::OK(); for (auto& child : children_) { child.SeekToFirst(); if (child.Valid()) { + assert(child.status().ok()); minHeap_.push(&child); + } else { + considerStatus(child.status()); } } direction_ = kForward; current_ = CurrentForward(); } - virtual void SeekToLast() override { + void SeekToLast() override { ClearHeaps(); InitMaxHeap(); + status_ = Status::OK(); for (auto& child : children_) { child.SeekToLast(); if (child.Valid()) { + assert(child.status().ok()); maxHeap_->push(&child); + } else { + considerStatus(child.status()); } } direction_ = kReverse; current_ = CurrentReverse(); } - virtual void Seek(const Slice& target) override { + void Seek(const Slice& target) override { ClearHeaps(); + status_ = Status::OK(); for (auto& child : children_) { { PERF_TIMER_GUARD(seek_child_seek_time); @@ -112,8 +137,11 @@ class MergingIterator : public InternalIterator { PERF_COUNTER_ADD(seek_child_seek_count, 1); if (child.Valid()) { + assert(child.status().ok()); PERF_TIMER_GUARD(seek_min_heap_time); minHeap_.push(&child); + } else { + considerStatus(child.status()); } } direction_ = kForward; @@ -123,9 +151,10 @@ class MergingIterator : public InternalIterator { } } - virtual void SeekForPrev(const Slice& target) override { + void SeekForPrev(const Slice& target) override { ClearHeaps(); InitMaxHeap(); + status_ = Status::OK(); for (auto& child : children_) { { @@ -135,8 +164,11 @@ class MergingIterator : public InternalIterator { PERF_COUNTER_ADD(seek_child_seek_count, 1); if (child.Valid()) { + assert(child.status().ok()); PERF_TIMER_GUARD(seek_max_heap_time); maxHeap_->push(&child); + } else { + considerStatus(child.status()); } } direction_ = kReverse; @@ -146,7 +178,7 @@ class MergingIterator : public InternalIterator { } } - virtual void Next() override { + void Next() override { assert(Valid()); // Ensure that all children are positioned after key(). @@ -154,21 +186,7 @@ class MergingIterator : public InternalIterator { // true for all of the non-current children since current_ is // the smallest child and key() == current_->key(). if (direction_ != kForward) { - // Otherwise, advance the non-current children. We advance current_ - // just after the if-block. - ClearHeaps(); - for (auto& child : children_) { - if (&child != current_) { - child.Seek(key()); - if (child.Valid() && comparator_->Equal(key(), child.key())) { - child.Next(); - } - } - if (child.Valid()) { - minHeap_.push(&child); - } - } - direction_ = kForward; + SwitchToForward(); // The loop advanced all non-current children to be > key() so current_ // should still be strictly the smallest key. assert(current_ == CurrentForward()); @@ -184,15 +202,17 @@ class MergingIterator : public InternalIterator { // current is still valid after the Next() call above. Call // replace_top() to restore the heap property. When the same child // iterator yields a sequence of keys, this is cheap. + assert(current_->status().ok()); minHeap_.replace_top(current_); } else { // current stopped being valid, remove it from the heap. + considerStatus(current_->status()); minHeap_.pop(); } current_ = CurrentForward(); } - virtual void Prev() override { + void Prev() override { assert(Valid()); // Ensure that all children are positioned before key(). // If we are moving in the reverse direction, it is already @@ -203,28 +223,19 @@ class MergingIterator : public InternalIterator { // just after the if-block. ClearHeaps(); InitMaxHeap(); + Slice target = key(); for (auto& child : children_) { if (&child != current_) { - if (!prefix_seek_mode_) { - child.Seek(key()); - if (child.Valid()) { - // Child is at first entry >= key(). Step back one to be < key() - TEST_SYNC_POINT_CALLBACK("MergeIterator::Prev:BeforePrev", - &child); - child.Prev(); - } else { - // Child has no entries >= key(). Position at last entry. - TEST_SYNC_POINT("MergeIterator::Prev:BeforeSeekToLast"); - child.SeekToLast(); - } - } else { - child.SeekForPrev(key()); - if (child.Valid() && comparator_->Equal(key(), child.key())) { - child.Prev(); - } + child.SeekForPrev(target); + TEST_SYNC_POINT_CALLBACK("MergeIterator::Prev:BeforePrev", &child); + considerStatus(child.status()); + if (child.Valid() && comparator_->Equal(target, child.key())) { + child.Prev(); + considerStatus(child.status()); } } if (child.Valid()) { + assert(child.status().ok()); maxHeap_->push(&child); } } @@ -250,50 +261,40 @@ class MergingIterator : public InternalIterator { // current is still valid after the Prev() call above. Call // replace_top() to restore the heap property. When the same child // iterator yields a sequence of keys, this is cheap. + assert(current_->status().ok()); maxHeap_->replace_top(current_); } else { // current stopped being valid, remove it from the heap. + considerStatus(current_->status()); maxHeap_->pop(); } current_ = CurrentReverse(); } - virtual Slice key() const override { + Slice key() const override { assert(Valid()); return current_->key(); } - virtual Slice value() const override { + Slice value() const override { assert(Valid()); return current_->value(); } - virtual Status status() const override { - Status s; - for (auto& child : children_) { - s = child.status(); - if (!s.ok()) { - break; - } - } - return s; - } - - virtual void SetPinnedItersMgr( - PinnedIteratorsManager* pinned_iters_mgr) override { + void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override { pinned_iters_mgr_ = pinned_iters_mgr; for (auto& child : children_) { child.SetPinnedItersMgr(pinned_iters_mgr); } } - virtual bool IsKeyPinned() const override { + bool IsKeyPinned() const override { assert(Valid()); return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && current_->IsKeyPinned(); } - virtual bool IsValuePinned() const override { + bool IsValuePinned() const override { assert(Valid()); return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && current_->IsValuePinned(); @@ -307,13 +308,15 @@ class MergingIterator : public InternalIterator { void InitMaxHeap(); bool is_arena_mode_; - const Comparator* comparator_; + const InternalKeyComparator* comparator_; autovector children_; // Cached pointer to child iterator with the current key, or nullptr if no // child iterators are valid. This is the top of minHeap_ or maxHeap_ // depending on the direction. IteratorWrapper* current_; + // If any of the children have non-ok status, this is one of them. + Status status_; // Which direction is the iterator moving? enum Direction { kForward, @@ -328,6 +331,8 @@ class MergingIterator : public InternalIterator { std::unique_ptr maxHeap_; PinnedIteratorsManager* pinned_iters_mgr_; + void SwitchToForward(); + IteratorWrapper* CurrentForward() const { assert(direction_ == kForward); return !minHeap_.empty() ? minHeap_.top() : nullptr; @@ -340,6 +345,27 @@ class MergingIterator : public InternalIterator { } }; +void MergingIterator::SwitchToForward() { + // Otherwise, advance the non-current children. We advance current_ + // just after the if-block. + ClearHeaps(); + Slice target = key(); + for (auto& child : children_) { + if (&child != current_) { + child.Seek(target); + considerStatus(child.status()); + if (child.Valid() && comparator_->Equal(target, child.key())) { + child.Next(); + considerStatus(child.status()); + } + } + if (child.Valid()) { + minHeap_.push(&child); + } + } + direction_ = kForward; +} + void MergingIterator::ClearHeaps() { minHeap_.clear(); if (maxHeap_) { @@ -353,12 +379,12 @@ void MergingIterator::InitMaxHeap() { } } -InternalIterator* NewMergingIterator(const Comparator* cmp, +InternalIterator* NewMergingIterator(const InternalKeyComparator* cmp, InternalIterator** list, int n, Arena* arena, bool prefix_seek_mode) { assert(n >= 0); if (n == 0) { - return NewEmptyInternalIterator(arena); + return NewEmptyInternalIterator(arena); } else if (n == 1) { return list[0]; } else { @@ -371,18 +397,28 @@ InternalIterator* NewMergingIterator(const Comparator* cmp, } } -MergeIteratorBuilder::MergeIteratorBuilder(const Comparator* comparator, - Arena* a, bool prefix_seek_mode) +MergeIteratorBuilder::MergeIteratorBuilder( + const InternalKeyComparator* comparator, Arena* a, bool prefix_seek_mode) : first_iter(nullptr), use_merging_iter(false), arena(a) { auto mem = arena->AllocateAligned(sizeof(MergingIterator)); merge_iter = new (mem) MergingIterator(comparator, nullptr, 0, true, prefix_seek_mode); } +MergeIteratorBuilder::~MergeIteratorBuilder() { + if (first_iter != nullptr) { + first_iter->~InternalIterator(); + } + if (merge_iter != nullptr) { + merge_iter->~MergingIterator(); + } +} + void MergeIteratorBuilder::AddIterator(InternalIterator* iter) { if (!use_merging_iter && first_iter != nullptr) { merge_iter->AddIterator(first_iter); use_merging_iter = true; + first_iter = nullptr; } if (use_merging_iter) { merge_iter->AddIterator(iter); @@ -392,13 +428,15 @@ void MergeIteratorBuilder::AddIterator(InternalIterator* iter) { } InternalIterator* MergeIteratorBuilder::Finish() { + InternalIterator* ret = nullptr; if (!use_merging_iter) { - return first_iter; + ret = first_iter; + first_iter = nullptr; } else { - auto ret = merge_iter; + ret = merge_iter; merge_iter = nullptr; - return ret; } + return ret; } } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/merging_iterator.h b/thirdparty/rocksdb/table/merging_iterator.h index 48a28d86fd..21ff79bf6b 100644 --- a/thirdparty/rocksdb/table/merging_iterator.h +++ b/thirdparty/rocksdb/table/merging_iterator.h @@ -9,14 +9,17 @@ #pragma once +#include "db/dbformat.h" #include "rocksdb/types.h" namespace rocksdb { class Comparator; -class InternalIterator; class Env; class Arena; +template +class InternalIteratorBase; +using InternalIterator = InternalIteratorBase; // Return an iterator that provided the union of the data in // children[0,n-1]. Takes ownership of the child iterators and @@ -26,10 +29,9 @@ class Arena; // key is present in K child iterators, it will be yielded K times. // // REQUIRES: n >= 0 -extern InternalIterator* NewMergingIterator(const Comparator* comparator, - InternalIterator** children, int n, - Arena* arena = nullptr, - bool prefix_seek_mode = false); +extern InternalIterator* NewMergingIterator( + const InternalKeyComparator* comparator, InternalIterator** children, int n, + Arena* arena = nullptr, bool prefix_seek_mode = false); class MergingIterator; @@ -38,9 +40,9 @@ class MergeIteratorBuilder { public: // comparator: the comparator used in merging comparator // arena: where the merging iterator needs to be allocated from. - explicit MergeIteratorBuilder(const Comparator* comparator, Arena* arena, - bool prefix_seek_mode = false); - ~MergeIteratorBuilder() {} + explicit MergeIteratorBuilder(const InternalKeyComparator* comparator, + Arena* arena, bool prefix_seek_mode = false); + ~MergeIteratorBuilder(); // Add iter to the merging iterator. void AddIterator(InternalIterator* iter); diff --git a/thirdparty/rocksdb/table/meta_blocks.cc b/thirdparty/rocksdb/table/meta_blocks.cc index 19925d7889..57111cfebf 100644 --- a/thirdparty/rocksdb/table/meta_blocks.cc +++ b/thirdparty/rocksdb/table/meta_blocks.cc @@ -11,6 +11,7 @@ #include "rocksdb/table.h" #include "rocksdb/table_properties.h" #include "table/block.h" +#include "table/block_fetcher.h" #include "table/format.h" #include "table/internal_iterator.h" #include "table/persistent_cache_helper.h" @@ -37,8 +38,12 @@ Slice MetaIndexBuilder::Finish() { return meta_index_block_->Finish(); } +// Property block will be read sequentially and cached in a heap located +// object, so there's no need for restart points. Thus we set the restart +// interval to infinity to save space. PropertyBlockBuilder::PropertyBlockBuilder() - : properties_block_(new BlockBuilder(1 /* restart interval */)) {} + : properties_block_( + new BlockBuilder(port::kMaxInt32 /* restart interval */)) {} void PropertyBlockBuilder::Add(const std::string& name, const std::string& val) { @@ -70,7 +75,13 @@ void PropertyBlockBuilder::AddTableProperty(const TableProperties& props) { Add(TablePropertiesNames::kIndexPartitions, props.index_partitions); Add(TablePropertiesNames::kTopLevelIndexSize, props.top_level_index_size); } + Add(TablePropertiesNames::kIndexKeyIsUserKey, props.index_key_is_user_key); + Add(TablePropertiesNames::kIndexValueIsDeltaEncoded, + props.index_value_is_delta_encoded); Add(TablePropertiesNames::kNumEntries, props.num_entries); + Add(TablePropertiesNames::kDeletedKeys, props.num_deletions); + Add(TablePropertiesNames::kMergeOperands, props.num_merge_operands); + Add(TablePropertiesNames::kNumRangeDeletions, props.num_range_deletions); Add(TablePropertiesNames::kNumDataBlocks, props.num_data_blocks); Add(TablePropertiesNames::kFilterSize, props.filter_size); Add(TablePropertiesNames::kFormatVersion, props.format_version); @@ -104,6 +115,9 @@ void PropertyBlockBuilder::AddTableProperty(const TableProperties& props) { if (!props.compression_name.empty()) { Add(TablePropertiesNames::kCompression, props.compression_name); } + if (!props.compression_options.empty()) { + Add(TablePropertiesNames::kCompressionOptions, props.compression_options); + } } Slice PropertyBlockBuilder::Finish() { @@ -140,6 +154,16 @@ bool NotifyCollectTableCollectorsOnAdd( return all_succeeded; } +void NotifyCollectTableCollectorsOnBlockAdd( + const std::vector>& collectors, + const uint64_t blockRawBytes, const uint64_t blockCompressedBytesFast, + const uint64_t blockCompressedBytesSlow) { + for (auto& collector : collectors) { + collector->BlockAdd(blockRawBytes, blockCompressedBytesFast, + blockCompressedBytesSlow); + } +} + bool NotifyCollectTableCollectorsOnFinish( const std::vector>& collectors, Logger* info_log, PropertyBlockBuilder* builder) { @@ -163,7 +187,11 @@ bool NotifyCollectTableCollectorsOnFinish( Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer, const Footer& footer, const ImmutableCFOptions& ioptions, - TableProperties** table_properties) { + TableProperties** table_properties, bool verify_checksum, + BlockHandle* ret_block_handle, + CacheAllocationPtr* verification_buf, + bool /*compression_type_missing*/, + MemoryAllocator* memory_allocator) { assert(table_properties); Slice v = handle_value; @@ -174,10 +202,17 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, BlockContents block_contents; ReadOptions read_options; - read_options.verify_checksums = false; + read_options.verify_checksums = verify_checksum; Status s; - s = ReadBlockContents(file, prefetch_buffer, footer, read_options, handle, - &block_contents, ioptions, false /* decompress */); + PersistentCacheOptions cache_options; + + BlockFetcher block_fetcher( + file, prefetch_buffer, footer, read_options, handle, &block_contents, + ioptions, false /* decompress */, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), cache_options, memory_allocator); + s = block_fetcher.ReadBlockContents(); + // property block is never compressed. Need to add uncompress logic if we are + // to compress it.. if (!s.ok()) { return s; @@ -185,8 +220,9 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, Block properties_block(std::move(block_contents), kDisableGlobalSequenceNumber); - BlockIter iter; - properties_block.NewIterator(BytewiseComparator(), &iter); + DataBlockIter iter; + properties_block.NewIterator(BytewiseComparator(), + BytewiseComparator(), &iter); auto new_table_properties = new TableProperties(); // All pre-defined properties of type uint64_t @@ -197,6 +233,10 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, &new_table_properties->index_partitions}, {TablePropertiesNames::kTopLevelIndexSize, &new_table_properties->top_level_index_size}, + {TablePropertiesNames::kIndexKeyIsUserKey, + &new_table_properties->index_key_is_user_key}, + {TablePropertiesNames::kIndexValueIsDeltaEncoded, + &new_table_properties->index_value_is_delta_encoded}, {TablePropertiesNames::kFilterSize, &new_table_properties->filter_size}, {TablePropertiesNames::kRawKeySize, &new_table_properties->raw_key_size}, {TablePropertiesNames::kRawValueSize, @@ -204,6 +244,12 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, {TablePropertiesNames::kNumDataBlocks, &new_table_properties->num_data_blocks}, {TablePropertiesNames::kNumEntries, &new_table_properties->num_entries}, + {TablePropertiesNames::kDeletedKeys, + &new_table_properties->num_deletions}, + {TablePropertiesNames::kMergeOperands, + &new_table_properties->num_merge_operands}, + {TablePropertiesNames::kNumRangeDeletions, + &new_table_properties->num_range_deletions}, {TablePropertiesNames::kFormatVersion, &new_table_properties->format_version}, {TablePropertiesNames::kFixedKeyLen, @@ -217,16 +263,19 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, }; std::string last_key; - for (iter.SeekToFirst(); iter.Valid(); iter.Next()) { + for (iter.SeekToFirstOrReport(); iter.Valid(); iter.NextOrReport()) { s = iter.status(); if (!s.ok()) { break; } auto key = iter.key().ToString(); - // properties block is strictly sorted with no duplicate key. - assert(last_key.empty() || - BytewiseComparator()->Compare(key, last_key) > 0); + // properties block should be strictly sorted with no duplicate key. + if (!last_key.empty() && + BytewiseComparator()->Compare(key, last_key) <= 0) { + s = Status::Corruption("properties unsorted"); + break; + } last_key = key; auto raw_val = iter.value(); @@ -236,6 +285,12 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, {key, handle.offset() + iter.ValueOffset()}); if (pos != predefined_uint64_properties.end()) { + if (key == TablePropertiesNames::kDeletedKeys || + key == TablePropertiesNames::kMergeOperands) { + // Insert in user-collected properties for API backwards compatibility + new_table_properties->user_collected_properties.insert( + {key, raw_val.ToString()}); + } // handle predefined rocksdb properties uint64_t val; if (!GetVarint64(&raw_val, &val)) { @@ -261,6 +316,8 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, new_table_properties->property_collectors_names = raw_val.ToString(); } else if (key == TablePropertiesNames::kCompression) { new_table_properties->compression_name = raw_val.ToString(); + } else if (key == TablePropertiesNames::kCompressionOptions) { + new_table_properties->compression_options = raw_val.ToString(); } else { // handle user-collected properties new_table_properties->user_collected_properties.insert( @@ -269,6 +326,16 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, } if (s.ok()) { *table_properties = new_table_properties; + if (ret_block_handle != nullptr) { + *ret_block_handle = handle; + } + if (verification_buf != nullptr) { + size_t len = handle.size() + kBlockTrailerSize; + *verification_buf = rocksdb::AllocateBlock(len, memory_allocator); + if (verification_buf->get() != nullptr) { + memcpy(verification_buf->get(), block_contents.data.data(), len); + } + } } else { delete new_table_properties; } @@ -278,8 +345,10 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size, uint64_t table_magic_number, - const ImmutableCFOptions &ioptions, - TableProperties** properties) { + const ImmutableCFOptions& ioptions, + TableProperties** properties, + bool compression_type_missing, + MemoryAllocator* memory_allocator) { // -- Read metaindex block Footer footer; auto s = ReadFooterFromFile(file, nullptr /* prefetch_buffer */, file_size, @@ -292,16 +361,24 @@ Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size, BlockContents metaindex_contents; ReadOptions read_options; read_options.verify_checksums = false; - s = ReadBlockContents(file, nullptr /* prefetch_buffer */, footer, - read_options, metaindex_handle, &metaindex_contents, - ioptions, false /* decompress */); + PersistentCacheOptions cache_options; + + BlockFetcher block_fetcher( + file, nullptr /* prefetch_buffer */, footer, read_options, + metaindex_handle, &metaindex_contents, ioptions, false /* decompress */, + false /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + cache_options, memory_allocator); + s = block_fetcher.ReadBlockContents(); if (!s.ok()) { return s; } + // property blocks are never compressed. Need to add uncompress logic if we + // are to compress it. Block metaindex_block(std::move(metaindex_contents), kDisableGlobalSequenceNumber); std::unique_ptr meta_iter( - metaindex_block.NewIterator(BytewiseComparator())); + metaindex_block.NewIterator(BytewiseComparator(), + BytewiseComparator())); // -- Read property block bool found_properties_block = true; @@ -312,8 +389,11 @@ Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size, TableProperties table_properties; if (found_properties_block == true) { - s = ReadProperties(meta_iter->value(), file, nullptr /* prefetch_buffer */, - footer, ioptions, properties); + s = ReadProperties( + meta_iter->value(), file, nullptr /* prefetch_buffer */, footer, + ioptions, properties, false /* verify_checksum */, + nullptr /* ret_block_hanel */, nullptr /* ret_block_contents */, + compression_type_missing, memory_allocator); } else { s = Status::NotFound(); } @@ -336,9 +416,11 @@ Status FindMetaBlock(InternalIterator* meta_index_iter, Status FindMetaBlock(RandomAccessFileReader* file, uint64_t file_size, uint64_t table_magic_number, - const ImmutableCFOptions &ioptions, + const ImmutableCFOptions& ioptions, const std::string& meta_block_name, - BlockHandle* block_handle) { + BlockHandle* block_handle, + bool /*compression_type_missing*/, + MemoryAllocator* memory_allocator) { Footer footer; auto s = ReadFooterFromFile(file, nullptr /* prefetch_buffer */, file_size, &footer, table_magic_number); @@ -350,17 +432,24 @@ Status FindMetaBlock(RandomAccessFileReader* file, uint64_t file_size, BlockContents metaindex_contents; ReadOptions read_options; read_options.verify_checksums = false; - s = ReadBlockContents(file, nullptr /* prefetch_buffer */, footer, - read_options, metaindex_handle, &metaindex_contents, - ioptions, false /* do decompression */); + PersistentCacheOptions cache_options; + BlockFetcher block_fetcher( + file, nullptr /* prefetch_buffer */, footer, read_options, + metaindex_handle, &metaindex_contents, ioptions, + false /* do decompression */, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), cache_options, memory_allocator); + s = block_fetcher.ReadBlockContents(); if (!s.ok()) { return s; } + // meta blocks are never compressed. Need to add uncompress logic if we are to + // compress it. Block metaindex_block(std::move(metaindex_contents), kDisableGlobalSequenceNumber); std::unique_ptr meta_iter; - meta_iter.reset(metaindex_block.NewIterator(BytewiseComparator())); + meta_iter.reset(metaindex_block.NewIterator( + BytewiseComparator(), BytewiseComparator())); return FindMetaBlock(meta_iter.get(), meta_block_name, block_handle); } @@ -370,7 +459,8 @@ Status ReadMetaBlock(RandomAccessFileReader* file, uint64_t table_magic_number, const ImmutableCFOptions& ioptions, const std::string& meta_block_name, - BlockContents* contents) { + BlockContents* contents, bool /*compression_type_missing*/, + MemoryAllocator* memory_allocator) { Status status; Footer footer; status = ReadFooterFromFile(file, prefetch_buffer, file_size, &footer, @@ -384,19 +474,27 @@ Status ReadMetaBlock(RandomAccessFileReader* file, BlockContents metaindex_contents; ReadOptions read_options; read_options.verify_checksums = false; - status = ReadBlockContents(file, prefetch_buffer, footer, read_options, + PersistentCacheOptions cache_options; + + BlockFetcher block_fetcher(file, prefetch_buffer, footer, read_options, metaindex_handle, &metaindex_contents, ioptions, - false /* decompress */); + false /* decompress */, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), cache_options, + memory_allocator); + status = block_fetcher.ReadBlockContents(); if (!status.ok()) { return status; } + // meta block is never compressed. Need to add uncompress logic if we are to + // compress it. // Finding metablock Block metaindex_block(std::move(metaindex_contents), kDisableGlobalSequenceNumber); std::unique_ptr meta_iter; - meta_iter.reset(metaindex_block.NewIterator(BytewiseComparator())); + meta_iter.reset(metaindex_block.NewIterator( + BytewiseComparator(), BytewiseComparator())); BlockHandle block_handle; status = FindMetaBlock(meta_iter.get(), meta_block_name, &block_handle); @@ -406,9 +504,11 @@ Status ReadMetaBlock(RandomAccessFileReader* file, } // Reading metablock - return ReadBlockContents(file, prefetch_buffer, footer, read_options, - block_handle, contents, ioptions, - false /* decompress */); + BlockFetcher block_fetcher2( + file, prefetch_buffer, footer, read_options, block_handle, contents, + ioptions, false /* decompress */, false /*maybe_compressed*/, + UncompressionDict::GetEmptyDict(), cache_options, memory_allocator); + return block_fetcher2.ReadBlockContents(); } } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/meta_blocks.h b/thirdparty/rocksdb/table/meta_blocks.h index 220985d9e1..6efd1225e1 100644 --- a/thirdparty/rocksdb/table/meta_blocks.h +++ b/thirdparty/rocksdb/table/meta_blocks.h @@ -11,12 +11,13 @@ #include "db/builder.h" #include "db/table_properties_collector.h" -#include "util/kv_map.h" #include "rocksdb/comparator.h" +#include "rocksdb/memory_allocator.h" #include "rocksdb/options.h" #include "rocksdb/slice.h" #include "table/block_builder.h" #include "table/format.h" +#include "util/kv_map.h" namespace rocksdb { @@ -27,7 +28,6 @@ class Footer; class Logger; class RandomAccessFile; struct TableProperties; -class InternalIterator; class MetaIndexBuilder { public: @@ -83,6 +83,11 @@ bool NotifyCollectTableCollectorsOnAdd( const std::vector>& collectors, Logger* info_log); +void NotifyCollectTableCollectorsOnBlockAdd( + const std::vector>& collectors, + uint64_t blockRawBytes, uint64_t blockCompressedBytesFast, + uint64_t blockCompressedBytesSlow); + // NotifyCollectTableCollectorsOnAdd() triggers the `Finish` event for all // property collectors. The collected properties will be added to `builder`. bool NotifyCollectTableCollectorsOnFinish( @@ -96,16 +101,26 @@ bool NotifyCollectTableCollectorsOnFinish( Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file, FilePrefetchBuffer* prefetch_buffer, const Footer& footer, const ImmutableCFOptions& ioptions, - TableProperties** table_properties); + TableProperties** table_properties, bool verify_checksum, + BlockHandle* block_handle, + CacheAllocationPtr* verification_buf, + bool compression_type_missing = false, + MemoryAllocator* memory_allocator = nullptr); // Directly read the properties from the properties block of a plain table. // @returns a status to indicate if the operation succeeded. On success, // *table_properties will point to a heap-allocated TableProperties // object, otherwise value of `table_properties` will not be modified. +// certain tables do not have compression_type byte setup properly for +// uncompressed blocks, caller can request to reset compression type by +// passing compression_type_missing = true, the same applies to +// `ReadProperties`, `FindMetaBlock`, and `ReadMetaBlock` Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size, uint64_t table_magic_number, - const ImmutableCFOptions &ioptions, - TableProperties** properties); + const ImmutableCFOptions& ioptions, + TableProperties** properties, + bool compression_type_missing = false, + MemoryAllocator* memory_allocator = nullptr); // Find the meta block from the meta index block. Status FindMetaBlock(InternalIterator* meta_index_iter, @@ -115,9 +130,11 @@ Status FindMetaBlock(InternalIterator* meta_index_iter, // Find the meta block Status FindMetaBlock(RandomAccessFileReader* file, uint64_t file_size, uint64_t table_magic_number, - const ImmutableCFOptions &ioptions, + const ImmutableCFOptions& ioptions, const std::string& meta_block_name, - BlockHandle* block_handle); + BlockHandle* block_handle, + bool compression_type_missing = false, + MemoryAllocator* memory_allocator = nullptr); // Read the specified meta block with name meta_block_name // from `file` and initialize `contents` with contents of this block. @@ -127,6 +144,8 @@ Status ReadMetaBlock(RandomAccessFileReader* file, uint64_t table_magic_number, const ImmutableCFOptions& ioptions, const std::string& meta_block_name, - BlockContents* contents); + BlockContents* contents, + bool compression_type_missing = false, + MemoryAllocator* memory_allocator = nullptr); } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/mock_table.cc b/thirdparty/rocksdb/table/mock_table.cc index 86c380865c..65a4361696 100644 --- a/thirdparty/rocksdb/table/mock_table.cc +++ b/thirdparty/rocksdb/table/mock_table.cc @@ -26,14 +26,16 @@ stl_wrappers::KVMap MakeMockFile( return stl_wrappers::KVMap(l, stl_wrappers::LessOfComparator(&icmp_)); } -InternalIterator* MockTableReader::NewIterator(const ReadOptions&, - Arena* arena, - bool skip_filters) { +InternalIterator* MockTableReader::NewIterator( + const ReadOptions&, const SliceTransform* /* prefix_extractor */, + Arena* /*arena*/, bool /*skip_filters*/, bool /*for_compaction*/) { return new MockTableIterator(table_); } Status MockTableReader::Get(const ReadOptions&, const Slice& key, - GetContext* get_context, bool skip_filters) { + GetContext* get_context, + const SliceTransform* /*prefix_extractor*/, + bool /*skip_filters*/) { std::unique_ptr iter(new MockTableIterator(table_)); for (iter->Seek(key); iter->Valid(); iter->Next()) { ParsedInternalKey parsed_key; @@ -41,7 +43,8 @@ Status MockTableReader::Get(const ReadOptions&, const Slice& key, return Status::Corruption(Slice()); } - if (!get_context->SaveValue(parsed_key, iter->value())) { + bool dont_care __attribute__((__unused__)); + if (!get_context->SaveValue(parsed_key, iter->value(), &dont_care)) { break; } } @@ -56,10 +59,10 @@ std::shared_ptr MockTableReader::GetTableProperties() MockTableFactory::MockTableFactory() : next_id_(1) {} Status MockTableFactory::NewTableReader( - const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table_reader, - bool prefetch_index_and_filter_in_cache) const { + const TableReaderOptions& /*table_reader_options*/, + std::unique_ptr&& file, uint64_t /*file_size*/, + std::unique_ptr* table_reader, + bool /*prefetch_index_and_filter_in_cache*/) const { uint32_t id = GetIDFromFile(file.get()); MutexLock lock_guard(&file_system_.mutex); @@ -75,8 +78,8 @@ Status MockTableFactory::NewTableReader( } TableBuilder* MockTableFactory::NewTableBuilder( - const TableBuilderOptions& table_builder_options, uint32_t column_family_id, - WritableFileWriter* file) const { + const TableBuilderOptions& /*table_builder_options*/, + uint32_t /*column_family_id*/, WritableFileWriter* file) const { uint32_t id = GetAndWriteNextID(file); return new MockTableBuilder(id, &file_system_); @@ -90,7 +93,7 @@ Status MockTableFactory::CreateMockTable(Env* env, const std::string& fname, return s; } - WritableFileWriter file_writer(std::move(file), EnvOptions()); + WritableFileWriter file_writer(std::move(file), fname, EnvOptions()); uint32_t id = GetAndWriteNextID(&file_writer); file_system_.files.insert({id, std::move(file_contents)}); diff --git a/thirdparty/rocksdb/table/mock_table.h b/thirdparty/rocksdb/table/mock_table.h index 71609a173f..2f123a963c 100644 --- a/thirdparty/rocksdb/table/mock_table.h +++ b/thirdparty/rocksdb/table/mock_table.h @@ -39,13 +39,16 @@ class MockTableReader : public TableReader { explicit MockTableReader(const stl_wrappers::KVMap& table) : table_(table) {} InternalIterator* NewIterator(const ReadOptions&, - Arena* arena, - bool skip_filters = false) override; + const SliceTransform* prefix_extractor, + Arena* arena = nullptr, + bool skip_filters = false, + bool for_compaction = false) override; - Status Get(const ReadOptions&, const Slice& key, GetContext* get_context, + Status Get(const ReadOptions& readOptions, const Slice& key, + GetContext* get_context, const SliceTransform* prefix_extractor, bool skip_filters = false) override; - uint64_t ApproximateOffsetOf(const Slice& key) override { return 0; } + uint64_t ApproximateOffsetOf(const Slice& /*key*/) override { return 0; } virtual size_t ApproximateMemoryUsage() const override { return 0; } @@ -154,8 +157,8 @@ class MockTableFactory : public TableFactory { const char* Name() const override { return "MockTable"; } Status NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table_reader, + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table_reader, bool prefetch_index_and_filter_in_cache = true) const override; TableBuilder* NewTableBuilder( const TableBuilderOptions& table_builder_options, @@ -168,8 +171,8 @@ class MockTableFactory : public TableFactory { stl_wrappers::KVMap file_contents); virtual Status SanitizeOptions( - const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { + const DBOptions& /*db_opts*/, + const ColumnFamilyOptions& /*cf_opts*/) const override { return Status::OK(); } diff --git a/thirdparty/rocksdb/table/partitioned_filter_block.cc b/thirdparty/rocksdb/table/partitioned_filter_block.cc index 202245939f..aab0f5509b 100644 --- a/thirdparty/rocksdb/table/partitioned_filter_block.cc +++ b/thirdparty/rocksdb/table/partitioned_filter_block.cc @@ -5,6 +5,13 @@ #include "table/partitioned_filter_block.h" +#ifdef ROCKSDB_MALLOC_USABLE_SIZE +#ifdef OS_FREEBSD +#include +#else +#include +#endif +#endif #include #include "monitoring/perf_context_imp.h" @@ -19,13 +26,20 @@ namespace rocksdb { PartitionedFilterBlockBuilder::PartitionedFilterBlockBuilder( const SliceTransform* prefix_extractor, bool whole_key_filtering, FilterBitsBuilder* filter_bits_builder, int index_block_restart_interval, + const bool use_value_delta_encoding, PartitionedIndexBuilder* const p_index_builder, const uint32_t partition_size) : FullFilterBlockBuilder(prefix_extractor, whole_key_filtering, filter_bits_builder), - index_on_filter_block_builder_(index_block_restart_interval), + index_on_filter_block_builder_(index_block_restart_interval, + true /*use_delta_encoding*/, + use_value_delta_encoding), + index_on_filter_block_builder_without_seq_(index_block_restart_interval, + true /*use_delta_encoding*/, + use_value_delta_encoding), p_index_builder_(p_index_builder), - filters_in_partition_(0) { + filters_in_partition_(0), + num_added_(0) { filters_per_partition_ = filter_bits_builder_->CalculateNumEntry(partition_size); } @@ -47,12 +61,14 @@ void PartitionedFilterBlockBuilder::MaybeCutAFilterBlock() { std::string& index_key = p_index_builder_->GetPartitionKey(); filters.push_back({index_key, filter}); filters_in_partition_ = 0; + Reset(); } void PartitionedFilterBlockBuilder::AddKey(const Slice& key) { MaybeCutAFilterBlock(); filter_bits_builder_->AddKey(key); filters_in_partition_++; + num_added_++; } Slice PartitionedFilterBlockBuilder::Finish( @@ -62,7 +78,19 @@ Slice PartitionedFilterBlockBuilder::Finish( FilterEntry& last_entry = filters.front(); std::string handle_encoding; last_partition_block_handle.EncodeTo(&handle_encoding); - index_on_filter_block_builder_.Add(last_entry.key, handle_encoding); + std::string handle_delta_encoding; + PutVarsignedint64( + &handle_delta_encoding, + last_partition_block_handle.size() - last_encoded_handle_.size()); + last_encoded_handle_ = last_partition_block_handle; + const Slice handle_delta_encoding_slice(handle_delta_encoding); + index_on_filter_block_builder_.Add(last_entry.key, handle_encoding, + &handle_delta_encoding_slice); + if (!p_index_builder_->seperator_is_key_plus_seq()) { + index_on_filter_block_builder_without_seq_.Add( + ExtractUserKey(last_entry.key), handle_encoding, + &handle_delta_encoding_slice); + } filters.pop_front(); } else { MaybeCutAFilterBlock(); @@ -72,7 +100,11 @@ Slice PartitionedFilterBlockBuilder::Finish( if (UNLIKELY(filters.empty())) { *status = Status::OK(); if (finishing_filters) { - return index_on_filter_block_builder_.Finish(); + if (p_index_builder_->seperator_is_key_plus_seq()) { + return index_on_filter_block_builder_.Finish(); + } else { + return index_on_filter_block_builder_without_seq_.Finish(); + } } else { // This is the rare case where no key was added to the filter return Slice(); @@ -88,13 +120,16 @@ Slice PartitionedFilterBlockBuilder::Finish( PartitionedFilterBlockReader::PartitionedFilterBlockReader( const SliceTransform* prefix_extractor, bool _whole_key_filtering, - BlockContents&& contents, FilterBitsReader* filter_bits_reader, - Statistics* stats, const Comparator& comparator, - const BlockBasedTable* table) + BlockContents&& contents, FilterBitsReader* /*filter_bits_reader*/, + Statistics* stats, const InternalKeyComparator comparator, + const BlockBasedTable* table, const bool index_key_includes_seq, + const bool index_value_is_full) : FilterBlockReader(contents.data.size(), stats, _whole_key_filtering), prefix_extractor_(prefix_extractor), comparator_(comparator), - table_(table) { + table_(table), + index_key_includes_seq_(index_key_includes_seq), + index_value_is_full_(index_value_is_full) { idx_on_fltr_blk_.reset(new Block(std::move(contents), kDisableGlobalSequenceNumber, 0 /* read_amp_bytes_per_bit */, stats)); @@ -109,17 +144,15 @@ PartitionedFilterBlockReader::~PartitionedFilterBlockReader() { return; } char cache_key[BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length]; - BlockIter biter; + IndexBlockIter biter; BlockHandle handle; - idx_on_fltr_blk_->NewIterator(&comparator_, &biter, true); + Statistics* kNullStats = nullptr; + idx_on_fltr_blk_->NewIterator( + &comparator_, comparator_.user_comparator(), &biter, kNullStats, true, + index_key_includes_seq_, index_value_is_full_); biter.SeekToFirst(); for (; biter.Valid(); biter.Next()) { - auto input = biter.value(); - auto s = handle.DecodeFrom(&input); - assert(s.ok()); - if (!s.ok()) { - continue; - } + handle = biter.value(); auto key = BlockBasedTable::GetCacheKey(table_->rep_->cache_key_prefix, table_->rep_->cache_key_prefix_size, handle, cache_key); @@ -128,7 +161,8 @@ PartitionedFilterBlockReader::~PartitionedFilterBlockReader() { } bool PartitionedFilterBlockReader::KeyMayMatch( - const Slice& key, uint64_t block_offset, const bool no_io, + const Slice& key, const SliceTransform* prefix_extractor, + uint64_t block_offset, const bool no_io, const Slice* const const_ikey_ptr) { assert(const_ikey_ptr != nullptr); assert(block_offset == kNotValid); @@ -143,12 +177,14 @@ bool PartitionedFilterBlockReader::KeyMayMatch( return false; } bool cached = false; - auto filter_partition = GetFilterPartition(nullptr /* prefetch_buffer */, - &filter_handle, no_io, &cached); + auto filter_partition = + GetFilterPartition(nullptr /* prefetch_buffer */, filter_handle, no_io, + &cached, prefix_extractor); if (UNLIKELY(!filter_partition.value)) { return true; } - auto res = filter_partition.value->KeyMayMatch(key, block_offset, no_io); + auto res = filter_partition.value->KeyMayMatch(key, prefix_extractor, + block_offset, no_io); if (cached) { return res; } @@ -161,11 +197,15 @@ bool PartitionedFilterBlockReader::KeyMayMatch( } bool PartitionedFilterBlockReader::PrefixMayMatch( - const Slice& prefix, uint64_t block_offset, const bool no_io, + const Slice& prefix, const SliceTransform* prefix_extractor, + uint64_t block_offset, const bool no_io, const Slice* const const_ikey_ptr) { +#ifdef NDEBUG + (void)block_offset; +#endif assert(const_ikey_ptr != nullptr); assert(block_offset == kNotValid); - if (!prefix_extractor_) { + if (!prefix_extractor_ && !prefix_extractor) { return true; } if (UNLIKELY(idx_on_fltr_blk_->size() == 0)) { @@ -176,12 +216,14 @@ bool PartitionedFilterBlockReader::PrefixMayMatch( return false; } bool cached = false; - auto filter_partition = GetFilterPartition(nullptr /* prefetch_buffer */, - &filter_handle, no_io, &cached); + auto filter_partition = + GetFilterPartition(nullptr /* prefetch_buffer */, filter_handle, no_io, + &cached, prefix_extractor); if (UNLIKELY(!filter_partition.value)) { return true; } - auto res = filter_partition.value->PrefixMayMatch(prefix, kNotValid, no_io); + auto res = filter_partition.value->PrefixMayMatch(prefix, prefix_extractor, + kNotValid, no_io); if (cached) { return res; } @@ -193,26 +235,26 @@ bool PartitionedFilterBlockReader::PrefixMayMatch( return res; } -Slice PartitionedFilterBlockReader::GetFilterPartitionHandle( +BlockHandle PartitionedFilterBlockReader::GetFilterPartitionHandle( const Slice& entry) { - BlockIter iter; - idx_on_fltr_blk_->NewIterator(&comparator_, &iter, true); + IndexBlockIter iter; + Statistics* kNullStats = nullptr; + idx_on_fltr_blk_->NewIterator( + &comparator_, comparator_.user_comparator(), &iter, kNullStats, true, + index_key_includes_seq_, index_value_is_full_); iter.Seek(entry); if (UNLIKELY(!iter.Valid())) { - return Slice(); + return BlockHandle(0, 0); } assert(iter.Valid()); - Slice handle_value = iter.value(); - return handle_value; + BlockHandle fltr_blk_handle = iter.value(); + return fltr_blk_handle; } BlockBasedTable::CachableEntry PartitionedFilterBlockReader::GetFilterPartition( - FilePrefetchBuffer* prefetch_buffer, Slice* handle_value, const bool no_io, - bool* cached) { - BlockHandle fltr_blk_handle; - auto s = fltr_blk_handle.DecodeFrom(handle_value); - assert(s.ok()); + FilePrefetchBuffer* prefetch_buffer, BlockHandle& fltr_blk_handle, + const bool no_io, bool* cached, const SliceTransform* prefix_extractor) { const bool is_a_filter_partition = true; auto block_cache = table_->rep_->table_options.block_cache.get(); if (LIKELY(block_cache != nullptr)) { @@ -231,74 +273,76 @@ PartitionedFilterBlockReader::GetFilterPartition( } } return table_->GetFilter(/*prefetch_buffer*/ nullptr, fltr_blk_handle, - is_a_filter_partition, no_io); + is_a_filter_partition, no_io, + /* get_context */ nullptr, prefix_extractor); } else { auto filter = table_->ReadFilter(prefetch_buffer, fltr_blk_handle, - is_a_filter_partition); + is_a_filter_partition, prefix_extractor); return {filter, nullptr}; } } size_t PartitionedFilterBlockReader::ApproximateMemoryUsage() const { - return idx_on_fltr_blk_->size(); + size_t usage = idx_on_fltr_blk_->usable_size(); +#ifdef ROCKSDB_MALLOC_USABLE_SIZE + usage += malloc_usable_size((void*)this); +#else + usage += sizeof(*this); +#endif // ROCKSDB_MALLOC_USABLE_SIZE + return usage; + // TODO(myabandeh): better estimation for filter_map_ size +} + +// Release the cached entry and decrement its ref count. +void ReleaseFilterCachedEntry(void* arg, void* h) { + Cache* cache = reinterpret_cast(arg); + Cache::Handle* handle = reinterpret_cast(h); + cache->Release(handle); } // TODO(myabandeh): merge this with the same function in IndexReader -void PartitionedFilterBlockReader::CacheDependencies(bool pin) { +void PartitionedFilterBlockReader::CacheDependencies( + bool pin, const SliceTransform* prefix_extractor) { // Before read partitions, prefetch them to avoid lots of IOs auto rep = table_->rep_; - BlockIter biter; - BlockHandle handle; - idx_on_fltr_blk_->NewIterator(&comparator_, &biter, true); + IndexBlockIter biter; + Statistics* kNullStats = nullptr; + idx_on_fltr_blk_->NewIterator( + &comparator_, comparator_.user_comparator(), &biter, kNullStats, true, + index_key_includes_seq_, index_value_is_full_); // Index partitions are assumed to be consecuitive. Prefetch them all. // Read the first block offset biter.SeekToFirst(); - Slice input = biter.value(); - Status s = handle.DecodeFrom(&input); - assert(s.ok()); - if (!s.ok()) { - ROCKS_LOG_WARN(rep->ioptions.info_log, - "Could not read first index partition"); - return; - } + BlockHandle handle = biter.value(); uint64_t prefetch_off = handle.offset(); // Read the last block's offset biter.SeekToLast(); - input = biter.value(); - s = handle.DecodeFrom(&input); - assert(s.ok()); - if (!s.ok()) { - ROCKS_LOG_WARN(rep->ioptions.info_log, - "Could not read last index partition"); - return; - } + handle = biter.value(); uint64_t last_off = handle.offset() + handle.size() + kBlockTrailerSize; uint64_t prefetch_len = last_off - prefetch_off; std::unique_ptr prefetch_buffer; auto& file = table_->rep_->file; prefetch_buffer.reset(new FilePrefetchBuffer()); - s = prefetch_buffer->Prefetch(file.get(), prefetch_off, prefetch_len); + Status s; + s = prefetch_buffer->Prefetch(file.get(), prefetch_off, + static_cast(prefetch_len)); // After prefetch, read the partitions one by one biter.SeekToFirst(); Cache* block_cache = rep->table_options.block_cache.get(); for (; biter.Valid(); biter.Next()) { - input = biter.value(); - s = handle.DecodeFrom(&input); - assert(s.ok()); - if (!s.ok()) { - ROCKS_LOG_WARN(rep->ioptions.info_log, "Could not read index partition"); - continue; - } - + handle = biter.value(); const bool no_io = true; const bool is_a_filter_partition = true; - auto filter = table_->GetFilter(prefetch_buffer.get(), handle, - is_a_filter_partition, !no_io); + auto filter = table_->GetFilter( + prefetch_buffer.get(), handle, is_a_filter_partition, !no_io, + /* get_context */ nullptr, prefix_extractor); if (LIKELY(filter.IsSet())) { if (pin) { filter_map_[handle.offset()] = std::move(filter); + RegisterCleanup(&ReleaseFilterCachedEntry, block_cache, + filter.cache_handle); } else { block_cache->Release(filter.cache_handle); } diff --git a/thirdparty/rocksdb/table/partitioned_filter_block.h b/thirdparty/rocksdb/table/partitioned_filter_block.h index 1a00a86e6c..5d55da5449 100644 --- a/thirdparty/rocksdb/table/partitioned_filter_block.h +++ b/thirdparty/rocksdb/table/partitioned_filter_block.h @@ -26,6 +26,7 @@ class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder { explicit PartitionedFilterBlockBuilder( const SliceTransform* prefix_extractor, bool whole_key_filtering, FilterBitsBuilder* filter_bits_builder, int index_block_restart_interval, + const bool use_value_delta_encoding, PartitionedIndexBuilder* const p_index_builder, const uint32_t partition_size); @@ -33,12 +34,16 @@ class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder { void AddKey(const Slice& key) override; + size_t NumAdded() const override { return num_added_; } + virtual Slice Finish(const BlockHandle& last_partition_block_handle, Status* status) override; private: // Filter data BlockBuilder index_on_filter_block_builder_; // top-level index builder + BlockBuilder + index_on_filter_block_builder_without_seq_; // same for user keys struct FilterEntry { std::string key; Slice filter; @@ -59,41 +64,48 @@ class PartitionedFilterBlockBuilder : public FullFilterBlockBuilder { uint32_t filters_per_partition_; // The current number of filters in the last partition uint32_t filters_in_partition_; + // Number of keys added + size_t num_added_; + BlockHandle last_encoded_handle_; }; -class PartitionedFilterBlockReader : public FilterBlockReader { +class PartitionedFilterBlockReader : public FilterBlockReader, + public Cleanable { public: - explicit PartitionedFilterBlockReader(const SliceTransform* prefix_extractor, - bool whole_key_filtering, - BlockContents&& contents, - FilterBitsReader* filter_bits_reader, - Statistics* stats, - const Comparator& comparator, - const BlockBasedTable* table); + explicit PartitionedFilterBlockReader( + const SliceTransform* prefix_extractor, bool whole_key_filtering, + BlockContents&& contents, FilterBitsReader* filter_bits_reader, + Statistics* stats, const InternalKeyComparator comparator, + const BlockBasedTable* table, const bool index_key_includes_seq, + const bool index_value_is_full); virtual ~PartitionedFilterBlockReader(); virtual bool IsBlockBased() override { return false; } virtual bool KeyMayMatch( - const Slice& key, uint64_t block_offset = kNotValid, - const bool no_io = false, + const Slice& key, const SliceTransform* prefix_extractor, + uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) override; virtual bool PrefixMayMatch( - const Slice& prefix, uint64_t block_offset = kNotValid, - const bool no_io = false, + const Slice& prefix, const SliceTransform* prefix_extractor, + uint64_t block_offset = kNotValid, const bool no_io = false, const Slice* const const_ikey_ptr = nullptr) override; virtual size_t ApproximateMemoryUsage() const override; private: - Slice GetFilterPartitionHandle(const Slice& entry); + BlockHandle GetFilterPartitionHandle(const Slice& entry); BlockBasedTable::CachableEntry GetFilterPartition( - FilePrefetchBuffer* prefetch_buffer, Slice* handle, const bool no_io, - bool* cached); - virtual void CacheDependencies(bool pin) override; + FilePrefetchBuffer* prefetch_buffer, BlockHandle& handle, + const bool no_io, bool* cached, + const SliceTransform* prefix_extractor = nullptr); + virtual void CacheDependencies( + bool bin, const SliceTransform* prefix_extractor) override; const SliceTransform* prefix_extractor_; std::unique_ptr idx_on_fltr_blk_; - const Comparator& comparator_; + const InternalKeyComparator comparator_; const BlockBasedTable* table_; + const bool index_key_includes_seq_; + const bool index_value_is_full_; std::unordered_map> filter_map_; diff --git a/thirdparty/rocksdb/table/partitioned_filter_block_test.cc b/thirdparty/rocksdb/table/partitioned_filter_block_test.cc index 1bc529ed97..8068f14d81 100644 --- a/thirdparty/rocksdb/table/partitioned_filter_block_test.cc +++ b/thirdparty/rocksdb/table/partitioned_filter_block_test.cc @@ -27,18 +27,32 @@ class MockedBlockBasedTable : public BlockBasedTable { rep->cache_key_prefix_size = 10; } - virtual CachableEntry GetFilter( + CachableEntry GetFilter( FilePrefetchBuffer*, const BlockHandle& filter_blk_handle, - const bool /* unused */, bool /* unused */) const override { + const bool /* unused */, bool /* unused */, GetContext* /* unused */, + const SliceTransform* prefix_extractor) const override { Slice slice = slices[filter_blk_handle.offset()]; auto obj = new FullFilterBlockReader( - nullptr, true, BlockContents(slice, false, kNoCompression), + prefix_extractor, true, BlockContents(slice), rep_->table_options.filter_policy->GetFilterBitsReader(slice), nullptr); return {obj, nullptr}; } + + FilterBlockReader* ReadFilter( + FilePrefetchBuffer*, const BlockHandle& filter_blk_handle, + const bool /* unused */, + const SliceTransform* prefix_extractor) const override { + Slice slice = slices[filter_blk_handle.offset()]; + auto obj = new FullFilterBlockReader( + prefix_extractor, true, BlockContents(slice), + rep_->table_options.filter_policy->GetFilterBitsReader(slice), nullptr); + return obj; + } }; -class PartitionedFilterBlockTest : public testing::Test { +class PartitionedFilterBlockTest + : public testing::Test, + virtual public ::testing::WithParamInterface { public: BlockBasedTableOptions table_options_; InternalKeyComparator icomp = InternalKeyComparator(BytewiseComparator()); @@ -48,10 +62,12 @@ class PartitionedFilterBlockTest : public testing::Test { table_options_.no_block_cache = true; // Otherwise BlockBasedTable::Close // will access variable that are not // initialized in our mocked version + table_options_.format_version = GetParam(); + table_options_.index_block_restart_interval = 3; } std::shared_ptr cache_; - ~PartitionedFilterBlockTest() {} + ~PartitionedFilterBlockTest() override {} const std::string keys[4] = {"afoo", "bar", "box", "hello"}; const std::string missing_keys[2] = {"missing", "other"}; @@ -75,7 +91,8 @@ class PartitionedFilterBlockTest : public testing::Test { auto partition_size = filter_bits_reader->CalculateSpace(num_keys, &dont_care1, &dont_care2); delete filter_bits_reader; - return partition_size + table_options_.block_size_deviation; + return partition_size + + partition_size * table_options_.block_size_deviation / 100; } int last_offset = 10; @@ -87,27 +104,34 @@ class PartitionedFilterBlockTest : public testing::Test { } PartitionedIndexBuilder* NewIndexBuilder() { - return PartitionedIndexBuilder::CreateIndexBuilder(&icomp, table_options_); + const bool kValueDeltaEncoded = true; + return PartitionedIndexBuilder::CreateIndexBuilder( + &icomp, !kValueDeltaEncoded, table_options_); } PartitionedFilterBlockBuilder* NewBuilder( - PartitionedIndexBuilder* const p_index_builder) { + PartitionedIndexBuilder* const p_index_builder, + const SliceTransform* prefix_extractor = nullptr) { assert(table_options_.block_size_deviation <= 100); auto partition_size = static_cast( - table_options_.metadata_block_size * - ( 100 - table_options_.block_size_deviation)); + ((table_options_.metadata_block_size * + (100 - table_options_.block_size_deviation)) + + 99) / + 100); partition_size = std::max(partition_size, static_cast(1)); + const bool kValueDeltaEncoded = true; return new PartitionedFilterBlockBuilder( - nullptr, table_options_.whole_key_filtering, + prefix_extractor, table_options_.whole_key_filtering, table_options_.filter_policy->GetFilterBitsBuilder(), - table_options_.index_block_restart_interval, p_index_builder, - partition_size); + table_options_.index_block_restart_interval, !kValueDeltaEncoded, + p_index_builder, partition_size); } std::unique_ptr table; PartitionedFilterBlockReader* NewReader( - PartitionedFilterBlockBuilder* builder) { + PartitionedFilterBlockBuilder* builder, PartitionedIndexBuilder* pib, + const SliceTransform* prefix_extractor) { BlockHandle bh; Status status; Slice slice; @@ -117,40 +141,51 @@ class PartitionedFilterBlockTest : public testing::Test { } while (status.IsIncomplete()); const Options options; const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); const EnvOptions env_options; - table.reset(new MockedBlockBasedTable(new BlockBasedTable::Rep( - ioptions, env_options, table_options_, icomp, false))); + const bool kSkipFilters = true; + const bool kImmortal = true; + table.reset(new MockedBlockBasedTable( + new BlockBasedTable::Rep(ioptions, env_options, table_options_, icomp, + !kSkipFilters, 0, !kImmortal))); auto reader = new PartitionedFilterBlockReader( - nullptr, true, BlockContents(slice, false, kNoCompression), nullptr, - nullptr, *icomp.user_comparator(), table.get()); + prefix_extractor, true, BlockContents(slice), nullptr, nullptr, icomp, + table.get(), pib->seperator_is_key_plus_seq(), + !pib->get_use_value_delta_encoding()); return reader; } void VerifyReader(PartitionedFilterBlockBuilder* builder, - bool empty = false) { - std::unique_ptr reader(NewReader(builder)); + PartitionedIndexBuilder* pib, bool empty = false, + const SliceTransform* prefix_extractor = nullptr) { + std::unique_ptr reader( + NewReader(builder, pib, prefix_extractor)); // Querying added keys const bool no_io = true; for (auto key : keys) { auto ikey = InternalKey(key, 0, ValueType::kTypeValue); const Slice ikey_slice = Slice(*ikey.rep()); - ASSERT_TRUE(reader->KeyMayMatch(key, kNotValid, !no_io, &ikey_slice)); + ASSERT_TRUE(reader->KeyMayMatch(key, prefix_extractor, kNotValid, !no_io, + &ikey_slice)); } { // querying a key twice auto ikey = InternalKey(keys[0], 0, ValueType::kTypeValue); const Slice ikey_slice = Slice(*ikey.rep()); - ASSERT_TRUE(reader->KeyMayMatch(keys[0], kNotValid, !no_io, &ikey_slice)); + ASSERT_TRUE(reader->KeyMayMatch(keys[0], prefix_extractor, kNotValid, + !no_io, &ikey_slice)); } // querying missing keys for (auto key : missing_keys) { auto ikey = InternalKey(key, 0, ValueType::kTypeValue); const Slice ikey_slice = Slice(*ikey.rep()); if (empty) { - ASSERT_TRUE(reader->KeyMayMatch(key, kNotValid, !no_io, &ikey_slice)); + ASSERT_TRUE(reader->KeyMayMatch(key, prefix_extractor, kNotValid, + !no_io, &ikey_slice)); } else { // assuming a good hash function - ASSERT_FALSE(reader->KeyMayMatch(key, kNotValid, !no_io, &ikey_slice)); + ASSERT_FALSE(reader->KeyMayMatch(key, prefix_extractor, kNotValid, + !no_io, &ikey_slice)); } } } @@ -173,14 +208,14 @@ class PartitionedFilterBlockTest : public testing::Test { builder->Add(keys[i]); CutABlock(pib.get(), keys[i]); - VerifyReader(builder.get()); + VerifyReader(builder.get(), pib.get()); return CountNumOfIndexPartitions(pib.get()); } - void TestBlockPerTwoKeys() { + void TestBlockPerTwoKeys(const SliceTransform* prefix_extractor = nullptr) { std::unique_ptr pib(NewIndexBuilder()); std::unique_ptr builder( - NewBuilder(pib.get())); + NewBuilder(pib.get(), prefix_extractor)); int i = 0; builder->Add(keys[i]); i++; @@ -193,7 +228,7 @@ class PartitionedFilterBlockTest : public testing::Test { builder->Add(keys[i]); CutABlock(pib.get(), keys[i]); - VerifyReader(builder.get()); + VerifyReader(builder.get(), pib.get(), prefix_extractor); } void TestBlockPerAllKeys() { @@ -211,7 +246,7 @@ class PartitionedFilterBlockTest : public testing::Test { builder->Add(keys[i]); CutABlock(pib.get(), keys[i]); - VerifyReader(builder.get()); + VerifyReader(builder.get(), pib.get()); } void CutABlock(PartitionedIndexBuilder* builder, @@ -248,14 +283,19 @@ class PartitionedFilterBlockTest : public testing::Test { } }; -TEST_F(PartitionedFilterBlockTest, EmptyBuilder) { +INSTANTIATE_TEST_CASE_P(FormatDef, PartitionedFilterBlockTest, + testing::Values(test::kDefaultFormatVersion)); +INSTANTIATE_TEST_CASE_P(FormatLatest, PartitionedFilterBlockTest, + testing::Values(test::kLatestFormatVersion)); + +TEST_P(PartitionedFilterBlockTest, EmptyBuilder) { std::unique_ptr pib(NewIndexBuilder()); std::unique_ptr builder(NewBuilder(pib.get())); const bool empty = true; - VerifyReader(builder.get(), empty); + VerifyReader(builder.get(), pib.get(), empty); } -TEST_F(PartitionedFilterBlockTest, OneBlock) { +TEST_P(PartitionedFilterBlockTest, OneBlock) { uint64_t max_index_size = MaxIndexSize(); for (uint64_t i = 1; i < max_index_size + 1; i++) { table_options_.metadata_block_size = i; @@ -263,7 +303,7 @@ TEST_F(PartitionedFilterBlockTest, OneBlock) { } } -TEST_F(PartitionedFilterBlockTest, TwoBlocksPerKey) { +TEST_P(PartitionedFilterBlockTest, TwoBlocksPerKey) { uint64_t max_index_size = MaxIndexSize(); for (uint64_t i = 1; i < max_index_size + 1; i++) { table_options_.metadata_block_size = i; @@ -271,7 +311,35 @@ TEST_F(PartitionedFilterBlockTest, TwoBlocksPerKey) { } } -TEST_F(PartitionedFilterBlockTest, OneBlockPerKey) { +// This reproduces the bug that a prefix is the same among multiple consecutive +// blocks but the bug would add it only to the first block. +TEST_P(PartitionedFilterBlockTest, SamePrefixInMultipleBlocks) { + // some small number to cause partition cuts + table_options_.metadata_block_size = 1; + std::unique_ptr prefix_extractor + (rocksdb::NewFixedPrefixTransform(1)); + std::unique_ptr pib(NewIndexBuilder()); + std::unique_ptr builder( + NewBuilder(pib.get(), prefix_extractor.get())); + const std::string pkeys[3] = {"p-key1", "p-key2", "p-key3"}; + builder->Add(pkeys[0]); + CutABlock(pib.get(), pkeys[0], pkeys[1]); + builder->Add(pkeys[1]); + CutABlock(pib.get(), pkeys[1], pkeys[2]); + builder->Add(pkeys[2]); + CutABlock(pib.get(), pkeys[2]); + std::unique_ptr reader( + NewReader(builder.get(), pib.get(), prefix_extractor.get())); + for (auto key : pkeys) { + auto ikey = InternalKey(key, 0, ValueType::kTypeValue); + const Slice ikey_slice = Slice(*ikey.rep()); + ASSERT_TRUE(reader->PrefixMayMatch(prefix_extractor->Transform(key), + prefix_extractor.get(), kNotValid, + false /*no_io*/, &ikey_slice)); + } +} + +TEST_P(PartitionedFilterBlockTest, OneBlockPerKey) { uint64_t max_index_size = MaxIndexSize(); for (uint64_t i = 1; i < max_index_size + 1; i++) { table_options_.metadata_block_size = i; @@ -279,7 +347,7 @@ TEST_F(PartitionedFilterBlockTest, OneBlockPerKey) { } } -TEST_F(PartitionedFilterBlockTest, PartitionCount) { +TEST_P(PartitionedFilterBlockTest, PartitionCount) { int num_keys = sizeof(keys) / sizeof(*keys); table_options_.metadata_block_size = std::max(MaxIndexSize(), MaxFilterSize()); diff --git a/thirdparty/rocksdb/table/persistent_cache_helper.cc b/thirdparty/rocksdb/table/persistent_cache_helper.cc index ec1cac0b9d..4e90697a6e 100644 --- a/thirdparty/rocksdb/table/persistent_cache_helper.cc +++ b/thirdparty/rocksdb/table/persistent_cache_helper.cc @@ -29,12 +29,9 @@ void PersistentCacheHelper::InsertUncompressedPage( const BlockContents& contents) { assert(cache_options.persistent_cache); assert(!cache_options.persistent_cache->IsCompressed()); - if (!contents.cachable || contents.compression_type != kNoCompression) { - // We shouldn't cache this. Either - // (1) content is not cacheable - // (2) content is compressed - return; - } + // Precondition: + // (1) content is cacheable + // (2) content is not compressed // construct the page key char cache_key[BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length]; @@ -49,6 +46,9 @@ void PersistentCacheHelper::InsertUncompressedPage( Status PersistentCacheHelper::LookupRawPage( const PersistentCacheOptions& cache_options, const BlockHandle& handle, std::unique_ptr* raw_data, const size_t raw_data_size) { +#ifdef NDEBUG + (void)raw_data_size; +#endif assert(cache_options.persistent_cache); assert(cache_options.persistent_cache->IsCompressed()); @@ -106,8 +106,7 @@ Status PersistentCacheHelper::LookupUncompressedPage( // update stats RecordTick(cache_options.statistics, PERSISTENT_CACHE_HIT); // construct result and return - *contents = - BlockContents(std::move(data), size, false /*cacheable*/, kNoCompression); + *contents = BlockContents(std::move(data), size); return Status::OK(); } diff --git a/thirdparty/rocksdb/table/plain_table_builder.cc b/thirdparty/rocksdb/table/plain_table_builder.cc index 964804358a..453b6c768b 100644 --- a/thirdparty/rocksdb/table/plain_table_builder.cc +++ b/thirdparty/rocksdb/table/plain_table_builder.cc @@ -57,7 +57,7 @@ extern const uint64_t kPlainTableMagicNumber = 0x8242229663bf9564ull; extern const uint64_t kLegacyPlainTableMagicNumber = 0x4f3418eb7a8f13b8ull; PlainTableBuilder::PlainTableBuilder( - const ImmutableCFOptions& ioptions, + const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, WritableFileWriter* file, uint32_t user_key_len, @@ -66,20 +66,21 @@ PlainTableBuilder::PlainTableBuilder( uint32_t num_probes, size_t huge_page_tlb_size, double hash_table_ratio, bool store_index_in_file) : ioptions_(ioptions), + moptions_(moptions), bloom_block_(num_probes), file_(file), bloom_bits_per_key_(bloom_bits_per_key), huge_page_tlb_size_(huge_page_tlb_size), - encoder_(encoding_type, user_key_len, ioptions.prefix_extractor, + encoder_(encoding_type, user_key_len, moptions.prefix_extractor.get(), index_sparseness), store_index_in_file_(store_index_in_file), - prefix_extractor_(ioptions.prefix_extractor) { + prefix_extractor_(moptions.prefix_extractor.get()) { // Build index block and save it in the file if hash_table_ratio > 0 if (store_index_in_file_) { assert(hash_table_ratio > 0 || IsTotalOrderMode()); - index_builder_.reset( - new PlainTableIndexBuilder(&arena_, ioptions, index_sparseness, - hash_table_ratio, huge_page_tlb_size_)); + index_builder_.reset(new PlainTableIndexBuilder( + &arena_, ioptions, moptions.prefix_extractor.get(), index_sparseness, + hash_table_ratio, huge_page_tlb_size_)); properties_.user_collected_properties [PlainTablePropertyNames::kBloomVersion] = "1"; // For future use } @@ -96,8 +97,8 @@ PlainTableBuilder::PlainTableBuilder( properties_.format_version = (encoding_type == kPlain) ? 0 : 1; properties_.column_family_id = column_family_id; properties_.column_family_name = column_family_name; - properties_.prefix_extractor_name = ioptions_.prefix_extractor != nullptr - ? ioptions_.prefix_extractor->Name() + properties_.prefix_extractor_name = moptions_.prefix_extractor != nullptr + ? moptions_.prefix_extractor->Name() : "nullptr"; std::string val; @@ -131,11 +132,11 @@ void PlainTableBuilder::Add(const Slice& key, const Slice& value) { // Store key hash if (store_index_in_file_) { - if (ioptions_.prefix_extractor == nullptr) { + if (moptions_.prefix_extractor == nullptr) { keys_or_prefixes_hashes_.push_back(GetSliceHash(internal_key.user_key)); } else { Slice prefix = - ioptions_.prefix_extractor->Transform(internal_key.user_key); + moptions_.prefix_extractor->Transform(internal_key.user_key); keys_or_prefixes_hashes_.push_back(GetSliceHash(prefix)); } } @@ -165,6 +166,12 @@ void PlainTableBuilder::Add(const Slice& key, const Slice& value) { properties_.num_entries++; properties_.raw_key_size += key.size(); properties_.raw_value_size += value.size(); + if (internal_key.type == kTypeDeletion || + internal_key.type == kTypeSingleDeletion) { + properties_.num_deletions++; + } else if (internal_key.type == kTypeMerge) { + properties_.num_merge_operands++; + } // notify property collectors NotifyCollectTableCollectorsOnAdd( diff --git a/thirdparty/rocksdb/table/plain_table_builder.h b/thirdparty/rocksdb/table/plain_table_builder.h index 1d1f6c7586..ca0879a4e1 100644 --- a/thirdparty/rocksdb/table/plain_table_builder.h +++ b/thirdparty/rocksdb/table/plain_table_builder.h @@ -32,7 +32,7 @@ class PlainTableBuilder: public TableBuilder { // will be part of level specified by 'level'. A value of -1 means // that the caller does not know which level the output file will reside. PlainTableBuilder( - const ImmutableCFOptions& ioptions, + const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions, const std::vector>* int_tbl_prop_collector_factories, uint32_t column_family_id, WritableFileWriter* file, @@ -79,6 +79,7 @@ class PlainTableBuilder: public TableBuilder { private: Arena arena_; const ImmutableCFOptions& ioptions_; + const MutableCFOptions& moptions_; std::vector> table_properties_collectors_; diff --git a/thirdparty/rocksdb/table/plain_table_factory.cc b/thirdparty/rocksdb/table/plain_table_factory.cc index 5f7809b967..a6e59c142f 100644 --- a/thirdparty/rocksdb/table/plain_table_factory.cc +++ b/thirdparty/rocksdb/table/plain_table_factory.cc @@ -19,15 +19,16 @@ namespace rocksdb { Status PlainTableFactory::NewTableReader( const TableReaderOptions& table_reader_options, - unique_ptr&& file, uint64_t file_size, - unique_ptr* table, - bool prefetch_index_and_filter_in_cache) const { + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table, + bool /*prefetch_index_and_filter_in_cache*/) const { return PlainTableReader::Open( table_reader_options.ioptions, table_reader_options.env_options, table_reader_options.internal_comparator, std::move(file), file_size, table, table_options_.bloom_bits_per_key, table_options_.hash_table_ratio, table_options_.index_sparseness, table_options_.huge_page_tlb_size, - table_options_.full_scan_mode); + table_options_.full_scan_mode, table_reader_options.immortal, + table_reader_options.prefix_extractor); } TableBuilder* PlainTableFactory::NewTableBuilder( @@ -38,7 +39,7 @@ TableBuilder* PlainTableFactory::NewTableBuilder( // tables // return new PlainTableBuilder( - table_builder_options.ioptions, + table_builder_options.ioptions, table_builder_options.moptions, table_builder_options.int_tbl_prop_collector_factories, column_family_id, file, table_options_.user_key_len, table_options_.encoding_type, table_options_.index_sparseness, table_options_.bloom_bits_per_key, @@ -102,7 +103,7 @@ Status GetMemTableRepFactoryFromString( std::vector opts_list = StringSplit(opts_str, ':'); size_t len = opts_list.size(); - if (opts_list.size() <= 0 || opts_list.size() > 2) { + if (opts_list.empty() || opts_list.size() > 2) { return Status::InvalidArgument("Can't parse memtable_factory option ", opts_str); } @@ -146,15 +147,8 @@ Status GetMemTableRepFactoryFromString( mem_factory = new VectorRepFactory(); } } else if (opts_list[0] == "cuckoo") { - // Expecting format - // cuckoo: - if (2 == len) { - size_t write_buffer_size = ParseSizeT(opts_list[1]); - mem_factory = NewHashCuckooRepFactory(write_buffer_size); - } else if (1 == len) { - return Status::InvalidArgument("Can't parse memtable_factory option ", - opts_str); - } + return Status::NotSupported( + "cuckoo hash memtable is not supported anymore."); } else { return Status::InvalidArgument("Unrecognized memtable_factory option ", opts_str); @@ -195,7 +189,7 @@ Status GetPlainTableOptionsFromMap( const PlainTableOptions& table_options, const std::unordered_map& opts_map, PlainTableOptions* new_table_options, bool input_strings_escaped, - bool ignore_unknown_options) { + bool /*ignore_unknown_options*/) { assert(new_table_options); *new_table_options = table_options; for (const auto& o : opts_map) { @@ -210,6 +204,8 @@ Status GetPlainTableOptionsFromMap( (iter->second.verification != OptionVerificationType::kByName && iter->second.verification != OptionVerificationType::kByNameAllowNull && + iter->second.verification != + OptionVerificationType::kByNameAllowFromNull && iter->second.verification != OptionVerificationType::kDeprecated)) { // Restore "new_options" to the default "base_options". *new_table_options = table_options; diff --git a/thirdparty/rocksdb/table/plain_table_factory.h b/thirdparty/rocksdb/table/plain_table_factory.h index 6c9ca44f30..990df482ed 100644 --- a/thirdparty/rocksdb/table/plain_table_factory.h +++ b/thirdparty/rocksdb/table/plain_table_factory.h @@ -17,7 +17,6 @@ namespace rocksdb { struct EnvOptions; -using std::unique_ptr; class Status; class RandomAccessFile; class WritableFile; @@ -149,8 +148,8 @@ class PlainTableFactory : public TableFactory { const char* Name() const override { return "PlainTable"; } Status NewTableReader(const TableReaderOptions& table_reader_options, - unique_ptr&& file, - uint64_t file_size, unique_ptr* table, + std::unique_ptr&& file, + uint64_t file_size, std::unique_ptr* table, bool prefetch_index_and_filter_in_cache) const override; TableBuilder* NewTableBuilder( @@ -161,18 +160,19 @@ class PlainTableFactory : public TableFactory { const PlainTableOptions& table_options() const; - static const char kValueTypeSeqId0 = char(0xFF); + static const char kValueTypeSeqId0 = char(~0); // Sanitizes the specified DB Options. - Status SanitizeOptions(const DBOptions& db_opts, - const ColumnFamilyOptions& cf_opts) const override { + Status SanitizeOptions( + const DBOptions& /*db_opts*/, + const ColumnFamilyOptions& /*cf_opts*/) const override { return Status::OK(); } void* GetOptions() override { return &table_options_; } - Status GetOptionString(std::string* opt_string, - const std::string& delimiter) const override { + Status GetOptionString(std::string* /*opt_string*/, + const std::string& /*delimiter*/) const override { return Status::OK(); } diff --git a/thirdparty/rocksdb/table/plain_table_index.cc b/thirdparty/rocksdb/table/plain_table_index.cc index 39a6b53d60..4374092397 100644 --- a/thirdparty/rocksdb/table/plain_table_index.cc +++ b/thirdparty/rocksdb/table/plain_table_index.cc @@ -203,7 +203,7 @@ Slice PlainTableIndexBuilder::FillIndexes( assert(sub_index_offset == sub_index_size_); ROCKS_LOG_DEBUG(ioptions_.info_log, - "hash table size: %d, suffix_map length %" ROCKSDB_PRIszt, + "hash table size: %" PRIu32 ", suffix_map length %" PRIu32, index_size_, sub_index_size_); return Slice(allocated, GetTotalSize()); } diff --git a/thirdparty/rocksdb/table/plain_table_index.h b/thirdparty/rocksdb/table/plain_table_index.h index 2916be4192..360d998279 100644 --- a/thirdparty/rocksdb/table/plain_table_index.h +++ b/thirdparty/rocksdb/table/plain_table_index.h @@ -112,6 +112,7 @@ class PlainTableIndex { class PlainTableIndexBuilder { public: PlainTableIndexBuilder(Arena* arena, const ImmutableCFOptions& ioptions, + const SliceTransform* prefix_extractor, size_t index_sparseness, double hash_table_ratio, size_t huge_page_tlb_size) : arena_(arena), @@ -123,7 +124,9 @@ class PlainTableIndexBuilder { num_keys_per_prefix_(0), prev_key_prefix_hash_(0), index_sparseness_(index_sparseness), - prefix_extractor_(ioptions.prefix_extractor), + index_size_(0), + sub_index_size_(0), + prefix_extractor_(prefix_extractor), hash_table_ratio_(hash_table_ratio), huge_page_tlb_size_(huge_page_tlb_size) {} diff --git a/thirdparty/rocksdb/table/plain_table_key_coding.cc b/thirdparty/rocksdb/table/plain_table_key_coding.cc index 3e87c03d13..6f5ee9b4ad 100644 --- a/thirdparty/rocksdb/table/plain_table_key_coding.cc +++ b/thirdparty/rocksdb/table/plain_table_key_coding.cc @@ -288,7 +288,7 @@ Status PlainTableKeyDecoder::NextPlainEncodingKey(uint32_t start_offset, ParsedInternalKey* parsed_key, Slice* internal_key, uint32_t* bytes_read, - bool* seekable) { + bool* /*seekable*/) { uint32_t user_key_size = 0; Status s; if (fixed_user_key_len_ != kPlainTableVariableLength) { diff --git a/thirdparty/rocksdb/table/plain_table_key_coding.h b/thirdparty/rocksdb/table/plain_table_key_coding.h index 321e0aed59..9a27ad06b7 100644 --- a/thirdparty/rocksdb/table/plain_table_key_coding.h +++ b/thirdparty/rocksdb/table/plain_table_key_coding.h @@ -114,7 +114,7 @@ class PlainTableFileReader { }; // Keep buffers for two recent reads. - std::array, 2> buffers_; + std::array, 2> buffers_; uint32_t num_buf_; Status status_; diff --git a/thirdparty/rocksdb/table/plain_table_reader.cc b/thirdparty/rocksdb/table/plain_table_reader.cc index d4d9edb741..b0c6dcf07e 100644 --- a/thirdparty/rocksdb/table/plain_table_reader.cc +++ b/thirdparty/rocksdb/table/plain_table_reader.cc @@ -54,7 +54,7 @@ inline uint32_t GetFixed32Element(const char* base, size_t offset) { class PlainTableIterator : public InternalIterator { public: explicit PlainTableIterator(PlainTableReader* table, bool use_prefix_seek); - ~PlainTableIterator(); + ~PlainTableIterator() override; bool Valid() const override; @@ -91,20 +91,20 @@ class PlainTableIterator : public InternalIterator { }; extern const uint64_t kPlainTableMagicNumber; -PlainTableReader::PlainTableReader(const ImmutableCFOptions& ioptions, - unique_ptr&& file, - const EnvOptions& storage_options, - const InternalKeyComparator& icomparator, - EncodingType encoding_type, - uint64_t file_size, - const TableProperties* table_properties) +PlainTableReader::PlainTableReader( + const ImmutableCFOptions& ioptions, + std::unique_ptr&& file, + const EnvOptions& storage_options, const InternalKeyComparator& icomparator, + EncodingType encoding_type, uint64_t file_size, + const TableProperties* table_properties, + const SliceTransform* prefix_extractor) : internal_comparator_(icomparator), encoding_type_(encoding_type), full_scan_mode_(false), user_key_len_(static_cast(table_properties->fixed_key_len)), - prefix_extractor_(ioptions.prefix_extractor), + prefix_extractor_(prefix_extractor), enable_bloom_(false), - bloom_(6, nullptr), + bloom_(6), file_info_(std::move(file), storage_options, static_cast(table_properties->data_size)), ioptions_(ioptions), @@ -114,22 +114,22 @@ PlainTableReader::PlainTableReader(const ImmutableCFOptions& ioptions, PlainTableReader::~PlainTableReader() { } -Status PlainTableReader::Open(const ImmutableCFOptions& ioptions, - const EnvOptions& env_options, - const InternalKeyComparator& internal_comparator, - unique_ptr&& file, - uint64_t file_size, - unique_ptr* table_reader, - const int bloom_bits_per_key, - double hash_table_ratio, size_t index_sparseness, - size_t huge_page_tlb_size, bool full_scan_mode) { +Status PlainTableReader::Open( + const ImmutableCFOptions& ioptions, const EnvOptions& env_options, + const InternalKeyComparator& internal_comparator, + std::unique_ptr&& file, uint64_t file_size, + std::unique_ptr* table_reader, const int bloom_bits_per_key, + double hash_table_ratio, size_t index_sparseness, size_t huge_page_tlb_size, + bool full_scan_mode, const bool immortal_table, + const SliceTransform* prefix_extractor) { if (file_size > PlainTableIndex::kMaxFileSize) { return Status::NotSupported("File is too large for PlainTableReader!"); } TableProperties* props = nullptr; auto s = ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber, - ioptions, &props); + ioptions, &props, + true /* compression_type_missing */); if (!s.ok()) { return s; } @@ -141,12 +141,12 @@ Status PlainTableReader::Open(const ImmutableCFOptions& ioptions, if (!full_scan_mode && !prefix_extractor_in_file.empty() /* old version sst file*/ && prefix_extractor_in_file != "nullptr") { - if (!ioptions.prefix_extractor) { + if (!prefix_extractor) { return Status::InvalidArgument( "Prefix extractor is missing when opening a PlainTable built " "using a prefix extractor"); - } else if (prefix_extractor_in_file.compare( - ioptions.prefix_extractor->Name()) != 0) { + } else if (prefix_extractor_in_file.compare(prefix_extractor->Name()) != + 0) { return Status::InvalidArgument( "Prefix extractor given doesn't match the one used to build " "PlainTable"); @@ -163,7 +163,7 @@ Status PlainTableReader::Open(const ImmutableCFOptions& ioptions, std::unique_ptr new_reader(new PlainTableReader( ioptions, std::move(file), env_options, internal_comparator, - encoding_type, file_size, props)); + encoding_type, file_size, props, prefix_extractor)); s = new_reader->MmapDataIfNeeded(); if (!s.ok()) { @@ -182,6 +182,10 @@ Status PlainTableReader::Open(const ImmutableCFOptions& ioptions, new_reader->full_scan_mode_ = true; } + if (immortal_table && new_reader->file_info_.is_mmap_mode) { + new_reader->dummy_cleanable_.reset(new Cleanable()); + } + *table_reader = std::move(new_reader); return s; } @@ -189,9 +193,9 @@ Status PlainTableReader::Open(const ImmutableCFOptions& ioptions, void PlainTableReader::SetupForCompaction() { } -InternalIterator* PlainTableReader::NewIterator(const ReadOptions& options, - Arena* arena, - bool skip_filters) { +InternalIterator* PlainTableReader::NewIterator( + const ReadOptions& options, const SliceTransform* /* prefix_extractor */, + Arena* arena, bool /*skip_filters*/, bool /*for_compaction*/) { bool use_prefix_seek = !IsTotalOrderMode() && !options.total_order_seek; if (arena == nullptr) { return new PlainTableIterator(this, use_prefix_seek); @@ -202,7 +206,8 @@ InternalIterator* PlainTableReader::NewIterator(const ReadOptions& options, } Status PlainTableReader::PopulateIndexRecordList( - PlainTableIndexBuilder* index_builder, vector* prefix_hashes) { + PlainTableIndexBuilder* index_builder, + std::vector* prefix_hashes) { Slice prev_key_prefix_slice; std::string prev_key_prefix_buf; uint32_t pos = data_start_offset_; @@ -210,7 +215,7 @@ Status PlainTableReader::PopulateIndexRecordList( bool is_first_record = true; Slice key_prefix_slice; PlainTableKeyDecoder decoder(&file_info_, encoding_type_, user_key_len_, - ioptions_.prefix_extractor); + prefix_extractor_); while (pos < file_info_.data_end_offset) { uint32_t key_offset = pos; ParsedInternalKey key; @@ -252,10 +257,9 @@ Status PlainTableReader::PopulateIndexRecordList( return s; } -void PlainTableReader::AllocateAndFillBloom(int bloom_bits_per_key, - int num_prefixes, - size_t huge_page_tlb_size, - vector* prefix_hashes) { +void PlainTableReader::AllocateAndFillBloom( + int bloom_bits_per_key, int num_prefixes, size_t huge_page_tlb_size, + std::vector* prefix_hashes) { if (!IsTotalOrderMode()) { uint32_t bloom_total_bits = num_prefixes * bloom_bits_per_key; if (bloom_total_bits > 0) { @@ -267,7 +271,7 @@ void PlainTableReader::AllocateAndFillBloom(int bloom_bits_per_key, } } -void PlainTableReader::FillBloom(vector* prefix_hashes) { +void PlainTableReader::FillBloom(std::vector* prefix_hashes) { assert(bloom_.IsInitialized()); for (auto prefix_hash : *prefix_hashes) { bloom_.AddHash(prefix_hash); @@ -277,7 +281,7 @@ void PlainTableReader::FillBloom(vector* prefix_hashes) { Status PlainTableReader::MmapDataIfNeeded() { if (file_info_.is_mmap_mode) { // Get mmapped memory. - return file_info_.file->Read(0, file_size_, &file_info_.file_data, nullptr); + return file_info_.file->Read(0, static_cast(file_size_), &file_info_.file_data, nullptr); } return Status::OK(); } @@ -294,7 +298,8 @@ Status PlainTableReader::PopulateIndex(TableProperties* props, Status s = ReadMetaBlock(file_info_.file.get(), nullptr /* prefetch_buffer */, file_size_, kPlainTableMagicNumber, ioptions_, PlainTableIndexBuilder::kPlainTableIndexBlock, - &index_block_contents); + &index_block_contents, + true /* compression_type_missing */); bool index_in_file = s.ok(); @@ -304,7 +309,8 @@ Status PlainTableReader::PopulateIndex(TableProperties* props, if (index_in_file) { s = ReadMetaBlock(file_info_.file.get(), nullptr /* prefetch_buffer */, file_size_, kPlainTableMagicNumber, ioptions_, - BloomBlockBuilder::kBloomBlock, &bloom_block_contents); + BloomBlockBuilder::kBloomBlock, &bloom_block_contents, + true /* compression_type_missing */); bloom_in_file = s.ok() && bloom_block_contents.data.size() > 0; } @@ -330,9 +336,8 @@ Status PlainTableReader::PopulateIndex(TableProperties* props, index_block = nullptr; } - if ((ioptions_.prefix_extractor == nullptr) && - (hash_table_ratio != 0)) { - // ioptions.prefix_extractor is requried for a hash-based look-up. + if ((prefix_extractor_ == nullptr) && (hash_table_ratio != 0)) { + // moptions.prefix_extractor is requried for a hash-based look-up. return Status::NotSupported( "PlainTable requires a prefix extractor enable prefix hash mode."); } @@ -377,8 +382,9 @@ Status PlainTableReader::PopulateIndex(TableProperties* props, bloom_bits_per_key = 0; } - PlainTableIndexBuilder index_builder(&arena_, ioptions_, index_sparseness, - hash_table_ratio, huge_page_tlb_size); + PlainTableIndexBuilder index_builder(&arena_, ioptions_, prefix_extractor_, + index_sparseness, hash_table_ratio, + huge_page_tlb_size); std::vector prefix_hashes; if (!index_in_file) { @@ -537,8 +543,10 @@ void PlainTableReader::Prepare(const Slice& target) { } } -Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target, - GetContext* get_context, bool skip_filters) { +Status PlainTableReader::Get(const ReadOptions& /*ro*/, const Slice& target, + GetContext* get_context, + const SliceTransform* /* prefix_extractor */, + bool /*skip_filters*/) { // Check bloom filter first. Slice prefix_slice; uint32_t prefix_hash; @@ -565,7 +573,7 @@ Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target, uint32_t offset; bool prefix_match; PlainTableKeyDecoder decoder(&file_info_, encoding_type_, user_key_len_, - ioptions_.prefix_extractor); + prefix_extractor_); Status s = GetOffset(&decoder, target, prefix_slice, prefix_hash, prefix_match, &offset); @@ -594,7 +602,9 @@ Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target, // TODO(ljin): since we know the key comparison result here, // can we enable the fast path? if (internal_comparator_.Compare(found_key, parsed_target) >= 0) { - if (!get_context->SaveValue(found_key, found_value)) { + bool dont_care __attribute__((__unused__)); + if (!get_context->SaveValue(found_key, found_value, &dont_care, + dummy_cleanable_.get())) { break; } } @@ -602,7 +612,7 @@ Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target, return Status::OK(); } -uint64_t PlainTableReader::ApproximateOffsetOf(const Slice& key) { +uint64_t PlainTableReader::ApproximateOffsetOf(const Slice& /*key*/) { return 0; } @@ -624,6 +634,7 @@ bool PlainTableIterator::Valid() const { } void PlainTableIterator::SeekToFirst() { + status_ = Status::OK(); next_offset_ = table_->data_start_offset_; if (next_offset_ >= table_->file_info_.data_end_offset) { next_offset_ = offset_ = table_->file_info_.data_end_offset; @@ -635,6 +646,7 @@ void PlainTableIterator::SeekToFirst() { void PlainTableIterator::SeekToLast() { assert(false); status_ = Status::NotSupported("SeekToLast() is not supported in PlainTable"); + next_offset_ = offset_ = table_->file_info_.data_end_offset; } void PlainTableIterator::Seek(const Slice& target) { @@ -675,6 +687,7 @@ void PlainTableIterator::Seek(const Slice& target) { if (!table_->IsTotalOrderMode()) { prefix_hash = GetSliceHash(prefix_slice); if (!table_->MatchBloom(prefix_hash)) { + status_ = Status::OK(); offset_ = next_offset_ = table_->file_info_.data_end_offset; return; } @@ -706,10 +719,11 @@ void PlainTableIterator::Seek(const Slice& target) { } } -void PlainTableIterator::SeekForPrev(const Slice& target) { +void PlainTableIterator::SeekForPrev(const Slice& /*target*/) { assert(false); status_ = Status::NotSupported("SeekForPrev() is not supported in PlainTable"); + offset_ = next_offset_ = table_->file_info_.data_end_offset; } void PlainTableIterator::Next() { diff --git a/thirdparty/rocksdb/table/plain_table_reader.h b/thirdparty/rocksdb/table/plain_table_reader.h index 6bf8da2f98..022886b729 100644 --- a/thirdparty/rocksdb/table/plain_table_reader.h +++ b/thirdparty/rocksdb/table/plain_table_reader.h @@ -38,20 +38,16 @@ class TableReader; class InternalKeyComparator; class PlainTableKeyDecoder; class GetContext; -class InternalIterator; -using std::unique_ptr; -using std::unordered_map; -using std::vector; extern const uint32_t kPlainTableVariableLength; struct PlainTableReaderFileInfo { bool is_mmap_mode; Slice file_data; uint32_t data_end_offset; - unique_ptr file; + std::unique_ptr file; - PlainTableReaderFileInfo(unique_ptr&& _file, + PlainTableReaderFileInfo(std::unique_ptr&& _file, const EnvOptions& storage_options, uint32_t _data_size_offset) : is_mmap_mode(storage_options.use_mmap_reads), @@ -72,19 +68,23 @@ class PlainTableReader: public TableReader { static Status Open(const ImmutableCFOptions& ioptions, const EnvOptions& env_options, const InternalKeyComparator& internal_comparator, - unique_ptr&& file, - uint64_t file_size, unique_ptr* table, + std::unique_ptr&& file, + uint64_t file_size, std::unique_ptr* table, const int bloom_bits_per_key, double hash_table_ratio, size_t index_sparseness, size_t huge_page_tlb_size, - bool full_scan_mode); + bool full_scan_mode, const bool immortal_table = false, + const SliceTransform* prefix_extractor = nullptr); InternalIterator* NewIterator(const ReadOptions&, + const SliceTransform* prefix_extractor, Arena* arena = nullptr, - bool skip_filters = false) override; + bool skip_filters = false, + bool for_compaction = false) override; void Prepare(const Slice& target) override; - Status Get(const ReadOptions&, const Slice& key, GetContext* get_context, + Status Get(const ReadOptions& readOptions, const Slice& key, + GetContext* get_context, const SliceTransform* prefix_extractor, bool skip_filters = false) override; uint64_t ApproximateOffsetOf(const Slice& key) override; @@ -101,11 +101,12 @@ class PlainTableReader: public TableReader { } PlainTableReader(const ImmutableCFOptions& ioptions, - unique_ptr&& file, + std::unique_ptr&& file, const EnvOptions& env_options, const InternalKeyComparator& internal_comparator, EncodingType encoding_type, uint64_t file_size, - const TableProperties* table_properties); + const TableProperties* table_properties, + const SliceTransform* prefix_extractor); virtual ~PlainTableReader(); protected: @@ -149,10 +150,11 @@ class PlainTableReader: public TableReader { DynamicBloom bloom_; PlainTableReaderFileInfo file_info_; Arena arena_; - std::unique_ptr index_block_alloc_; - std::unique_ptr bloom_block_alloc_; + CacheAllocationPtr index_block_alloc_; + CacheAllocationPtr bloom_block_alloc_; const ImmutableCFOptions& ioptions_; + std::unique_ptr dummy_cleanable_; uint64_t file_size_; std::shared_ptr table_properties_; @@ -197,14 +199,14 @@ class PlainTableReader: public TableReader { // If bloom_ is not null, all the keys' full-key hash will be added to the // bloom filter. Status PopulateIndexRecordList(PlainTableIndexBuilder* index_builder, - vector* prefix_hashes); + std::vector* prefix_hashes); // Internal helper function to allocate memory for bloom filter and fill it void AllocateAndFillBloom(int bloom_bits_per_key, int num_prefixes, size_t huge_page_tlb_size, - vector* prefix_hashes); + std::vector* prefix_hashes); - void FillBloom(vector* prefix_hashes); + void FillBloom(std::vector* prefix_hashes); // Read the key and value at `offset` to parameters for keys, the and // `seekable`. diff --git a/thirdparty/rocksdb/table/sst_file_reader.cc b/thirdparty/rocksdb/table/sst_file_reader.cc new file mode 100644 index 0000000000..54408bb50e --- /dev/null +++ b/thirdparty/rocksdb/table/sst_file_reader.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2011-present, Facebook, 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/sst_file_reader.h" + +#include "db/db_iter.h" +#include "db/dbformat.h" +#include "options/cf_options.h" +#include "table/get_context.h" +#include "table/table_builder.h" +#include "table/table_reader.h" +#include "util/file_reader_writer.h" + +namespace rocksdb { + +struct SstFileReader::Rep { + Options options; + EnvOptions soptions; + ImmutableCFOptions ioptions; + MutableCFOptions moptions; + + std::unique_ptr table_reader; + + Rep(const Options& opts) + : options(opts), + soptions(options), + ioptions(options), + moptions(ColumnFamilyOptions(options)) {} +}; + +SstFileReader::SstFileReader(const Options& options) : rep_(new Rep(options)) {} + +SstFileReader::~SstFileReader() {} + +Status SstFileReader::Open(const std::string& file_path) { + auto r = rep_.get(); + Status s; + uint64_t file_size = 0; + std::unique_ptr file; + std::unique_ptr file_reader; + s = r->options.env->GetFileSize(file_path, &file_size); + if (s.ok()) { + s = r->options.env->NewRandomAccessFile(file_path, &file, r->soptions); + } + if (s.ok()) { + file_reader.reset(new RandomAccessFileReader(std::move(file), file_path)); + } + if (s.ok()) { + TableReaderOptions t_opt(r->ioptions, r->moptions.prefix_extractor.get(), + r->soptions, r->ioptions.internal_comparator); + // Allow open file with global sequence number for backward compatibility. + t_opt.largest_seqno = kMaxSequenceNumber; + s = r->options.table_factory->NewTableReader(t_opt, std::move(file_reader), + file_size, &r->table_reader); + } + return s; +} + +Iterator* SstFileReader::NewIterator(const ReadOptions& options) { + auto r = rep_.get(); + auto sequence = options.snapshot != nullptr + ? options.snapshot->GetSequenceNumber() + : kMaxSequenceNumber; + auto internal_iter = + r->table_reader->NewIterator(options, r->moptions.prefix_extractor.get()); + return NewDBIterator(r->options.env, options, r->ioptions, r->moptions, + r->ioptions.user_comparator, internal_iter, sequence, + r->moptions.max_sequential_skip_in_iterations, + nullptr /* read_callback */); +} + +std::shared_ptr SstFileReader::GetTableProperties() + const { + return rep_->table_reader->GetTableProperties(); +} + +Status SstFileReader::VerifyChecksum() { + return rep_->table_reader->VerifyChecksum(); +} + +} // namespace rocksdb + +#endif // !ROCKSDB_LITE diff --git a/thirdparty/rocksdb/table/sst_file_reader_test.cc b/thirdparty/rocksdb/table/sst_file_reader_test.cc new file mode 100644 index 0000000000..51bc975af0 --- /dev/null +++ b/thirdparty/rocksdb/table/sst_file_reader_test.cc @@ -0,0 +1,174 @@ +// Copyright (c) 2011-present, Facebook, 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 + +#include "rocksdb/db.h" +#include "rocksdb/sst_file_reader.h" +#include "rocksdb/sst_file_writer.h" +#include "table/sst_file_writer_collectors.h" +#include "util/testharness.h" +#include "util/testutil.h" +#include "utilities/merge_operators.h" + +namespace rocksdb { + +std::string EncodeAsString(uint64_t v) { + char buf[16]; + snprintf(buf, sizeof(buf), "%08" PRIu64, v); + return std::string(buf); +} + +std::string EncodeAsUint64(uint64_t v) { + std::string dst; + PutFixed64(&dst, v); + return dst; +} + +class SstFileReaderTest : public testing::Test { + public: + SstFileReaderTest() { + options_.merge_operator = MergeOperators::CreateUInt64AddOperator(); + sst_name_ = test::PerThreadDBPath("sst_file"); + } + + ~SstFileReaderTest() { + Status s = Env::Default()->DeleteFile(sst_name_); + assert(s.ok()); + } + + void CreateFile(const std::string& file_name, + const std::vector& keys) { + SstFileWriter writer(soptions_, options_); + ASSERT_OK(writer.Open(file_name)); + for (size_t i = 0; i + 2 < keys.size(); i += 3) { + ASSERT_OK(writer.Put(keys[i], keys[i])); + ASSERT_OK(writer.Merge(keys[i + 1], EncodeAsUint64(i + 1))); + ASSERT_OK(writer.Delete(keys[i + 2])); + } + ASSERT_OK(writer.Finish()); + } + + void CheckFile(const std::string& file_name, + const std::vector& keys, + bool check_global_seqno = false) { + ReadOptions ropts; + SstFileReader reader(options_); + ASSERT_OK(reader.Open(file_name)); + ASSERT_OK(reader.VerifyChecksum()); + std::unique_ptr iter(reader.NewIterator(ropts)); + iter->SeekToFirst(); + for (size_t i = 0; i + 2 < keys.size(); i += 3) { + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(keys[i]), 0); + ASSERT_EQ(iter->value().compare(keys[i]), 0); + iter->Next(); + ASSERT_TRUE(iter->Valid()); + ASSERT_EQ(iter->key().compare(keys[i + 1]), 0); + ASSERT_EQ(iter->value().compare(EncodeAsUint64(i + 1)), 0); + iter->Next(); + } + ASSERT_FALSE(iter->Valid()); + if (check_global_seqno) { + auto properties = reader.GetTableProperties(); + ASSERT_TRUE(properties); + auto& user_properties = properties->user_collected_properties; + ASSERT_TRUE( + user_properties.count(ExternalSstFilePropertyNames::kGlobalSeqno)); + } + } + + void CreateFileAndCheck(const std::vector& keys) { + CreateFile(sst_name_, keys); + CheckFile(sst_name_, keys); + } + + protected: + Options options_; + EnvOptions soptions_; + std::string sst_name_; +}; + +const uint64_t kNumKeys = 100; + +TEST_F(SstFileReaderTest, Basic) { + std::vector keys; + for (uint64_t i = 0; i < kNumKeys; i++) { + keys.emplace_back(EncodeAsString(i)); + } + CreateFileAndCheck(keys); +} + +TEST_F(SstFileReaderTest, Uint64Comparator) { + options_.comparator = test::Uint64Comparator(); + std::vector keys; + for (uint64_t i = 0; i < kNumKeys; i++) { + keys.emplace_back(EncodeAsUint64(i)); + } + CreateFileAndCheck(keys); +} + +TEST_F(SstFileReaderTest, ReadFileWithGlobalSeqno) { + std::vector keys; + for (uint64_t i = 0; i < kNumKeys; i++) { + keys.emplace_back(EncodeAsString(i)); + } + // Generate a SST file. + CreateFile(sst_name_, keys); + + // Ingest the file into a db, to assign it a global sequence number. + Options options; + options.create_if_missing = true; + std::string db_name = test::PerThreadDBPath("test_db"); + DB* db; + ASSERT_OK(DB::Open(options, db_name, &db)); + // Bump sequence number. + ASSERT_OK(db->Put(WriteOptions(), keys[0], "foo")); + ASSERT_OK(db->Flush(FlushOptions())); + // Ingest the file. + IngestExternalFileOptions ingest_options; + ingest_options.write_global_seqno = true; + ASSERT_OK(db->IngestExternalFile({sst_name_}, ingest_options)); + std::vector live_files; + uint64_t manifest_file_size = 0; + ASSERT_OK(db->GetLiveFiles(live_files, &manifest_file_size)); + // Get the ingested file. + std::string ingested_file; + for (auto& live_file : live_files) { + if (live_file.substr(live_file.size() - 4, std::string::npos) == ".sst") { + if (ingested_file.empty() || ingested_file < live_file) { + ingested_file = live_file; + } + } + } + ASSERT_FALSE(ingested_file.empty()); + delete db; + + // Verify the file can be open and read by SstFileReader. + CheckFile(db_name + ingested_file, keys, true /* check_global_seqno */); + + // Cleanup. + ASSERT_OK(DestroyDB(db_name, options)); +} + +} // namespace rocksdb + +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 SstFileReader is not supported in ROCKSDB_LITE\n"); + return 0; +} + +#endif // ROCKSDB_LITE diff --git a/thirdparty/rocksdb/table/sst_file_writer.cc b/thirdparty/rocksdb/table/sst_file_writer.cc index adcd91f92e..b9a7273e07 100644 --- a/thirdparty/rocksdb/table/sst_file_writer.cc +++ b/thirdparty/rocksdb/table/sst_file_writer.cc @@ -27,7 +27,7 @@ const size_t kFadviseTrigger = 1024 * 1024; // 1MB struct SstFileWriter::Rep { Rep(const EnvOptions& _env_options, const Options& options, Env::IOPriority _io_priority, const Comparator* _user_comparator, - ColumnFamilyHandle* _cfh, bool _invalidate_page_cache) + ColumnFamilyHandle* _cfh, bool _invalidate_page_cache, bool _skip_filters) : env_options(_env_options), ioptions(options), mutable_cf_options(options), @@ -35,7 +35,8 @@ struct SstFileWriter::Rep { internal_comparator(_user_comparator), cfh(_cfh), invalidate_page_cache(_invalidate_page_cache), - last_fadvise_size(0) {} + last_fadvise_size(0), + skip_filters(_skip_filters) {} std::unique_ptr file_writer; std::unique_ptr builder; @@ -49,11 +50,12 @@ struct SstFileWriter::Rep { std::string column_family_name; ColumnFamilyHandle* cfh; // If true, We will give the OS a hint that this file pages is not needed - // everytime we write 1MB to the file. + // every time we write 1MB to the file. bool invalidate_page_cache; // The size of the file during the last time we called Fadvise to remove // cached pages from page cache. uint64_t last_fadvise_size; + bool skip_filters; Status Add(const Slice& user_key, const Slice& value, const ValueType value_type) { if (!builder) { @@ -99,6 +101,42 @@ struct SstFileWriter::Rep { return Status::OK(); } + Status DeleteRange(const Slice& begin_key, const Slice& end_key) { + if (!builder) { + return Status::InvalidArgument("File is not opened"); + } + + RangeTombstone tombstone(begin_key, end_key, 0 /* Sequence Number */); + if (file_info.num_range_del_entries == 0) { + file_info.smallest_range_del_key.assign(tombstone.start_key_.data(), + tombstone.start_key_.size()); + file_info.largest_range_del_key.assign(tombstone.end_key_.data(), + tombstone.end_key_.size()); + } else { + if (internal_comparator.user_comparator()->Compare( + tombstone.start_key_, file_info.smallest_range_del_key) < 0) { + file_info.smallest_range_del_key.assign(tombstone.start_key_.data(), + tombstone.start_key_.size()); + } + if (internal_comparator.user_comparator()->Compare( + tombstone.end_key_, file_info.largest_range_del_key) > 0) { + file_info.largest_range_del_key.assign(tombstone.end_key_.data(), + tombstone.end_key_.size()); + } + } + + auto ikey_and_end_key = tombstone.Serialize(); + builder->Add(ikey_and_end_key.first.Encode(), ikey_and_end_key.second); + + // update file info + file_info.num_range_del_entries++; + file_info.file_size = builder->FileSize(); + + InvalidatePageCache(false /* closing */); + + return Status::OK(); + } + void InvalidatePageCache(bool closing) { if (invalidate_page_cache == false) { // Fadvise disabled @@ -122,9 +160,9 @@ SstFileWriter::SstFileWriter(const EnvOptions& env_options, const Comparator* user_comparator, ColumnFamilyHandle* column_family, bool invalidate_page_cache, - Env::IOPriority io_priority) + Env::IOPriority io_priority, bool skip_filters) : rep_(new Rep(env_options, options, io_priority, user_comparator, - column_family, invalidate_page_cache)) { + column_family, invalidate_page_cache, skip_filters)) { rep_->file_info.file_size = 0; } @@ -148,14 +186,24 @@ Status SstFileWriter::Open(const std::string& file_path) { sst_file->SetIOPriority(r->io_priority); CompressionType compression_type; + CompressionOptions compression_opts; if (r->ioptions.bottommost_compression != kDisableCompressionOption) { compression_type = r->ioptions.bottommost_compression; + if (r->ioptions.bottommost_compression_opts.enabled) { + compression_opts = r->ioptions.bottommost_compression_opts; + } else { + compression_opts = r->ioptions.compression_opts; + } } else if (!r->ioptions.compression_per_level.empty()) { // Use the compression of the last level if we have per level compression compression_type = *(r->ioptions.compression_per_level.rbegin()); + compression_opts = r->ioptions.compression_opts; } else { compression_type = r->mutable_cf_options.compression; + compression_opts = r->ioptions.compression_opts; } + uint64_t sample_for_compression = + r->mutable_cf_options.sample_for_compression; std::vector> int_tbl_prop_collector_factories; @@ -187,22 +235,21 @@ Status SstFileWriter::Open(const std::string& file_path) { } TableBuilderOptions table_builder_options( - r->ioptions, r->internal_comparator, &int_tbl_prop_collector_factories, - compression_type, r->ioptions.compression_opts, - nullptr /* compression_dict */, false /* skip_filters */, + r->ioptions, r->mutable_cf_options, r->internal_comparator, + &int_tbl_prop_collector_factories, compression_type, + sample_for_compression, compression_opts, r->skip_filters, r->column_family_name, unknown_level); - r->file_writer.reset( - new WritableFileWriter(std::move(sst_file), r->env_options)); + r->file_writer.reset(new WritableFileWriter( + std::move(sst_file), file_path, r->env_options, r->ioptions.env, + nullptr /* stats */, r->ioptions.listeners)); // TODO(tec) : If table_factory is using compressed block cache, we will // be adding the external sst file blocks into it, which is wasteful. r->builder.reset(r->ioptions.table_factory->NewTableBuilder( table_builder_options, cf_id, r->file_writer.get())); + r->file_info = ExternalSstFileInfo(); r->file_info.file_path = file_path; - r->file_info.file_size = 0; - r->file_info.num_entries = 0; - r->file_info.sequence_number = 0; r->file_info.version = 2; return s; } @@ -223,12 +270,18 @@ Status SstFileWriter::Delete(const Slice& user_key) { return rep_->Add(user_key, Slice(), ValueType::kTypeDeletion); } +Status SstFileWriter::DeleteRange(const Slice& begin_key, + const Slice& end_key) { + return rep_->DeleteRange(begin_key, end_key); +} + Status SstFileWriter::Finish(ExternalSstFileInfo* file_info) { Rep* r = rep_.get(); if (!r->builder) { return Status::InvalidArgument("File is not opened"); } - if (r->file_info.num_entries == 0) { + if (r->file_info.num_entries == 0 && + r->file_info.num_range_del_entries == 0) { return Status::InvalidArgument("Cannot create sst file with no entries"); } diff --git a/thirdparty/rocksdb/table/sst_file_writer_collectors.h b/thirdparty/rocksdb/table/sst_file_writer_collectors.h index ce3a45f5a7..e1827939f2 100644 --- a/thirdparty/rocksdb/table/sst_file_writer_collectors.h +++ b/thirdparty/rocksdb/table/sst_file_writer_collectors.h @@ -5,6 +5,8 @@ #pragma once #include +#include "db/dbformat.h" +#include "db/table_properties_collector.h" #include "rocksdb/types.h" #include "util/string_util.h" @@ -26,13 +28,21 @@ class SstFileWriterPropertiesCollector : public IntTblPropCollector { SequenceNumber global_seqno) : version_(version), global_seqno_(global_seqno) {} - virtual Status InternalAdd(const Slice& key, const Slice& value, - uint64_t file_size) override { + virtual Status InternalAdd(const Slice& /*key*/, const Slice& /*value*/, + uint64_t /*file_size*/) override { // Intentionally left blank. Have no interest in collecting stats for // individual key/value pairs. return Status::OK(); } + virtual void BlockAdd(uint64_t /* blockRawBytes */, + uint64_t /* blockCompressedBytesFast */, + uint64_t /* blockCompressedBytesSlow */) override { + // Intentionally left blank. No interest in collecting stats for + // blocks. + return; + } + virtual Status Finish(UserCollectedProperties* properties) override { // File version std::string version_val; @@ -68,7 +78,7 @@ class SstFileWriterPropertiesCollectorFactory : version_(version), global_seqno_(global_seqno) {} virtual IntTblPropCollector* CreateIntTblPropCollector( - uint32_t column_family_id) override { + uint32_t /*column_family_id*/) override { return new SstFileWriterPropertiesCollector(version_, global_seqno_); } diff --git a/thirdparty/rocksdb/table/table_builder.h b/thirdparty/rocksdb/table/table_builder.h index e5e7d6e22f..20d9a55f2f 100644 --- a/thirdparty/rocksdb/table/table_builder.h +++ b/thirdparty/rocksdb/table/table_builder.h @@ -13,6 +13,7 @@ #include #include #include +#include "db/dbformat.h" #include "db/table_properties_collector.h" #include "options/cf_options.h" #include "rocksdb/options.h" @@ -27,59 +28,83 @@ class Status; struct TableReaderOptions { // @param skip_filters Disables loading/accessing the filter block TableReaderOptions(const ImmutableCFOptions& _ioptions, + const SliceTransform* _prefix_extractor, const EnvOptions& _env_options, const InternalKeyComparator& _internal_comparator, - bool _skip_filters = false, int _level = -1) + bool _skip_filters = false, bool _immortal = false, + int _level = -1) + : TableReaderOptions(_ioptions, _prefix_extractor, _env_options, + _internal_comparator, _skip_filters, _immortal, + _level, 0 /* _largest_seqno */) {} + + // @param skip_filters Disables loading/accessing the filter block + TableReaderOptions(const ImmutableCFOptions& _ioptions, + const SliceTransform* _prefix_extractor, + const EnvOptions& _env_options, + const InternalKeyComparator& _internal_comparator, + bool _skip_filters, bool _immortal, int _level, + SequenceNumber _largest_seqno) : ioptions(_ioptions), + prefix_extractor(_prefix_extractor), env_options(_env_options), internal_comparator(_internal_comparator), skip_filters(_skip_filters), - level(_level) {} + immortal(_immortal), + level(_level), + largest_seqno(_largest_seqno) {} const ImmutableCFOptions& ioptions; + const SliceTransform* prefix_extractor; const EnvOptions& env_options; const InternalKeyComparator& internal_comparator; // This is only used for BlockBasedTable (reader) bool skip_filters; + // Whether the table will be valid as long as the DB is open + bool immortal; // what level this table/file is on, -1 for "not set, don't know" int level; + // largest seqno in the table + SequenceNumber largest_seqno; }; struct TableBuilderOptions { TableBuilderOptions( - const ImmutableCFOptions& _ioptions, + const ImmutableCFOptions& _ioptions, const MutableCFOptions& _moptions, const InternalKeyComparator& _internal_comparator, const std::vector>* _int_tbl_prop_collector_factories, - CompressionType _compression_type, - const CompressionOptions& _compression_opts, - const std::string* _compression_dict, bool _skip_filters, + CompressionType _compression_type, uint64_t _sample_for_compression, + const CompressionOptions& _compression_opts, bool _skip_filters, const std::string& _column_family_name, int _level, - const uint64_t _creation_time = 0, const int64_t _oldest_key_time = 0) + const uint64_t _creation_time = 0, const int64_t _oldest_key_time = 0, + const uint64_t _target_file_size = 0) : ioptions(_ioptions), + moptions(_moptions), internal_comparator(_internal_comparator), int_tbl_prop_collector_factories(_int_tbl_prop_collector_factories), compression_type(_compression_type), + sample_for_compression(_sample_for_compression), compression_opts(_compression_opts), - compression_dict(_compression_dict), skip_filters(_skip_filters), column_family_name(_column_family_name), level(_level), creation_time(_creation_time), - oldest_key_time(_oldest_key_time) {} + oldest_key_time(_oldest_key_time), + target_file_size(_target_file_size) {} const ImmutableCFOptions& ioptions; + const MutableCFOptions& moptions; const InternalKeyComparator& internal_comparator; const std::vector>* int_tbl_prop_collector_factories; CompressionType compression_type; + uint64_t sample_for_compression; const CompressionOptions& compression_opts; - // Data for presetting the compression library's dictionary, or nullptr. - const std::string* compression_dict; bool skip_filters; // only used by BlockBasedTableBuilder const std::string& column_family_name; int level; // what level this table/file is on, -1 for "not set, don't know" const uint64_t creation_time; const int64_t oldest_key_time; + const uint64_t target_file_size; }; // TableBuilder provides the interface used to build a Table diff --git a/thirdparty/rocksdb/table/table_properties.cc b/thirdparty/rocksdb/table/table_properties.cc index 24453f6f9c..b7aaea4816 100644 --- a/thirdparty/rocksdb/table/table_properties.cc +++ b/thirdparty/rocksdb/table/table_properties.cc @@ -78,6 +78,11 @@ std::string TableProperties::ToString( AppendProperty(result, "# data blocks", num_data_blocks, prop_delim, kv_delim); AppendProperty(result, "# entries", num_entries, prop_delim, kv_delim); + AppendProperty(result, "# deletions", num_deletions, prop_delim, kv_delim); + AppendProperty(result, "# merge operands", num_merge_operands, prop_delim, + kv_delim); + AppendProperty(result, "# range deletions", num_range_deletions, prop_delim, + kv_delim); AppendProperty(result, "raw key size", raw_key_size, prop_delim, kv_delim); AppendProperty(result, "raw average key size", @@ -90,7 +95,13 @@ std::string TableProperties::ToString( prop_delim, kv_delim); AppendProperty(result, "data block size", data_size, prop_delim, kv_delim); - AppendProperty(result, "index block size", index_size, prop_delim, kv_delim); + char index_block_size_str[80]; + snprintf(index_block_size_str, sizeof(index_block_size_str), + "index block size (user-key? %d, delta-value? %d)", + static_cast(index_key_is_user_key), + static_cast(index_value_is_delta_encoded)); + AppendProperty(result, index_block_size_str, index_size, prop_delim, + kv_delim); if (index_partitions != 0) { AppendProperty(result, "# index partitions", index_partitions, prop_delim, kv_delim); @@ -107,6 +118,11 @@ std::string TableProperties::ToString( filter_policy_name.empty() ? std::string("N/A") : filter_policy_name, prop_delim, kv_delim); + AppendProperty(result, "prefix extractor name", + prefix_extractor_name.empty() ? std::string("N/A") + : prefix_extractor_name, + prop_delim, kv_delim); + AppendProperty(result, "column family ID", column_family_id == rocksdb::TablePropertiesCollectorFactory:: Context::kUnknownColumnFamily @@ -137,6 +153,11 @@ std::string TableProperties::ToString( compression_name.empty() ? std::string("N/A") : compression_name, prop_delim, kv_delim); + AppendProperty( + result, "SST file compression options", + compression_options.empty() ? std::string("N/A") : compression_options, + prop_delim, kv_delim); + AppendProperty(result, "creation time", creation_time, prop_delim, kv_delim); AppendProperty(result, "time stamp of earliest key", oldest_key_time, @@ -150,11 +171,16 @@ void TableProperties::Add(const TableProperties& tp) { index_size += tp.index_size; index_partitions += tp.index_partitions; top_level_index_size += tp.top_level_index_size; + index_key_is_user_key += tp.index_key_is_user_key; + index_value_is_delta_encoded += tp.index_value_is_delta_encoded; filter_size += tp.filter_size; raw_key_size += tp.raw_key_size; raw_value_size += tp.raw_value_size; num_data_blocks += tp.num_data_blocks; num_entries += tp.num_entries; + num_deletions += tp.num_deletions; + num_merge_operands += tp.num_merge_operands; + num_range_deletions += tp.num_range_deletions; } const std::string TablePropertiesNames::kDataSize = @@ -165,6 +191,10 @@ const std::string TablePropertiesNames::kIndexPartitions = "rocksdb.index.partitions"; const std::string TablePropertiesNames::kTopLevelIndexSize = "rocksdb.top-level.index.size"; +const std::string TablePropertiesNames::kIndexKeyIsUserKey = + "rocksdb.index.key.is.user.key"; +const std::string TablePropertiesNames::kIndexValueIsDeltaEncoded = + "rocksdb.index.value.is.delta.encoded"; const std::string TablePropertiesNames::kFilterSize = "rocksdb.filter.size"; const std::string TablePropertiesNames::kRawKeySize = @@ -175,6 +205,11 @@ const std::string TablePropertiesNames::kNumDataBlocks = "rocksdb.num.data.blocks"; const std::string TablePropertiesNames::kNumEntries = "rocksdb.num.entries"; +const std::string TablePropertiesNames::kDeletedKeys = "rocksdb.deleted.keys"; +const std::string TablePropertiesNames::kMergeOperands = + "rocksdb.merge.operands"; +const std::string TablePropertiesNames::kNumRangeDeletions = + "rocksdb.num.range-deletions"; const std::string TablePropertiesNames::kFilterPolicy = "rocksdb.filter.policy"; const std::string TablePropertiesNames::kFormatVersion = @@ -193,6 +228,8 @@ const std::string TablePropertiesNames::kPrefixExtractorName = const std::string TablePropertiesNames::kPropertyCollectors = "rocksdb.property.collectors"; const std::string TablePropertiesNames::kCompression = "rocksdb.compression"; +const std::string TablePropertiesNames::kCompressionOptions = + "rocksdb.compression_options"; const std::string TablePropertiesNames::kCreationTime = "rocksdb.creation.time"; const std::string TablePropertiesNames::kOldestKeyTime = "rocksdb.oldest.key.time"; @@ -215,8 +252,9 @@ Status SeekToPropertiesBlock(InternalIterator* meta_iter, bool* is_found) { // Seek to the compression dictionary block. // Return true if it successfully seeks to that block. -Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found) { - return SeekToMetaBlock(meta_iter, kCompressionDictBlock, is_found); +Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found, + BlockHandle* block_handle) { + return SeekToMetaBlock(meta_iter, kCompressionDictBlock, is_found, block_handle); } Status SeekToRangeDelBlock(InternalIterator* meta_iter, bool* is_found, diff --git a/thirdparty/rocksdb/table/table_properties_internal.h b/thirdparty/rocksdb/table/table_properties_internal.h index 2a89427341..888b43d245 100644 --- a/thirdparty/rocksdb/table/table_properties_internal.h +++ b/thirdparty/rocksdb/table/table_properties_internal.h @@ -10,7 +10,6 @@ namespace rocksdb { -class InternalIterator; class BlockHandle; // Seek to the properties block. @@ -21,7 +20,8 @@ Status SeekToPropertiesBlock(InternalIterator* meta_iter, bool* is_found); // Seek to the compression dictionary block. // If it successfully seeks to the properties block, "is_found" will be // set to true. -Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found); +Status SeekToCompressionDictBlock(InternalIterator* meta_iter, bool* is_found, + BlockHandle* block_handle); // TODO(andrewkr) should not put all meta block in table_properties.h/cc Status SeekToRangeDelBlock(InternalIterator* meta_iter, bool* is_found, diff --git a/thirdparty/rocksdb/table/table_reader.h b/thirdparty/rocksdb/table/table_reader.h index 18fcda2737..a5f15e1304 100644 --- a/thirdparty/rocksdb/table/table_reader.h +++ b/thirdparty/rocksdb/table/table_reader.h @@ -9,6 +9,8 @@ #pragma once #include +#include "db/range_tombstone_fragmenter.h" +#include "rocksdb/slice_transform.h" #include "table/internal_iterator.h" namespace rocksdb { @@ -20,7 +22,6 @@ class Arena; struct ReadOptions; struct TableProperties; class GetContext; -class InternalIterator; // A Table is a sorted map from strings to strings. Tables are // immutable and persistent. A Table may be safely accessed from @@ -39,11 +40,13 @@ class TableReader { // skip_filters: disables checking the bloom filters even if they exist. This // option is effective only for block-based table format. virtual InternalIterator* NewIterator(const ReadOptions&, + const SliceTransform* prefix_extractor, Arena* arena = nullptr, - bool skip_filters = false) = 0; + bool skip_filters = false, + bool for_compaction = false) = 0; - virtual InternalIterator* NewRangeTombstoneIterator( - const ReadOptions& read_options) { + virtual FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator( + const ReadOptions& /*read_options*/) { return nullptr; } @@ -62,7 +65,7 @@ class TableReader { virtual std::shared_ptr GetTableProperties() const = 0; // Prepare work that can be done before the real Get() - virtual void Prepare(const Slice& target) {} + virtual void Prepare(const Slice& /*target*/) {} // Report an approximation of how much memory has been used. virtual size_t ApproximateMemoryUsage() const = 0; @@ -79,7 +82,9 @@ class TableReader { // skip_filters: disables checking the bloom filters even if they exist. This // option is effective only for block-based table format. virtual Status Get(const ReadOptions& readOptions, const Slice& key, - GetContext* get_context, bool skip_filters = false) = 0; + GetContext* get_context, + const SliceTransform* prefix_extractor, + bool skip_filters = false) = 0; // Prefetch data corresponding to a give range of keys // Typically this functionality is required for table implementations that @@ -94,7 +99,8 @@ class TableReader { } // convert db file to a human readable form - virtual Status DumpTable(WritableFile* out_file) { + virtual Status DumpTable(WritableFile* /*out_file*/, + const SliceTransform* /*prefix_extractor*/) { return Status::NotSupported("DumpTable() not supported"); } diff --git a/thirdparty/rocksdb/table/table_reader_bench.cc b/thirdparty/rocksdb/table/table_reader_bench.cc index 85e48c1fea..a9b75715b5 100644 --- a/thirdparty/rocksdb/table/table_reader_bench.cc +++ b/thirdparty/rocksdb/table/table_reader_bench.cc @@ -11,8 +11,6 @@ int main() { } #else -#include - #include "db/db_impl.h" #include "db/dbformat.h" #include "monitoring/histogram.h" @@ -25,11 +23,12 @@ int main() { #include "table/plain_table_factory.h" #include "table/table_builder.h" #include "util/file_reader_writer.h" +#include "util/gflags_compat.h" #include "util/testharness.h" #include "util/testutil.h" -using GFLAGS::ParseCommandLineFlags; -using GFLAGS::SetUsageMessage; +using GFLAGS_NAMESPACE::ParseCommandLineFlags; +using GFLAGS_NAMESPACE::SetUsageMessage; namespace rocksdb { @@ -71,37 +70,39 @@ uint64_t Now(Env* env, bool measured_by_nanosecond) { namespace { void TableReaderBenchmark(Options& opts, EnvOptions& env_options, ReadOptions& read_options, int num_keys1, - int num_keys2, int num_iter, int prefix_len, + int num_keys2, int num_iter, int /*prefix_len*/, bool if_query_empty_keys, bool for_iterator, bool through_db, bool measured_by_nanosecond) { rocksdb::InternalKeyComparator ikc(opts.comparator); - std::string file_name = test::TmpDir() - + "/rocksdb_table_reader_benchmark"; - std::string dbname = test::TmpDir() + "/rocksdb_table_reader_bench_db"; + std::string file_name = + test::PerThreadDBPath("rocksdb_table_reader_benchmark"); + std::string dbname = test::PerThreadDBPath("rocksdb_table_reader_bench_db"); WriteOptions wo; Env* env = Env::Default(); TableBuilder* tb = nullptr; DB* db = nullptr; Status s; const ImmutableCFOptions ioptions(opts); - unique_ptr file_writer; + const ColumnFamilyOptions cfo(opts); + const MutableCFOptions moptions(cfo); + std::unique_ptr file_writer; if (!through_db) { - unique_ptr file; + std::unique_ptr file; env->NewWritableFile(file_name, &file, env_options); std::vector > int_tbl_prop_collector_factories; - file_writer.reset(new WritableFileWriter(std::move(file), env_options)); + file_writer.reset( + new WritableFileWriter(std::move(file), file_name, env_options)); int unknown_level = -1; tb = opts.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, ikc, &int_tbl_prop_collector_factories, - CompressionType::kNoCompression, - CompressionOptions(), - nullptr /* compression_dict */, - false /* skip_filters */, kDefaultColumnFamilyName, - unknown_level), + TableBuilderOptions( + ioptions, moptions, ikc, &int_tbl_prop_collector_factories, + CompressionType::kNoCompression, 0 /* sample_for_compression */, + CompressionOptions(), false /* skip_filters */, + kDefaultColumnFamilyName, unknown_level), 0 /* column_family_id */, file_writer.get()); } else { s = DB::Open(opts, dbname, &db); @@ -126,9 +127,9 @@ void TableReaderBenchmark(Options& opts, EnvOptions& env_options, db->Flush(FlushOptions()); } - unique_ptr table_reader; + std::unique_ptr table_reader; if (!through_db) { - unique_ptr raf; + std::unique_ptr raf; s = env->NewRandomAccessFile(file_name, &raf, env_options); if (!s.ok()) { fprintf(stderr, "Create File Error: %s\n", s.ToString().c_str()); @@ -136,11 +137,12 @@ void TableReaderBenchmark(Options& opts, EnvOptions& env_options, } uint64_t file_size; env->GetFileSize(file_name, &file_size); - unique_ptr file_reader( + std::unique_ptr file_reader( new RandomAccessFileReader(std::move(raf), file_name)); s = opts.table_factory->NewTableReader( - TableReaderOptions(ioptions, env_options, ikc), std::move(file_reader), - file_size, &table_reader); + TableReaderOptions(ioptions, moptions.prefix_extractor.get(), + env_options, ikc), + std::move(file_reader), file_size, &table_reader); if (!s.ok()) { fprintf(stderr, "Open Table Error: %s\n", s.ToString().c_str()); exit(1); @@ -168,13 +170,13 @@ void TableReaderBenchmark(Options& opts, EnvOptions& env_options, if (!through_db) { PinnableSlice value; MergeContext merge_context; - RangeDelAggregator range_del_agg(ikc, {} /* snapshots */); + SequenceNumber max_covering_tombstone_seq = 0; GetContext get_context(ioptions.user_comparator, ioptions.merge_operator, ioptions.info_log, ioptions.statistics, GetContext::kNotFound, Slice(key), &value, nullptr, &merge_context, - &range_del_agg, env); - s = table_reader->Get(read_options, key, &get_context); + &max_covering_tombstone_seq, env); + s = table_reader->Get(read_options, key, &get_context, nullptr); } else { s = db->Get(read_options, key, &result); } @@ -196,7 +198,7 @@ void TableReaderBenchmark(Options& opts, EnvOptions& env_options, Iterator* iter = nullptr; InternalIterator* iiter = nullptr; if (!through_db) { - iiter = table_reader->NewIterator(read_options); + iiter = table_reader->NewIterator(read_options, nullptr); } else { iter = db->NewIterator(read_options); } diff --git a/thirdparty/rocksdb/table/table_test.cc b/thirdparty/rocksdb/table/table_test.cc index 178cf4243d..f217fe50aa 100644 --- a/thirdparty/rocksdb/table/table_test.cc +++ b/thirdparty/rocksdb/table/table_test.cc @@ -37,6 +37,7 @@ #include "table/block_based_table_factory.h" #include "table/block_based_table_reader.h" #include "table/block_builder.h" +#include "table/block_fetcher.h" #include "table/format.h" #include "table/get_context.h" #include "table/internal_iterator.h" @@ -64,13 +65,17 @@ namespace { // DummyPropertiesCollector used to test BlockBasedTableProperties class DummyPropertiesCollector : public TablePropertiesCollector { public: - const char* Name() const { return ""; } + const char* Name() const override { return ""; } - Status Finish(UserCollectedProperties* properties) { return Status::OK(); } + Status Finish(UserCollectedProperties* /*properties*/) override { + return Status::OK(); + } - Status Add(const Slice& user_key, const Slice& value) { return Status::OK(); } + Status Add(const Slice& /*user_key*/, const Slice& /*value*/) override { + return Status::OK(); + } - virtual UserCollectedProperties GetReadableProperties() const { + UserCollectedProperties GetReadableProperties() const override { return UserCollectedProperties{}; } }; @@ -78,21 +83,21 @@ class DummyPropertiesCollector : public TablePropertiesCollector { class DummyPropertiesCollectorFactory1 : public TablePropertiesCollectorFactory { public: - virtual TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context context) { + TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollectorFactory::Context /*context*/) override { return new DummyPropertiesCollector(); } - const char* Name() const { return "DummyPropertiesCollector1"; } + const char* Name() const override { return "DummyPropertiesCollector1"; } }; class DummyPropertiesCollectorFactory2 : public TablePropertiesCollectorFactory { public: - virtual TablePropertiesCollector* CreateTablePropertiesCollector( - TablePropertiesCollectorFactory::Context context) { + TablePropertiesCollector* CreateTablePropertiesCollector( + TablePropertiesCollectorFactory::Context /*context*/) override { return new DummyPropertiesCollector(); } - const char* Name() const { return "DummyPropertiesCollector2"; } + const char* Name() const override { return "DummyPropertiesCollector2"; } }; // Return reverse of "key". @@ -105,23 +110,23 @@ std::string Reverse(const Slice& key) { class ReverseKeyComparator : public Comparator { public: - virtual const char* Name() const override { + const char* Name() const override { return "rocksdb.ReverseBytewiseComparator"; } - virtual int Compare(const Slice& a, const Slice& b) const override { + int Compare(const Slice& a, const Slice& b) const override { return BytewiseComparator()->Compare(Reverse(a), Reverse(b)); } - virtual void FindShortestSeparator(std::string* start, - const Slice& limit) const override { + void FindShortestSeparator(std::string* start, + const Slice& limit) const override { std::string s = Reverse(*start); std::string l = Reverse(limit); BytewiseComparator()->FindShortestSeparator(&s, l); *start = Reverse(s); } - virtual void FindShortSuccessor(std::string* key) const override { + void FindShortSuccessor(std::string* key) const override { std::string s = Reverse(*key); BytewiseComparator()->FindShortSuccessor(&s); *key = Reverse(s); @@ -159,6 +164,7 @@ class Constructor { // been added so far. Returns the keys in sorted order in "*keys" // and stores the key/value pairs in "*kvmap" void Finish(const Options& options, const ImmutableCFOptions& ioptions, + const MutableCFOptions& moptions, const BlockBasedTableOptions& table_options, const InternalKeyComparator& internal_comparator, std::vector* keys, stl_wrappers::KVMap* kvmap) { @@ -169,7 +175,7 @@ class Constructor { keys->push_back(kv.first); } data_.clear(); - Status s = FinishImpl(options, ioptions, table_options, + Status s = FinishImpl(options, ioptions, moptions, table_options, internal_comparator, *kvmap); ASSERT_TRUE(s.ok()) << s.ToString(); } @@ -177,11 +183,13 @@ class Constructor { // Construct the data structure from the data in "data" virtual Status FinishImpl(const Options& options, const ImmutableCFOptions& ioptions, + const MutableCFOptions& moptions, const BlockBasedTableOptions& table_options, const InternalKeyComparator& internal_comparator, const stl_wrappers::KVMap& data) = 0; - virtual InternalIterator* NewIterator() const = 0; + virtual InternalIterator* NewIterator( + const SliceTransform* prefix_extractor = nullptr) const = 0; virtual const stl_wrappers::KVMap& data() { return data_; } @@ -204,14 +212,13 @@ class BlockConstructor: public Constructor { : Constructor(cmp), comparator_(cmp), block_(nullptr) { } - ~BlockConstructor() { - delete block_; - } - virtual Status FinishImpl(const Options& options, - const ImmutableCFOptions& ioptions, - const BlockBasedTableOptions& table_options, - const InternalKeyComparator& internal_comparator, - const stl_wrappers::KVMap& kv_map) override { + ~BlockConstructor() override { delete block_; } + Status FinishImpl(const Options& /*options*/, + const ImmutableCFOptions& /*ioptions*/, + const MutableCFOptions& /*moptions*/, + const BlockBasedTableOptions& table_options, + const InternalKeyComparator& /*internal_comparator*/, + const stl_wrappers::KVMap& kv_map) override { delete block_; block_ = nullptr; BlockBuilder builder(table_options.block_restart_interval); @@ -223,12 +230,12 @@ class BlockConstructor: public Constructor { data_ = builder.Finish().ToString(); BlockContents contents; contents.data = data_; - contents.cachable = false; block_ = new Block(std::move(contents), kDisableGlobalSequenceNumber); return Status::OK(); } - virtual InternalIterator* NewIterator() const override { - return block_->NewIterator(comparator_); + InternalIterator* NewIterator( + const SliceTransform* /*prefix_extractor*/) const override { + return block_->NewIterator(comparator_, comparator_); } private: @@ -245,32 +252,32 @@ class KeyConvertingIterator : public InternalIterator { explicit KeyConvertingIterator(InternalIterator* iter, bool arena_mode = false) : iter_(iter), arena_mode_(arena_mode) {} - virtual ~KeyConvertingIterator() { + ~KeyConvertingIterator() override { if (arena_mode_) { iter_->~InternalIterator(); } else { delete iter_; } } - virtual bool Valid() const override { return iter_->Valid(); } - virtual void Seek(const Slice& target) override { + bool Valid() const override { return iter_->Valid() && status_.ok(); } + void Seek(const Slice& target) override { ParsedInternalKey ikey(target, kMaxSequenceNumber, kTypeValue); std::string encoded; AppendInternalKey(&encoded, ikey); iter_->Seek(encoded); } - virtual void SeekForPrev(const Slice& target) override { + void SeekForPrev(const Slice& target) override { ParsedInternalKey ikey(target, kMaxSequenceNumber, kTypeValue); std::string encoded; AppendInternalKey(&encoded, ikey); iter_->SeekForPrev(encoded); } - virtual void SeekToFirst() override { iter_->SeekToFirst(); } - virtual void SeekToLast() override { iter_->SeekToLast(); } - virtual void Next() override { iter_->Next(); } - virtual void Prev() override { iter_->Prev(); } + void SeekToFirst() override { iter_->SeekToFirst(); } + void SeekToLast() override { iter_->SeekToLast(); } + void Next() override { iter_->Next(); } + void Prev() override { iter_->Prev(); } - virtual Slice key() const override { + Slice key() const override { assert(Valid()); ParsedInternalKey parsed_key; if (!ParseInternalKey(iter_->key(), &parsed_key)) { @@ -280,8 +287,8 @@ class KeyConvertingIterator : public InternalIterator { return parsed_key.user_key; } - virtual Slice value() const override { return iter_->value(); } - virtual Status status() const override { + Slice value() const override { return iter_->value(); } + Status status() const override { return status_.ok() ? iter_->status() : status_; } @@ -298,31 +305,32 @@ class KeyConvertingIterator : public InternalIterator { class TableConstructor: public Constructor { public: explicit TableConstructor(const Comparator* cmp, - bool convert_to_internal_key = false) + bool convert_to_internal_key = false, + int level = -1) : Constructor(cmp), - convert_to_internal_key_(convert_to_internal_key) {} - ~TableConstructor() { Reset(); } - - virtual Status FinishImpl(const Options& options, - const ImmutableCFOptions& ioptions, - const BlockBasedTableOptions& table_options, - const InternalKeyComparator& internal_comparator, - const stl_wrappers::KVMap& kv_map) override { + convert_to_internal_key_(convert_to_internal_key), + level_(level) {} + ~TableConstructor() override { Reset(); } + + Status FinishImpl(const Options& options, const ImmutableCFOptions& ioptions, + const MutableCFOptions& moptions, + const BlockBasedTableOptions& /*table_options*/, + const InternalKeyComparator& internal_comparator, + const stl_wrappers::KVMap& kv_map) override { Reset(); soptions.use_mmap_reads = ioptions.allow_mmap_reads; - file_writer_.reset(test::GetWritableFileWriter(new test::StringSink())); - unique_ptr builder; + file_writer_.reset(test::GetWritableFileWriter(new test::StringSink(), + "" /* don't care */)); + std::unique_ptr builder; std::vector> int_tbl_prop_collector_factories; std::string column_family_name; - int unknown_level = -1; builder.reset(ioptions.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, internal_comparator, + TableBuilderOptions(ioptions, moptions, internal_comparator, &int_tbl_prop_collector_factories, - options.compression, CompressionOptions(), - nullptr /* compression_dict */, - false /* skip_filters */, column_family_name, - unknown_level), + options.compression, options.sample_for_compression, + options.compression_opts, false /* skip_filters */, + column_family_name, level_), TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, file_writer_.get())); @@ -341,20 +349,26 @@ class TableConstructor: public Constructor { file_writer_->Flush(); EXPECT_TRUE(s.ok()) << s.ToString(); - EXPECT_EQ(GetSink()->contents().size(), builder->FileSize()); + EXPECT_EQ(TEST_GetSink()->contents().size(), builder->FileSize()); // Open the table uniq_id_ = cur_uniq_id_++; file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource( - GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads))); + TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads))); + const bool kSkipFilters = true; + const bool kImmortal = true; return ioptions.table_factory->NewTableReader( - TableReaderOptions(ioptions, soptions, internal_comparator), - std::move(file_reader_), GetSink()->contents().size(), &table_reader_); + TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions, + internal_comparator, !kSkipFilters, !kImmortal, + level_), + std::move(file_reader_), TEST_GetSink()->contents().size(), + &table_reader_); } - virtual InternalIterator* NewIterator() const override { + InternalIterator* NewIterator( + const SliceTransform* prefix_extractor) const override { ReadOptions ro; - InternalIterator* iter = table_reader_->NewIterator(ro); + InternalIterator* iter = table_reader_->NewIterator(ro, prefix_extractor); if (convert_to_internal_key_) { return new KeyConvertingIterator(iter); } else { @@ -371,19 +385,20 @@ class TableConstructor: public Constructor { return table_reader_->ApproximateOffsetOf(key); } - virtual Status Reopen(const ImmutableCFOptions& ioptions) { + virtual Status Reopen(const ImmutableCFOptions& ioptions, + const MutableCFOptions& moptions) { file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource( - GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads))); + TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads))); return ioptions.table_factory->NewTableReader( - TableReaderOptions(ioptions, soptions, *last_internal_key_), - std::move(file_reader_), GetSink()->contents().size(), &table_reader_); + TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions, + *last_internal_key_), + std::move(file_reader_), TEST_GetSink()->contents().size(), + &table_reader_); } - virtual TableReader* GetTableReader() { - return table_reader_.get(); - } + virtual TableReader* GetTableReader() { return table_reader_.get(); } - virtual bool AnywayDeleteIterator() const override { + bool AnywayDeleteIterator() const override { return convert_to_internal_key_; } @@ -391,6 +406,10 @@ class TableConstructor: public Constructor { bool ConvertToInternalKey() { return convert_to_internal_key_; } + test::StringSink* TEST_GetSink() { + return static_cast(file_writer_->writable_file()); + } + private: void Reset() { uniq_id_ = 0; @@ -399,15 +418,12 @@ class TableConstructor: public Constructor { file_reader_.reset(); } - test::StringSink* GetSink() { - return static_cast(file_writer_->writable_file()); - } - uint64_t uniq_id_; - unique_ptr file_writer_; - unique_ptr file_reader_; - unique_ptr table_reader_; + std::unique_ptr file_writer_; + std::unique_ptr file_reader_; + std::unique_ptr table_reader_; bool convert_to_internal_key_; + int level_; TableConstructor(); @@ -430,13 +446,12 @@ class MemTableConstructor: public Constructor { wb, kMaxSequenceNumber, 0 /* column_family_id */); memtable_->Ref(); } - ~MemTableConstructor() { - delete memtable_->Unref(); - } - virtual Status FinishImpl(const Options&, const ImmutableCFOptions& ioptions, - const BlockBasedTableOptions& table_options, - const InternalKeyComparator& internal_comparator, - const stl_wrappers::KVMap& kv_map) override { + ~MemTableConstructor() override { delete memtable_->Unref(); } + Status FinishImpl(const Options&, const ImmutableCFOptions& ioptions, + const MutableCFOptions& /*moptions*/, + const BlockBasedTableOptions& /*table_options*/, + const InternalKeyComparator& /*internal_comparator*/, + const stl_wrappers::KVMap& kv_map) override { delete memtable_->Unref(); ImmutableCFOptions mem_ioptions(ioptions); memtable_ = new MemTable(internal_comparator_, mem_ioptions, @@ -450,14 +465,15 @@ class MemTableConstructor: public Constructor { } return Status::OK(); } - virtual InternalIterator* NewIterator() const override { + InternalIterator* NewIterator( + const SliceTransform* /*prefix_extractor*/) const override { return new KeyConvertingIterator( memtable_->NewIterator(ReadOptions(), &arena_), true); } - virtual bool AnywayDeleteIterator() const override { return true; } + bool AnywayDeleteIterator() const override { return true; } - virtual bool IsArenaMode() const override { return true; } + bool IsArenaMode() const override { return true; } private: mutable Arena arena_; @@ -471,21 +487,19 @@ class MemTableConstructor: public Constructor { class InternalIteratorFromIterator : public InternalIterator { public: explicit InternalIteratorFromIterator(Iterator* it) : it_(it) {} - virtual bool Valid() const override { return it_->Valid(); } - virtual void Seek(const Slice& target) override { it_->Seek(target); } - virtual void SeekForPrev(const Slice& target) override { - it_->SeekForPrev(target); - } - virtual void SeekToFirst() override { it_->SeekToFirst(); } - virtual void SeekToLast() override { it_->SeekToLast(); } - virtual void Next() override { it_->Next(); } - virtual void Prev() override { it_->Prev(); } + bool Valid() const override { return it_->Valid(); } + void Seek(const Slice& target) override { it_->Seek(target); } + void SeekForPrev(const Slice& target) override { it_->SeekForPrev(target); } + void SeekToFirst() override { it_->SeekToFirst(); } + void SeekToLast() override { it_->SeekToLast(); } + void Next() override { it_->Next(); } + void Prev() override { it_->Prev(); } Slice key() const override { return it_->key(); } Slice value() const override { return it_->value(); } - virtual Status status() const override { return it_->status(); } + Status status() const override { return it_->status(); } private: - unique_ptr it_; + std::unique_ptr it_; }; class DBConstructor: public Constructor { @@ -496,14 +510,13 @@ class DBConstructor: public Constructor { db_ = nullptr; NewDB(); } - ~DBConstructor() { - delete db_; - } - virtual Status FinishImpl(const Options& options, - const ImmutableCFOptions& ioptions, - const BlockBasedTableOptions& table_options, - const InternalKeyComparator& internal_comparator, - const stl_wrappers::KVMap& kv_map) override { + ~DBConstructor() override { delete db_; } + Status FinishImpl(const Options& /*options*/, + const ImmutableCFOptions& /*ioptions*/, + const MutableCFOptions& /*moptions*/, + const BlockBasedTableOptions& /*table_options*/, + const InternalKeyComparator& /*internal_comparator*/, + const stl_wrappers::KVMap& kv_map) override { delete db_; db_ = nullptr; NewDB(); @@ -515,15 +528,16 @@ class DBConstructor: public Constructor { return Status::OK(); } - virtual InternalIterator* NewIterator() const override { + InternalIterator* NewIterator( + const SliceTransform* /*prefix_extractor*/) const override { return new InternalIteratorFromIterator(db_->NewIterator(ReadOptions())); } - virtual DB* db() const override { return db_; } + DB* db() const override { return db_; } private: void NewDB() { - std::string name = test::TmpDir() + "/table_testdb"; + std::string name = test::PerThreadDBPath("table_testdb"); Options options; options.comparator = comparator_; @@ -655,9 +669,9 @@ class FixedOrLessPrefixTransform : public SliceTransform { prefix_len_(prefix_len) { } - virtual const char* Name() const override { return "rocksdb.FixedPrefix"; } + const char* Name() const override { return "rocksdb.FixedPrefix"; } - virtual Slice Transform(const Slice& src) const override { + Slice Transform(const Slice& src) const override { assert(InDomain(src)); if (src.size() < prefix_len_) { return src; @@ -665,17 +679,19 @@ class FixedOrLessPrefixTransform : public SliceTransform { return Slice(src.data(), prefix_len_); } - virtual bool InDomain(const Slice& src) const override { return true; } + bool InDomain(const Slice& /*src*/) const override { return true; } - virtual bool InRange(const Slice& dst) const override { + bool InRange(const Slice& dst) const override { return (dst.size() <= prefix_len_); } + bool FullLengthEnabled(size_t* /*len*/) const override { return false; } }; class HarnessTest : public testing::Test { public: HarnessTest() : ioptions_(options_), + moptions_(options_), constructor_(nullptr), write_buffer_(options_.db_write_buffer_size) {} @@ -774,9 +790,10 @@ class HarnessTest : public testing::Test { break; } ioptions_ = ImmutableCFOptions(options_); + moptions_ = MutableCFOptions(options_); } - ~HarnessTest() { delete constructor_; } + ~HarnessTest() override { delete constructor_; } void Add(const std::string& key, const std::string& value) { constructor_->Add(key, value); @@ -785,7 +802,7 @@ class HarnessTest : public testing::Test { void Test(Random* rnd) { std::vector keys; stl_wrappers::KVMap data; - constructor_->Finish(options_, ioptions_, table_options_, + constructor_->Finish(options_, ioptions_, moptions_, table_options_, *internal_comparator_, &keys, &data); TestForwardScan(keys, data); @@ -795,7 +812,7 @@ class HarnessTest : public testing::Test { TestRandomAccess(rnd, keys, data); } - void TestForwardScan(const std::vector& keys, + void TestForwardScan(const std::vector& /*keys*/, const stl_wrappers::KVMap& data) { InternalIterator* iter = constructor_->NewIterator(); ASSERT_TRUE(!iter->Valid()); @@ -813,7 +830,7 @@ class HarnessTest : public testing::Test { } } - void TestBackwardScan(const std::vector& keys, + void TestBackwardScan(const std::vector& /*keys*/, const stl_wrappers::KVMap& data) { InternalIterator* iter = constructor_->NewIterator(); ASSERT_TRUE(!iter->Valid()); @@ -963,15 +980,38 @@ class HarnessTest : public testing::Test { // Returns nullptr if not running against a DB DB* db() const { return constructor_->db(); } + void RandomizedHarnessTest(size_t part, size_t total) { + std::vector args = GenerateArgList(); + assert(part); + assert(part <= total); + for (size_t i = 0; i < args.size(); i++) { + if ((i % total) + 1 != part) { + continue; + } + Init(args[i]); + Random rnd(test::RandomSeed() + 5); + for (int num_entries = 0; num_entries < 2000; + num_entries += (num_entries < 50 ? 1 : 200)) { + for (int e = 0; e < num_entries; e++) { + std::string v; + Add(test::RandomKey(&rnd, rnd.Skewed(4)), + test::RandomString(&rnd, rnd.Skewed(5), &v).ToString()); + } + Test(&rnd); + } + } + } + private: Options options_ = Options(); ImmutableCFOptions ioptions_; + MutableCFOptions moptions_; BlockBasedTableOptions table_options_ = BlockBasedTableOptions(); Constructor* constructor_; WriteBufferManager write_buffer_; bool support_prev_; bool only_support_prefix_seek_; - shared_ptr internal_comparator_; + std::shared_ptr internal_comparator_; }; static bool Between(uint64_t val, uint64_t low, uint64_t high) { @@ -1003,9 +1043,32 @@ class TableTest : public testing::Test { }; class GeneralTableTest : public TableTest {}; -class BlockBasedTableTest : public TableTest {}; +class BlockBasedTableTest + : public TableTest, + virtual public ::testing::WithParamInterface { + public: + BlockBasedTableTest() : format_(GetParam()) {} + + BlockBasedTableOptions GetBlockBasedTableOptions() { + BlockBasedTableOptions options; + options.format_version = format_; + return options; + } + + protected: + uint64_t IndexUncompressedHelper(bool indexCompress); + + private: + uint32_t format_; +}; class PlainTableTest : public TableTest {}; class TablePropertyTest : public testing::Test {}; +class BBTTailPrefetchTest : public TableTest {}; + +INSTANTIATE_TEST_CASE_P(FormatDef, BlockBasedTableTest, + testing::Values(test::kDefaultFormatVersion)); +INSTANTIATE_TEST_CASE_P(FormatLatest, BlockBasedTableTest, + testing::Values(test::kLatestFormatVersion)); // This test serves as the living tutorial for the prefix scan of user collected // properties. @@ -1046,7 +1109,7 @@ TEST_F(TablePropertyTest, PrefixScanTest) { // This test include all the basic checks except those for index size and block // size, which will be conducted in separated unit tests. -TEST_F(BlockBasedTableTest, BasicBlockBasedTableProperties) { +TEST_P(BlockBasedTableTest, BasicBlockBasedTableProperties) { TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); c.Add("a1", "val1"); @@ -1064,13 +1127,18 @@ TEST_F(BlockBasedTableTest, BasicBlockBasedTableProperties) { stl_wrappers::KVMap kvmap; Options options; options.compression = kNoCompression; - BlockBasedTableOptions table_options; + options.statistics = CreateDBStatistics(); + options.statistics->set_stats_level(StatsLevel::kAll); + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_restart_interval = 1; options.table_factory.reset(NewBlockBasedTableFactory(table_options)); - const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + ImmutableCFOptions ioptions(options); + MutableCFOptions moptions(options); + ioptions.statistics = options.statistics.get(); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); + ASSERT_EQ(options.statistics->getTickerCount(NUMBER_BLOCK_NOT_COMPRESSED), 0); auto& props = *c.GetTableReader()->GetTableProperties(); ASSERT_EQ(kvmap.size(), props.num_entries); @@ -1094,7 +1162,43 @@ TEST_F(BlockBasedTableTest, BasicBlockBasedTableProperties) { c.ResetTableReader(); } -TEST_F(BlockBasedTableTest, BlockBasedTableProperties2) { +#ifdef SNAPPY +uint64_t BlockBasedTableTest::IndexUncompressedHelper(bool compressed) { + TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); + constexpr size_t kNumKeys = 10000; + + for (size_t k = 0; k < kNumKeys; ++k) { + c.Add("key" + ToString(k), "val" + ToString(k)); + } + + std::vector keys; + stl_wrappers::KVMap kvmap; + Options options; + options.compression = kSnappyCompression; + options.statistics = CreateDBStatistics(); + options.statistics->set_stats_level(StatsLevel::kAll); + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + table_options.block_restart_interval = 1; + table_options.enable_index_compression = compressed; + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + + ImmutableCFOptions ioptions(options); + MutableCFOptions moptions(options); + ioptions.statistics = options.statistics.get(); + c.Finish(options, ioptions, moptions, table_options, + GetPlainInternalComparator(options.comparator), &keys, &kvmap); + c.ResetTableReader(); + return options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED); +} +TEST_P(BlockBasedTableTest, IndexUncompressed) { + uint64_t tbl1_compressed_cnt = IndexUncompressedHelper(true); + uint64_t tbl2_compressed_cnt = IndexUncompressedHelper(false); + // tbl1_compressed_cnt should include 1 index block + EXPECT_EQ(tbl2_compressed_cnt + 1, tbl1_compressed_cnt); +} +#endif // SNAPPY + +TEST_P(BlockBasedTableTest, BlockBasedTableProperties2) { TableConstructor c(&reverse_key_comparator); std::vector keys; stl_wrappers::KVMap kvmap; @@ -1102,11 +1206,12 @@ TEST_F(BlockBasedTableTest, BlockBasedTableProperties2) { { Options options; options.compression = CompressionType::kNoCompression; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); options.table_factory.reset(NewBlockBasedTableFactory(table_options)); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); auto& props = *c.GetTableReader()->GetTableProperties(); @@ -1128,7 +1233,7 @@ TEST_F(BlockBasedTableTest, BlockBasedTableProperties2) { { Options options; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); options.table_factory.reset(NewBlockBasedTableFactory(table_options)); options.comparator = &reverse_key_comparator; options.merge_operator = MergeOperators::CreateUInt64AddOperator(); @@ -1139,7 +1244,8 @@ TEST_F(BlockBasedTableTest, BlockBasedTableProperties2) { new DummyPropertiesCollectorFactory2()); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); auto& props = *c.GetTableReader()->GetTableProperties(); @@ -1154,11 +1260,18 @@ TEST_F(BlockBasedTableTest, BlockBasedTableProperties2) { } } -TEST_F(BlockBasedTableTest, RangeDelBlock) { +TEST_P(BlockBasedTableTest, RangeDelBlock) { TableConstructor c(BytewiseComparator()); std::vector keys = {"1pika", "2chu"}; std::vector vals = {"p", "c"}; + std::vector expected_tombstones = { + {"1pika", "2chu", 0}, + {"2chu", "c", 1}, + {"2chu", "c", 0}, + {"c", "p", 0}, + }; + for (int i = 0; i < 2; i++) { RangeTombstone t(keys[i], vals[i], i); std::pair p = t.Serialize(); @@ -1169,15 +1282,16 @@ TEST_F(BlockBasedTableTest, RangeDelBlock) { stl_wrappers::KVMap kvmap; Options options; options.compression = kNoCompression; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_restart_interval = 1; options.table_factory.reset(NewBlockBasedTableFactory(table_options)); const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); std::unique_ptr internal_cmp( new InternalKeyComparator(options.comparator)); - c.Finish(options, ioptions, table_options, *internal_cmp, &sorted_keys, - &kvmap); + c.Finish(options, ioptions, moptions, table_options, *internal_cmp, + &sorted_keys, &kvmap); for (int j = 0; j < 2; ++j) { std::unique_ptr iter( @@ -1190,32 +1304,34 @@ TEST_F(BlockBasedTableTest, RangeDelBlock) { ASSERT_FALSE(iter->Valid()); iter->SeekToFirst(); ASSERT_TRUE(iter->Valid()); - for (int i = 0; i < 2; i++) { + for (size_t i = 0; i < expected_tombstones.size(); i++) { ASSERT_TRUE(iter->Valid()); ParsedInternalKey parsed_key; ASSERT_TRUE(ParseInternalKey(iter->key(), &parsed_key)); RangeTombstone t(parsed_key, iter->value()); - ASSERT_EQ(t.start_key_, keys[i]); - ASSERT_EQ(t.end_key_, vals[i]); - ASSERT_EQ(t.seq_, i); + const auto& expected_t = expected_tombstones[i]; + ASSERT_EQ(t.start_key_, expected_t.start_key_); + ASSERT_EQ(t.end_key_, expected_t.end_key_); + ASSERT_EQ(t.seq_, expected_t.seq_); iter->Next(); } ASSERT_TRUE(!iter->Valid()); } } -TEST_F(BlockBasedTableTest, FilterPolicyNameProperties) { +TEST_P(BlockBasedTableTest, FilterPolicyNameProperties) { TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); c.Add("a1", "val1"); std::vector keys; stl_wrappers::KVMap kvmap; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.filter_policy.reset(NewBloomFilterPolicy(10)); Options options; options.table_factory.reset(NewBlockBasedTableFactory(table_options)); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); auto& props = *c.GetTableReader()->GetTableProperties(); ASSERT_EQ("rocksdb.BuiltinBloomFilter", props.filter_policy_name); @@ -1258,13 +1374,14 @@ void PrefetchRange(TableConstructor* c, Options* opt, table_options->block_cache = NewLRUCache(16 * 1024 * 1024, 4); opt->table_factory.reset(NewBlockBasedTableFactory(*table_options)); const ImmutableCFOptions ioptions2(*opt); - ASSERT_OK(c->Reopen(ioptions2)); + const MutableCFOptions moptions(*opt); + ASSERT_OK(c->Reopen(ioptions2, moptions)); // prefetch auto* table_reader = dynamic_cast(c->GetTableReader()); Status s; - unique_ptr begin, end; - unique_ptr i_begin, i_end; + std::unique_ptr begin, end; + std::unique_ptr i_begin, i_end; if (key_begin != nullptr) { if (c->ConvertToInternalKey()) { i_begin.reset(new InternalKey(key_begin, kMaxSequenceNumber, kTypeValue)); @@ -1291,14 +1408,14 @@ void PrefetchRange(TableConstructor* c, Options* opt, c->ResetTableReader(); } -TEST_F(BlockBasedTableTest, PrefetchTest) { +TEST_P(BlockBasedTableTest, PrefetchTest) { // The purpose of this test is to test the prefetching operation built into // BlockBasedTable. Options opt; - unique_ptr ikc; + std::unique_ptr ikc; ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); opt.compression = kNoCompression; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_size = 1024; // big enough so we don't ever lose cached values. table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); @@ -1315,7 +1432,8 @@ TEST_F(BlockBasedTableTest, PrefetchTest) { std::vector keys; stl_wrappers::KVMap kvmap; const ImmutableCFOptions ioptions(opt); - c.Finish(opt, ioptions, table_options, *ikc, &keys, &kvmap); + const MutableCFOptions moptions(opt); + c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); c.ResetTableReader(); // We get the following data spread : @@ -1358,8 +1476,8 @@ TEST_F(BlockBasedTableTest, PrefetchTest) { c.ResetTableReader(); } -TEST_F(BlockBasedTableTest, TotalOrderSeekOnHashIndex) { - BlockBasedTableOptions table_options; +TEST_P(BlockBasedTableTest, TotalOrderSeekOnHashIndex) { + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); for (int i = 0; i < 4; ++i) { Options options; // Make each key/value an individual block @@ -1410,14 +1528,16 @@ TEST_F(BlockBasedTableTest, TotalOrderSeekOnHashIndex) { std::vector keys; stl_wrappers::KVMap kvmap; const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); auto props = c.GetTableReader()->GetTableProperties(); ASSERT_EQ(7u, props->num_data_blocks); auto* reader = c.GetTableReader(); ReadOptions ro; ro.total_order_seek = true; - std::unique_ptr iter(reader->NewIterator(ro)); + std::unique_ptr iter( + reader->NewIterator(ro, moptions.prefix_extractor.get())); iter->Seek(InternalKey("b", 0, kTypeValue).Encode()); ASSERT_OK(iter->status()); @@ -1448,8 +1568,8 @@ TEST_F(BlockBasedTableTest, TotalOrderSeekOnHashIndex) { } } -TEST_F(BlockBasedTableTest, NoopTransformSeek) { - BlockBasedTableOptions table_options; +TEST_P(BlockBasedTableTest, NoopTransformSeek) { + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.filter_policy.reset(NewBloomFilterPolicy(10)); Options options; @@ -1466,15 +1586,17 @@ TEST_F(BlockBasedTableTest, NoopTransformSeek) { std::vector keys; stl_wrappers::KVMap kvmap; const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); const InternalKeyComparator internal_comparator(options.comparator); - c.Finish(options, ioptions, table_options, internal_comparator, &keys, - &kvmap); + c.Finish(options, ioptions, moptions, table_options, internal_comparator, + &keys, &kvmap); auto* reader = c.GetTableReader(); for (int i = 0; i < 2; ++i) { ReadOptions ro; ro.total_order_seek = (i == 0); - std::unique_ptr iter(reader->NewIterator(ro)); + std::unique_ptr iter( + reader->NewIterator(ro, moptions.prefix_extractor.get())); iter->Seek(key.Encode()); ASSERT_OK(iter->status()); @@ -1483,10 +1605,10 @@ TEST_F(BlockBasedTableTest, NoopTransformSeek) { } } -TEST_F(BlockBasedTableTest, SkipPrefixBloomFilter) { +TEST_P(BlockBasedTableTest, SkipPrefixBloomFilter) { // if DB is opened with a prefix extractor of a different name, // prefix bloom is skipped when read the file - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.filter_policy.reset(NewBloomFilterPolicy(2)); table_options.whole_key_filtering = false; @@ -1501,14 +1623,18 @@ TEST_F(BlockBasedTableTest, SkipPrefixBloomFilter) { std::vector keys; stl_wrappers::KVMap kvmap; const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); const InternalKeyComparator internal_comparator(options.comparator); - c.Finish(options, ioptions, table_options, internal_comparator, &keys, - &kvmap); + c.Finish(options, ioptions, moptions, table_options, internal_comparator, + &keys, &kvmap); + // TODO(Zhongyi): update test to use MutableCFOptions options.prefix_extractor.reset(NewFixedPrefixTransform(9)); const ImmutableCFOptions new_ioptions(options); - c.Reopen(new_ioptions); + const MutableCFOptions new_moptions(options); + c.Reopen(new_ioptions, new_moptions); auto reader = c.GetTableReader(); - std::unique_ptr db_iter(reader->NewIterator(ReadOptions())); + std::unique_ptr db_iter( + reader->NewIterator(ReadOptions(), new_moptions.prefix_extractor.get())); // Test point lookup // only one kv @@ -1528,7 +1654,7 @@ static std::string RandomString(Random* rnd, int len) { } void AddInternalKey(TableConstructor* c, const std::string& prefix, - int suffix_len = 800) { + int /*suffix_len*/ = 800) { static Random rnd(1023); InternalKey k(prefix + RandomString(&rnd, 800), 0, kTypeValue); c->Add(k.Encode().ToString(), "v"); @@ -1565,14 +1691,17 @@ void TableTest::IndexTest(BlockBasedTableOptions table_options) { std::unique_ptr comparator( new InternalKeyComparator(BytewiseComparator())); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, *comparator, &keys, &kvmap); + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, + &kvmap); auto reader = c.GetTableReader(); auto props = reader->GetTableProperties(); ASSERT_EQ(5u, props->num_data_blocks); + // TODO(Zhongyi): update test to use MutableCFOptions std::unique_ptr index_iter( - reader->NewIterator(ReadOptions())); + reader->NewIterator(ReadOptions(), moptions.prefix_extractor.get())); // -- Find keys do not exist, but have common prefix. std::vector prefixes = {"001", "003", "005", "007", "009"}; @@ -1643,24 +1772,24 @@ void TableTest::IndexTest(BlockBasedTableOptions table_options) { c.ResetTableReader(); } -TEST_F(TableTest, BinaryIndexTest) { - BlockBasedTableOptions table_options; +TEST_P(BlockBasedTableTest, BinaryIndexTest) { + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.index_type = BlockBasedTableOptions::kBinarySearch; IndexTest(table_options); } -TEST_F(TableTest, HashIndexTest) { - BlockBasedTableOptions table_options; +TEST_P(BlockBasedTableTest, HashIndexTest) { + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.index_type = BlockBasedTableOptions::kHashSearch; IndexTest(table_options); } -TEST_F(TableTest, PartitionIndexTest) { +TEST_P(BlockBasedTableTest, PartitionIndexTest) { const int max_index_keys = 5; const int est_max_index_key_value_size = 32; const int est_max_index_size = max_index_keys * est_max_index_key_value_size; for (int i = 1; i <= est_max_index_size + 1; i++) { - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch; table_options.metadata_block_size = i; IndexTest(table_options); @@ -1670,7 +1799,7 @@ TEST_F(TableTest, PartitionIndexTest) { // It's very hard to figure out the index block size of a block accurately. // To make sure we get the index size, we just make sure as key number // grows, the filter block size also grows. -TEST_F(BlockBasedTableTest, IndexSizeStat) { +TEST_P(BlockBasedTableTest, IndexSizeStat) { uint64_t last_index_size = 0; // we need to use random keys since the pure human readable texts @@ -1696,12 +1825,13 @@ TEST_F(BlockBasedTableTest, IndexSizeStat) { stl_wrappers::KVMap kvmap; Options options; options.compression = kNoCompression; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_restart_interval = 1; options.table_factory.reset(NewBlockBasedTableFactory(table_options)); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &ks, &kvmap); auto index_size = c.GetTableReader()->GetTableProperties()->index_size; ASSERT_GT(index_size, last_index_size); @@ -1710,12 +1840,12 @@ TEST_F(BlockBasedTableTest, IndexSizeStat) { } } -TEST_F(BlockBasedTableTest, NumBlockStat) { +TEST_P(BlockBasedTableTest, NumBlockStat) { Random rnd(test::RandomSeed()); TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); Options options; options.compression = kNoCompression; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_restart_interval = 1; table_options.block_size = 1000; options.table_factory.reset(NewBlockBasedTableFactory(table_options)); @@ -1729,7 +1859,8 @@ TEST_F(BlockBasedTableTest, NumBlockStat) { std::vector ks; stl_wrappers::KVMap kvmap; const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &ks, &kvmap); ASSERT_EQ(kvmap.size(), c.GetTableReader()->GetTableProperties()->num_data_blocks); @@ -1801,11 +1932,11 @@ class BlockCachePropertiesSnapshot { // Make sure, by default, index/filter blocks were pre-loaded (meaning we won't // use block cache to store them). -TEST_F(BlockBasedTableTest, BlockCacheDisabledTest) { +TEST_P(BlockBasedTableTest, BlockCacheDisabledTest) { Options options; options.create_if_missing = true; options.statistics = CreateDBStatistics(); - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_cache = NewLRUCache(1024, 4); table_options.filter_policy.reset(NewBloomFilterPolicy(10)); options.table_factory.reset(new BlockBasedTableFactory(table_options)); @@ -1815,7 +1946,8 @@ TEST_F(BlockBasedTableTest, BlockCacheDisabledTest) { TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); c.Add("key", "value"); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); // preloading filter/index blocks is enabled. @@ -1835,7 +1967,8 @@ TEST_F(BlockBasedTableTest, BlockCacheDisabledTest) { GetContext::kNotFound, Slice(), nullptr, nullptr, nullptr, nullptr, nullptr); // a hack that just to trigger BlockBasedTable::GetFilter. - reader->Get(ReadOptions(), "non-exist-key", &get_context); + reader->Get(ReadOptions(), "non-exist-key", &get_context, + moptions.prefix_extractor.get()); BlockCachePropertiesSnapshot props(options.statistics.get()); props.AssertIndexBlockStat(0, 0); props.AssertFilterBlockStat(0, 0); @@ -1844,15 +1977,15 @@ TEST_F(BlockBasedTableTest, BlockCacheDisabledTest) { // Due to the difficulities of the intersaction between statistics, this test // only tests the case when "index block is put to block cache" -TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { +TEST_P(BlockBasedTableTest, FilterBlockInBlockCache) { // -- Table construction Options options; options.create_if_missing = true; options.statistics = CreateDBStatistics(); // Enable the cache for index/filter blocks - BlockBasedTableOptions table_options; - table_options.block_cache = NewLRUCache(1024, 4); + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + table_options.block_cache = NewLRUCache(2048, 2); table_options.cache_index_and_filter_blocks = true; options.table_factory.reset(new BlockBasedTableFactory(table_options)); std::vector keys; @@ -1861,7 +1994,8 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); c.Add("key", "value"); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); // preloading filter/index blocks is prohibited. auto* reader = dynamic_cast(c.GetTableReader()); @@ -1870,7 +2004,7 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { // -- PART 1: Open with regular block cache. // Since block_cache is disabled, no cache activities will be involved. - unique_ptr iter; + std::unique_ptr iter; int64_t last_cache_bytes_read = 0; // At first, no block will be accessed. @@ -1887,7 +2021,7 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { // Only index block will be accessed { - iter.reset(c.NewIterator()); + iter.reset(c.NewIterator(moptions.prefix_extractor.get())); BlockCachePropertiesSnapshot props(options.statistics.get()); // NOTE: to help better highlight the "detla" of each ticker, I use // + to indicate the increment of changed @@ -1916,7 +2050,7 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { // Data block will be in cache { - iter.reset(c.NewIterator()); + iter.reset(c.NewIterator(moptions.prefix_extractor.get())); iter->SeekToFirst(); BlockCachePropertiesSnapshot props(options.statistics.get()); props.AssertEqual(1, 1 + 1, /* index block hit */ @@ -1938,7 +2072,8 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { options.statistics = CreateDBStatistics(); options.table_factory.reset(new BlockBasedTableFactory(table_options)); const ImmutableCFOptions ioptions2(options); - c.Reopen(ioptions2); + const MutableCFOptions moptions2(options); + c.Reopen(ioptions2, moptions2); { BlockCachePropertiesSnapshot props(options.statistics.get()); props.AssertEqual(1, // index block miss @@ -1951,7 +2086,7 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { // Both index and data block get accessed. // It first cache index block then data block. But since the cache size // is only 1, index block will be purged after data block is inserted. - iter.reset(c.NewIterator()); + iter.reset(c.NewIterator(moptions2.prefix_extractor.get())); BlockCachePropertiesSnapshot props(options.statistics.get()); props.AssertEqual(1 + 1, // index block miss 0, 0, // data block miss @@ -1983,8 +2118,9 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { InternalKey internal_key(user_key, 0, kTypeValue); c3.Add(internal_key.Encode().ToString(), "hello"); ImmutableCFOptions ioptions3(options); + MutableCFOptions moptions3(options); // Generate table without filter policy - c3.Finish(options, ioptions3, table_options, + c3.Finish(options, ioptions3, moptions3, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); c3.ResetTableReader(); @@ -1993,14 +2129,16 @@ TEST_F(BlockBasedTableTest, FilterBlockInBlockCache) { options.table_factory.reset(new BlockBasedTableFactory(table_options)); options.statistics = CreateDBStatistics(); ImmutableCFOptions ioptions4(options); - ASSERT_OK(c3.Reopen(ioptions4)); + MutableCFOptions moptions4(options); + ASSERT_OK(c3.Reopen(ioptions4, moptions4)); reader = dynamic_cast(c3.GetTableReader()); ASSERT_TRUE(!reader->TEST_filter_block_preloaded()); PinnableSlice value; GetContext get_context(options.comparator, nullptr, nullptr, nullptr, GetContext::kNotFound, user_key, &value, nullptr, nullptr, nullptr, nullptr); - ASSERT_OK(reader->Get(ReadOptions(), user_key, &get_context)); + ASSERT_OK(reader->Get(ReadOptions(), internal_key.Encode(), &get_context, + moptions4.prefix_extractor.get())); ASSERT_STREQ(value.data(), "hello"); BlockCachePropertiesSnapshot props(options.statistics.get()); props.AssertFilterBlockStat(0, 0); @@ -2031,7 +2169,7 @@ void ValidateBlockRestartInterval(int value, int expected) { delete factory; } -TEST_F(BlockBasedTableTest, InvalidOptions) { +TEST_P(BlockBasedTableTest, InvalidOptions) { // invalid values for block_size_deviation (<0 or >100) are silently set to 0 ValidateBlockSizeDeviation(-10, 0); ValidateBlockSizeDeviation(-1, 0); @@ -2051,7 +2189,7 @@ TEST_F(BlockBasedTableTest, InvalidOptions) { ValidateBlockRestartInterval(1000, 1000); } -TEST_F(BlockBasedTableTest, BlockReadCountTest) { +TEST_P(BlockBasedTableTest, BlockReadCountTest) { // bloom_filter_type = 0 -- block-based filter // bloom_filter_type = 0 -- full filter for (int bloom_filter_type = 0; bloom_filter_type < 2; ++bloom_filter_type) { @@ -2060,7 +2198,7 @@ TEST_F(BlockBasedTableTest, BlockReadCountTest) { Options options; options.create_if_missing = true; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_cache = NewLRUCache(1, 0); table_options.cache_index_and_filter_blocks = index_and_filter_in_cache; table_options.filter_policy.reset( @@ -2075,8 +2213,9 @@ TEST_F(BlockBasedTableTest, BlockReadCountTest) { std::string encoded_key = internal_key.Encode().ToString(); c.Add(encoded_key, "hello"); ImmutableCFOptions ioptions(options); + MutableCFOptions moptions(options); // Generate table with filter policy - c.Finish(options, ioptions, table_options, + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); auto reader = c.GetTableReader(); PinnableSlice value; @@ -2084,7 +2223,8 @@ TEST_F(BlockBasedTableTest, BlockReadCountTest) { GetContext::kNotFound, user_key, &value, nullptr, nullptr, nullptr, nullptr); get_perf_context()->Reset(); - ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context)); + ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context, + moptions.prefix_extractor.get())); if (index_and_filter_in_cache) { // data, index and filter block ASSERT_EQ(get_perf_context()->block_read_count, 3); @@ -2105,7 +2245,8 @@ TEST_F(BlockBasedTableTest, BlockReadCountTest) { GetContext::kNotFound, user_key, &value, nullptr, nullptr, nullptr, nullptr); get_perf_context()->Reset(); - ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context)); + ASSERT_OK(reader->Get(ReadOptions(), encoded_key, &get_context, + moptions.prefix_extractor.get())); ASSERT_EQ(get_context.State(), GetContext::kNotFound); if (index_and_filter_in_cache) { @@ -2134,10 +2275,10 @@ class MockCache : public LRUCache { double high_pri_pool_ratio) : LRUCache(capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio) {} - virtual Status Insert(const Slice& key, void* value, size_t charge, - void (*deleter)(const Slice& key, void* value), - Handle** handle = nullptr, - Priority priority = Priority::LOW) override { + Status Insert(const Slice& key, void* value, size_t charge, + void (*deleter)(const Slice& key, void* value), + Handle** handle = nullptr, + Priority priority = Priority::LOW) override { // Replace the deleter with our own so that we keep track of data blocks // erased from the cache deleters_[key.ToString()] = deleter; @@ -2145,8 +2286,7 @@ class MockCache : public LRUCache { priority); } // This is called by the application right after inserting a data block - virtual void TEST_mark_as_data_block(const Slice& key, - size_t charge) override { + void TEST_mark_as_data_block(const Slice& key, size_t charge) override { marked_data_in_cache_[key.ToString()] = charge; marked_size_ += charge; } @@ -2176,96 +2316,136 @@ std::map MockCache::marked_data_in_cache_; // object depends on the table to be live, it then must be destructed before the // table is closed. This test makes sure that the only items remains in the // cache after the table is closed are raw data blocks. -TEST_F(BlockBasedTableTest, NoObjectInCacheAfterTableClose) { - for (auto index_type : - {BlockBasedTableOptions::IndexType::kBinarySearch, +TEST_P(BlockBasedTableTest, NoObjectInCacheAfterTableClose) { + std::vector compression_types{kNoCompression}; + + // The following are the compression library versions supporting compression + // dictionaries. See the test case CacheCompressionDict in the + // DBBlockCacheTest suite. +#ifdef ZLIB + compression_types.push_back(kZlibCompression); +#endif // ZLIB +#if LZ4_VERSION_NUMBER >= 10400 + compression_types.push_back(kLZ4Compression); + compression_types.push_back(kLZ4HCCompression); +#endif // LZ4_VERSION_NUMBER >= 10400 +#if ZSTD_VERSION_NUMBER >= 500 + compression_types.push_back(kZSTD); +#endif // ZSTD_VERSION_NUMBER >= 500 + + for (int level: {-1, 0, 1, 10}) { + for (auto index_type : + {BlockBasedTableOptions::IndexType::kBinarySearch, BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch}) { - for (bool block_based_filter : {true, false}) { - for (bool partition_filter : {true, false}) { - if (partition_filter && - (block_based_filter || - index_type != - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch)) { - continue; - } - for (bool index_and_filter_in_cache : {true, false}) { - for (bool pin_l0 : {true, false}) { - if (pin_l0 && !index_and_filter_in_cache) { - continue; + for (bool block_based_filter : {true, false}) { + for (bool partition_filter : {true, false}) { + if (partition_filter && + (block_based_filter || + index_type != + BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch)) { + continue; + } + for (bool index_and_filter_in_cache : {true, false}) { + for (bool pin_l0 : {true, false}) { + for (bool pin_top_level : {true, false}) { + if (pin_l0 && !index_and_filter_in_cache) { + continue; + } + + for (auto compression_type : compression_types) { + for (uint32_t max_dict_bytes : {0, 1 << 14}) { + if (compression_type == kNoCompression && max_dict_bytes) + continue; + + // Create a table + Options opt; + std::unique_ptr ikc; + ikc.reset(new test::PlainInternalKeyComparator( + opt.comparator)); + opt.compression = compression_type; + opt.compression_opts.max_dict_bytes = max_dict_bytes; + BlockBasedTableOptions table_options = + GetBlockBasedTableOptions(); + table_options.block_size = 1024; + table_options.index_type = index_type; + table_options.pin_l0_filter_and_index_blocks_in_cache = + pin_l0; + table_options.pin_top_level_index_and_filter = + pin_top_level; + table_options.partition_filters = partition_filter; + table_options.cache_index_and_filter_blocks = + index_and_filter_in_cache; + // big enough so we don't ever lose cached values. + table_options.block_cache = std::make_shared( + 16 * 1024 * 1024, 4, false, 0.0); + table_options.filter_policy.reset( + rocksdb::NewBloomFilterPolicy(10, block_based_filter)); + opt.table_factory.reset(NewBlockBasedTableFactory( + table_options)); + + bool convert_to_internal_key = false; + TableConstructor c(BytewiseComparator(), + convert_to_internal_key, level); + std::string user_key = "k01"; + std::string key = + InternalKey(user_key, 0, kTypeValue).Encode().ToString(); + c.Add(key, "hello"); + std::vector keys; + stl_wrappers::KVMap kvmap; + const ImmutableCFOptions ioptions(opt); + const MutableCFOptions moptions(opt); + c.Finish(opt, ioptions, moptions, table_options, *ikc, + &keys, &kvmap); + + // Doing a read to make index/filter loaded into the cache + auto table_reader = + dynamic_cast(c.GetTableReader()); + PinnableSlice value; + GetContext get_context(opt.comparator, nullptr, nullptr, + nullptr, GetContext::kNotFound, user_key, &value, + nullptr, nullptr, nullptr, nullptr); + InternalKey ikey(user_key, 0, kTypeValue); + auto s = table_reader->Get(ReadOptions(), key, &get_context, + moptions.prefix_extractor.get()); + ASSERT_EQ(get_context.State(), GetContext::kFound); + ASSERT_STREQ(value.data(), "hello"); + + // Close the table + c.ResetTableReader(); + + auto usage = table_options.block_cache->GetUsage(); + auto pinned_usage = + table_options.block_cache->GetPinnedUsage(); + // The only usage must be for marked data blocks + ASSERT_EQ(usage, MockCache::marked_size_); + // There must be some pinned data since PinnableSlice has + // not released them yet + ASSERT_GT(pinned_usage, 0); + // Release pinnable slice reousrces + value.Reset(); + pinned_usage = table_options.block_cache->GetPinnedUsage(); + ASSERT_EQ(pinned_usage, 0); + } + } + } } - // Create a table - Options opt; - unique_ptr ikc; - ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); - opt.compression = kNoCompression; - BlockBasedTableOptions table_options; - table_options.block_size = 1024; - table_options.index_type = - BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch; - table_options.pin_l0_filter_and_index_blocks_in_cache = pin_l0; - table_options.partition_filters = partition_filter; - table_options.cache_index_and_filter_blocks = - index_and_filter_in_cache; - // big enough so we don't ever lose cached values. - table_options.block_cache = std::shared_ptr( - new MockCache(16 * 1024 * 1024, 4, false, 0.0)); - table_options.filter_policy.reset( - rocksdb::NewBloomFilterPolicy(10, block_based_filter)); - opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); - - TableConstructor c(BytewiseComparator()); - std::string user_key = "k01"; - std::string key = - InternalKey(user_key, 0, kTypeValue).Encode().ToString(); - c.Add(key, "hello"); - std::vector keys; - stl_wrappers::KVMap kvmap; - const ImmutableCFOptions ioptions(opt); - c.Finish(opt, ioptions, table_options, *ikc, &keys, &kvmap); - - // Doing a read to make index/filter loaded into the cache - auto table_reader = - dynamic_cast(c.GetTableReader()); - PinnableSlice value; - GetContext get_context(opt.comparator, nullptr, nullptr, nullptr, - GetContext::kNotFound, user_key, &value, - nullptr, nullptr, nullptr, nullptr); - InternalKey ikey(user_key, 0, kTypeValue); - auto s = table_reader->Get(ReadOptions(), key, &get_context); - ASSERT_EQ(get_context.State(), GetContext::kFound); - ASSERT_STREQ(value.data(), "hello"); - - // Close the table - c.ResetTableReader(); - - auto usage = table_options.block_cache->GetUsage(); - auto pinned_usage = table_options.block_cache->GetPinnedUsage(); - // The only usage must be for marked data blocks - ASSERT_EQ(usage, MockCache::marked_size_); - // There must be some pinned data since PinnableSlice has not - // released them yet - ASSERT_GT(pinned_usage, 0); - // Release pinnable slice reousrces - value.Reset(); - pinned_usage = table_options.block_cache->GetPinnedUsage(); - ASSERT_EQ(pinned_usage, 0); } } } } - } + } // level } -TEST_F(BlockBasedTableTest, BlockCacheLeak) { +TEST_P(BlockBasedTableTest, BlockCacheLeak) { // Check that when we reopen a table we don't lose access to blocks already // in the cache. This test checks whether the Table actually makes use of the // unique ID from the file. Options opt; - unique_ptr ikc; + std::unique_ptr ikc; ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); opt.compression = kNoCompression; - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.block_size = 1024; // big enough so we don't ever lose cached values. table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); @@ -2282,9 +2462,11 @@ TEST_F(BlockBasedTableTest, BlockCacheLeak) { std::vector keys; stl_wrappers::KVMap kvmap; const ImmutableCFOptions ioptions(opt); - c.Finish(opt, ioptions, table_options, *ikc, &keys, &kvmap); + const MutableCFOptions moptions(opt); + c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); - unique_ptr iter(c.NewIterator()); + std::unique_ptr iter( + c.NewIterator(moptions.prefix_extractor.get())); iter->SeekToFirst(); while (iter->Valid()) { iter->key(); @@ -2292,12 +2474,15 @@ TEST_F(BlockBasedTableTest, BlockCacheLeak) { iter->Next(); } ASSERT_OK(iter->status()); + iter.reset(); const ImmutableCFOptions ioptions1(opt); - ASSERT_OK(c.Reopen(ioptions1)); + const MutableCFOptions moptions1(opt); + ASSERT_OK(c.Reopen(ioptions1, moptions1)); auto table_reader = dynamic_cast(c.GetTableReader()); for (const std::string& key : keys) { - ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), key)); + InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); + ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); } c.ResetTableReader(); @@ -2305,15 +2490,89 @@ TEST_F(BlockBasedTableTest, BlockCacheLeak) { table_options.block_cache = NewLRUCache(16 * 1024 * 1024, 4); opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); const ImmutableCFOptions ioptions2(opt); - ASSERT_OK(c.Reopen(ioptions2)); + const MutableCFOptions moptions2(opt); + ASSERT_OK(c.Reopen(ioptions2, moptions2)); table_reader = dynamic_cast(c.GetTableReader()); for (const std::string& key : keys) { - ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), key)); + InternalKey ikey(key, kMaxSequenceNumber, kTypeValue); + ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), ikey.Encode())); } c.ResetTableReader(); } -TEST_F(BlockBasedTableTest, NewIndexIteratorLeak) { +namespace { +class CustomMemoryAllocator : public MemoryAllocator { + public: + const char* Name() const override { return "CustomMemoryAllocator"; } + + void* Allocate(size_t size) override { + ++numAllocations; + auto ptr = new char[size + 16]; + memcpy(ptr, "memory_allocator_", 16); // mangle first 16 bytes + return reinterpret_cast(ptr + 16); + } + void Deallocate(void* p) override { + ++numDeallocations; + char* ptr = reinterpret_cast(p) - 16; + delete[] ptr; + } + + std::atomic numAllocations; + std::atomic numDeallocations; +}; +} // namespace + +TEST_P(BlockBasedTableTest, MemoryAllocator) { + auto custom_memory_allocator = std::make_shared(); + { + Options opt; + std::unique_ptr ikc; + ikc.reset(new test::PlainInternalKeyComparator(opt.comparator)); + opt.compression = kNoCompression; + BlockBasedTableOptions table_options; + table_options.block_size = 1024; + LRUCacheOptions lruOptions; + lruOptions.memory_allocator = custom_memory_allocator; + lruOptions.capacity = 16 * 1024 * 1024; + lruOptions.num_shard_bits = 4; + table_options.block_cache = NewLRUCache(std::move(lruOptions)); + opt.table_factory.reset(NewBlockBasedTableFactory(table_options)); + + TableConstructor c(BytewiseComparator(), + true /* convert_to_internal_key_ */); + c.Add("k01", "hello"); + c.Add("k02", "hello2"); + c.Add("k03", std::string(10000, 'x')); + c.Add("k04", std::string(200000, 'x')); + c.Add("k05", std::string(300000, 'x')); + c.Add("k06", "hello3"); + c.Add("k07", std::string(100000, 'x')); + std::vector keys; + stl_wrappers::KVMap kvmap; + const ImmutableCFOptions ioptions(opt); + const MutableCFOptions moptions(opt); + c.Finish(opt, ioptions, moptions, table_options, *ikc, &keys, &kvmap); + + std::unique_ptr iter( + c.NewIterator(moptions.prefix_extractor.get())); + iter->SeekToFirst(); + while (iter->Valid()) { + iter->key(); + iter->value(); + iter->Next(); + } + ASSERT_OK(iter->status()); + } + + // out of scope, block cache should have been deleted, all allocations + // deallocated + EXPECT_EQ(custom_memory_allocator->numAllocations.load(), + custom_memory_allocator->numDeallocations.load()); + // make sure that allocations actually happened through the cache allocator + EXPECT_GT(custom_memory_allocator->numAllocations.load(), 0); +} + +TEST_P(BlockBasedTableTest, NewIndexIteratorLeak) { // A regression test to avoid data race described in // https://github.com/facebook/rocksdb/issues/1267 TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); @@ -2322,13 +2581,14 @@ TEST_F(BlockBasedTableTest, NewIndexIteratorLeak) { c.Add("a1", "val1"); Options options; options.prefix_extractor.reset(NewFixedPrefixTransform(1)); - BlockBasedTableOptions table_options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); table_options.index_type = BlockBasedTableOptions::kHashSearch; table_options.cache_index_and_filter_blocks = true; table_options.block_cache = NewLRUCache(0); options.table_factory.reset(NewBlockBasedTableFactory(table_options)); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, GetPlainInternalComparator(options.comparator), &keys, &kvmap); rocksdb::SyncPoint::GetInstance()->LoadDependencyAndMarkers( @@ -2355,13 +2615,16 @@ TEST_F(BlockBasedTableTest, NewIndexIteratorLeak) { std::function func1 = [&]() { TEST_SYNC_POINT("BlockBasedTableTest::NewIndexIteratorLeak:Thread1Marker"); - std::unique_ptr iter(reader->NewIterator(ro)); + // TODO(Zhongyi): update test to use MutableCFOptions + std::unique_ptr iter( + reader->NewIterator(ro, moptions.prefix_extractor.get())); iter->Seek(InternalKey("a1", 0, kTypeValue).Encode()); }; std::function func2 = [&]() { TEST_SYNC_POINT("BlockBasedTableTest::NewIndexIteratorLeak:Thread2Marker"); - std::unique_ptr iter(reader->NewIterator(ro)); + std::unique_ptr iter( + reader->NewIterator(ro, moptions.prefix_extractor.get())); }; auto thread1 = port::Thread(func1); @@ -2382,21 +2645,21 @@ TEST_F(PlainTableTest, BasicPlainTableProperties) { PlainTableFactory factory(plain_table_options); test::StringSink sink; - unique_ptr file_writer( - test::GetWritableFileWriter(new test::StringSink())); + std::unique_ptr file_writer( + test::GetWritableFileWriter(new test::StringSink(), "" /* don't care */)); Options options; const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); InternalKeyComparator ikc(options.comparator); std::vector> int_tbl_prop_collector_factories; std::string column_family_name; int unknown_level = -1; std::unique_ptr builder(factory.NewTableBuilder( - TableBuilderOptions(ioptions, ikc, &int_tbl_prop_collector_factories, - kNoCompression, CompressionOptions(), - nullptr /* compression_dict */, - false /* skip_filters */, column_family_name, - unknown_level), + TableBuilderOptions( + ioptions, moptions, ikc, &int_tbl_prop_collector_factories, + kNoCompression, 0 /* sample_for_compression */, CompressionOptions(), + false /* skip_filters */, column_family_name, unknown_level), TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, file_writer.get())); @@ -2411,14 +2674,14 @@ TEST_F(PlainTableTest, BasicPlainTableProperties) { test::StringSink* ss = static_cast(file_writer->writable_file()); - unique_ptr file_reader( + std::unique_ptr file_reader( test::GetRandomAccessFileReader( new test::StringSource(ss->contents(), 72242, true))); TableProperties* props = nullptr; auto s = ReadTableProperties(file_reader.get(), ss->contents().size(), kPlainTableMagicNumber, ioptions, - &props); + &props, true /* compression_type_missing */); std::unique_ptr props_guard(props); ASSERT_OK(s); @@ -2448,7 +2711,8 @@ TEST_F(GeneralTableTest, ApproximateOffsetOfPlain) { BlockBasedTableOptions table_options; table_options.block_size = 1024; const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, internal_comparator, + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, internal_comparator, &keys, &kvmap); ASSERT_TRUE(Between(c.ApproximateOffsetOf("abc"), 0, 0)); @@ -2483,14 +2747,15 @@ static void DoCompressionTest(CompressionType comp) { BlockBasedTableOptions table_options; table_options.block_size = 1024; const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, ikc, &keys, &kvmap); + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, ikc, &keys, &kvmap); ASSERT_TRUE(Between(c.ApproximateOffsetOf("abc"), 0, 0)); ASSERT_TRUE(Between(c.ApproximateOffsetOf("k01"), 0, 0)); ASSERT_TRUE(Between(c.ApproximateOffsetOf("k02"), 0, 0)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k03"), 2000, 3000)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 2000, 3000)); - ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 4000, 6100)); + ASSERT_TRUE(Between(c.ApproximateOffsetOf("k03"), 2000, 3500)); + ASSERT_TRUE(Between(c.ApproximateOffsetOf("k04"), 2000, 3500)); + ASSERT_TRUE(Between(c.ApproximateOffsetOf("xyz"), 4000, 6500)); c.ResetTableReader(); } @@ -2536,25 +2801,63 @@ TEST_F(GeneralTableTest, ApproximateOffsetOfCompressed) { } } -TEST_F(HarnessTest, Randomized) { - std::vector args = GenerateArgList(); - for (unsigned int i = 0; i < args.size(); i++) { - Init(args[i]); - Random rnd(test::RandomSeed() + 5); - for (int num_entries = 0; num_entries < 2000; - num_entries += (num_entries < 50 ? 1 : 200)) { - if ((num_entries % 10) == 0) { - fprintf(stderr, "case %d of %d: num_entries = %d\n", (i + 1), - static_cast(args.size()), num_entries); - } - for (int e = 0; e < num_entries; e++) { - std::string v; - Add(test::RandomKey(&rnd, rnd.Skewed(4)), - test::RandomString(&rnd, rnd.Skewed(5), &v).ToString()); - } - Test(&rnd); - } - } +#ifndef ROCKSDB_VALGRIND_RUN +// RandomizedHarnessTest is very slow for certain combination of arguments +// Split into 8 pieces to reduce the time individual tests take. +TEST_F(HarnessTest, Randomized1) { + // part 1 out of 8 + const size_t part = 1; + const size_t total = 8; + RandomizedHarnessTest(part, total); +} + +TEST_F(HarnessTest, Randomized2) { + // part 2 out of 8 + const size_t part = 2; + const size_t total = 8; + RandomizedHarnessTest(part, total); +} + +TEST_F(HarnessTest, Randomized3) { + // part 3 out of 8 + const size_t part = 3; + const size_t total = 8; + RandomizedHarnessTest(part, total); +} + +TEST_F(HarnessTest, Randomized4) { + // part 4 out of 8 + const size_t part = 4; + const size_t total = 8; + RandomizedHarnessTest(part, total); +} + +TEST_F(HarnessTest, Randomized5) { + // part 5 out of 8 + const size_t part = 5; + const size_t total = 8; + RandomizedHarnessTest(part, total); +} + +TEST_F(HarnessTest, Randomized6) { + // part 6 out of 8 + const size_t part = 6; + const size_t total = 8; + RandomizedHarnessTest(part, total); +} + +TEST_F(HarnessTest, Randomized7) { + // part 7 out of 8 + const size_t part = 7; + const size_t total = 8; + RandomizedHarnessTest(part, total); +} + +TEST_F(HarnessTest, Randomized8) { + // part 8 out of 8 + const size_t part = 8; + const size_t total = 8; + RandomizedHarnessTest(part, total); } #ifndef ROCKSDB_LITE @@ -2582,6 +2885,7 @@ TEST_F(HarnessTest, RandomizedLongDB) { ASSERT_GT(files, 0); } #endif // ROCKSDB_LITE +#endif // ROCKSDB_VALGRIND_RUN class MemTableTest : public testing::Test {}; @@ -2617,7 +2921,8 @@ TEST_F(MemTableTest, Simple) { iter = memtable->NewIterator(ReadOptions(), &arena); arena_iter_guard.set(iter); } else { - iter = memtable->NewRangeTombstoneIterator(ReadOptions()); + iter = memtable->NewRangeTombstoneIterator( + ReadOptions(), kMaxSequenceNumber /* read_seq */); iter_guard.reset(iter); } if (iter == nullptr) { @@ -2717,6 +3022,26 @@ TEST_F(HarnessTest, FooterTests) { ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); ASSERT_EQ(decoded_footer.version(), 1U); } + { + // xxhash64 block based + std::string encoded; + Footer footer(kBlockBasedTableMagicNumber, 1); + BlockHandle meta_index(10, 5), index(20, 15); + footer.set_metaindex_handle(meta_index); + footer.set_index_handle(index); + footer.set_checksum(kxxHash64); + footer.EncodeTo(&encoded); + Footer decoded_footer; + Slice encoded_slice(encoded); + decoded_footer.DecodeFrom(&encoded_slice); + ASSERT_EQ(decoded_footer.table_magic_number(), kBlockBasedTableMagicNumber); + ASSERT_EQ(decoded_footer.checksum(), kxxHash64); + ASSERT_EQ(decoded_footer.metaindex_handle().offset(), meta_index.offset()); + ASSERT_EQ(decoded_footer.metaindex_handle().size(), meta_index.size()); + ASSERT_EQ(decoded_footer.index_handle().offset(), index.offset()); + ASSERT_EQ(decoded_footer.index_handle().size(), index.size()); + ASSERT_EQ(decoded_footer.version(), 1U); + } // Plain table is not supported in ROCKSDB_LITE #ifndef ROCKSDB_LITE { @@ -2781,10 +3106,14 @@ TEST_F(HarnessTest, FooterTests) { } class IndexBlockRestartIntervalTest - : public BlockBasedTableTest, - public ::testing::WithParamInterface { + : public TableTest, + public ::testing::WithParamInterface> { public: - static std::vector GetRestartValues() { return {-1, 0, 1, 8, 16, 32}; } + static std::vector> GetRestartValues() { + return {{-1, false}, {0, false}, {1, false}, {8, false}, + {16, false}, {32, false}, {-1, true}, {0, true}, + {1, true}, {8, true}, {16, true}, {32, true}}; + } }; INSTANTIATE_TEST_CASE_P( @@ -2796,12 +3125,16 @@ TEST_P(IndexBlockRestartIntervalTest, IndexBlockRestartInterval) { const int kKeySize = 100; const int kValSize = 500; - int index_block_restart_interval = GetParam(); + const int index_block_restart_interval = std::get<0>(GetParam()); + const bool value_delta_encoding = std::get<1>(GetParam()); Options options; BlockBasedTableOptions table_options; table_options.block_size = 64; // small block size to get big index block table_options.index_block_restart_interval = index_block_restart_interval; + if (value_delta_encoding) { + table_options.format_version = 4; + } options.table_factory.reset(new BlockBasedTableFactory(table_options)); TableConstructor c(BytewiseComparator()); @@ -2816,10 +3149,13 @@ TEST_P(IndexBlockRestartIntervalTest, IndexBlockRestartInterval) { std::unique_ptr comparator( new InternalKeyComparator(BytewiseComparator())); const ImmutableCFOptions ioptions(options); - c.Finish(options, ioptions, table_options, *comparator, &keys, &kvmap); + const MutableCFOptions moptions(options); + c.Finish(options, ioptions, moptions, table_options, *comparator, &keys, + &kvmap); auto reader = c.GetTableReader(); - std::unique_ptr db_iter(reader->NewIterator(ReadOptions())); + std::unique_ptr db_iter( + reader->NewIterator(ReadOptions(), moptions.prefix_extractor.get())); // Test point lookup for (auto& kv : kvmap) { @@ -2845,7 +3181,7 @@ TEST_P(IndexBlockRestartIntervalTest, IndexBlockRestartInterval) { class PrefixTest : public testing::Test { public: PrefixTest() : testing::Test() {} - ~PrefixTest() {} + ~PrefixTest() override {} }; namespace { @@ -2865,7 +3201,7 @@ class TestPrefixExtractor : public rocksdb::SliceTransform { return true; } - bool InRange(const rocksdb::Slice& dst) const override { return true; } + bool InRange(const rocksdb::Slice& /*dst*/) const override { return true; } bool IsValid(const rocksdb::Slice& src) const { if (src.size() != 4) { @@ -2901,7 +3237,7 @@ TEST_F(PrefixTest, PrefixAndWholeKeyTest) { bbto.block_size = 262144; bbto.whole_key_filtering = true; - const std::string kDBPath = test::TmpDir() + "/table_prefix_test"; + const std::string kDBPath = test::PerThreadDBPath("table_prefix_test"); options.table_factory.reset(NewBlockBasedTableFactory(bbto)); DestroyDB(kDBPath, options); rocksdb::DB* db; @@ -2923,13 +3259,22 @@ TEST_F(PrefixTest, PrefixAndWholeKeyTest) { // rocksdb still works. } -TEST_F(BlockBasedTableTest, TableWithGlobalSeqno) { - BlockBasedTableOptions bbto; +/* + * Disable TableWithGlobalSeqno since RocksDB does not store global_seqno in + * the SST file any more. Instead, RocksDB deduces global_seqno from the + * MANIFEST while reading from an SST. Therefore, it's not possible to test the + * functionality of global_seqno in a single, isolated unit test without the + * involvement of Version, VersionSet, etc. + */ +TEST_P(BlockBasedTableTest, DISABLED_TableWithGlobalSeqno) { + BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); test::StringSink* sink = new test::StringSink(); - unique_ptr file_writer(test::GetWritableFileWriter(sink)); + std::unique_ptr file_writer( + test::GetWritableFileWriter(sink, "" /* don't care */)); Options options; options.table_factory.reset(NewBlockBasedTableFactory(bbto)); const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); InternalKeyComparator ikc(options.comparator); std::vector> int_tbl_prop_collector_factories; @@ -2938,9 +3283,9 @@ TEST_F(BlockBasedTableTest, TableWithGlobalSeqno) { 0 /* global_seqno*/)); std::string column_family_name; std::unique_ptr builder(options.table_factory->NewTableBuilder( - TableBuilderOptions(ioptions, ikc, &int_tbl_prop_collector_factories, - kNoCompression, CompressionOptions(), - nullptr /* compression_dict */, + TableBuilderOptions(ioptions, moptions, ikc, + &int_tbl_prop_collector_factories, kNoCompression, + 0 /* sample_for_compression */, CompressionOptions(), false /* skip_filters */, column_family_name, -1), TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, file_writer.get())); @@ -2962,14 +3307,14 @@ TEST_F(BlockBasedTableTest, TableWithGlobalSeqno) { // Helper function to get version, global_seqno, global_seqno_offset std::function GetVersionAndGlobalSeqno = [&]() { - unique_ptr file_reader( + std::unique_ptr file_reader( test::GetRandomAccessFileReader( new test::StringSource(ss_rw.contents(), 73342, true))); TableProperties* props = nullptr; ASSERT_OK(ReadTableProperties(file_reader.get(), ss_rw.contents().size(), kBlockBasedTableMagicNumber, ioptions, - &props)); + &props, true /* compression_type_missing */)); UserCollectedProperties user_props = props->user_collected_properties; version = DecodeFixed32( @@ -2991,17 +3336,19 @@ TEST_F(BlockBasedTableTest, TableWithGlobalSeqno) { }; // Helper function to get the contents of the table InternalIterator - unique_ptr table_reader; + std::unique_ptr table_reader; std::function GetTableInternalIter = [&]() { - unique_ptr file_reader( + std::unique_ptr file_reader( test::GetRandomAccessFileReader( new test::StringSource(ss_rw.contents(), 73342, true))); options.table_factory->NewTableReader( - TableReaderOptions(ioptions, EnvOptions(), ikc), std::move(file_reader), - ss_rw.contents().size(), &table_reader); + TableReaderOptions(ioptions, moptions.prefix_extractor.get(), + EnvOptions(), ikc), + std::move(file_reader), ss_rw.contents().size(), &table_reader); - return table_reader->NewIterator(ReadOptions()); + return table_reader->NewIterator(ReadOptions(), + moptions.prefix_extractor.get()); }; GetVersionAndGlobalSeqno(); @@ -3100,6 +3447,430 @@ TEST_F(BlockBasedTableTest, TableWithGlobalSeqno) { delete iter; } +TEST_P(BlockBasedTableTest, BlockAlignTest) { + BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); + bbto.block_align = true; + test::StringSink* sink = new test::StringSink(); + std::unique_ptr file_writer( + test::GetWritableFileWriter(sink, "" /* don't care */)); + Options options; + options.compression = kNoCompression; + options.table_factory.reset(NewBlockBasedTableFactory(bbto)); + const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); + InternalKeyComparator ikc(options.comparator); + std::vector> + int_tbl_prop_collector_factories; + std::string column_family_name; + std::unique_ptr builder(options.table_factory->NewTableBuilder( + TableBuilderOptions(ioptions, moptions, ikc, + &int_tbl_prop_collector_factories, kNoCompression, + 0 /* sample_for_compression */, CompressionOptions(), + false /* skip_filters */, column_family_name, -1), + TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, + file_writer.get())); + + for (int i = 1; i <= 10000; ++i) { + std::ostringstream ostr; + ostr << std::setfill('0') << std::setw(5) << i; + std::string key = ostr.str(); + std::string value = "val"; + InternalKey ik(key, 0, kTypeValue); + + builder->Add(ik.Encode(), value); + } + ASSERT_OK(builder->Finish()); + file_writer->Flush(); + + test::RandomRWStringSink ss_rw(sink); + std::unique_ptr file_reader( + test::GetRandomAccessFileReader( + new test::StringSource(ss_rw.contents(), 73342, true))); + + // Helper function to get version, global_seqno, global_seqno_offset + std::function VerifyBlockAlignment = [&]() { + TableProperties* props = nullptr; + ASSERT_OK(ReadTableProperties(file_reader.get(), ss_rw.contents().size(), + kBlockBasedTableMagicNumber, ioptions, + &props, true /* compression_type_missing */)); + + uint64_t data_block_size = props->data_size / props->num_data_blocks; + ASSERT_EQ(data_block_size, 4096); + ASSERT_EQ(props->data_size, data_block_size * props->num_data_blocks); + delete props; + }; + + VerifyBlockAlignment(); + + // The below block of code verifies that we can read back the keys. Set + // block_align to false when creating the reader to ensure we can flip between + // the two modes without any issues + std::unique_ptr table_reader; + bbto.block_align = false; + Options options2; + options2.table_factory.reset(NewBlockBasedTableFactory(bbto)); + ImmutableCFOptions ioptions2(options2); + const MutableCFOptions moptions2(options2); + + ASSERT_OK(ioptions.table_factory->NewTableReader( + TableReaderOptions(ioptions2, moptions2.prefix_extractor.get(), + EnvOptions(), + GetPlainInternalComparator(options2.comparator)), + std::move(file_reader), ss_rw.contents().size(), &table_reader)); + + std::unique_ptr db_iter(table_reader->NewIterator( + ReadOptions(), moptions2.prefix_extractor.get())); + + int expected_key = 1; + for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) { + std::ostringstream ostr; + ostr << std::setfill('0') << std::setw(5) << expected_key++; + std::string key = ostr.str(); + std::string value = "val"; + + ASSERT_OK(db_iter->status()); + ASSERT_EQ(ExtractUserKey(db_iter->key()).ToString(), key); + ASSERT_EQ(db_iter->value().ToString(), value); + } + expected_key--; + ASSERT_EQ(expected_key, 10000); + table_reader.reset(); +} + +TEST_P(BlockBasedTableTest, PropertiesBlockRestartPointTest) { + BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); + bbto.block_align = true; + test::StringSink* sink = new test::StringSink(); + std::unique_ptr file_writer( + test::GetWritableFileWriter(sink, "" /* don't care */)); + + Options options; + options.compression = kNoCompression; + options.table_factory.reset(NewBlockBasedTableFactory(bbto)); + + const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); + InternalKeyComparator ikc(options.comparator); + std::vector> + int_tbl_prop_collector_factories; + std::string column_family_name; + + std::unique_ptr builder(options.table_factory->NewTableBuilder( + TableBuilderOptions(ioptions, moptions, ikc, + &int_tbl_prop_collector_factories, kNoCompression, + 0 /* sample_for_compression */, CompressionOptions(), + false /* skip_filters */, column_family_name, -1), + TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, + file_writer.get())); + + for (int i = 1; i <= 10000; ++i) { + std::ostringstream ostr; + ostr << std::setfill('0') << std::setw(5) << i; + std::string key = ostr.str(); + std::string value = "val"; + InternalKey ik(key, 0, kTypeValue); + + builder->Add(ik.Encode(), value); + } + ASSERT_OK(builder->Finish()); + file_writer->Flush(); + + test::RandomRWStringSink ss_rw(sink); + std::unique_ptr file_reader( + test::GetRandomAccessFileReader( + new test::StringSource(ss_rw.contents(), 73342, true))); + + { + RandomAccessFileReader* file = file_reader.get(); + uint64_t file_size = ss_rw.contents().size(); + + Footer footer; + ASSERT_OK(ReadFooterFromFile(file, nullptr /* prefetch_buffer */, file_size, + &footer, kBlockBasedTableMagicNumber)); + + auto BlockFetchHelper = [&](const BlockHandle& handle, + BlockContents* contents) { + ReadOptions read_options; + read_options.verify_checksums = false; + PersistentCacheOptions cache_options; + + BlockFetcher block_fetcher( + file, nullptr /* prefetch_buffer */, footer, read_options, handle, + contents, ioptions, false /* decompress */, + false /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + cache_options); + + ASSERT_OK(block_fetcher.ReadBlockContents()); + }; + + // -- Read metaindex block + auto metaindex_handle = footer.metaindex_handle(); + BlockContents metaindex_contents; + + BlockFetchHelper(metaindex_handle, &metaindex_contents); + Block metaindex_block(std::move(metaindex_contents), + kDisableGlobalSequenceNumber); + + std::unique_ptr meta_iter( + metaindex_block.NewIterator(BytewiseComparator(), + BytewiseComparator())); + bool found_properties_block = true; + ASSERT_OK(SeekToPropertiesBlock(meta_iter.get(), &found_properties_block)); + ASSERT_TRUE(found_properties_block); + + // -- Read properties block + Slice v = meta_iter->value(); + BlockHandle properties_handle; + ASSERT_OK(properties_handle.DecodeFrom(&v)); + BlockContents properties_contents; + + BlockFetchHelper(properties_handle, &properties_contents); + Block properties_block(std::move(properties_contents), + kDisableGlobalSequenceNumber); + + ASSERT_EQ(properties_block.NumRestarts(), 1); + } +} + +TEST_P(BlockBasedTableTest, PropertiesMetaBlockLast) { + // The properties meta-block should come at the end since we always need to + // read it when opening a file, unlike index/filter/other meta-blocks, which + // are sometimes read depending on the user's configuration. This ordering + // allows us to do a small readahead on the end of the file to read properties + // and meta-index blocks with one I/O. + TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */); + c.Add("a1", "val1"); + c.Add("b2", "val2"); + c.Add("c3", "val3"); + c.Add("d4", "val4"); + c.Add("e5", "val5"); + c.Add("f6", "val6"); + c.Add("g7", "val7"); + c.Add("h8", "val8"); + c.Add("j9", "val9"); + + // write an SST file + Options options; + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + table_options.filter_policy.reset(NewBloomFilterPolicy( + 8 /* bits_per_key */, false /* use_block_based_filter */)); + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + ImmutableCFOptions ioptions(options); + MutableCFOptions moptions(options); + std::vector keys; + stl_wrappers::KVMap kvmap; + c.Finish(options, ioptions, moptions, table_options, + GetPlainInternalComparator(options.comparator), &keys, &kvmap); + + // get file reader + test::StringSink* table_sink = c.TEST_GetSink(); + std::unique_ptr table_reader{ + test::GetRandomAccessFileReader( + new test::StringSource(table_sink->contents(), 0 /* unique_id */, + false /* allow_mmap_reads */))}; + size_t table_size = table_sink->contents().size(); + + // read footer + Footer footer; + ASSERT_OK(ReadFooterFromFile(table_reader.get(), + nullptr /* prefetch_buffer */, table_size, + &footer, kBlockBasedTableMagicNumber)); + + // read metaindex + auto metaindex_handle = footer.metaindex_handle(); + BlockContents metaindex_contents; + PersistentCacheOptions pcache_opts; + BlockFetcher block_fetcher( + table_reader.get(), nullptr /* prefetch_buffer */, footer, ReadOptions(), + metaindex_handle, &metaindex_contents, ioptions, false /* decompress */, + false /*maybe_compressed*/, UncompressionDict::GetEmptyDict(), + pcache_opts, nullptr /*memory_allocator*/); + ASSERT_OK(block_fetcher.ReadBlockContents()); + Block metaindex_block(std::move(metaindex_contents), + kDisableGlobalSequenceNumber); + + // verify properties block comes last + std::unique_ptr metaindex_iter{ + metaindex_block.NewIterator(options.comparator, + options.comparator)}; + uint64_t max_offset = 0; + std::string key_at_max_offset; + for (metaindex_iter->SeekToFirst(); metaindex_iter->Valid(); + metaindex_iter->Next()) { + BlockHandle handle; + Slice value = metaindex_iter->value(); + ASSERT_OK(handle.DecodeFrom(&value)); + if (handle.offset() > max_offset) { + max_offset = handle.offset(); + key_at_max_offset = metaindex_iter->key().ToString(); + } + } + ASSERT_EQ(kPropertiesBlock, key_at_max_offset); + // index handle is stored in footer rather than metaindex block, so need + // separate logic to verify it comes before properties block. + ASSERT_GT(max_offset, footer.index_handle().offset()); + c.ResetTableReader(); +} + +TEST_P(BlockBasedTableTest, BadOptions) { + rocksdb::Options options; + options.compression = kNoCompression; + BlockBasedTableOptions bbto = GetBlockBasedTableOptions(); + bbto.block_size = 4000; + bbto.block_align = true; + + const std::string kDBPath = + test::PerThreadDBPath("block_based_table_bad_options_test"); + options.table_factory.reset(NewBlockBasedTableFactory(bbto)); + DestroyDB(kDBPath, options); + rocksdb::DB* db; + ASSERT_NOK(rocksdb::DB::Open(options, kDBPath, &db)); + + bbto.block_size = 4096; + options.compression = kSnappyCompression; + options.table_factory.reset(NewBlockBasedTableFactory(bbto)); + ASSERT_NOK(rocksdb::DB::Open(options, kDBPath, &db)); +} + +TEST_F(BBTTailPrefetchTest, TestTailPrefetchStats) { + TailPrefetchStats tpstats; + ASSERT_EQ(0, tpstats.GetSuggestedPrefetchSize()); + tpstats.RecordEffectiveSize(size_t{1000}); + tpstats.RecordEffectiveSize(size_t{1005}); + tpstats.RecordEffectiveSize(size_t{1002}); + ASSERT_EQ(1005, tpstats.GetSuggestedPrefetchSize()); + + // One single super large value shouldn't influence much + tpstats.RecordEffectiveSize(size_t{1002000}); + tpstats.RecordEffectiveSize(size_t{999}); + ASSERT_LE(1005, tpstats.GetSuggestedPrefetchSize()); + ASSERT_GT(1200, tpstats.GetSuggestedPrefetchSize()); + + // Only history of 32 is kept + for (int i = 0; i < 32; i++) { + tpstats.RecordEffectiveSize(size_t{100}); + } + ASSERT_EQ(100, tpstats.GetSuggestedPrefetchSize()); + + // 16 large values and 16 small values. The result should be closer + // to the small value as the algorithm. + for (int i = 0; i < 16; i++) { + tpstats.RecordEffectiveSize(size_t{1000}); + } + tpstats.RecordEffectiveSize(size_t{10}); + tpstats.RecordEffectiveSize(size_t{20}); + for (int i = 0; i < 6; i++) { + tpstats.RecordEffectiveSize(size_t{100}); + } + ASSERT_LE(80, tpstats.GetSuggestedPrefetchSize()); + ASSERT_GT(200, tpstats.GetSuggestedPrefetchSize()); +} + +TEST_F(BBTTailPrefetchTest, FilePrefetchBufferMinOffset) { + TailPrefetchStats tpstats; + FilePrefetchBuffer buffer(nullptr, 0, 0, false, true); + buffer.TryReadFromCache(500, 10, nullptr); + buffer.TryReadFromCache(480, 10, nullptr); + buffer.TryReadFromCache(490, 10, nullptr); + ASSERT_EQ(480, buffer.min_offset_read()); +} + +TEST_P(BlockBasedTableTest, DataBlockHashIndex) { + const int kNumKeys = 500; + const int kKeySize = 8; + const int kValSize = 40; + + BlockBasedTableOptions table_options = GetBlockBasedTableOptions(); + table_options.data_block_index_type = + BlockBasedTableOptions::kDataBlockBinaryAndHash; + + Options options; + options.comparator = BytewiseComparator(); + + options.table_factory.reset(new BlockBasedTableFactory(table_options)); + + TableConstructor c(options.comparator); + + static Random rnd(1048); + for (int i = 0; i < kNumKeys; i++) { + // padding one "0" to mark existent keys. + std::string random_key(RandomString(&rnd, kKeySize - 1) + "1"); + InternalKey k(random_key, 0, kTypeValue); + c.Add(k.Encode().ToString(), RandomString(&rnd, kValSize)); + } + + std::vector keys; + stl_wrappers::KVMap kvmap; + const ImmutableCFOptions ioptions(options); + const MutableCFOptions moptions(options); + const InternalKeyComparator internal_comparator(options.comparator); + c.Finish(options, ioptions, moptions, table_options, internal_comparator, + &keys, &kvmap); + + auto reader = c.GetTableReader(); + + std::unique_ptr seek_iter; + seek_iter.reset( + reader->NewIterator(ReadOptions(), moptions.prefix_extractor.get())); + for (int i = 0; i < 2; ++i) { + ReadOptions ro; + // for every kv, we seek using two method: Get() and Seek() + // Get() will use the SuffixIndexHash in Block. For non-existent key it + // will invalidate the iterator + // Seek() will use the default BinarySeek() in Block. So for non-existent + // key it will land at the closest key that is large than target. + + // Search for existent keys + for (auto& kv : kvmap) { + if (i == 0) { + // Search using Seek() + seek_iter->Seek(kv.first); + ASSERT_OK(seek_iter->status()); + ASSERT_TRUE(seek_iter->Valid()); + ASSERT_EQ(seek_iter->key(), kv.first); + ASSERT_EQ(seek_iter->value(), kv.second); + } else { + // Search using Get() + PinnableSlice value; + std::string user_key = ExtractUserKey(kv.first).ToString(); + GetContext get_context(options.comparator, nullptr, nullptr, nullptr, + GetContext::kNotFound, user_key, &value, nullptr, + nullptr, nullptr, nullptr); + ASSERT_OK(reader->Get(ro, kv.first, &get_context, + moptions.prefix_extractor.get())); + ASSERT_EQ(get_context.State(), GetContext::kFound); + ASSERT_EQ(value, Slice(kv.second)); + value.Reset(); + } + } + + // Search for non-existent keys + for (auto& kv : kvmap) { + std::string user_key = ExtractUserKey(kv.first).ToString(); + user_key.back() = '0'; // make it non-existent key + InternalKey internal_key(user_key, 0, kTypeValue); + std::string encoded_key = internal_key.Encode().ToString(); + if (i == 0) { // Search using Seek() + seek_iter->Seek(encoded_key); + ASSERT_OK(seek_iter->status()); + if (seek_iter->Valid()) { + ASSERT_TRUE(BytewiseComparator()->Compare( + user_key, ExtractUserKey(seek_iter->key())) < 0); + } + } else { // Search using Get() + PinnableSlice value; + GetContext get_context(options.comparator, nullptr, nullptr, nullptr, + GetContext::kNotFound, user_key, &value, nullptr, + nullptr, nullptr, nullptr); + ASSERT_OK(reader->Get(ro, encoded_key, &get_context, + moptions.prefix_extractor.get())); + ASSERT_EQ(get_context.State(), GetContext::kNotFound); + value.Reset(); + } + } + } +} + } // namespace rocksdb int main(int argc, char** argv) { diff --git a/thirdparty/rocksdb/table/two_level_iterator.cc b/thirdparty/rocksdb/table/two_level_iterator.cc index 2236a2a726..a8f617dee2 100644 --- a/thirdparty/rocksdb/table/two_level_iterator.cc +++ b/thirdparty/rocksdb/table/two_level_iterator.cc @@ -19,45 +19,37 @@ namespace rocksdb { namespace { -class TwoLevelIterator : public InternalIterator { +class TwoLevelIndexIterator : public InternalIteratorBase { public: - explicit TwoLevelIterator(TwoLevelIteratorState* state, - InternalIterator* first_level_iter, - bool need_free_iter_and_state); + explicit TwoLevelIndexIterator( + TwoLevelIteratorState* state, + InternalIteratorBase* first_level_iter); - virtual ~TwoLevelIterator() { - // Assert that the TwoLevelIterator is never deleted while Pinning is - // Enabled. - assert(!pinned_iters_mgr_ || - (pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled())); - first_level_iter_.DeleteIter(!need_free_iter_and_state_); - second_level_iter_.DeleteIter(false); - if (need_free_iter_and_state_) { - delete state_; - } else { - state_->~TwoLevelIteratorState(); - } + ~TwoLevelIndexIterator() override { + first_level_iter_.DeleteIter(false /* is_arena_mode */); + second_level_iter_.DeleteIter(false /* is_arena_mode */); + delete state_; } - virtual void Seek(const Slice& target) override; - virtual void SeekForPrev(const Slice& target) override; - virtual void SeekToFirst() override; - virtual void SeekToLast() override; - virtual void Next() override; - virtual void Prev() override; + void Seek(const Slice& target) override; + void SeekForPrev(const Slice& target) override; + void SeekToFirst() override; + void SeekToLast() override; + void Next() override; + void Prev() override; - virtual bool Valid() const override { return second_level_iter_.Valid(); } - virtual Slice key() const override { + bool Valid() const override { return second_level_iter_.Valid(); } + Slice key() const override { assert(Valid()); return second_level_iter_.key(); } - virtual Slice value() const override { + BlockHandle value() const override { assert(Valid()); return second_level_iter_.value(); } - virtual Status status() const override { - // It'd be nice if status() returned a const Status& instead of a Status + Status status() const override { if (!first_level_iter_.status().ok()) { + assert(second_level_iter_.iter() == nullptr); return first_level_iter_.status(); } else if (second_level_iter_.iter() != nullptr && !second_level_iter_.status().ok()) { @@ -66,22 +58,10 @@ class TwoLevelIterator : public InternalIterator { return status_; } } - virtual void SetPinnedItersMgr( - PinnedIteratorsManager* pinned_iters_mgr) override { - pinned_iters_mgr_ = pinned_iters_mgr; - first_level_iter_.SetPinnedItersMgr(pinned_iters_mgr); - if (second_level_iter_.iter()) { - second_level_iter_.SetPinnedItersMgr(pinned_iters_mgr); - } - } - virtual bool IsKeyPinned() const override { - return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && - second_level_iter_.iter() && second_level_iter_.IsKeyPinned(); - } - virtual bool IsValuePinned() const override { - return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && - second_level_iter_.iter() && second_level_iter_.IsValuePinned(); - } + void SetPinnedItersMgr( + PinnedIteratorsManager* /*pinned_iters_mgr*/) override {} + bool IsKeyPinned() const override { return false; } + bool IsValuePinned() const override { return false; } private: void SaveError(const Status& s) { @@ -89,34 +69,24 @@ class TwoLevelIterator : public InternalIterator { } void SkipEmptyDataBlocksForward(); void SkipEmptyDataBlocksBackward(); - void SetSecondLevelIterator(InternalIterator* iter); + void SetSecondLevelIterator(InternalIteratorBase* iter); void InitDataBlock(); TwoLevelIteratorState* state_; - IteratorWrapper first_level_iter_; - IteratorWrapper second_level_iter_; // May be nullptr - bool need_free_iter_and_state_; - PinnedIteratorsManager* pinned_iters_mgr_; + IteratorWrapperBase first_level_iter_; + IteratorWrapperBase second_level_iter_; // May be nullptr Status status_; // If second_level_iter is non-nullptr, then "data_block_handle_" holds the // "index_value" passed to block_function_ to create the second_level_iter. - std::string data_block_handle_; + BlockHandle data_block_handle_; }; -TwoLevelIterator::TwoLevelIterator(TwoLevelIteratorState* state, - InternalIterator* first_level_iter, - bool need_free_iter_and_state) - : state_(state), - first_level_iter_(first_level_iter), - need_free_iter_and_state_(need_free_iter_and_state), - pinned_iters_mgr_(nullptr) {} +TwoLevelIndexIterator::TwoLevelIndexIterator( + TwoLevelIteratorState* state, + InternalIteratorBase* first_level_iter) + : state_(state), first_level_iter_(first_level_iter) {} -void TwoLevelIterator::Seek(const Slice& target) { - if (state_->check_prefix_may_match && - !state_->PrefixMayMatch(target)) { - SetSecondLevelIterator(nullptr); - return; - } +void TwoLevelIndexIterator::Seek(const Slice& target) { first_level_iter_.Seek(target); InitDataBlock(); @@ -126,18 +96,14 @@ void TwoLevelIterator::Seek(const Slice& target) { SkipEmptyDataBlocksForward(); } -void TwoLevelIterator::SeekForPrev(const Slice& target) { - if (state_->check_prefix_may_match && !state_->PrefixMayMatch(target)) { - SetSecondLevelIterator(nullptr); - return; - } +void TwoLevelIndexIterator::SeekForPrev(const Slice& target) { first_level_iter_.Seek(target); InitDataBlock(); if (second_level_iter_.iter() != nullptr) { second_level_iter_.SeekForPrev(target); } if (!Valid()) { - if (!first_level_iter_.Valid()) { + if (!first_level_iter_.Valid() && first_level_iter_.status().ok()) { first_level_iter_.SeekToLast(); InitDataBlock(); if (second_level_iter_.iter() != nullptr) { @@ -148,7 +114,7 @@ void TwoLevelIterator::SeekForPrev(const Slice& target) { } } -void TwoLevelIterator::SeekToFirst() { +void TwoLevelIndexIterator::SeekToFirst() { first_level_iter_.SeekToFirst(); InitDataBlock(); if (second_level_iter_.iter() != nullptr) { @@ -157,7 +123,7 @@ void TwoLevelIterator::SeekToFirst() { SkipEmptyDataBlocksForward(); } -void TwoLevelIterator::SeekToLast() { +void TwoLevelIndexIterator::SeekToLast() { first_level_iter_.SeekToLast(); InitDataBlock(); if (second_level_iter_.iter() != nullptr) { @@ -166,25 +132,23 @@ void TwoLevelIterator::SeekToLast() { SkipEmptyDataBlocksBackward(); } -void TwoLevelIterator::Next() { +void TwoLevelIndexIterator::Next() { assert(Valid()); second_level_iter_.Next(); SkipEmptyDataBlocksForward(); } -void TwoLevelIterator::Prev() { +void TwoLevelIndexIterator::Prev() { assert(Valid()); second_level_iter_.Prev(); SkipEmptyDataBlocksBackward(); } -void TwoLevelIterator::SkipEmptyDataBlocksForward() { +void TwoLevelIndexIterator::SkipEmptyDataBlocksForward() { while (second_level_iter_.iter() == nullptr || - (!second_level_iter_.Valid() && - !second_level_iter_.status().IsIncomplete())) { + (!second_level_iter_.Valid() && second_level_iter_.status().ok())) { // Move to next block - if (!first_level_iter_.Valid() || - state_->KeyReachedUpperBound(first_level_iter_.key())) { + if (!first_level_iter_.Valid()) { SetSecondLevelIterator(nullptr); return; } @@ -196,10 +160,9 @@ void TwoLevelIterator::SkipEmptyDataBlocksForward() { } } -void TwoLevelIterator::SkipEmptyDataBlocksBackward() { +void TwoLevelIndexIterator::SkipEmptyDataBlocksBackward() { while (second_level_iter_.iter() == nullptr || - (!second_level_iter_.Valid() && - !second_level_iter_.status().IsIncomplete())) { + (!second_level_iter_.Valid() && second_level_iter_.status().ok())) { // Move to next block if (!first_level_iter_.Valid()) { SetSecondLevelIterator(nullptr); @@ -213,36 +176,26 @@ void TwoLevelIterator::SkipEmptyDataBlocksBackward() { } } -void TwoLevelIterator::SetSecondLevelIterator(InternalIterator* iter) { - if (second_level_iter_.iter() != nullptr) { - SaveError(second_level_iter_.status()); - } - - if (pinned_iters_mgr_ && iter) { - iter->SetPinnedItersMgr(pinned_iters_mgr_); - } - - InternalIterator* old_iter = second_level_iter_.Set(iter); - if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) { - pinned_iters_mgr_->PinIterator(old_iter); - } else { - delete old_iter; - } +void TwoLevelIndexIterator::SetSecondLevelIterator( + InternalIteratorBase* iter) { + InternalIteratorBase* old_iter = second_level_iter_.Set(iter); + delete old_iter; } -void TwoLevelIterator::InitDataBlock() { +void TwoLevelIndexIterator::InitDataBlock() { if (!first_level_iter_.Valid()) { SetSecondLevelIterator(nullptr); } else { - Slice handle = first_level_iter_.value(); + BlockHandle handle = first_level_iter_.value(); if (second_level_iter_.iter() != nullptr && !second_level_iter_.status().IsIncomplete() && - handle.compare(data_block_handle_) == 0) { + handle.offset() == data_block_handle_.offset()) { // second_level_iter is already constructed with this iterator, so // no need to change anything } else { - InternalIterator* iter = state_->NewSecondaryIterator(handle); - data_block_handle_.assign(handle.data(), handle.size()); + InternalIteratorBase* iter = + state_->NewSecondaryIterator(handle); + data_block_handle_ = handle; SetSecondLevelIterator(iter); } } @@ -250,18 +203,9 @@ void TwoLevelIterator::InitDataBlock() { } // namespace -InternalIterator* NewTwoLevelIterator(TwoLevelIteratorState* state, - InternalIterator* first_level_iter, - Arena* arena, - bool need_free_iter_and_state) { - if (arena == nullptr) { - return new TwoLevelIterator(state, first_level_iter, - need_free_iter_and_state); - } else { - auto mem = arena->AllocateAligned(sizeof(TwoLevelIterator)); - return new (mem) - TwoLevelIterator(state, first_level_iter, need_free_iter_and_state); - } +InternalIteratorBase* NewTwoLevelIterator( + TwoLevelIteratorState* state, + InternalIteratorBase* first_level_iter) { + return new TwoLevelIndexIterator(state, first_level_iter); } - } // namespace rocksdb diff --git a/thirdparty/rocksdb/table/two_level_iterator.h b/thirdparty/rocksdb/table/two_level_iterator.h index 34b33c83f6..55d5c01a4a 100644 --- a/thirdparty/rocksdb/table/two_level_iterator.h +++ b/thirdparty/rocksdb/table/two_level_iterator.h @@ -16,19 +16,14 @@ namespace rocksdb { struct ReadOptions; class InternalKeyComparator; -class Arena; +// TwoLevelIteratorState expects iterators are not created using the arena struct TwoLevelIteratorState { - explicit TwoLevelIteratorState(bool _check_prefix_may_match) - : check_prefix_may_match(_check_prefix_may_match) {} + TwoLevelIteratorState() {} virtual ~TwoLevelIteratorState() {} - virtual InternalIterator* NewSecondaryIterator(const Slice& handle) = 0; - virtual bool PrefixMayMatch(const Slice& internal_key) = 0; - virtual bool KeyReachedUpperBound(const Slice& internal_key) = 0; - - // If call PrefixMayMatch() - bool check_prefix_may_match; + virtual InternalIteratorBase* NewSecondaryIterator( + const BlockHandle& handle) = 0; }; @@ -41,13 +36,9 @@ struct TwoLevelIteratorState { // // Uses a supplied function to convert an index_iter value into // an iterator over the contents of the corresponding block. -// arena: If not null, the arena is used to allocate the Iterator. -// When destroying the iterator, the destructor will destroy -// all the states but those allocated in arena. -// need_free_iter_and_state: free `state` and `first_level_iter` if -// true. Otherwise, just call destructor. -extern InternalIterator* NewTwoLevelIterator( - TwoLevelIteratorState* state, InternalIterator* first_level_iter, - Arena* arena = nullptr, bool need_free_iter_and_state = true); +// Note: this function expects first_level_iter was not created using the arena +extern InternalIteratorBase* NewTwoLevelIterator( + TwoLevelIteratorState* state, + InternalIteratorBase* first_level_iter); } // namespace rocksdb diff --git a/thirdparty/rocksdb/third-party/fbson/COMMIT.md b/thirdparty/rocksdb/third-party/fbson/COMMIT.md deleted file mode 100644 index b38b5424d3..0000000000 --- a/thirdparty/rocksdb/third-party/fbson/COMMIT.md +++ /dev/null @@ -1,5 +0,0 @@ -fbson commit: -https://github.com/facebook/mysql-5.6/commit/55ef9ff25c934659a70b4094e9b406c48e9dd43d - -# TODO. -* Had to convert zero sized array to [1] sized arrays due to the fact that MS Compiler complains about it not being standard. At some point need to contribute this change back to MySql where this code was taken from. diff --git a/thirdparty/rocksdb/third-party/fbson/FbsonDocument.h b/thirdparty/rocksdb/third-party/fbson/FbsonDocument.h deleted file mode 100644 index 6fb8a93f17..0000000000 --- a/thirdparty/rocksdb/third-party/fbson/FbsonDocument.h +++ /dev/null @@ -1,893 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -/* - * This header defines FbsonDocument, FbsonKeyValue, and various value classes - * which are derived from FbsonValue, and a forward iterator for container - * values - essentially everything that is related to FBSON binary data - * structures. - * - * Implementation notes: - * - * None of the classes in this header file can be instantiated directly (i.e. - * you cannot create a FbsonKeyValue or FbsonValue object - all constructors - * are declared non-public). We use the classes as wrappers on the packed FBSON - * bytes (serialized), and cast the classes (types) to the underlying packed - * byte array. - * - * For the same reason, we cannot define any FBSON value class to be virtual, - * since we never call constructors, and will not instantiate vtbl and vptrs. - * - * Therefore, the classes are defined as packed structures (i.e. no data - * alignment and padding), and the private member variables of the classes are - * defined precisely in the same order as the FBSON spec. This ensures we - * access the packed FBSON bytes correctly. - * - * The packed structures are highly optimized for in-place operations with low - * overhead. The reads (and in-place writes) are performed directly on packed - * bytes. There is no memory allocation at all at runtime. - * - * For updates/writes of values that will expand the original FBSON size, the - * write will fail, and the caller needs to handle buffer increase. - * - * ** Iterator ** - * Both ObjectVal class and ArrayVal class have iterator type that you can use - * to declare an iterator on a container object to go through the key-value - * pairs or value list. The iterator has both non-const and const types. - * - * Note: iterators are forward direction only. - * - * ** Query ** - * Querying into containers is through the member functions find (for key/value - * pairs) and get (for array elements), and is in streaming style. We don't - * need to read/scan the whole FBSON packed bytes in order to return results. - * Once the key/index is found, we will stop search. You can use text to query - * both objects and array (for array, text will be converted to integer index), - * and use index to retrieve from array. Array index is 0-based. - * - * ** External dictionary ** - * During query processing, you can also pass a call-back function, so the - * search will first try to check if the key string exists in the dictionary. - * If so, search will be based on the id instead of the key string. - * - * @author Tian Xia - */ - -#ifndef FBSON_FBSONDOCUMENT_H -#define FBSON_FBSONDOCUMENT_H - -#include -#include -#include - -namespace fbson { - -#pragma pack(push, 1) - -#define FBSON_VER 1 - -// forward declaration -class FbsonValue; -class ObjectVal; - -/* - * FbsonDocument is the main object that accesses and queries FBSON packed - * bytes. NOTE: FbsonDocument only allows object container as the top level - * FBSON value. However, you can use the static method "createValue" to get any - * FbsonValue object from the packed bytes. - * - * FbsonDocument object also dereferences to an object container value - * (ObjectVal) once FBSON is loaded. - * - * ** Load ** - * FbsonDocument is usable after loading packed bytes (memory location) into - * the object. We only need the header and first few bytes of the payload after - * header to verify the FBSON. - * - * Note: creating an FbsonDocument (through createDocument) does not allocate - * any memory. The document object is an efficient wrapper on the packed bytes - * which is accessed directly. - * - * ** Query ** - * Query is through dereferencing into ObjectVal. - */ -class FbsonDocument { - public: - // create an FbsonDocument object from FBSON packed bytes - static FbsonDocument* createDocument(const char* pb, uint32_t size); - - // create an FbsonValue from FBSON packed bytes - static FbsonValue* createValue(const char* pb, uint32_t size); - - uint8_t version() { return header_.ver_; } - - FbsonValue* getValue() { return ((FbsonValue*)payload_); } - - ObjectVal* operator->() { return ((ObjectVal*)payload_); } - - const ObjectVal* operator->() const { return ((const ObjectVal*)payload_); } - - private: - /* - * FbsonHeader class defines FBSON header (internal to FbsonDocument). - * - * Currently it only contains version information (1-byte). We may expand the - * header to include checksum of the FBSON binary for more security. - */ - struct FbsonHeader { - uint8_t ver_; - } header_; - - char payload_[1]; - - FbsonDocument(); - - FbsonDocument(const FbsonDocument&) = delete; - FbsonDocument& operator=(const FbsonDocument&) = delete; -}; - -/* - * FbsonFwdIteratorT implements FBSON's iterator template. - * - * Note: it is an FORWARD iterator only due to the design of FBSON format. - */ -template -class FbsonFwdIteratorT { - typedef Iter_Type iterator; - typedef typename std::iterator_traits::pointer pointer; - typedef typename std::iterator_traits::reference reference; - - public: - explicit FbsonFwdIteratorT(const iterator& i) : current_(i) {} - - // allow non-const to const iterator conversion (same container type) - template - FbsonFwdIteratorT(const FbsonFwdIteratorT& rhs) - : current_(rhs.base()) {} - - bool operator==(const FbsonFwdIteratorT& rhs) const { - return (current_ == rhs.current_); - } - - bool operator!=(const FbsonFwdIteratorT& rhs) const { - return !operator==(rhs); - } - - bool operator<(const FbsonFwdIteratorT& rhs) const { - return (current_ < rhs.current_); - } - - bool operator>(const FbsonFwdIteratorT& rhs) const { return !operator<(rhs); } - - FbsonFwdIteratorT& operator++() { - current_ = (iterator)(((char*)current_) + current_->numPackedBytes()); - return *this; - } - - FbsonFwdIteratorT operator++(int) { - auto tmp = *this; - current_ = (iterator)(((char*)current_) + current_->numPackedBytes()); - return tmp; - } - - explicit operator pointer() { return current_; } - - reference operator*() const { return *current_; } - - pointer operator->() const { return current_; } - - iterator base() const { return current_; } - - private: - iterator current_; -}; - -typedef int (*hDictInsert)(const char* key, unsigned len); -typedef int (*hDictFind)(const char* key, unsigned len); - -/* - * FbsonType defines 10 primitive types and 2 container types, as described - * below. - * - * primitive_value ::= - * 0x00 //null value (0 byte) - * | 0x01 //boolean true (0 byte) - * | 0x02 //boolean false (0 byte) - * | 0x03 int8 //char/int8 (1 byte) - * | 0x04 int16 //int16 (2 bytes) - * | 0x05 int32 //int32 (4 bytes) - * | 0x06 int64 //int64 (8 bytes) - * | 0x07 double //floating point (8 bytes) - * | 0x08 string //variable length string - * | 0x09 binary //variable length binary - * - * container ::= - * 0x0A int32 key_value_list //object, int32 is the total bytes of the object - * | 0x0B int32 value_list //array, int32 is the total bytes of the array - */ -enum class FbsonType : char { - T_Null = 0x00, - T_True = 0x01, - T_False = 0x02, - T_Int8 = 0x03, - T_Int16 = 0x04, - T_Int32 = 0x05, - T_Int64 = 0x06, - T_Double = 0x07, - T_String = 0x08, - T_Binary = 0x09, - T_Object = 0x0A, - T_Array = 0x0B, - NUM_TYPES, -}; - -typedef std::underlying_type::type FbsonTypeUnder; - -/* - * FbsonKeyValue class defines FBSON key type, as described below. - * - * key ::= - * 0x00 int8 //1-byte dictionary id - * | int8 (byte*) //int8 (>0) is the size of the key string - * - * value ::= primitive_value | container - * - * FbsonKeyValue can be either an id mapping to the key string in an external - * dictionary, or it is the original key string. Whether to read an id or a - * string is decided by the first byte (size_). - * - * Note: a key object must be followed by a value object. Therefore, a key - * object implicitly refers to a key-value pair, and you can get the value - * object right after the key object. The function numPackedBytes hence - * indicates the total size of the key-value pair, so that we will be able go - * to next pair from the key. - * - * ** Dictionary size ** - * By default, the dictionary size is 255 (1-byte). Users can define - * "USE_LARGE_DICT" to increase the dictionary size to 655535 (2-byte). - */ -class FbsonKeyValue { - public: -#ifdef USE_LARGE_DICT - static const int sMaxKeyId = 65535; - typedef uint16_t keyid_type; -#else - static const int sMaxKeyId = 255; - typedef uint8_t keyid_type; -#endif // #ifdef USE_LARGE_DICT - - static const uint8_t sMaxKeyLen = 64; - - // size of the key. 0 indicates it is stored as id - uint8_t klen() const { return size_; } - - // get the key string. Note the string may not be null terminated. - const char* getKeyStr() const { return key_.str_; } - - keyid_type getKeyId() const { return key_.id_; } - - unsigned int keyPackedBytes() const { - return size_ ? (sizeof(size_) + size_) - : (sizeof(size_) + sizeof(keyid_type)); - } - - FbsonValue* value() const { - return (FbsonValue*)(((char*)this) + keyPackedBytes()); - } - - // size of the total packed bytes (key+value) - unsigned int numPackedBytes() const; - - private: - uint8_t size_; - - union key_ { - keyid_type id_; - char str_[1]; - } key_; - - FbsonKeyValue(); -}; - -/* - * FbsonValue is the base class of all FBSON types. It contains only one member - * variable - type info, which can be retrieved by member functions is[Type]() - * or type(). - */ -class FbsonValue { - public: - static const uint32_t sMaxValueLen = 1 << 24; // 16M - - bool isNull() const { return (type_ == FbsonType::T_Null); } - bool isTrue() const { return (type_ == FbsonType::T_True); } - bool isFalse() const { return (type_ == FbsonType::T_False); } - bool isInt8() const { return (type_ == FbsonType::T_Int8); } - bool isInt16() const { return (type_ == FbsonType::T_Int16); } - bool isInt32() const { return (type_ == FbsonType::T_Int32); } - bool isInt64() const { return (type_ == FbsonType::T_Int64); } - bool isDouble() const { return (type_ == FbsonType::T_Double); } - bool isString() const { return (type_ == FbsonType::T_String); } - bool isBinary() const { return (type_ == FbsonType::T_Binary); } - bool isObject() const { return (type_ == FbsonType::T_Object); } - bool isArray() const { return (type_ == FbsonType::T_Array); } - - FbsonType type() const { return type_; } - - // size of the total packed bytes - unsigned int numPackedBytes() const; - - // size of the value in bytes - unsigned int size() const; - - // get the raw byte array of the value - const char* getValuePtr() const; - - // find the FBSON value by a key path string (null terminated) - FbsonValue* findPath(const char* key_path, - const char* delim = ".", - hDictFind handler = nullptr) { - return findPath(key_path, (unsigned int)strlen(key_path), delim, handler); - } - - // find the FBSON value by a key path string (with length) - FbsonValue* findPath(const char* key_path, - unsigned int len, - const char* delim, - hDictFind handler); - - protected: - FbsonType type_; // type info - - FbsonValue(); -}; - -/* - * NumerValT is the template class (derived from FbsonValue) of all number - * types (integers and double). - */ -template -class NumberValT : public FbsonValue { - public: - T val() const { return num_; } - - unsigned int numPackedBytes() const { return sizeof(FbsonValue) + sizeof(T); } - - // catch all unknow specialization of the template class - bool setVal(T value) { return false; } - - private: - T num_; - - NumberValT(); -}; - -typedef NumberValT Int8Val; - -// override setVal for Int8Val -template <> -inline bool Int8Val::setVal(int8_t value) { - if (!isInt8()) { - return false; - } - - num_ = value; - return true; -} - -typedef NumberValT Int16Val; - -// override setVal for Int16Val -template <> -inline bool Int16Val::setVal(int16_t value) { - if (!isInt16()) { - return false; - } - - num_ = value; - return true; -} - -typedef NumberValT Int32Val; - -// override setVal for Int32Val -template <> -inline bool Int32Val::setVal(int32_t value) { - if (!isInt32()) { - return false; - } - - num_ = value; - return true; -} - -typedef NumberValT Int64Val; - -// override setVal for Int64Val -template <> -inline bool Int64Val::setVal(int64_t value) { - if (!isInt64()) { - return false; - } - - num_ = value; - return true; -} - -typedef NumberValT DoubleVal; - -// override setVal for DoubleVal -template <> -inline bool DoubleVal::setVal(double value) { - if (!isDouble()) { - return false; - } - - num_ = value; - return true; -} - -/* - * BlobVal is the base class (derived from FbsonValue) for string and binary - * types. The size_ indicates the total bytes of the payload_. - */ -class BlobVal : public FbsonValue { - public: - // size of the blob payload only - unsigned int getBlobLen() const { return size_; } - - // return the blob as byte array - const char* getBlob() const { return payload_; } - - // size of the total packed bytes - unsigned int numPackedBytes() const { - return sizeof(FbsonValue) + sizeof(size_) + size_; - } - - protected: - uint32_t size_; - char payload_[1]; - - // set new blob bytes - bool internalSetVal(const char* blob, uint32_t blobSize) { - // if we cannot fit the new blob, fail the operation - if (blobSize > size_) { - return false; - } - - memcpy(payload_, blob, blobSize); - - // Set the reset of the bytes to 0. Note we cannot change the size_ of the - // current payload, as all values are packed. - memset(payload_ + blobSize, 0, size_ - blobSize); - - return true; - } - - BlobVal(); - - private: - // Disable as this class can only be allocated dynamically - BlobVal(const BlobVal&) = delete; - BlobVal& operator=(const BlobVal&) = delete; -}; - -/* - * Binary type - */ -class BinaryVal : public BlobVal { - public: - bool setVal(const char* blob, uint32_t blobSize) { - if (!isBinary()) { - return false; - } - - return internalSetVal(blob, blobSize); - } - - private: - BinaryVal(); -}; - -/* - * String type - * Note: FBSON string may not be a c-string (NULL-terminated) - */ -class StringVal : public BlobVal { - public: - bool setVal(const char* str, uint32_t blobSize) { - if (!isString()) { - return false; - } - - return internalSetVal(str, blobSize); - } - - private: - StringVal(); -}; - -/* - * ContainerVal is the base class (derived from FbsonValue) for object and - * array types. The size_ indicates the total bytes of the payload_. - */ -class ContainerVal : public FbsonValue { - public: - // size of the container payload only - unsigned int getContainerSize() const { return size_; } - - // return the container payload as byte array - const char* getPayload() const { return payload_; } - - // size of the total packed bytes - unsigned int numPackedBytes() const { - return sizeof(FbsonValue) + sizeof(size_) + size_; - } - - protected: - uint32_t size_; - char payload_[1]; - - ContainerVal(); - - ContainerVal(const ContainerVal&) = delete; - ContainerVal& operator=(const ContainerVal&) = delete; -}; - -/* - * Object type - */ -class ObjectVal : public ContainerVal { - public: - // find the FBSON value by a key string (null terminated) - FbsonValue* find(const char* key, hDictFind handler = nullptr) const { - if (!key) - return nullptr; - - return find(key, (unsigned int)strlen(key), handler); - } - - // find the FBSON value by a key string (with length) - FbsonValue* find(const char* key, - unsigned int klen, - hDictFind handler = nullptr) const { - if (!key || !klen) - return nullptr; - - int key_id = -1; - if (handler && (key_id = handler(key, klen)) >= 0) { - return find(key_id); - } - - return internalFind(key, klen); - } - - // find the FBSON value by a key dictionary ID - FbsonValue* find(int key_id) const { - if (key_id < 0 || key_id > FbsonKeyValue::sMaxKeyId) - return nullptr; - - const char* pch = payload_; - const char* fence = payload_ + size_; - - while (pch < fence) { - FbsonKeyValue* pkey = (FbsonKeyValue*)(pch); - if (!pkey->klen() && key_id == pkey->getKeyId()) { - return pkey->value(); - } - pch += pkey->numPackedBytes(); - } - - assert(pch == fence); - - return nullptr; - } - - typedef FbsonKeyValue value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef FbsonFwdIteratorT iterator; - typedef FbsonFwdIteratorT const_iterator; - - iterator begin() { return iterator((pointer)payload_); } - - const_iterator begin() const { return const_iterator((pointer)payload_); } - - iterator end() { return iterator((pointer)(payload_ + size_)); } - - const_iterator end() const { - return const_iterator((pointer)(payload_ + size_)); - } - - private: - FbsonValue* internalFind(const char* key, unsigned int klen) const { - const char* pch = payload_; - const char* fence = payload_ + size_; - - while (pch < fence) { - FbsonKeyValue* pkey = (FbsonKeyValue*)(pch); - if (klen == pkey->klen() && strncmp(key, pkey->getKeyStr(), klen) == 0) { - return pkey->value(); - } - pch += pkey->numPackedBytes(); - } - - assert(pch == fence); - - return nullptr; - } - - private: - ObjectVal(); -}; - -/* - * Array type - */ -class ArrayVal : public ContainerVal { - public: - // get the FBSON value at index - FbsonValue* get(int idx) const { - if (idx < 0) - return nullptr; - - const char* pch = payload_; - const char* fence = payload_ + size_; - - while (pch < fence && idx-- > 0) - pch += ((FbsonValue*)pch)->numPackedBytes(); - - if (idx == -1) - return (FbsonValue*)pch; - else { - assert(pch == fence); - return nullptr; - } - } - - // Get number of elements in array - unsigned int numElem() const { - const char* pch = payload_; - const char* fence = payload_ + size_; - - unsigned int num = 0; - while (pch < fence) { - ++num; - pch += ((FbsonValue*)pch)->numPackedBytes(); - } - - assert(pch == fence); - - return num; - } - - typedef FbsonValue value_type; - typedef value_type* pointer; - typedef const value_type* const_pointer; - typedef FbsonFwdIteratorT iterator; - typedef FbsonFwdIteratorT const_iterator; - - iterator begin() { return iterator((pointer)payload_); } - - const_iterator begin() const { return const_iterator((pointer)payload_); } - - iterator end() { return iterator((pointer)(payload_ + size_)); } - - const_iterator end() const { - return const_iterator((pointer)(payload_ + size_)); - } - - private: - ArrayVal(); -}; - -inline FbsonDocument* FbsonDocument::createDocument(const char* pb, - uint32_t size) { - if (!pb || size < sizeof(FbsonHeader) + sizeof(FbsonValue)) { - return nullptr; - } - - FbsonDocument* doc = (FbsonDocument*)pb; - if (doc->header_.ver_ != FBSON_VER) { - return nullptr; - } - - FbsonValue* val = (FbsonValue*)doc->payload_; - if (!val->isObject() || size != sizeof(FbsonHeader) + val->numPackedBytes()) { - return nullptr; - } - - return doc; -} - -inline FbsonValue* FbsonDocument::createValue(const char* pb, uint32_t size) { - if (!pb || size < sizeof(FbsonHeader) + sizeof(FbsonValue)) { - return nullptr; - } - - FbsonDocument* doc = (FbsonDocument*)pb; - if (doc->header_.ver_ != FBSON_VER) { - return nullptr; - } - - FbsonValue* val = (FbsonValue*)doc->payload_; - if (size != sizeof(FbsonHeader) + val->numPackedBytes()) { - return nullptr; - } - - return val; -} - -inline unsigned int FbsonKeyValue::numPackedBytes() const { - unsigned int ks = keyPackedBytes(); - FbsonValue* val = (FbsonValue*)(((char*)this) + ks); - return ks + val->numPackedBytes(); -} - -// Poor man's "virtual" function FbsonValue::numPackedBytes -inline unsigned int FbsonValue::numPackedBytes() const { - switch (type_) { - case FbsonType::T_Null: - case FbsonType::T_True: - case FbsonType::T_False: { - return sizeof(type_); - } - - case FbsonType::T_Int8: { - return sizeof(type_) + sizeof(int8_t); - } - case FbsonType::T_Int16: { - return sizeof(type_) + sizeof(int16_t); - } - case FbsonType::T_Int32: { - return sizeof(type_) + sizeof(int32_t); - } - case FbsonType::T_Int64: { - return sizeof(type_) + sizeof(int64_t); - } - case FbsonType::T_Double: { - return sizeof(type_) + sizeof(double); - } - case FbsonType::T_String: - case FbsonType::T_Binary: { - return ((BlobVal*)(this))->numPackedBytes(); - } - - case FbsonType::T_Object: - case FbsonType::T_Array: { - return ((ContainerVal*)(this))->numPackedBytes(); - } - default: - return 0; - } -} - -inline unsigned int FbsonValue::size() const { - switch (type_) { - case FbsonType::T_Int8: { - return sizeof(int8_t); - } - case FbsonType::T_Int16: { - return sizeof(int16_t); - } - case FbsonType::T_Int32: { - return sizeof(int32_t); - } - case FbsonType::T_Int64: { - return sizeof(int64_t); - } - case FbsonType::T_Double: { - return sizeof(double); - } - case FbsonType::T_String: - case FbsonType::T_Binary: { - return ((BlobVal*)(this))->getBlobLen(); - } - - case FbsonType::T_Object: - case FbsonType::T_Array: { - return ((ContainerVal*)(this))->getContainerSize(); - } - case FbsonType::T_Null: - case FbsonType::T_True: - case FbsonType::T_False: - default: - return 0; - } -} - -inline const char* FbsonValue::getValuePtr() const { - switch (type_) { - case FbsonType::T_Int8: - case FbsonType::T_Int16: - case FbsonType::T_Int32: - case FbsonType::T_Int64: - case FbsonType::T_Double: - return ((char*)this) + sizeof(FbsonType); - - case FbsonType::T_String: - case FbsonType::T_Binary: - return ((BlobVal*)(this))->getBlob(); - - case FbsonType::T_Object: - case FbsonType::T_Array: - return ((ContainerVal*)(this))->getPayload(); - - case FbsonType::T_Null: - case FbsonType::T_True: - case FbsonType::T_False: - default: - return nullptr; - } -} - -inline FbsonValue* FbsonValue::findPath(const char* key_path, - unsigned int kp_len, - const char* delim = ".", - hDictFind handler = nullptr) { - if (!key_path || !kp_len) - return nullptr; - - if (!delim) - delim = "."; // default delimiter - - FbsonValue* pval = this; - const char* fence = key_path + kp_len; - char idx_buf[21]; // buffer to parse array index (integer value) - - while (pval && key_path < fence) { - const char* key = key_path; - unsigned int klen = 0; - // find the current key - for (; key_path != fence && *key_path != *delim; ++key_path, ++klen) - ; - - if (!klen) - return nullptr; - - switch (pval->type_) { - case FbsonType::T_Object: { - pval = ((ObjectVal*)pval)->find(key, klen, handler); - break; - } - - case FbsonType::T_Array: { - // parse string into an integer (array index) - if (klen >= sizeof(idx_buf)) - return nullptr; - - memcpy(idx_buf, key, klen); - idx_buf[klen] = 0; - - char* end = nullptr; - int index = (int)strtol(idx_buf, &end, 10); - if (end && !*end) - pval = ((fbson::ArrayVal*)pval)->get(index); - else - // incorrect index string - return nullptr; - break; - } - - default: - return nullptr; - } - - // skip the delimiter - if (key_path < fence) { - ++key_path; - if (key_path == fence) - // we have a trailing delimiter at the end - return nullptr; - } - } - - return pval; -} - -#pragma pack(pop) - -} // namespace fbson - -#endif // FBSON_FBSONDOCUMENT_H diff --git a/thirdparty/rocksdb/third-party/fbson/FbsonJsonParser.h b/thirdparty/rocksdb/third-party/fbson/FbsonJsonParser.h deleted file mode 100644 index 63b03e2b90..0000000000 --- a/thirdparty/rocksdb/third-party/fbson/FbsonJsonParser.h +++ /dev/null @@ -1,741 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -/* - * This file defines FbsonJsonParserT (template) and FbsonJsonParser. - * - * FbsonJsonParserT is a template class which implements a JSON parser. - * FbsonJsonParserT parses JSON text, and serialize it to FBSON binary format - * by using FbsonWriterT object. By default, FbsonJsonParserT creates a new - * FbsonWriterT object with an output stream object. However, you can also - * pass in your FbsonWriterT or any stream object that implements some basic - * interface of std::ostream (see FbsonStream.h). - * - * FbsonJsonParser specializes FbsonJsonParserT with FbsonOutStream type (see - * FbsonStream.h). So unless you want to provide own a different output stream - * type, use FbsonJsonParser object. - * - * ** Parsing JSON ** - * FbsonJsonParserT parses JSON string, and directly serializes into FBSON - * packed bytes. There are three ways to parse a JSON string: (1) using - * c-string, (2) using string with len, (3) using std::istream object. You can - * use custome streambuf to redirect output. FbsonOutBuffer is a streambuf used - * internally if the input is raw character buffer. - * - * You can reuse an FbsonJsonParserT object to parse/serialize multiple JSON - * strings, and the previous FBSON will be overwritten. - * - * If parsing fails (returned false), the error code will be set to one of - * FbsonErrType, and can be retrieved by calling getErrorCode(). - * - * ** External dictionary ** - * During parsing a JSON string, you can pass a call-back function to map a key - * string to an id, and store the dictionary id in FBSON to save space. The - * purpose of using an external dictionary is more towards a collection of - * documents (which has common keys) rather than a single document, so that - * space saving will be significant. - * - * ** Endianness ** - * Note: FBSON serialization doesn't assume endianness of the server. However - * you will need to ensure that the endianness at the reader side is the same - * as that at the writer side (if they are on different machines). Otherwise, - * proper conversion is needed when a number value is returned to the - * caller/writer. - * - * @author Tian Xia - */ - -#ifndef FBSON_FBSONPARSER_H -#define FBSON_FBSONPARSER_H - -#include -#include -#include "FbsonDocument.h" -#include "FbsonWriter.h" - -namespace fbson { - -const char* const kJsonDelim = " ,]}\t\r\n"; -const char* const kWhiteSpace = " \t\n\r"; - -/* - * Error codes - */ -enum class FbsonErrType { - E_NONE = 0, - E_INVALID_VER, - E_EMPTY_STR, - E_OUTPUT_FAIL, - E_INVALID_DOCU, - E_INVALID_VALUE, - E_INVALID_KEY, - E_INVALID_STR, - E_INVALID_OBJ, - E_INVALID_ARR, - E_INVALID_HEX, - E_INVALID_OCTAL, - E_INVALID_DECIMAL, - E_INVALID_EXPONENT, - E_HEX_OVERFLOW, - E_OCTAL_OVERFLOW, - E_DECIMAL_OVERFLOW, - E_DOUBLE_OVERFLOW, - E_EXPONENT_OVERFLOW, -}; - -/* - * Template FbsonJsonParserT - */ -template -class FbsonJsonParserT { - public: - FbsonJsonParserT() : err_(FbsonErrType::E_NONE) {} - - explicit FbsonJsonParserT(OS_TYPE& os) - : writer_(os), err_(FbsonErrType::E_NONE) {} - - // parse a UTF-8 JSON string - bool parse(const std::string& str, hDictInsert handler = nullptr) { - return parse(str.c_str(), (unsigned int)str.size(), handler); - } - - // parse a UTF-8 JSON c-style string (NULL terminated) - bool parse(const char* c_str, hDictInsert handler = nullptr) { - return parse(c_str, (unsigned int)strlen(c_str), handler); - } - - // parse a UTF-8 JSON string with length - bool parse(const char* pch, unsigned int len, hDictInsert handler = nullptr) { - if (!pch || len == 0) { - err_ = FbsonErrType::E_EMPTY_STR; - return false; - } - - FbsonInBuffer sb(pch, len); - std::istream in(&sb); - return parse(in, handler); - } - - // parse UTF-8 JSON text from an input stream - bool parse(std::istream& in, hDictInsert handler = nullptr) { - bool res = false; - - // reset output stream - writer_.reset(); - - trim(in); - - if (in.peek() == '{') { - in.ignore(); - res = parseObject(in, handler); - } else if (in.peek() == '[') { - in.ignore(); - res = parseArray(in, handler); - } else { - err_ = FbsonErrType::E_INVALID_DOCU; - } - - trim(in); - if (res && !in.eof()) { - err_ = FbsonErrType::E_INVALID_DOCU; - return false; - } - - return res; - } - - FbsonWriterT& getWriter() { return writer_; } - - FbsonErrType getErrorCode() { return err_; } - - // clear error code - void clearErr() { err_ = FbsonErrType::E_NONE; } - - private: - // parse a JSON object (comma-separated list of key-value pairs) - bool parseObject(std::istream& in, hDictInsert handler) { - if (!writer_.writeStartObject()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - - trim(in); - - if (in.peek() == '}') { - in.ignore(); - // empty object - if (!writer_.writeEndObject()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - return true; - } - - while (in.good()) { - if (in.get() != '"') { - err_ = FbsonErrType::E_INVALID_KEY; - return false; - } - - if (!parseKVPair(in, handler)) { - return false; - } - - trim(in); - - char ch = in.get(); - if (ch == '}') { - // end of the object - if (!writer_.writeEndObject()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - return true; - } else if (ch != ',') { - err_ = FbsonErrType::E_INVALID_OBJ; - return false; - } - - trim(in); - } - - err_ = FbsonErrType::E_INVALID_OBJ; - return false; - } - - // parse a JSON array (comma-separated list of values) - bool parseArray(std::istream& in, hDictInsert handler) { - if (!writer_.writeStartArray()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - - trim(in); - - if (in.peek() == ']') { - in.ignore(); - // empty array - if (!writer_.writeEndArray()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - return true; - } - - while (in.good()) { - if (!parseValue(in, handler)) { - return false; - } - - trim(in); - - char ch = in.get(); - if (ch == ']') { - // end of the array - if (!writer_.writeEndArray()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - return true; - } else if (ch != ',') { - err_ = FbsonErrType::E_INVALID_ARR; - return false; - } - - trim(in); - } - - err_ = FbsonErrType::E_INVALID_ARR; - return false; - } - - // parse a key-value pair, separated by ":" - bool parseKVPair(std::istream& in, hDictInsert handler) { - if (parseKey(in, handler) && parseValue(in, handler)) { - return true; - } - - return false; - } - - // parse a key (must be string) - bool parseKey(std::istream& in, hDictInsert handler) { - char key[FbsonKeyValue::sMaxKeyLen]; - int i = 0; - while (in.good() && in.peek() != '"' && i < FbsonKeyValue::sMaxKeyLen) { - key[i++] = in.get(); - } - - if (!in.good() || in.peek() != '"' || i == 0) { - err_ = FbsonErrType::E_INVALID_KEY; - return false; - } - - in.ignore(); // discard '"' - - int key_id = -1; - if (handler) { - key_id = handler(key, i); - } - - if (key_id < 0) { - writer_.writeKey(key, i); - } else { - writer_.writeKey(key_id); - } - - trim(in); - - if (in.get() != ':') { - err_ = FbsonErrType::E_INVALID_OBJ; - return false; - } - - return true; - } - - // parse a value - bool parseValue(std::istream& in, hDictInsert handler) { - bool res = false; - - trim(in); - - switch (in.peek()) { - case 'N': - case 'n': { - in.ignore(); - res = parseNull(in); - break; - } - case 'T': - case 't': { - in.ignore(); - res = parseTrue(in); - break; - } - case 'F': - case 'f': { - in.ignore(); - res = parseFalse(in); - break; - } - case '"': { - in.ignore(); - res = parseString(in); - break; - } - case '{': { - in.ignore(); - res = parseObject(in, handler); - break; - } - case '[': { - in.ignore(); - res = parseArray(in, handler); - break; - } - default: { - res = parseNumber(in); - break; - } - } - - return res; - } - - // parse NULL value - bool parseNull(std::istream& in) { - if (tolower(in.get()) == 'u' && tolower(in.get()) == 'l' && - tolower(in.get()) == 'l') { - writer_.writeNull(); - return true; - } - - err_ = FbsonErrType::E_INVALID_VALUE; - return false; - } - - // parse TRUE value - bool parseTrue(std::istream& in) { - if (tolower(in.get()) == 'r' && tolower(in.get()) == 'u' && - tolower(in.get()) == 'e') { - writer_.writeBool(true); - return true; - } - - err_ = FbsonErrType::E_INVALID_VALUE; - return false; - } - - // parse FALSE value - bool parseFalse(std::istream& in) { - if (tolower(in.get()) == 'a' && tolower(in.get()) == 'l' && - tolower(in.get()) == 's' && tolower(in.get()) == 'e') { - writer_.writeBool(false); - return true; - } - - err_ = FbsonErrType::E_INVALID_VALUE; - return false; - } - - // parse a string - bool parseString(std::istream& in) { - if (!writer_.writeStartString()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - - bool escaped = false; - char buffer[4096]; // write 4KB at a time - int nread = 0; - while (in.good()) { - char ch = in.get(); - if (ch != '"' || escaped) { - buffer[nread++] = ch; - if (nread == 4096) { - // flush buffer - if (!writer_.writeString(buffer, nread)) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - nread = 0; - } - // set/reset escape - if (ch == '\\' || escaped) { - escaped = !escaped; - } - } else { - // write all remaining bytes in the buffer - if (nread > 0) { - if (!writer_.writeString(buffer, nread)) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - } - // end writing string - if (!writer_.writeEndString()) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - return true; - } - } - - err_ = FbsonErrType::E_INVALID_STR; - return false; - } - - // parse a number - // Number format can be hex, octal, or decimal (including float). - // Only decimal can have (+/-) sign prefix. - bool parseNumber(std::istream& in) { - bool ret = false; - switch (in.peek()) { - case '0': { - in.ignore(); - - if (in.peek() == 'x' || in.peek() == 'X') { - in.ignore(); - ret = parseHex(in); - } else if (in.peek() == '.') { - in.ignore(); - ret = parseDouble(in, 0, 0, 1); - } else { - ret = parseOctal(in); - } - - break; - } - case '-': { - in.ignore(); - ret = parseDecimal(in, -1); - break; - } - case '+': - in.ignore(); - // fall through - default: - ret = parseDecimal(in, 1); - break; - } - - return ret; - } - - // parse a number in hex format - bool parseHex(std::istream& in) { - uint64_t val = 0; - int num_digits = 0; - char ch = tolower(in.peek()); - while (in.good() && !strchr(kJsonDelim, ch) && (++num_digits) <= 16) { - if (ch >= '0' && ch <= '9') { - val = (val << 4) + (ch - '0'); - } else if (ch >= 'a' && ch <= 'f') { - val = (val << 4) + (ch - 'a' + 10); - } else { // unrecognized hex digit - err_ = FbsonErrType::E_INVALID_HEX; - return false; - } - - in.ignore(); - ch = tolower(in.peek()); - } - - int size = 0; - if (num_digits <= 2) { - size = writer_.writeInt8((int8_t)val); - } else if (num_digits <= 4) { - size = writer_.writeInt16((int16_t)val); - } else if (num_digits <= 8) { - size = writer_.writeInt32((int32_t)val); - } else if (num_digits <= 16) { - size = writer_.writeInt64(val); - } else { - err_ = FbsonErrType::E_HEX_OVERFLOW; - return false; - } - - if (size == 0) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - - return true; - } - - // parse a number in octal format - bool parseOctal(std::istream& in) { - int64_t val = 0; - char ch = in.peek(); - while (in.good() && !strchr(kJsonDelim, ch)) { - if (ch >= '0' && ch <= '7') { - val = val * 8 + (ch - '0'); - } else { - err_ = FbsonErrType::E_INVALID_OCTAL; - return false; - } - - // check if the number overflows - if (val < 0) { - err_ = FbsonErrType::E_OCTAL_OVERFLOW; - return false; - } - - in.ignore(); - ch = in.peek(); - } - - int size = 0; - if (val <= std::numeric_limits::max()) { - size = writer_.writeInt8((int8_t)val); - } else if (val <= std::numeric_limits::max()) { - size = writer_.writeInt16((int16_t)val); - } else if (val <= std::numeric_limits::max()) { - size = writer_.writeInt32((int32_t)val); - } else { // val <= INT64_MAX - size = writer_.writeInt64(val); - } - - if (size == 0) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - - return true; - } - - // parse a number in decimal (including float) - bool parseDecimal(std::istream& in, int sign) { - int64_t val = 0; - int precision = 0; - - char ch = 0; - while (in.good() && (ch = in.peek()) == '0') - in.ignore(); - - while (in.good() && !strchr(kJsonDelim, ch)) { - if (ch >= '0' && ch <= '9') { - val = val * 10 + (ch - '0'); - ++precision; - } else if (ch == '.') { - // note we don't pop out '.' - return parseDouble(in, static_cast(val), precision, sign); - } else { - err_ = FbsonErrType::E_INVALID_DECIMAL; - return false; - } - - in.ignore(); - - // if the number overflows int64_t, first parse it as double iff we see a - // decimal point later. Otherwise, will treat it as overflow - if (val < 0 && val > std::numeric_limits::min()) { - return parseDouble(in, static_cast(val), precision, sign); - } - - ch = in.peek(); - } - - if (sign < 0) { - val = -val; - } - - int size = 0; - if (val >= std::numeric_limits::min() && - val <= std::numeric_limits::max()) { - size = writer_.writeInt8((int8_t)val); - } else if (val >= std::numeric_limits::min() && - val <= std::numeric_limits::max()) { - size = writer_.writeInt16((int16_t)val); - } else if (val >= std::numeric_limits::min() && - val <= std::numeric_limits::max()) { - size = writer_.writeInt32((int32_t)val); - } else { // val <= INT64_MAX - size = writer_.writeInt64(val); - } - - if (size == 0) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - - return true; - } - - // parse IEEE745 double precision: - // Significand precision length - 15 - // Maximum exponent value - 308 - // - // "If a decimal string with at most 15 significant digits is converted to - // IEEE 754 double precision representation and then converted back to a - // string with the same number of significant digits, then the final string - // should match the original" - bool parseDouble(std::istream& in, double val, int precision, int sign) { - int integ = precision; - int frac = 0; - bool is_frac = false; - - char ch = in.peek(); - if (ch == '.') { - is_frac = true; - in.ignore(); - ch = in.peek(); - } - - int exp = 0; - while (in.good() && !strchr(kJsonDelim, ch)) { - if (ch >= '0' && ch <= '9') { - if (precision < 15) { - val = val * 10 + (ch - '0'); - if (is_frac) { - ++frac; - } else { - ++integ; - } - ++precision; - } else if (!is_frac) { - ++exp; - } - } else if (ch == 'e' || ch == 'E') { - in.ignore(); - int exp2; - if (!parseExponent(in, exp2)) { - return false; - } - - exp += exp2; - // check if exponent overflows - if (exp > 308 || exp < -308) { - err_ = FbsonErrType::E_EXPONENT_OVERFLOW; - return false; - } - - is_frac = true; - break; - } - - in.ignore(); - ch = in.peek(); - } - - if (!is_frac) { - err_ = FbsonErrType::E_DECIMAL_OVERFLOW; - return false; - } - - val *= std::pow(10, exp - frac); - if (std::isnan(val) || std::isinf(val)) { - err_ = FbsonErrType::E_DOUBLE_OVERFLOW; - return false; - } - - if (sign < 0) { - val = -val; - } - - if (writer_.writeDouble(val) == 0) { - err_ = FbsonErrType::E_OUTPUT_FAIL; - return false; - } - - return true; - } - - // parse the exponent part of a double number - bool parseExponent(std::istream& in, int& exp) { - bool neg = false; - - char ch = in.peek(); - if (ch == '+') { - in.ignore(); - ch = in.peek(); - } else if (ch == '-') { - neg = true; - in.ignore(); - ch = in.peek(); - } - - exp = 0; - while (in.good() && !strchr(kJsonDelim, ch)) { - if (ch >= '0' && ch <= '9') { - exp = exp * 10 + (ch - '0'); - } else { - err_ = FbsonErrType::E_INVALID_EXPONENT; - return false; - } - - if (exp > 308) { - err_ = FbsonErrType::E_EXPONENT_OVERFLOW; - return false; - } - - in.ignore(); - ch = in.peek(); - } - - if (neg) { - exp = -exp; - } - - return true; - } - - void trim(std::istream& in) { - while (in.good() && strchr(kWhiteSpace, in.peek())) { - in.ignore(); - } - } - - private: - FbsonWriterT writer_; - FbsonErrType err_; -}; - -typedef FbsonJsonParserT FbsonJsonParser; - -} // namespace fbson - -#endif // FBSON_FBSONPARSER_H diff --git a/thirdparty/rocksdb/third-party/fbson/FbsonStream.h b/thirdparty/rocksdb/third-party/fbson/FbsonStream.h deleted file mode 100644 index 12723ea30e..0000000000 --- a/thirdparty/rocksdb/third-party/fbson/FbsonStream.h +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -/* - * This header file defines FbsonInBuffer and FbsonOutStream classes. - * - * ** Input Buffer ** - * FbsonInBuffer is a customer input buffer to wrap raw character buffer. Its - * object instances are used to create std::istream objects interally. - * - * ** Output Stream ** - * FbsonOutStream is a custom output stream classes, to contain the FBSON - * serialized binary. The class is conveniently used to specialize templates of - * FbsonParser and FbsonWriter. - * - * @author Tian Xia - */ - -#ifndef FBSON_FBSONSTREAM_H -#define FBSON_FBSONSTREAM_H - -#ifndef __STDC_FORMAT_MACROS -#define __STDC_FORMAT_MACROS -#endif - -#if defined OS_WIN && !defined snprintf -#define snprintf _snprintf -#endif - -#include -#include - -namespace fbson { - -// lengths includes sign -#define MAX_INT_DIGITS 11 -#define MAX_INT64_DIGITS 20 -#define MAX_DOUBLE_DIGITS 23 // 1(sign)+16(significant)+1(decimal)+5(exponent) - -/* - * FBSON's implementation of input buffer - */ -class FbsonInBuffer : public std::streambuf { - public: - FbsonInBuffer(const char* str, uint32_t len) { - // this is read buffer and the str will not be changed - // so we use const_cast (ugly!) to remove constness - char* pch(const_cast(str)); - setg(pch, pch, pch + len); - } -}; - -/* - * FBSON's implementation of output stream. - * - * This is a wrapper of a char buffer. By default, the buffer capacity is 1024 - * bytes. We will double the buffer if realloc is needed for writes. - */ -class FbsonOutStream : public std::ostream { - public: - explicit FbsonOutStream(uint32_t capacity = 1024) - : std::ostream(nullptr), - head_(nullptr), - size_(0), - capacity_(capacity), - alloc_(true) { - if (capacity_ == 0) { - capacity_ = 1024; - } - - head_ = (char*)malloc(capacity_); - } - - FbsonOutStream(char* buffer, uint32_t capacity) - : std::ostream(nullptr), - head_(buffer), - size_(0), - capacity_(capacity), - alloc_(false) { - assert(buffer && capacity_ > 0); - } - - ~FbsonOutStream() { - if (alloc_) { - free(head_); - } - } - - void put(char c) { write(&c, 1); } - - void write(const char* c_str) { write(c_str, (uint32_t)strlen(c_str)); } - - void write(const char* bytes, uint32_t len) { - if (len == 0) - return; - - if (size_ + len > capacity_) { - realloc(len); - } - - memcpy(head_ + size_, bytes, len); - size_ += len; - } - - // write the integer to string - void write(int i) { - // snprintf automatically adds a NULL, so we need one more char - if (size_ + MAX_INT_DIGITS + 1 > capacity_) { - realloc(MAX_INT_DIGITS + 1); - } - - int len = snprintf(head_ + size_, MAX_INT_DIGITS + 1, "%d", i); - assert(len > 0); - size_ += len; - } - - // write the 64bit integer to string - void write(int64_t l) { - // snprintf automatically adds a NULL, so we need one more char - if (size_ + MAX_INT64_DIGITS + 1 > capacity_) { - realloc(MAX_INT64_DIGITS + 1); - } - - int len = snprintf(head_ + size_, MAX_INT64_DIGITS + 1, "%" PRIi64, l); - assert(len > 0); - size_ += len; - } - - // write the double to string - void write(double d) { - // snprintf automatically adds a NULL, so we need one more char - if (size_ + MAX_DOUBLE_DIGITS + 1 > capacity_) { - realloc(MAX_DOUBLE_DIGITS + 1); - } - - int len = snprintf(head_ + size_, MAX_DOUBLE_DIGITS + 1, "%.15g", d); - assert(len > 0); - size_ += len; - } - - pos_type tellp() const { return size_; } - - void seekp(pos_type pos) { size_ = (uint32_t)pos; } - - const char* getBuffer() const { return head_; } - - pos_type getSize() const { return tellp(); } - - private: - void realloc(uint32_t len) { - assert(capacity_ > 0); - - capacity_ *= 2; - while (capacity_ < size_ + len) { - capacity_ *= 2; - } - - if (alloc_) { - char* new_buf = (char*)::realloc(head_, capacity_); - assert(new_buf); - head_ = new_buf; - } else { - char* new_buf = (char*)::malloc(capacity_); - assert(new_buf); - memcpy(new_buf, head_, size_); - head_ = new_buf; - alloc_ = true; - } - } - - private: - char* head_; - uint32_t size_; - uint32_t capacity_; - bool alloc_; -}; - -} // namespace fbson - -#endif // FBSON_FBSONSTREAM_H diff --git a/thirdparty/rocksdb/third-party/fbson/FbsonUtil.h b/thirdparty/rocksdb/third-party/fbson/FbsonUtil.h deleted file mode 100644 index 2b6d6f5c97..0000000000 --- a/thirdparty/rocksdb/third-party/fbson/FbsonUtil.h +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -/* - * This header file defines miscellaneous utility classes. - * - * @author Tian Xia - */ - -#ifndef FBSON_FBSONUTIL_H -#define FBSON_FBSONUTIL_H - -#include -#include "FbsonDocument.h" - -namespace fbson { - -#define OUT_BUF_SIZE 1024 - -/* - * FbsonToJson converts an FbsonValue object to a JSON string. - */ -class FbsonToJson { - public: - FbsonToJson() : os_(buffer_, OUT_BUF_SIZE) {} - - // get json string - const char* json(const FbsonValue* pval) { - os_.clear(); - os_.seekp(0); - - if (pval) { - intern_json(pval); - } - - os_.put(0); - return os_.getBuffer(); - } - - private: - // recursively convert FbsonValue - void intern_json(const FbsonValue* val) { - switch (val->type()) { - case FbsonType::T_Null: { - os_.write("null", 4); - break; - } - case FbsonType::T_True: { - os_.write("true", 4); - break; - } - case FbsonType::T_False: { - os_.write("false", 5); - break; - } - case FbsonType::T_Int8: { - os_.write(((Int8Val*)val)->val()); - break; - } - case FbsonType::T_Int16: { - os_.write(((Int16Val*)val)->val()); - break; - } - case FbsonType::T_Int32: { - os_.write(((Int32Val*)val)->val()); - break; - } - case FbsonType::T_Int64: { - os_.write(((Int64Val*)val)->val()); - break; - } - case FbsonType::T_Double: { - os_.write(((DoubleVal*)val)->val()); - break; - } - case FbsonType::T_String: { - os_.put('"'); - os_.write(((StringVal*)val)->getBlob(), ((StringVal*)val)->getBlobLen()); - os_.put('"'); - break; - } - case FbsonType::T_Binary: { - os_.write("\"", 9); - os_.write(((BinaryVal*)val)->getBlob(), ((BinaryVal*)val)->getBlobLen()); - os_.write("\"", 9); - break; - } - case FbsonType::T_Object: { - object_to_json((ObjectVal*)val); - break; - } - case FbsonType::T_Array: { - array_to_json((ArrayVal*)val); - break; - } - default: - break; - } - } - - // convert object - void object_to_json(const ObjectVal* val) { - os_.put('{'); - - auto iter = val->begin(); - auto iter_fence = val->end(); - - while (iter < iter_fence) { - // write key - if (iter->klen()) { - os_.put('"'); - os_.write(iter->getKeyStr(), iter->klen()); - os_.put('"'); - } else { - os_.write(iter->getKeyId()); - } - os_.put(':'); - - // convert value - intern_json(iter->value()); - - ++iter; - if (iter != iter_fence) { - os_.put(','); - } - } - - assert(iter == iter_fence); - - os_.put('}'); - } - - // convert array to json - void array_to_json(const ArrayVal* val) { - os_.put('['); - - auto iter = val->begin(); - auto iter_fence = val->end(); - - while (iter != iter_fence) { - // convert value - intern_json((const FbsonValue*)iter); - ++iter; - if (iter != iter_fence) { - os_.put(','); - } - } - - assert(iter == iter_fence); - - os_.put(']'); - } - - private: - FbsonOutStream os_; - char buffer_[OUT_BUF_SIZE]; -}; - -} // namespace fbson - -#endif // FBSON_FBSONUTIL_H diff --git a/thirdparty/rocksdb/third-party/fbson/FbsonWriter.h b/thirdparty/rocksdb/third-party/fbson/FbsonWriter.h deleted file mode 100644 index a254e9bbf8..0000000000 --- a/thirdparty/rocksdb/third-party/fbson/FbsonWriter.h +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright (c) 2011-present, Facebook, 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). - -/* - * This file defines FbsonWriterT (template) and FbsonWriter. - * - * FbsonWriterT is a template class which implements an FBSON serializer. - * Users call various write functions of FbsonWriterT object to write values - * directly to FBSON packed bytes. All write functions of value or key return - * the number of bytes written to FBSON, or 0 if there is an error. To write an - * object, an array, or a string, you must call writeStart[..] before writing - * values or key, and call writeEnd[..] after finishing at the end. - * - * By default, an FbsonWriterT object creates an output stream buffer. - * Alternatively, you can also pass any output stream object to a writer, as - * long as the stream object implements some basic functions of std::ostream - * (such as FbsonOutStream, see FbsonStream.h). - * - * FbsonWriter specializes FbsonWriterT with FbsonOutStream type (see - * FbsonStream.h). So unless you want to provide own a different output stream - * type, use FbsonParser object. - * - * @author Tian Xia - */ - -#ifndef FBSON_FBSONWRITER_H -#define FBSON_FBSONWRITER_H - -#include -#include "FbsonDocument.h" -#include "FbsonStream.h" - -namespace fbson { - -template -class FbsonWriterT { - public: - FbsonWriterT() - : alloc_(true), hasHdr_(false), kvState_(WS_Value), str_pos_(0) { - os_ = new OS_TYPE(); - } - - explicit FbsonWriterT(OS_TYPE& os) - : os_(&os), - alloc_(false), - hasHdr_(false), - kvState_(WS_Value), - str_pos_(0) {} - - ~FbsonWriterT() { - if (alloc_) { - delete os_; - } - } - - void reset() { - os_->clear(); - os_->seekp(0); - hasHdr_ = false; - kvState_ = WS_Value; - for (; !stack_.empty(); stack_.pop()) - ; - } - - // write a key string (or key id if an external dict is provided) - uint32_t writeKey(const char* key, - uint8_t len, - hDictInsert handler = nullptr) { - if (len && !stack_.empty() && verifyKeyState()) { - int key_id = -1; - if (handler) { - key_id = handler(key, len); - } - - uint32_t size = sizeof(uint8_t); - if (key_id < 0) { - os_->put(len); - os_->write(key, len); - size += len; - } else if (key_id <= FbsonKeyValue::sMaxKeyId) { - FbsonKeyValue::keyid_type idx = key_id; - os_->put(0); - os_->write((char*)&idx, sizeof(FbsonKeyValue::keyid_type)); - size += sizeof(FbsonKeyValue::keyid_type); - } else { // key id overflow - assert(0); - return 0; - } - - kvState_ = WS_Key; - return size; - } - - return 0; - } - - // write a key id - uint32_t writeKey(FbsonKeyValue::keyid_type idx) { - if (!stack_.empty() && verifyKeyState()) { - os_->put(0); - os_->write((char*)&idx, sizeof(FbsonKeyValue::keyid_type)); - kvState_ = WS_Key; - return sizeof(uint8_t) + sizeof(FbsonKeyValue::keyid_type); - } - - return 0; - } - - uint32_t writeNull() { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_Null); - kvState_ = WS_Value; - return sizeof(FbsonValue); - } - - return 0; - } - - uint32_t writeBool(bool b) { - if (!stack_.empty() && verifyValueState()) { - if (b) { - os_->put((FbsonTypeUnder)FbsonType::T_True); - } else { - os_->put((FbsonTypeUnder)FbsonType::T_False); - } - - kvState_ = WS_Value; - return sizeof(FbsonValue); - } - - return 0; - } - - uint32_t writeInt8(int8_t v) { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_Int8); - os_->put(v); - kvState_ = WS_Value; - return sizeof(Int8Val); - } - - return 0; - } - - uint32_t writeInt16(int16_t v) { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_Int16); - os_->write((char*)&v, sizeof(int16_t)); - kvState_ = WS_Value; - return sizeof(Int16Val); - } - - return 0; - } - - uint32_t writeInt32(int32_t v) { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_Int32); - os_->write((char*)&v, sizeof(int32_t)); - kvState_ = WS_Value; - return sizeof(Int32Val); - } - - return 0; - } - - uint32_t writeInt64(int64_t v) { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_Int64); - os_->write((char*)&v, sizeof(int64_t)); - kvState_ = WS_Value; - return sizeof(Int64Val); - } - - return 0; - } - - uint32_t writeDouble(double v) { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_Double); - os_->write((char*)&v, sizeof(double)); - kvState_ = WS_Value; - return sizeof(DoubleVal); - } - - return 0; - } - - // must call writeStartString before writing a string val - bool writeStartString() { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_String); - str_pos_ = os_->tellp(); - - // fill the size bytes with 0 for now - uint32_t size = 0; - os_->write((char*)&size, sizeof(uint32_t)); - - kvState_ = WS_String; - return true; - } - - return false; - } - - // finish writing a string val - bool writeEndString() { - if (kvState_ == WS_String) { - std::streampos cur_pos = os_->tellp(); - int32_t size = (int32_t)(cur_pos - str_pos_ - sizeof(uint32_t)); - assert(size >= 0); - - os_->seekp(str_pos_); - os_->write((char*)&size, sizeof(uint32_t)); - os_->seekp(cur_pos); - - kvState_ = WS_Value; - return true; - } - - return false; - } - - uint32_t writeString(const char* str, uint32_t len) { - if (kvState_ == WS_String) { - os_->write(str, len); - return len; - } - - return 0; - } - - uint32_t writeString(char ch) { - if (kvState_ == WS_String) { - os_->put(ch); - return 1; - } - - return 0; - } - - // must call writeStartBinary before writing a binary val - bool writeStartBinary() { - if (!stack_.empty() && verifyValueState()) { - os_->put((FbsonTypeUnder)FbsonType::T_Binary); - str_pos_ = os_->tellp(); - - // fill the size bytes with 0 for now - uint32_t size = 0; - os_->write((char*)&size, sizeof(uint32_t)); - - kvState_ = WS_Binary; - return true; - } - - return false; - } - - // finish writing a binary val - bool writeEndBinary() { - if (kvState_ == WS_Binary) { - std::streampos cur_pos = os_->tellp(); - int32_t size = (int32_t)(cur_pos - str_pos_ - sizeof(uint32_t)); - assert(size >= 0); - - os_->seekp(str_pos_); - os_->write((char*)&size, sizeof(uint32_t)); - os_->seekp(cur_pos); - - kvState_ = WS_Value; - return true; - } - - return false; - } - - uint32_t writeBinary(const char* bin, uint32_t len) { - if (kvState_ == WS_Binary) { - os_->write(bin, len); - return len; - } - - return 0; - } - - // must call writeStartObject before writing an object val - bool writeStartObject() { - if (stack_.empty() || verifyValueState()) { - if (stack_.empty()) { - // if this is a new FBSON, write the header - if (!hasHdr_) { - writeHeader(); - } else - return false; - } - - os_->put((FbsonTypeUnder)FbsonType::T_Object); - // save the size position - stack_.push(WriteInfo({WS_Object, os_->tellp()})); - - // fill the size bytes with 0 for now - uint32_t size = 0; - os_->write((char*)&size, sizeof(uint32_t)); - - kvState_ = WS_Value; - return true; - } - - return false; - } - - // finish writing an object val - bool writeEndObject() { - if (!stack_.empty() && stack_.top().state == WS_Object && - kvState_ == WS_Value) { - WriteInfo& ci = stack_.top(); - std::streampos cur_pos = os_->tellp(); - int32_t size = (int32_t)(cur_pos - ci.sz_pos - sizeof(uint32_t)); - assert(size >= 0); - - os_->seekp(ci.sz_pos); - os_->write((char*)&size, sizeof(uint32_t)); - os_->seekp(cur_pos); - stack_.pop(); - - return true; - } - - return false; - } - - // must call writeStartArray before writing an array val - bool writeStartArray() { - if (stack_.empty() || verifyValueState()) { - if (stack_.empty()) { - // if this is a new FBSON, write the header - if (!hasHdr_) { - writeHeader(); - } else - return false; - } - - os_->put((FbsonTypeUnder)FbsonType::T_Array); - // save the size position - stack_.push(WriteInfo({WS_Array, os_->tellp()})); - - // fill the size bytes with 0 for now - uint32_t size = 0; - os_->write((char*)&size, sizeof(uint32_t)); - - kvState_ = WS_Value; - return true; - } - - return false; - } - - // finish writing an array val - bool writeEndArray() { - if (!stack_.empty() && stack_.top().state == WS_Array && - kvState_ == WS_Value) { - WriteInfo& ci = stack_.top(); - std::streampos cur_pos = os_->tellp(); - int32_t size = (int32_t)(cur_pos - ci.sz_pos - sizeof(uint32_t)); - assert(size >= 0); - - os_->seekp(ci.sz_pos); - os_->write((char*)&size, sizeof(uint32_t)); - os_->seekp(cur_pos); - stack_.pop(); - - return true; - } - - return false; - } - - OS_TYPE* getOutput() { return os_; } - - private: - // verify we are in the right state before writing a value - bool verifyValueState() { - assert(!stack_.empty()); - return (stack_.top().state == WS_Object && kvState_ == WS_Key) || - (stack_.top().state == WS_Array && kvState_ == WS_Value); - } - - // verify we are in the right state before writing a key - bool verifyKeyState() { - assert(!stack_.empty()); - return stack_.top().state == WS_Object && kvState_ == WS_Value; - } - - void writeHeader() { - os_->put(FBSON_VER); - hasHdr_ = true; - } - - private: - enum WriteState { - WS_NONE, - WS_Array, - WS_Object, - WS_Key, - WS_Value, - WS_String, - WS_Binary, - }; - - struct WriteInfo { - WriteState state; - std::streampos sz_pos; - }; - - private: - OS_TYPE* os_; - bool alloc_; - bool hasHdr_; - WriteState kvState_; // key or value state - std::streampos str_pos_; - std::stack stack_; -}; - -typedef FbsonWriterT FbsonWriter; - -} // namespace fbson - -#endif // FBSON_FBSONWRITER_H diff --git a/thirdparty/rocksdb/third-party/gtest-1.7.0/fused-src/gtest/gtest.h b/thirdparty/rocksdb/third-party/gtest-1.7.0/fused-src/gtest/gtest.h index e3f0cfb95c..3cec41a9e4 100644 --- a/thirdparty/rocksdb/third-party/gtest-1.7.0/fused-src/gtest/gtest.h +++ b/thirdparty/rocksdb/third-party/gtest-1.7.0/fused-src/gtest/gtest.h @@ -3410,10 +3410,6 @@ inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996 /* deprecated function */) -inline const char* StrNCpy(char* dest, const char* src, size_t n) { - return strncpy(dest, src, n); -} - // ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and // StrError() aren't needed on Windows CE at this time and thus not // defined there. diff --git a/thirdparty/rocksdb/thirdparty.inc b/thirdparty/rocksdb/thirdparty.inc index a364d1d448..f40b81fecc 100644 --- a/thirdparty/rocksdb/thirdparty.inc +++ b/thirdparty/rocksdb/thirdparty.inc @@ -1,14 +1,6 @@ # Edit definitions below to specify paths to include files and libraries of all 3rd party libraries -# -# Edit these lines to set defaults for use of external libraries -# -set(USE_GFLAGS_DEFAULT 0) # GFLAGS is disabled by default, enable with -DGFLAGS=1 cmake command line agrument -set(USE_SNAPPY_DEFAULT 0) # SNAPPY is disabled by default, enable with -DSNAPPY=1 cmake command line agrument -set(USE_LZ4_DEFAULT 0) # LZ4 is disabled by default, enable with -DLZ4=1 cmake command line agrument -set(USE_ZLIB_DEFAULT 0) # ZLIB is disabled by default, enable with -DZLIB=1 cmake command line agrument -set(USE_XPRESS_DEFAULT 0) # XPRESS is disabled by default, enable with -DXPRESS=1 cmake command line agrument - +# TODO: Make this work with find_package and/or get rid of it # # This example assumes all the libraries locate in directories under THIRDPARTY_HOME environment variable # Set environment variable THIRDPARTY_HOME to point to your third party libraries home (Unix style dir separators) @@ -17,24 +9,20 @@ set(USE_XPRESS_DEFAULT 0) # XPRESS is disabled by default, enable with -D set (THIRDPARTY_LIBS "") # Initialization, don't touch # -# Edit these 4 lines to define paths to GFLAGS +# Defaults # set(GFLAGS_HOME $ENV{THIRDPARTY_HOME}/Gflags.Library) -set(GFLAGS_INCLUDE ${GFLAGS_HOME}/inc/include) -set(GFLAGS_LIB_DEBUG ${GFLAGS_HOME}/bin/debug/amd64/gflags.lib) -set(GFLAGS_LIB_RELEASE ${GFLAGS_HOME}/bin/retail/amd64/gflags.lib) +set(GFLAGS_INCLUDE ${GFLAGS_HOME}/build/native/include) +set(GFLAGS_LIB_DEBUG ${GFLAGS_HOME}/lib/native/debug/amd64/gflags.lib) +set(GFLAGS_LIB_RELEASE ${GFLAGS_HOME}/lib/native/retail/amd64/gflags.lib) # ================================================== GFLAGS ================================================== -# -# Don't touch these lines -# -if (DEFINED GFLAGS) - set(USE_GFLAGS ${GFLAGS}) -else () - set(USE_GFLAGS ${USE_GFLAGS_DEFAULT}) +# For compatibility +if (GFLAGS) + set(WITH_GFLAGS ON) endif () -if (${USE_GFLAGS} EQUAL 1) +if (WITH_GFLAGS) message(STATUS "GFLAGS library is enabled") if(DEFINED ENV{GFLAGS_INCLUDE}) @@ -64,26 +52,22 @@ endif () # Edit these 4 lines to define paths to Snappy # set(SNAPPY_HOME $ENV{THIRDPARTY_HOME}/Snappy.Library) -set(SNAPPY_INCLUDE ${SNAPPY_HOME}/inc/inc) -set(SNAPPY_LIB_DEBUG ${SNAPPY_HOME}/bin/debug/amd64/snappy.lib) -set(SNAPPY_LIB_RELEASE ${SNAPPY_HOME}/bin/retail/amd64/snappy.lib) +set(SNAPPY_INCLUDE ${SNAPPY_HOME}/build/native/inc/inc) +set(SNAPPY_LIB_DEBUG ${SNAPPY_HOME}/lib/native/debug/amd64/snappy.lib) +set(SNAPPY_LIB_RELEASE ${SNAPPY_HOME}/lib/native/retail/amd64/snappy.lib) -# -# Don't touch these lines -# -if (DEFINED SNAPPY) - set(USE_SNAPPY ${SNAPPY}) -else () - set(USE_SNAPPY ${USE_SNAPPY_DEFAULT}) +# For compatibility +if(SNAPPY) + set(WITH_SNAPPY ON) endif () -if (${USE_SNAPPY} EQUAL 1) +if (WITH_SNAPPY) message(STATUS "SNAPPY library is enabled") - + if(DEFINED ENV{SNAPPY_INCLUDE}) set(SNAPPY_INCLUDE $ENV{SNAPPY_INCLUDE}) endif() - + if(DEFINED ENV{SNAPPY_LIB_DEBUG}) set(SNAPPY_LIB_DEBUG $ENV{SNAPPY_LIB_DEBUG}) endif() @@ -91,7 +75,7 @@ if (${USE_SNAPPY} EQUAL 1) if(DEFINED ENV{SNAPPY_LIB_RELEASE}) set(SNAPPY_LIB_RELEASE $ENV{SNAPPY_LIB_RELEASE}) endif() - + set(SNAPPY_CXX_FLAGS -DSNAPPY) set(SNAPPY_LIBS debug ${SNAPPY_LIB_DEBUG} optimized ${SNAPPY_LIB_RELEASE}) @@ -107,20 +91,17 @@ endif () # Edit these 4 lines to define paths to LZ4 # set(LZ4_HOME $ENV{THIRDPARTY_HOME}/LZ4.Library) -set(LZ4_INCLUDE ${LZ4_HOME}/inc/include) -set(LZ4_LIB_DEBUG ${LZ4_HOME}/bin/debug/amd64/lz4.lib) -set(LZ4_LIB_RELEASE ${LZ4_HOME}/bin/retail/amd64/lz4.lib) +set(LZ4_INCLUDE ${LZ4_HOME}/build/native/inc/inc) +set(LZ4_LIB_DEBUG ${LZ4_HOME}/lib/native/debug/amd64/lz4.lib) +set(LZ4_LIB_RELEASE ${LZ4_HOME}/lib/native/retail/amd64/lz4.lib) -# -# Don't touch these lines -# -if (DEFINED LZ4) - set(USE_LZ4 ${LZ4}) -else () - set(USE_LZ4 ${USE_LZ4_DEFAULT}) + +# For compatibility +if (LZ4) + set(WITH_LZ4 ON) endif () -if (${USE_LZ4} EQUAL 1) +if (WITH_LZ4) message(STATUS "LZ4 library is enabled") if(DEFINED ENV{LZ4_INCLUDE}) @@ -150,20 +131,16 @@ endif () # Edit these 4 lines to define paths to ZLIB # set(ZLIB_HOME $ENV{THIRDPARTY_HOME}/ZLIB.Library) -set(ZLIB_INCLUDE ${ZLIB_HOME}/inc/include) -set(ZLIB_LIB_DEBUG ${ZLIB_HOME}/bin/debug/amd64/zlib.lib) -set(ZLIB_LIB_RELEASE ${ZLIB_HOME}/bin/retail/amd64/zlib.lib) +set(ZLIB_INCLUDE ${ZLIB_HOME}/build/native/inc/inc) +set(ZLIB_LIB_DEBUG ${ZLIB_HOME}/lib/native/debug/amd64/zlib.lib) +set(ZLIB_LIB_RELEASE ${ZLIB_HOME}/lib/native/retail/amd64/zlib.lib) -# -# Don't touch these lines -# -if (DEFINED ZLIB) - set(USE_ZLIB ${ZLIB}) -else () - set(USE_ZLIB ${USE_ZLIB_DEFAULT}) +# For compatibilty +if (ZLIB) + set(WITH_ZLIB ON) endif () -if (${USE_ZLIB} EQUAL 1) +if (WITH_ZLIB) message(STATUS "ZLIB library is enabled") if(DEFINED ENV{ZLIB_INCLUDE}) @@ -188,13 +165,15 @@ else () message(STATUS "ZLIB library is disabled") endif () -if (DEFINED XPRESS) - set(USE_XPRESS ${XPRESS}) -else () - set(USE_XPRESS ${USE_XPRESS_DEFAULT}) +# ================================================== XPRESS ================================================== +# This makes use of built-in Windows API, no additional includes, links to a system lib + +# For compatibilty +if (XPRESS) + set(WITH_XPRESS ON) endif () -if (${USE_XPRESS} EQUAL 1) +if (WITH_XPRESS) message(STATUS "XPRESS is enabled") add_definitions(-DXPRESS) @@ -205,20 +184,56 @@ else () message(STATUS "XPRESS is disabled") endif () + +# ================================================== ZSTD ================================================== # -# Edit these 4 lines to define paths to Jemalloc +# Edit these 4 lines to define paths to ZSTD # -set(JEMALLOC_HOME $ENV{THIRDPARTY_HOME}/Jemalloc.Library) -set(JEMALLOC_INCLUDE ${JEMALLOC_HOME}/inc/include) -set(JEMALLOC_LIB_DEBUG ${JEMALLOC_HOME}/bin/debug/amd64/jemalloc.lib) -set(JEMALLOC_LIB_RELEASE ${JEMALLOC_HOME}/bin/retail/amd64/jemalloc.lib) +set(ZSTD_HOME $ENV{THIRDPARTY_HOME}/ZSTD.Library) +set(ZSTD_INCLUDE ${ZSTD_HOME}/build/native/inc) +set(ZSTD_LIB_DEBUG ${ZSTD_HOME}/lib/native/debug/amd64/libzstd_static.lib) +set(ZSTD_LIB_RELEASE ${ZSTD_HOME}/lib/native/retail/amd64/libzstd_static.lib) + +# For compatibility +if (ZSTD) + set(WITH_ZSTD ON) +endif () + +if (WITH_ZSTD) + message(STATUS "ZSTD library is enabled") + + if(DEFINED ENV{ZSTD_INCLUDE}) + set(ZSTD_INCLUDE $ENV{ZSTD_INCLUDE}) + endif() + + if(DEFINED ENV{ZSTD_LIB_DEBUG}) + set(ZSTD_LIB_DEBUG $ENV{ZSTD_LIB_DEBUG}) + endif() + + if(DEFINED ENV{ZSTD_LIB_RELEASE}) + set(ZSTD_LIB_RELEASE $ENV{ZSTD_LIB_RELEASE}) + endif() + + # ZSTD_STATIC_LINKING_ONLY only allows us to create an allocation functions override + # When jemalloc is in use + set(ZSTD_LIBS debug ${ZSTD_LIB_DEBUG} optimized ${ZSTD_LIB_RELEASE}) + + add_definitions(-DZSTD -DZSTD_STATIC_LINKING_ONLY) + include_directories(${ZSTD_INCLUDE}) + set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${ZSTD_LIBS}) +else () + message(STATUS "ZSTD library is disabled") +endif () -# ================================================== JEMALLOC ================================================== # -# Don't touch these lines +# Edit these 4 lines to define paths to Jemalloc # +set(JEMALLOC_HOME $ENV{THIRDPARTY_HOME}/Jemalloc.Library) +set(JEMALLOC_INCLUDE ${JEMALLOC_HOME}/build/native/inc) +set(JEMALLOC_LIB_DEBUG ${JEMALLOC_HOME}/lib/native/debug/amd64/jemalloc.lib) +set(JEMALLOC_LIB_RELEASE ${JEMALLOC_HOME}/lib/native/retail/amd64/jemalloc.lib) -# For compatibilty with previous +# ================================================== JEMALLOC ================================================== if(JEMALLOC) set(WITH_JEMALLOC ON) endif() @@ -245,9 +260,7 @@ if (WITH_JEMALLOC) include_directories(${JEMALLOC_INCLUDE}) set (THIRDPARTY_LIBS ${THIRDPARTY_LIBS} ${JEMALLOC_LIBS}) set (ARTIFACT_SUFFIX "_je") - - set(WITH_JEMALLOC ON) - + else () set (ARTIFACT_SUFFIX "") message(STATUS "JEMALLOC library is disabled") diff --git a/thirdparty/rocksdb/tools/advisor/README.md b/thirdparty/rocksdb/tools/advisor/README.md new file mode 100644 index 0000000000..f1e7165e4c --- /dev/null +++ b/thirdparty/rocksdb/tools/advisor/README.md @@ -0,0 +1,96 @@ +# Rocksdb Tuning Advisor + +## Motivation + +The performance of Rocksdb is contingent on its tuning. However, +because of the complexity of its underlying technology and a large number of +configurable parameters, a good configuration is sometimes hard to obtain. The aim of +the python command-line tool, Rocksdb Advisor, is to automate the process of +suggesting improvements in the configuration based on advice from Rocksdb +experts. + +## Overview + +Experts share their wisdom as rules comprising of conditions and suggestions in the INI format (refer +[rules.ini](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rules.ini)). +Users provide the Rocksdb configuration that they want to improve upon (as the +familiar Rocksdb OPTIONS file — +[example](https://github.com/facebook/rocksdb/blob/master/examples/rocksdb_option_file_example.ini)) +and the path of the file which contains Rocksdb logs and statistics. +The [Advisor](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rule_parser_example.py) +creates appropriate DataSource objects (for Rocksdb +[logs](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_log_parser.py), +[options](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_options_parser.py), +[statistics](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/db_stats_fetcher.py) etc.) +and provides them to the [Rules Engine](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rule_parser.py). +The Rules uses rules from experts to parse data-sources and trigger appropriate rules. +The Advisor's output gives information about which rules were triggered, +why they were triggered and what each of them suggests. Each suggestion +provided by a triggered rule advises some action on a Rocksdb +configuration option, for example, increase CFOptions.write_buffer_size, +set bloom_bits to 2 etc. + +## Usage + +### Prerequisites +The tool needs the following to run: +* python3 + +### Running the tool +An example command to run the tool: + +```shell +cd rocksdb/tools/advisor +python3 -m advisor.rule_parser_example --rules_spec=advisor/rules.ini --rocksdb_options=test/input_files/OPTIONS-000005 --log_files_path_prefix=test/input_files/LOG-0 --stats_dump_period_sec=20 +``` + +### Command-line arguments + +Most important amongst all the input that the Advisor needs, are the rules +spec and starting Rocksdb configuration. The configuration is provided as the +familiar Rocksdb Options file (refer [example](https://github.com/facebook/rocksdb/blob/master/examples/rocksdb_option_file_example.ini)). +The Rules spec is written in the INI format (more details in +[rules.ini](https://github.com/facebook/rocksdb/blob/master/tools/advisor/advisor/rules.ini)). + +In brief, a Rule is made of conditions and is triggered when all its +constituent conditions are triggered. When triggered, a Rule suggests changes +(increase/decrease/set to a suggested value) to certain Rocksdb options that +aim to improve Rocksdb performance. Every Condition has a 'source' i.e. +the data source that would be checked for triggering that condition. +For example, a log Condition (with 'source=LOG') is triggered if a particular +'regex' is found in the Rocksdb LOG files. As of now the Rules Engine +supports 3 types of Conditions (and consequently data-sources): +LOG, OPTIONS, TIME_SERIES. The TIME_SERIES data can be sourced from the +Rocksdb [statistics](https://github.com/facebook/rocksdb/blob/master/include/rocksdb/statistics.h) +or [perf context](https://github.com/facebook/rocksdb/blob/master/include/rocksdb/perf_context.h). + +For more information about the remaining command-line arguments, run: + +```shell +cd rocksdb/tools/advisor +python3 -m advisor.rule_parser_example --help +``` + +### Sample output + +Here, a Rocksdb log-based rule has been triggered: + +```shell +Rule: stall-too-many-memtables +LogCondition: stall-too-many-memtables regex: Stopping writes because we have \d+ immutable memtables \(waiting for flush\), max_write_buffer_number is set to \d+ +Suggestion: inc-bg-flush option : DBOptions.max_background_flushes action : increase suggested_values : ['2'] +Suggestion: inc-write-buffer option : CFOptions.max_write_buffer_number action : increase +scope: col_fam: +{'default'} +``` + +## Running the tests + +Tests for the code have been added to the +[test/](https://github.com/facebook/rocksdb/tree/master/tools/advisor/test) +directory. For example, to run the unit tests for db_log_parser.py: + +```shell +cd rocksdb/tools/advisor +python3 -m unittest -v test.test_db_log_parser +``` diff --git a/thirdparty/rocksdb/tools/advisor/advisor/__init__.py b/thirdparty/rocksdb/tools/advisor/advisor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/thirdparty/rocksdb/tools/advisor/advisor/bench_runner.py b/thirdparty/rocksdb/tools/advisor/advisor/bench_runner.py new file mode 100644 index 0000000000..7c7ee78824 --- /dev/null +++ b/thirdparty/rocksdb/tools/advisor/advisor/bench_runner.py @@ -0,0 +1,39 @@ +# Copyright (c) 2011-present, Facebook, 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). + +from abc import ABC, abstractmethod +import re + + +class BenchmarkRunner(ABC): + @staticmethod + @abstractmethod + def is_metric_better(new_metric, old_metric): + pass + + @abstractmethod + def run_experiment(self): + # should return a list of DataSource objects + pass + + @staticmethod + def get_info_log_file_name(log_dir, db_path): + # Example: DB Path = /dev/shm and OPTIONS file has option + # db_log_dir=/tmp/rocks/, then the name of the log file will be + # 'dev_shm_LOG' and its location will be /tmp/rocks. If db_log_dir is + # not specified in the OPTIONS file, then the location of the log file + # will be /dev/shm and the name of the file will be 'LOG' + file_name = '' + if log_dir: + # refer GetInfoLogPrefix() in rocksdb/util/filename.cc + # example db_path: /dev/shm/dbbench + file_name = db_path[1:] # to ignore the leading '/' character + to_be_replaced = re.compile('[^0-9a-zA-Z\-_\.]') + for character in to_be_replaced.findall(db_path): + file_name = file_name.replace(character, '_') + if not file_name.endswith('_'): + file_name += '_' + file_name += 'LOG' + return file_name diff --git a/thirdparty/rocksdb/tools/advisor/advisor/config_optimizer_example.py b/thirdparty/rocksdb/tools/advisor/advisor/config_optimizer_example.py new file mode 100644 index 0000000000..e3736387ea --- /dev/null +++ b/thirdparty/rocksdb/tools/advisor/advisor/config_optimizer_example.py @@ -0,0 +1,134 @@ +# Copyright (c) 2011-present, Facebook, 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). + +import argparse +from advisor.db_config_optimizer import ConfigOptimizer +from advisor.db_log_parser import NO_COL_FAMILY +from advisor.db_options_parser import DatabaseOptions +from advisor.rule_parser import RulesSpec + + +CONFIG_OPT_NUM_ITER = 10 + + +def main(args): + # initialise the RulesSpec parser + rule_spec_parser = RulesSpec(args.rules_spec) + # initialise the benchmark runner + bench_runner_module = __import__( + args.benchrunner_module, fromlist=[args.benchrunner_class] + ) + bench_runner_class = getattr(bench_runner_module, args.benchrunner_class) + ods_args = {} + if args.ods_client and args.ods_entity: + ods_args['client_script'] = args.ods_client + ods_args['entity'] = args.ods_entity + if args.ods_key_prefix: + ods_args['key_prefix'] = args.ods_key_prefix + db_bench_runner = bench_runner_class(args.benchrunner_pos_args, ods_args) + # initialise the database configuration + db_options = DatabaseOptions(args.rocksdb_options, args.misc_options) + # set the frequency at which stats are dumped in the LOG file and the + # location of the LOG file. + db_log_dump_settings = { + "DBOptions.stats_dump_period_sec": { + NO_COL_FAMILY: args.stats_dump_period_sec + } + } + db_options.update_options(db_log_dump_settings) + # initialise the configuration optimizer + config_optimizer = ConfigOptimizer( + db_bench_runner, + db_options, + rule_spec_parser, + args.base_db_path + ) + # run the optimiser to improve the database configuration for given + # benchmarks, with the help of expert-specified rules + final_db_options = config_optimizer.run() + # generate the final rocksdb options file + print( + 'Final configuration in: ' + + final_db_options.generate_options_config('final') + ) + print( + 'Final miscellaneous options: ' + + repr(final_db_options.get_misc_options()) + ) + + +if __name__ == '__main__': + ''' + An example run of this tool from the command-line would look like: + python3 -m advisor.config_optimizer_example + --base_db_path=/tmp/rocksdbtest-155919/dbbench + --rocksdb_options=temp/OPTIONS_boot.tmp --misc_options bloom_bits=2 + --rules_spec=advisor/rules.ini --stats_dump_period_sec=20 + --benchrunner_module=advisor.db_bench_runner + --benchrunner_class=DBBenchRunner --benchrunner_pos_args ./../../db_bench + readwhilewriting use_existing_db=true duration=90 + ''' + parser = argparse.ArgumentParser(description='This script is used for\ + searching for a better database configuration') + parser.add_argument( + '--rocksdb_options', required=True, type=str, + help='path of the starting Rocksdb OPTIONS file' + ) + # these are options that are column-family agnostic and are not yet + # supported by the Rocksdb Options file: eg. bloom_bits=2 + parser.add_argument( + '--misc_options', nargs='*', + help='whitespace-separated list of options that are not supported ' + + 'by the Rocksdb OPTIONS file, given in the ' + + '= format eg. "bloom_bits=2 ' + + 'rate_limiter_bytes_per_sec=128000000"') + parser.add_argument( + '--base_db_path', required=True, type=str, + help='path for the Rocksdb database' + ) + parser.add_argument( + '--rules_spec', required=True, type=str, + help='path of the file containing the expert-specified Rules' + ) + parser.add_argument( + '--stats_dump_period_sec', required=True, type=int, + help='the frequency (in seconds) at which STATISTICS are printed to ' + + 'the Rocksdb LOG file' + ) + # ODS arguments + parser.add_argument( + '--ods_client', type=str, help='the ODS client binary' + ) + parser.add_argument( + '--ods_entity', type=str, + help='the servers for which the ODS stats need to be fetched' + ) + parser.add_argument( + '--ods_key_prefix', type=str, + help='the prefix that needs to be attached to the keys of time ' + + 'series to be fetched from ODS' + ) + # benchrunner_module example: advisor.db_benchmark_client + parser.add_argument( + '--benchrunner_module', required=True, type=str, + help='the module containing the BenchmarkRunner class to be used by ' + + 'the Optimizer, example: advisor.db_bench_runner' + ) + # benchrunner_class example: DBBenchRunner + parser.add_argument( + '--benchrunner_class', required=True, type=str, + help='the name of the BenchmarkRunner class to be used by the ' + + 'Optimizer, should be present in the module provided in the ' + + 'benchrunner_module argument, example: DBBenchRunner' + ) + parser.add_argument( + '--benchrunner_pos_args', nargs='*', + help='whitespace-separated positional arguments that are passed on ' + + 'to the constructor of the BenchmarkRunner class provided in the ' + + 'benchrunner_class argument, example: "use_existing_db=true ' + + 'duration=900"' + ) + args = parser.parse_args() + main(args) diff --git a/thirdparty/rocksdb/tools/advisor/advisor/db_bench_runner.py b/thirdparty/rocksdb/tools/advisor/advisor/db_bench_runner.py new file mode 100644 index 0000000000..54424440b3 --- /dev/null +++ b/thirdparty/rocksdb/tools/advisor/advisor/db_bench_runner.py @@ -0,0 +1,245 @@ +# Copyright (c) 2011-present, Facebook, 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). + +from advisor.bench_runner import BenchmarkRunner +from advisor.db_log_parser import DataSource, DatabaseLogs, NO_COL_FAMILY +from advisor.db_stats_fetcher import ( + LogStatsParser, OdsStatsFetcher, DatabasePerfContext +) +import shutil +import subprocess +import time + + +''' +NOTE: This is not thread-safe, because the output file is simply overwritten. +''' + + +class DBBenchRunner(BenchmarkRunner): + OUTPUT_FILE = "temp/dbbench_out.tmp" + ERROR_FILE = "temp/dbbench_err.tmp" + DB_PATH = "DB path" + THROUGHPUT = "ops/sec" + PERF_CON = " PERF_CONTEXT:" + + @staticmethod + def is_metric_better(new_metric, old_metric): + # for db_bench 'throughput' is the metric returned by run_experiment + return new_metric >= old_metric + + @staticmethod + def get_opt_args_str(misc_options_dict): + # given a dictionary of options and their values, return a string + # that can be appended as command-line arguments + optional_args_str = "" + for option_name, option_value in misc_options_dict.items(): + if option_value: + optional_args_str += ( + " --" + option_name + "=" + str(option_value) + ) + return optional_args_str + + def __init__(self, positional_args, ods_args=None): + # parse positional_args list appropriately + self.db_bench_binary = positional_args[0] + self.benchmark = positional_args[1] + self.db_bench_args = None + if len(positional_args) > 2: + # options list with each option given as "